Skip to content

Commit

Permalink
增加更为密集的实验性高级布局
Browse files Browse the repository at this point in the history
  • Loading branch information
Steve-xmh committed May 3, 2024
1 parent 8e0028b commit a42a4f3
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 91 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@
"@pixi/sprite": "^7.4.0",
"@pixi/utils": "^7.4.0",
"@tauri-apps/api": "^1.5.3",
"@vicons/fa": "^0.12.0",
"@vicons/fluent": "^0.12.0",
"@vicons/material": "^0.12.0",
"@vue/tsconfig": "^0.5.1",
"bezier-easing": "^2.1.0",
"codemirror": "^6.0.1",
"fflate": "^0.8.0",
"jieba-wasm": "^0.0.2",
Expand Down
29 changes: 16 additions & 13 deletions src/components/ContextMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@
<template>
<NPopover :arrow="false" :options="lyricLineMenu.selectedWord === -1 ? lineContextMenu : wordOnlyContextMenu"
:show="lyricLineMenu.show" :x="lyricLineMenu.x" :y="lyricLineMenu.y" class="context-menu"
placement="bottom-start"
style="padding: 4px" trigger="manual" @clickoutside="lyricLineMenu.show = false">
<ContextMenuWordEdit v-if="lyricLineMenu.selectedWord !== -1"/>
<NDivider v-if="lyricLineMenu.selectedWord !== -1" style="margin: 4px 0"/>
<div class="context-menu-line-edit">
<div>行开始时间</div>
<TimeStampInput v-model:value="lineInput.lineStartTime" @update:value="onTimeUpdate"/>
<div>行结束时间</div>
<TimeStampInput v-model:value="lineInput.lineEndTime" @update:value="onTimeUpdate"/>
</div>
<NDivider style="margin: 4px 0"/>
placement="bottom-start" style="padding: 4px" trigger="manual" @clickoutside="lyricLineMenu.show = false">
<template v-if="settings.uiLayoutMode === UILayoutMode.Simple">
<ContextMenuWordEdit v-if="lyricLineMenu.selectedWord !== -1"/>
<NDivider v-if="lyricLineMenu.selectedWord !== -1" style="margin: 4px 0"/>
<div class="context-menu-line-edit">
<div>行开始时间</div>
<TimeStampInput v-model:value="lineInput.lineStartTime" @update:value="onTimeUpdate"/>
<div>行结束时间</div>
<TimeStampInput v-model:value="lineInput.lineEndTime" @update:value="onTimeUpdate"/>
</div>
<NDivider style="margin: 4px 0"/>
</template>
<NEl tag="button"
@click="() => { lyric.removeWord(lyricLineMenu.selectedLine, lyricLineMenu.selectedWord); lyricLineMenu.show = false; }">
<i18n-t keypath="contextMenu.deleteWord"/>
Expand All @@ -38,7 +39,8 @@
<NEl tag="button" @click="() => { lyric.removeLine(lyricLineMenu.selectedLine); lyricLineMenu.show = false; }">
<i18n-t keypath="contextMenu.deleteLine"/>
</NEl>
<NEl tag="button" @click="() => { lyric.insertNewLineAt(lyricLineMenu.selectedLine); lyricLineMenu.show = false; }">
<NEl tag="button"
@click="() => { lyric.insertNewLineAt(lyricLineMenu.selectedLine); lyricLineMenu.show = false; }">
<i18n-t keypath="contextMenu.insertBeforeLine"/>
</NEl>
<NEl tag="button"
Expand All @@ -59,7 +61,7 @@
<script setup lang="tsx">
import {NDivider, NEl, NPopover, useNotification} from "naive-ui";
import {onMounted, onUnmounted, reactive, watchEffect} from "vue";
import {useDialogs, useEditingLyric, useRightClickLyricLine} from "../store";
import {UILayoutMode, useDialogs, useEditingLyric, useRightClickLyricLine, useSettings} from "../store";
import type {DropdownMixedOption} from "naive-ui/es/dropdown/src/interface";
import {i18n} from '../i18n';
import ContextMenuWordEdit from "./ContextMenuWordEdit.vue";
Expand All @@ -84,6 +86,7 @@ const lyricLineMenu = useRightClickLyricLine();
const notify = useNotification();
const lyric = useEditingLyric();
const dialogs = useDialogs();
const settings = useSettings();
const lineInput = reactive({
lineStartTime: 0,
lineEndTime: 0,
Expand Down
93 changes: 52 additions & 41 deletions src/components/LyricLineEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,64 +11,76 @@

<template>
<div class="line" style="display: flex; align-items: center; gap: 12px" @contextmenu.prevent="
lyricMenu.showMenuForLyric(
props.line.id,
-1,
$event.clientX,
$event.clientY
)
">
lyricMenu.showMenuForLyric(
props.line.id,
-1,
$event.clientX,
$event.clientY
)
">
<NCheckbox :checked="props.line.selected" @click="
lyric.lyrics[props.line.id].selected = !lyric.lyrics[props.line.id].selected
"/>
lyric.lyrics[props.line.id].selected = !lyric.lyrics[props.line.id].selected
"/>
<div style="min-width: 3em; text-align: right">
{{ props.line.id + 1 }}
</div>
<div style="display: flex; flex: 1; gap: 8px; flex-direction: column">
<div style="display: flex; flex: 1; gap: 8px; flex-wrap: wrap">
<Draggable :list="props.line.words" item-key="id" @sort="onSort">
<div style="display: flex; flex: 1; gap: 8px; flex-direction: column; min-width: 0; overflow: hidden;">
<div
:style="settings.uiLayoutMode === UILayoutMode.Advanced ? 'overflow-x: auto; white-space: nowrap;' : 'display: flex; flex: direction: row; align-items: center;'">
<Draggable :list="props.line.words" :style="{
whiteSpace: settings.uiLayoutMode === UILayoutMode.Advanced && 'nowrap'
}" item-key="id" @sort="onSort">
<template #item="{ element }">
<LyricWordEditor :line-index="element.lineIndex" :word="element" :word-index="element.id"/>
</template>
</Draggable>
<NInput ref="inputRef" :placeholder="t('lyricLineEditor.newWordPlaceholder')" :value="editState.newWord" autosize
class="new-word" round
style="min-width: 100px" @change="onAddNewWord" @input="editState.newWord = $event"/>
<NInput v-if="settings.uiLayoutMode === UILayoutMode.Simple" ref="inputRef" :placeholder="t('lyricLineEditor.newWordPlaceholder')"
:value="editState.newWord" autosize round style="min-width: 100px;" @change="onAddNewWord"
@input="editState.newWord = $event"/>
</div>
<div v-if="settings.showTranslateLine">
<NInput :placeholder="t('lyricLineEditor.translateLinePlaceholder')" :value="editState.translateLine"
round
style="min-width: 100px"
@change="lyric.modifyTranslatedLine(props.line.id, editState.translateLine)"
@input="editState.translateLine = $event"/>
round style="min-width: 100px"
@change="lyric.modifyTranslatedLine(props.line.id, editState.translateLine)"
@input="editState.translateLine = $event"/>
</div>
<div v-if="settings.showRomanLine">
<NInput :placeholder="t('lyricLineEditor.romanLinePlaceholder')" :value="editState.romanLine" round
style="min-width: 100px"
@change="lyric.modifyRomanLine(props.line.id, editState.romanLine)" @input="editState.romanLine = $event"/>
style="min-width: 100px" @change="lyric.modifyRomanLine(props.line.id, editState.romanLine)"
@input="editState.romanLine = $event"/>
</div>
</div>
<NIcon v-if="props.line.isBG" color="#1166FF" size="24">
<VideoBackgroundEffect24Filled/>
</NIcon>
<NIcon v-if="props.line.isDuet" color="#63e2b7" size="24">
<TextAlignRight24Filled/>
</NIcon>
<NButton circle quaternary style="margin-left: 4px" @click="lyric.removeLine(props.line.id)">
<NIcon>
<Dismiss12Filled/>
<div>
<NIcon v-if="props.line.isBG" color="#1166FF" size="24">
<VideoBackgroundEffect24Filled/>
</NIcon>
</NButton>
<NIcon v-if="props.line.isDuet" color="#63e2b7" size="24">
<TextAlignRight24Filled/>
</NIcon>
</div>
<div style="display: flex; flex-direction: column;">
<NButton v-if="settings.uiLayoutMode === UILayoutMode.Advanced" circle quaternary style="margin-left: 4px"
@click="lyric.addNewWord(props.line.id, '')">
<NIcon size="24">
<Add24Filled/>
</NIcon>
</NButton>
<NButton circle quaternary style="margin-left: 4px" @click="lyric.removeLine(props.line.id)">
<NIcon size="24">
<Delete24Regular/>
</NIcon>
</NButton>
</div>
</div>
</template>

<script setup lang="tsx">
import {type InputInst, NButton, NCheckbox, NIcon, NInput} from "naive-ui";
import {useEditingLyric, useRightClickLyricLine, useSettings} from "../store";
import {UILayoutMode, useEditingLyric, useRightClickLyricLine, useSettings} from "../store";
import {reactive, ref, watch} from "vue";
import {useI18n} from "vue-i18n";
import Draggable from 'vuedraggable'
import {Dismiss12Filled, TextAlignRight24Filled, VideoBackgroundEffect24Filled} from "@vicons/fluent";
import {Add24Filled, Delete24Regular, TextAlignRight24Filled, VideoBackgroundEffect24Filled} from "@vicons/fluent";
import LyricWordEditor from "./LyricWordEditor.vue";
import type {LyricLineWithId} from "../store/lyric";
Expand Down Expand Up @@ -108,16 +120,15 @@ function onAddNewWord() {
</script>

<style lang="sass" scoped>
.new-word
opacity: 0
transition: opacity 0.2s
&:has(:focus)
opacity: 1
// .new-word
// opacity: 0
// transition: opacity 0.2s
@media screen and (max-width: 768px)
opacity: 1
// &:has(:focus)
// opacity: 1
// @media screen and (max-width: 768px)
// opacity: 1
.line:hover .new-word
opacity: 1
</style>
155 changes: 130 additions & 25 deletions src/components/LyricWordEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,99 @@
-->

<template>
<input v-if="edit.enable" ref="inputRef" :value="edit.value" class="word"
@blur="onFinishEditWord" @change="onFinishEditWord"
@input="edit.value = ($event.target as HTMLInputElement).value"/>
<button v-else :class="{
word: true,
'white-space': isWhiteSpace,
}" @click="onEditWord" @contextmenu.stop.prevent="
showMenuForLyric(
props.lineIndex,
props.wordIndex,
$event.clientX,
$event.clientY
)
">
{{ displayWord }}
<NTag v-if="!!props.word.emptyBeat" class="empty-beat" size="tiny">
{{ props.word.emptyBeat }}
</NTag>
</button>
<span style="display: inline-block; margin-right: 8px;">
<template v-if="settings.uiLayoutMode === UILayoutMode.Advanced">
<TimeStampInput :value="props.word.startTime" style="background-color: #11FF5522;"
@update:model-value="onEditWordStartTime"/>
<div
style="text-align: center; display: flex; flex-direction: row; align-items: center; justify-content: space-between; margin: 4px 0;">
<div>
<NButton circle quaternary
@click="() => { rightClick.selectedLine = props.lineIndex; rightClick.selectedWord = props.wordIndex; dialogs.splitWord = true; }">
<template #icon>
<NIcon size="20">
<Cut20Filled/>
</NIcon>
</template>
</NButton>
</div>
<div :class="{
'white-space': isWhiteSpace,
}" style="padding: 0.25em;">
<template v-if="edit.enable">
<input v-if="edit.enable" ref="inputRef" :value="edit.value" class="word-advanced"
@blur="onFinishEditWord" @change="onFinishEditWord"
@input="edit.value = ($event.target as HTMLInputElement).value"/>
</template>
<span v-else class="word-preview-advanced" @click="onEditWord">
{{ displayWord }}
</span>
</div>
<div>
<NButton circle quaternary @click="onDeleteWord">
<template #icon>
<NIcon size="20">
<Delete20Regular/>
</NIcon>
</template>
</NButton>
</div>
</div>
<TimeStampInput :value="props.word.endTime" style="background-color: #FF112222;"
@update:model-value="onEditWordEndTime"/>
<div class="word-edit">
<div>
<NIcon size="20">
<Drum/>
</NIcon>
</div>
<NInputNumber :min="0" :step="1" :value="props.word.emptyBeat" placeholder="0"
size="small" style="max-width: 7em" @update:value="onEditEmptyBeat"/>
</div>
</template>
<div v-else>
<input v-if="edit.enable" ref="inputRef" :value="edit.value" class="word" @blur="onFinishEditWord"
@change="onFinishEditWord" @input="edit.value = ($event.target as HTMLInputElement).value"/>
<button v-else :class="{
word: true,
'white-space': isWhiteSpace,
}" @click="onEditWord" @contextmenu.stop.prevent="
rightClick.showMenuForLyric(
props.lineIndex,
props.wordIndex,
$event.clientX,
$event.clientY
)
">
{{ displayWord }}
<NTag v-if="!!props.word.emptyBeat" class="empty-beat" size="tiny">
{{ props.word.emptyBeat }}
</NTag>
</button>
</div>
</span>
</template>

