Skip to content

Commit

Permalink
增加纯文本导入功能
Browse files Browse the repository at this point in the history
  • Loading branch information
Steve-xmh committed Aug 23, 2023
1 parent 84dc769 commit 93e21da
Show file tree
Hide file tree
Showing 13 changed files with 427 additions and 11 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@ungap/structured-clone": "^1.2.0",
"@vicons/fluent": "^0.12.0",
"@vue/tsconfig": "^0.4.0",
"codemirror": "^6.0.1",
"fflate": "^0.8.0",
"jieba-wasm": "^0.0.2",
"jss": "^10.10.0",
Expand All @@ -41,6 +42,7 @@
"sass": "^1.64.1",
"save-file": "^2.3.1",
"vue": "^3.3.4",
"vue-codemirror": "^6.1.1",
"vue-i18n": "9",
"vue-virtual-scroller": "next",
"vuedraggable": "next"
Expand Down
2 changes: 2 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
<TutorialModal />
<ProgressOverlay />
<UploadDBDialog />
<ImportPlainTextModal />
<ServiceWorkerUpdater v-if="enableSW" />
<!-- <SplitWordModal /> -->
</NNotificationProvider>
Expand All @@ -74,6 +75,7 @@ import ContextMenu from "./components/ContextMenu.vue";
import UploadDBDialog from "./components/modals/UploadDBDialog.vue";
import SplitWordModal from "./components/modals/SplitWordModal.vue";
import ServiceWorkerUpdater from "./components/ServiceWorkerUpdater.vue";
import ImportPlainTextModal from "./components/modals/ImportPlainTextModal.vue";
const LyricEditor = defineAsyncComponent(() => import("./components/LyricEditor.vue"));
const LyricSyncEditor = defineAsyncComponent(() => import("./components/LyricSyncEditor.vue"));
const AMLLPreviewView = defineAsyncComponent(() => import("./components/AMLLPreviewView.vue"));
Expand Down
11 changes: 7 additions & 4 deletions src/components/LyricLineEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@
<LyricWordEditor :line-index="element.lineIndex" :word="element.word" :word-index="element.id" />
</template>
</Draggable>
<NInput class="new-word" round autosize ref="inputRef" :placeholder="t('lyricLineEditor.newWordPlaceholder')" :value="editState.newWord"
<NInput class="new-word" round autosize ref="inputRef"
:placeholder="t('lyricLineEditor.newWordPlaceholder')" :value="editState.newWord"
@input="editState.newWord = $event" @change="onAddNewWord" style="min-width: 100px" />
</div>
<div v-if="settings.showTranslateLine">
<NInput round :placeholder="t('lyricLineEditor.translateLinePlaceholder')" :value="editState.translateLine" @input="editState.translateLine = $event"
<NInput round :placeholder="t('lyricLineEditor.translateLinePlaceholder')" :value="editState.translateLine"
@input="editState.translateLine = $event"
@change="lyric.modifyTranslatedLine(props.line.id, editState.translateLine)" style="min-width: 100px" />
</div>
<div v-if="settings.showRomanLine">
<NInput round :placeholder="t('lyricLineEditor.romanLinePlaceholder')" :value="editState.romanLine" @input="editState.romanLine = $event"
<NInput round :placeholder="t('lyricLineEditor.romanLinePlaceholder')" :value="editState.romanLine"
@input="editState.romanLine = $event"
@change="lyric.modifyRomanLine(props.line.id, editState.romanLine)" style="min-width: 100px" />
</div>
</div>
Expand Down Expand Up @@ -64,7 +67,7 @@ const editState = reactive({
romanLine: props.line.romanLyric,
});
const settings = useSettings();
const { t } = useI18n();
const { t } = useI18n({ useScope: "global" });
const inputRef = ref<InputInst | null>(null);
function onSort(e: CustomEvent & {
Expand Down
8 changes: 7 additions & 1 deletion src/components/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const settings = useSettings();
const aboutModalOpened = ref(false);
const notify = useNotification();
const dialogs = useDialogs();
const { t } = useI18n();
const { t } = useI18n({ useScope: "global" });
const MENU = ref({
file: [
Expand All @@ -104,6 +104,8 @@ const MENU = ref({
// { type: 'divider' },
{
label: t('topBar.menu.importLyric'), key: 'import-from', children: [{
label: t('topBar.menu.importLyricFromText'), key: 'import-from-text',
}, {
label: t('topBar.menu.importLyricFromLrc'), key: 'import-from-lrc',
}, {
label: t('topBar.menu.importLyricFromYrc'), key: 'import-from-yrc',
Expand Down Expand Up @@ -187,6 +189,10 @@ function onSelectMenu(key: string) {
fileDialog.remove();
break;
}
case "import-from-text": {
dialogs.importFromText = true;
break;
}
case "import-from-lrc": {
const fileDialog = document.createElement("input");
fileDialog.type = "file";
Expand Down
223 changes: 223 additions & 0 deletions src/components/modals/ImportPlainTextModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
<template>
<NModal preset="card" @close="dialogs.importFromText = false"
:class="{ 'import-plain-text-modal': true, 'fullscreen': inputs.fullscreen }"
content-style="display:flex;gap:16px;align-items:stretch;max-height:100%;overflow:hidden;" :show="dialogs.importFromText" transform-origin="center"
:title="t('importPlainTextModal.title')">
<template #header-extra>
<NButton @click="inputs.fullscreen = !inputs.fullscreen" quaternary circle>
<NIcon size="18">
<FullScreenMaximize16Filled />
</NIcon>
</NButton>
</template>
<div class="import-plain-text-editor">
<Codemirror v-model:model-value="inputs.textContent" :placeholder="t('importPlainTextModal.textPlaceholder')" style="height: 100%" />
</div>
<div class="import-plain-text-options">
<div>
<i18n-t keypath="importPlainTextModal.importMode" />
<NSelect v-model:value="inputs.importMode" :options="importModeOptions" />
<i18n-t keypath="importPlainTextModal.lyricSplitMode" />
<NSelect :disabled="inputs.importMode === 'lyric-only'" v-model:value="inputs.lyricSplitMode"
:options="lyricSplitModeOptions" />
<i18n-t keypath="importPlainTextModal.sameLineSeparator" />
<NInput :disabled="inputs.importMode === 'lyric-only' || inputs.lyricSplitMode === 'interleaved-line'"
v-model:value="inputs.sameLineSeparator"
:placeholder="t('importPlainTextModal.sameLineSeparatorPlaceholder')" />
<i18n-t keypath="importPlainTextModal.swapTransAndRoman" />
<NCheckbox :disabled="inputs.importMode === 'lyric-only' || inputs.importMode === 'lyric-with-translation'"
v-model:checked="inputs.swapTransAndRoman" style="justify-self: flex-end;" />
<i18n-t keypath="importPlainTextModal.wordSeparator" />
<NInput v-model:value="inputs.wordSeparator"
:placeholder="t('importPlainTextModal.wordSeparatorPlaceholder')" />
</div>
<div>
<NButton type="primary" @click="importLyric">
<i18n-t keypath="importPlainTextModal.importBtn" />
</NButton>
</div>
</div>
</NModal>
</template>

<script setup lang="ts">
import { NModal, NInput, NIcon, NButton, NSelect, useNotification, type SelectOption, NCheckbox } from 'naive-ui';
import { FullScreenMaximize16Filled } from "@vicons/fluent";
import { useEditingLyric, useDialogs } from '../../store';
import { useI18n } from "vue-i18n";
import { reactive } from "vue";
import { Codemirror } from 'vue-codemirror'
import type { LyricLine } from '../../store/lyric';
const lyric = useEditingLyric();
const dialogs = useDialogs();
const { t } = useI18n({ useScope: "global" });
const inputs = reactive({
textContent: "",
fullscreen: false,
importMode: "lyric-only",
lyricSplitMode: "interleaved-line",
sameLineSeparator: "|",
swapTransAndRoman: false,
wordSeparator: "\\",
});
const importModeOptions: SelectOption[] = [{
label: t("importPlainTextModal.lyricOnly"),
value: "lyric-only",
}, {
label: t("importPlainTextModal.lyricWithTranslation"),
value: "lyric-with-translation",
}, {
label: t("importPlainTextModal.lyricWithTranslationAndRoman"),
value: "lyric-with-translation-and-roman",
}];
const lyricSplitModeOptions: SelectOption[] = [{
label: t("importPlainTextModal.interleavedLine"),
value: "interleaved-line",
}, {
label: t("importPlainTextModal.sameLineWithSeparator"),
value: "same-line-separator",
}];
function importLyric() {
const origLines = inputs.textContent.split("\n");
const result: LyricLine[] = [];
function addLine(orig = "", trans = "", roman = "") {
result.push({
words: [{
word: orig,
startTime: 0,
endTime: 0,
}],
translatedLyric: trans,
romanLyric: roman,
isBG: false,
isDuet: false,
selected: false,
});
}
function addAsLyricOnly() {
for (const origLine of origLines) {
addLine(origLine);
}
}
switch (inputs.importMode) {
case "lyric-with-translation": {
switch (inputs.lyricSplitMode) {
case "interleaved-line": {
for (let i = 0; i < origLines.length; i += 2) {
addLine(origLines[i], origLines[i + 1]);
}
break;
}
case "same-line-separator": {
if (inputs.sameLineSeparator === "") {
addAsLyricOnly();
} else {
for (const origLine of origLines) {
const [orig, trans] = origLine.split(inputs.sameLineSeparator);
addLine(orig, trans);
}
}
break;
}
default:
return;
}
break;
}
case "lyric-with-translation-and-roman": {
switch (inputs.lyricSplitMode) {
case "interleaved-line": {
for (let i = 0; i < origLines.length; i += 3) {
addLine(origLines[i], origLines[i + 1], origLines[i + 2]);
}
break;
}
case "same-line-separator": {
if (inputs.sameLineSeparator === "") {
addAsLyricOnly();
} else {
for (const origLine of origLines) {
const [orig, trans, roman] = origLine.split(inputs.sameLineSeparator);
addLine(orig, trans, roman);
}
}
break;
}
default:
return;
}
break;
}
case "lyric-only":
addAsLyricOnly();
break;
default:
return;
}
if (inputs.swapTransAndRoman) {
result.forEach(line => {
[line.romanLyric, line.translatedLyric] = [line.translatedLyric, line.romanLyric];
});
}
if (inputs.wordSeparator.length > 0) {
result.forEach(line => {
const wholeLine = line.words.map(v => v.word).join("");
line.words = wholeLine.split(inputs.wordSeparator).map(word => ({
word,
startTime: 0,
endTime: 0,
}));
});
}
lyric.lyrics = result;
lyric.record();
dialogs.importFromText = false;
}
</script>

<style lang="sass">
.import-plain-text-modal
max-width: max(800px, 80vw)
min-height: min(100vh, 500px)
max-height: 100vh
&.fullscreen
max-width: unset
min-height: unset
width: 100vw
height: 100vh
.import-plain-text-editor
flex: 2
min-width: 0
border: solid 1px #4444
border-radius: 4px
position: relative
overflow: hidden
*
font-family: 'Fira Code', 'PingFang SC', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', 'Courier New', Courier, monospace
.import-plain-text-options
flex: 2
display: flex
flex-direction: column
justify-content: space-between
> *:last-child
align-self: flex-end
> *:first-child
display: grid
grid-template-columns: auto auto
grid-template-rows: auto auto auto
align-items: center
max-width: 100%
gap: 8px
</style>
2 changes: 1 addition & 1 deletion src/components/modals/ProgressOverlay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ import { storeToRefs } from "pinia";
import { useI18n } from "vue-i18n";
const { currentProgresses } = storeToRefs(useProgress());
const { t } = useI18n();
const { t } = useI18n({ useScope: "global" });
</script>
2 changes: 1 addition & 1 deletion src/components/modals/SplitWordModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { useI18n } from "vue-i18n";
const lyric = useEditingLyric();
const notify = useNotification();
const dialogs = useDialogs();
const { t } = useI18n();
const { t } = useI18n({ useScope: "global" });
const submitData = reactive({
name: "",
Expand Down
2 changes: 1 addition & 1 deletion src/components/modals/TutorialModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,6 @@ import { useSettings } from '../../store';
import { useI18n } from "vue-i18n";
const settings = useSettings();
const { t } = useI18n();
const { t } = useI18n({ useScope: "global" });
</script>
2 changes: 1 addition & 1 deletion src/components/modals/UploadDBDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import { useI18n } from "vue-i18n";
const lyric = useEditingLyric();
const notify = useNotification();
const dialogs = useDialogs();
const { t } = useI18n();
const { t } = useI18n({ useScope: "global" });
const submitData = reactive({
name: "",
Expand Down
2 changes: 1 addition & 1 deletion src/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createI18n } from "vue-i18n";
import type { DefineLocaleMessage } from "vue-i18n";
import { zhCN } from "./zh-cn";
import { enUS } from "./en-us";

Expand All @@ -18,6 +17,7 @@ export type LocateMessage = FullPartial<BaseSchema>;
export const i18n = createI18n<[typeof zhCN]>({
legacy: false,
globalInjection: true,
silentFallbackWarn: true,
locale: navigator.language,
fallbackLocale: [...navigator.languages, "zh-CN"],
messages: {
Expand Down
Loading

0 comments on commit 93e21da

Please sign in to comment.