新增播放详情页歌词右键菜单,新增歌词偏移设置
This commit is contained in:
@@ -234,5 +234,13 @@ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/19
|
||||
// 0 0 24 24
|
||||
path(fill='currentColor', d='M22 12L20 13L19 14L18 13L17 16L16 13L15 21L14 13L13 15L12 13L11 17L10 13L9 22L8 13L7 19L6 13L5 14L4 13L2 12L4 11L5 10L6 11L7 5L8 11L9 2L10 11L11 7L12 11L13 9L14 11L15 3L16 11L17 8L18 11L19 10L20 11L22 12Z')
|
||||
|
||||
g#icon-font-decrease(fill='currentColor')
|
||||
// 0 0 24 24
|
||||
path(d='M5.12,14L7.5,7.67L9.87,14M6.5,5L1,19H3.25L4.37,16H10.62L11.75,19H14L8.5,5H6.5M18,17L23,11.93L21.59,10.5L19,13.1V7H17V13.1L14.41,10.5L13,11.93L18,17Z')
|
||||
|
||||
g#icon-font-increase(fill='currentColor')
|
||||
// 0 0 24 24
|
||||
path(d='M5.12,14L7.5,7.67L9.87,14M6.5,5L1,19H3.25L4.37,16H10.62L11.75,19H14L8.5,5H6.5M18,7L13,12.07L14.41,13.5L17,10.9V17H19V10.9L21.59,13.5L23,12.07L18,7Z')
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="['right', $style.right]" :style="lrcFontSize">
|
||||
<div :class="['right', $style.right]" :style="lrcFontSize" @contextmenu.stop="handleShowLyricMenu">
|
||||
<div :class="['lyric', $style.lyric, { [$style.draging]: isMsDown }, { [$style.lrcActiveZoom]: isZoomActiveLrc }]" :style="lrcStyles" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric">
|
||||
<div :class="['pre', $style.lyricSpace]"></div>
|
||||
<div ref="dom_lyric_text"></div>
|
||||
@@ -25,6 +25,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<LyricMenu v-model="lyricMenuVisible" :xy="lyricMenuXY" :lyricInfo="lyricInfo" @updateLyric="handleUpdateLyric" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -32,14 +33,18 @@
|
||||
import { clipboardWriteText } from '@renderer/utils'
|
||||
import { lyric } from '@renderer/core/share/lyric'
|
||||
import { isFullscreen } from '@renderer/core/share'
|
||||
import { isPlay, isShowLrcSelectContent, isShowPlayComment } from '@renderer/core/share/player'
|
||||
import { onMounted, onBeforeUnmount, useCommit, useRefGetter, computed } from '@renderer/utils/vueTools'
|
||||
import { isPlay, isShowLrcSelectContent, isShowPlayComment, musicInfo as playerMusicInfo, musicInfoItem, setMusicInfo } from '@renderer/core/share/player'
|
||||
import { onMounted, onBeforeUnmount, useRefGetter, computed, reactive, ref } from '@renderer/utils/vueTools'
|
||||
import useLyric from '@renderer/utils/compositions/useLyric'
|
||||
import LyricMenu from './components/LyricMenu'
|
||||
import { player as eventPlayerNames } from '@renderer/event/names'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LyricMenu,
|
||||
},
|
||||
setup() {
|
||||
const setting = useRefGetter('setting')
|
||||
const setPlayDetailLyricFont = useCommit('setPlayDetailLyricFont')
|
||||
const {
|
||||
dom_lyric,
|
||||
dom_lyric_text,
|
||||
@@ -54,13 +59,37 @@ export default {
|
||||
handleSkipMouseLeave,
|
||||
} = useLyric({ isPlay, lyric })
|
||||
|
||||
const fontSizeUp = () => {
|
||||
if (setting.value.playDetail.style.fontSize >= 200) return
|
||||
setPlayDetailLyricFont(setting.value.playDetail.style.fontSize + 1)
|
||||
const lyricMenuVisible = ref(false)
|
||||
const lyricMenuXY = reactive({
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
const lyricInfo = reactive({
|
||||
lyric: '',
|
||||
tlyric: '',
|
||||
lxlyric: '',
|
||||
musicInfo: null,
|
||||
})
|
||||
const updateMusicInfo = () => {
|
||||
lyricInfo.lyric = playerMusicInfo.lrc
|
||||
lyricInfo.tlyric = playerMusicInfo.tlrc
|
||||
lyricInfo.lxlyric = playerMusicInfo.lxlrc
|
||||
lyricInfo.musicInfo = musicInfoItem.value
|
||||
}
|
||||
const fontSizeDown = () => {
|
||||
if (setting.value.playDetail.style.fontSize <= 70) return
|
||||
setPlayDetailLyricFont(setting.value.playDetail.style.fontSize - 1)
|
||||
const handleShowLyricMenu = event => {
|
||||
updateMusicInfo()
|
||||
lyricMenuXY.x = event.pageX
|
||||
lyricMenuXY.y = event.pageY
|
||||
lyricMenuVisible.value = true
|
||||
}
|
||||
const handleUpdateLyric = ({ lyric, tlyric, lxlyric, offset }) => {
|
||||
setMusicInfo({
|
||||
lrc: lyric,
|
||||
tlrc: tlyric,
|
||||
lxlrc: lxlyric,
|
||||
})
|
||||
console.log(offset)
|
||||
window.eventHub.emit(eventPlayerNames.updateLyricOffset, offset)
|
||||
}
|
||||
|
||||
const lrcStyles = computed(() => {
|
||||
@@ -79,16 +108,10 @@ export default {
|
||||
const isShowLyricProgressSetting = computed(() => setting.value.playDetail.isShowLyricProgressSetting)
|
||||
|
||||
onMounted(() => {
|
||||
window.eventHub.on('key_shift++_down', fontSizeUp)
|
||||
window.eventHub.on('key_numadd_down', fontSizeUp)
|
||||
window.eventHub.on('key_-_down', fontSizeDown)
|
||||
window.eventHub.on('key_numsub_down', fontSizeDown)
|
||||
window.eventHub.on(eventPlayerNames.updateLyric, updateMusicInfo)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.eventHub.off('key_shift++_down', fontSizeUp)
|
||||
window.eventHub.off('key_numadd_down', fontSizeUp)
|
||||
window.eventHub.off('key_-_down', fontSizeDown)
|
||||
window.eventHub.off('key_numsub_down', fontSizeDown)
|
||||
window.eventHub.off(eventPlayerNames.updateLyric, updateMusicInfo)
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -109,6 +132,11 @@ export default {
|
||||
isShowLyricProgressSetting,
|
||||
isZoomActiveLrc,
|
||||
isStopScroll,
|
||||
lyricMenuVisible,
|
||||
lyricMenuXY,
|
||||
handleShowLyricMenu,
|
||||
handleUpdateLyric,
|
||||
lyricInfo,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<teleport to="#root">
|
||||
<div :class="$style.container" :style="menuStyles" ref="dom_menu" :aria-hidden="!modelValue">
|
||||
<!-- <div :class="$style.group">
|
||||
<div :class="$style.title">{{$t('lyric_menu__align')}}</div>
|
||||
<div :class="$style.subGroup">
|
||||
<div :class="[$style.btn, { [$style.active]: playDetailSetting.style.align == 'left' }]" role="button" @click="setFontAlign('left')" ignore-tip :aria-label="$t('lyric_menu__align_left')">{{$t('lyric_menu__align_left')}}</div>
|
||||
<div :class="[$style.btn, { [$style.active]: playDetailSetting.style.align == 'center' }]" role="button" @click="setFontAlign('center')" ignore-tip :aria-label="$t('lyric_menu__align_center')">{{$t('lyric_menu__align_center')}}</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div :class="$style.group">
|
||||
<div :class="$style.subGroup">
|
||||
<div :class="$style.title">{{$t('lyric_menu__lrc_size', { size: playDetailSetting.style.fontSize })}}</div>
|
||||
<button :class="[$style.btn, $style.titleBtn]" :disabled="playDetailSetting.style.fontSize == 100" @click="fontSizeReset" ignore-tip :aria-label="$t('lyric_menu__size_reset')">{{$t('lyric_menu__size_reset')}}</button>
|
||||
</div>
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" @click="fontSizeUp(5)" @contextmenu="fontSizeUp(1)" :aria-label="$t('lyric_menu__size_add')">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="18px" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-font-increase"></use>
|
||||
</svg>
|
||||
</button>
|
||||
<button :class="$style.btn" @click="fontSizeDown(5)" @contextmenu="fontSizeDown(1)" :aria-label="$t('lyric_menu__size_dec')">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="18px" viewBox="0 0 24 24" space="preserve">
|
||||
<use xlink:href="#icon-font-decrease"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.group">
|
||||
<div :class="$style.subGroup">
|
||||
<div :class="$style.title">{{$t('lyric_menu__offset', { offset })}}</div>
|
||||
<button :class="[$style.btn, $style.titleBtn]" :disabled="offsetDisabled || !offset" @click="offsetReset">{{$t('lyric_menu__offset_reset')}}</button>
|
||||
</div>
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" @click="setOffset(10)" @contextmenu="setOffset(5)" :aria-label="$t('lyric_menu__offset_add_10')">+ 10ms</button>
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" @click="setOffset(-10)" @contextmenu="setOffset(-5)" :aria-label="$t('lyric_menu__offset_dec_10')">- 10ms</button>
|
||||
</div>
|
||||
<div :class="$style.subGroup">
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" @click="setOffset(100)" @contextmenu="setOffset(50)" :aria-label="$t('lyric_menu__offset_add_100')">+ 100ms</button>
|
||||
<button :class="$style.btn" :disabled="offsetDisabled" @click="setOffset(-100)" @contextmenu="setOffset(-50)" :aria-label="$t('lyric_menu__offset_dec_100')">- 100ms</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, useRefGetter, ref, useCommit, watch } from '@renderer/utils/vueTools'
|
||||
import useMenuLocation from '@renderer/utils/compositions/useMenuLocation'
|
||||
import { setLyricEdited, removeLyricEdited, debounce } from '@renderer/utils'
|
||||
|
||||
const offsetTagRxp = /^\s*\[offset:\s*(\S+(?:\d+)*)\s*\]/
|
||||
|
||||
const saveLyric = debounce((musicInfo, lyricInfo) => {
|
||||
setLyricEdited(musicInfo, lyricInfo)
|
||||
})
|
||||
const removeLyric = debounce(musicInfo => {
|
||||
removeLyricEdited(musicInfo)
|
||||
})
|
||||
|
||||
export default {
|
||||
name: 'LyricMenu',
|
||||
props: {
|
||||
modelValue: Boolean,
|
||||
xy: Object,
|
||||
lyricInfo: Object,
|
||||
},
|
||||
emits: ['updateLyric', 'update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const setting = useRefGetter('setting')
|
||||
const playDetailSetting = useRefGetter('playDetailSetting')
|
||||
const setPlayDetailLyricAlign = useCommit('setPlayDetailLyricAlign')
|
||||
const setPlayDetailLyricFont = useCommit('setPlayDetailLyricFont')
|
||||
|
||||
const offset = ref(0)
|
||||
const offsetDisabled = ref(true)
|
||||
|
||||
const visible = computed(() => props.modelValue)
|
||||
const musicInfo = computed(() => props.lyricInfo.musicInfo)
|
||||
const location = computed(() => props.xy)
|
||||
|
||||
const onHide = () => {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
const setFontAlign = val => {
|
||||
if (playDetailSetting.value.style.align == val) return
|
||||
setPlayDetailLyricAlign(val)
|
||||
}
|
||||
|
||||
const fontSizeUp = step => {
|
||||
if (setting.value.playDetail.style.fontSize >= 200) return
|
||||
setPlayDetailLyricFont(Math.min(setting.value.playDetail.style.fontSize + step, 200))
|
||||
}
|
||||
const fontSizeDown = step => {
|
||||
if (setting.value.playDetail.style.fontSize <= 70) return
|
||||
setPlayDetailLyricFont(Math.max(setting.value.playDetail.style.fontSize - step, 70))
|
||||
}
|
||||
const fontSizeReset = () => {
|
||||
setPlayDetailLyricFont(100)
|
||||
}
|
||||
|
||||
const updateLyric = offset => {
|
||||
let lyric = props.lyricInfo.lyric
|
||||
let tlyric = props.lyricInfo.tlyric
|
||||
let lxlyric = props.lyricInfo.lxlyric
|
||||
if (offsetTagRxp.test(lyric)) {
|
||||
lyric = lyric.replace(offsetTagRxp, `[offset:${offset}]`)
|
||||
if (tlyric) tlyric = tlyric.replace(offsetTagRxp, `[offset:${offset}]`)
|
||||
if (lxlyric) lxlyric = lxlyric.replace(offsetTagRxp, `[offset:${offset}]`)
|
||||
} else {
|
||||
lyric = `[offset:${offset}]\n` + lyric
|
||||
if (tlyric) tlyric = `[offset:${offset}]\n` + tlyric
|
||||
if (lxlyric) lxlyric = `[offset:${offset}]\n` + lxlyric
|
||||
}
|
||||
|
||||
if (offset) {
|
||||
saveLyric(props.lyricInfo.musicInfo, {
|
||||
lyric,
|
||||
tlyric,
|
||||
lxlyric,
|
||||
})
|
||||
} else removeLyric(props.lyricInfo.musicInfo)
|
||||
|
||||
emit('updateLyric', {
|
||||
lyric,
|
||||
tlyric,
|
||||
lxlyric,
|
||||
offset,
|
||||
})
|
||||
}
|
||||
const setOffset = step => {
|
||||
offset.value += step
|
||||
updateLyric(offset.value)
|
||||
}
|
||||
const offsetReset = () => {
|
||||
if (!offset.value) return
|
||||
offset.value = 0
|
||||
updateLyric(0)
|
||||
}
|
||||
|
||||
const parseLrcOffset = () => {
|
||||
let lrcOffset
|
||||
if (props.lyricInfo.lyric) {
|
||||
lrcOffset = offsetTagRxp.exec(props.lyricInfo.lyric)
|
||||
if (lrcOffset) {
|
||||
lrcOffset = parseInt(lrcOffset[1])
|
||||
if (Number.isNaN(lrcOffset)) lrcOffset = 0
|
||||
} else lrcOffset = 0
|
||||
offsetDisabled.value = false
|
||||
} else {
|
||||
offsetDisabled.value = true
|
||||
lrcOffset = 0
|
||||
}
|
||||
offset.value = lrcOffset
|
||||
}
|
||||
|
||||
|
||||
const { dom_menu, menuStyles } = useMenuLocation({
|
||||
visible,
|
||||
location,
|
||||
onHide,
|
||||
})
|
||||
|
||||
watch(musicInfo, () => {
|
||||
if (!props.modelValue) return
|
||||
parseLrcOffset()
|
||||
})
|
||||
watch(visible, val => {
|
||||
if (!val) return
|
||||
parseLrcOffset()
|
||||
})
|
||||
|
||||
return {
|
||||
dom_menu,
|
||||
menuStyles,
|
||||
playDetailSetting,
|
||||
offset,
|
||||
fontSizeUp,
|
||||
fontSizeDown,
|
||||
fontSizeReset,
|
||||
setOffset,
|
||||
offsetReset,
|
||||
setFontAlign,
|
||||
offsetDisabled,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.container {
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transform-origin: 0 0 0;
|
||||
transition: .25s ease;
|
||||
transition-property: transform, opacity;
|
||||
border-radius: @radius-border;
|
||||
background-color: @color-theme_2-background_2;
|
||||
box-shadow: 0 1px 8px 0 rgba(0,0,0,.2);
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.title {
|
||||
flex: auto;
|
||||
padding: 10px 0 10px 10px;
|
||||
color: @color-theme_2-font-label;
|
||||
white-space: nowrap;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.subGroup {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: auto;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
min-width: 60px;
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// color: @color-btn;
|
||||
padding: 0 10px;
|
||||
outline: none;
|
||||
transition: @transition-theme;
|
||||
transition-property: background-color, opacity;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
background-color: @color-theme_2-background_2;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
opacity: .7;
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
&.active {
|
||||
background-color: @color-theme_2-background_2;
|
||||
color: @color-btn-active;
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
cursor: default;
|
||||
opacity: .4;
|
||||
&:hover {
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.titleBtn {
|
||||
flex: none;
|
||||
padding: 0 10;
|
||||
min-width: 40px;
|
||||
opacity: .7;
|
||||
|
||||
&[disabled] {
|
||||
opacity: .3;
|
||||
}
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.container {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
}
|
||||
.btn {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
&.active {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user