<script setup lang="ts">
import {type InputInst, NTag} from "naive-ui";
import {useEditingLyric, useRightClickLyricLine} from "../store";
import {type InputInst, NButton, NIcon, NInputNumber, NTag} from "naive-ui";
import {Cut20Filled, Delete20Regular} from "@vicons/fluent";
import {Drum} from "@vicons/fa";
import {UILayoutMode, useDialogs, useEditingLyric, useRightClickLyricLine, useSettings} from "../store";
import {computed, nextTick, reactive, ref} from "vue";
import {i18n} from '../i18n';
import type {LyricWord} from "../utils/ttml-types";
import TimeStampInput from "./TimeStampInput.vue";
const inputRef = ref<InputInst | null>(null);
const props = defineProps<{
lineIndex: number;
wordIndex: number;
word: LyricWord;
}>();
const {modifyWord, removeWord} = useEditingLyric();
const {showMenuForLyric} = useRightClickLyricLine();
const dialogs = useDialogs();
const {modifyWord, removeWord, setWordTimeNoLast, setWordEndTime, modifyWordEmptyBeat} = useEditingLyric();
const rightClick = useRightClickLyricLine();
const settings = useSettings();
const isWhiteSpace = computed(() =>
props.word.word.trim().length === 0
Expand Down Expand Up @@ -80,6 +137,18 @@ function onFinishEditWord() {
function onDeleteWord() {
removeWord(props.lineIndex, props.wordIndex);
}
function onEditWordStartTime(time: number) {
setWordTimeNoLast(props.lineIndex, props.wordIndex, time);
}
function onEditWordEndTime(time: number) {
setWordEndTime(props.lineIndex, props.wordIndex, time);
}
function onEditEmptyBeat(value: number) {
modifyWordEmptyBeat(props.lineIndex, props.wordIndex, value);
}
</script>

<style lang="sass">
Expand All @@ -94,12 +163,48 @@ function onDeleteWord() {
border-radius: calc(var(--att-height-medium) / 2)
height: var(--att-height-medium)
&.white-space
opacity: 0.5
.word-preview-advanced
flex: 1
border-radius: calc(var(--att-height-medium) / 8)
padding: 0.25em 0.5em
&:hover
background: var(--att-theme-color)
color: var(--n-foreground-color)
height: 100%
.word-advanced
margin-right: 8px
padding: 4px 12px
border: 1px solid var(--att-border-color)
border-radius: calc(var(--att-height-medium) / 8)
color: var(--n-text-color)
cursor: text
//white-space: pre-wrap
background: transparent
min-width: 0
flex: 1
height: 100%
.white-space
opacity: 0.5
input.word
cursor: unset
button.word:hover
border: 1px solid var(--att-theme-color)
.word-edit
width: 100%
display: grid
padding: 0 1em
padding-top: 0.5em
grid-template-columns: auto auto
gap: 8px
align-items: center
> *:nth-child(2n)
justify-self: flex-end
min-width: 4em
</style>

0 comments on commit a42a4f3

Please sign in to comment.