forked from VOICEVOX/voicevox
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
書き出しファイル名をカスタマイズ可能にする (VOICEVOX#789)
* 設定値の型等に項目追加 * 与えたパターンからファイル名が決定できるようにした * 値を取ってくるのとファイル名のために加工するメソッドを分ける * utilityへ移動 * ツールチップが特定条件でしか出てこなかったのを修正 * コンポーネントの実装 * スタイルの調整 * 重複し得るファイル名指定で一括出力した場合に上書きされないようにする * 日付も挿入可能にする * デバッグ出力の削除 * 連番を必須にしつつコンポーネントを多少リファクタ * テストの修正 * Revert "重複し得るファイル名指定で一括出力した場合に上書きされないようにする" This reverts commit 8a1be6b. * サンプルテキストの修正 * テキストにタグ文字列を含めたとき悪いことができないようにする * 変数名などのリファクタ * 別名つけただけの型を削除 * 表示される文言の調整 * 出力ファイル名に$を使えなくしてマッチ対象の問題をちょっと強引に解決 * nextTickを使う * 日付がyyyymmddじゃなかった問題を修正 * 型定義をシンプルに * styleNameタグにかっこを含めない * 初回に設定ダイアログを開いたときデフォルト値をフォームに入れるように
- Loading branch information
Showing
8 changed files
with
407 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
<template> | ||
<q-dialog | ||
:model-value="openDialog" | ||
@update:model-value="updateOpenDialog" | ||
@before-show="initializeInput" | ||
> | ||
<q-card class="q-pa-md dialog-card"> | ||
<q-card-section> | ||
<div class="text-h5">書き出しファイル名パターン</div> | ||
<div class="text-body2 text-grey-8"> | ||
「$キャラ$」のようなタグを使って書き出すファイル名をカスタマイズできます | ||
</div> | ||
</q-card-section> | ||
<q-card-actions class="setting-card q-px-md q-py-sm"> | ||
<div class="row full-width justify-between"> | ||
<div class="col"> | ||
<q-input | ||
dense | ||
outlined | ||
bg-color="background-light" | ||
label="ファイル名パターン" | ||
suffix=".wav" | ||
:maxlength="maxLength" | ||
:error="hasError" | ||
:error-message="errorMessage" | ||
v-model="currentFileNamePattern" | ||
ref="patternInput" | ||
> | ||
<template v-slot:after> | ||
<q-btn | ||
label="デフォルトにリセット" | ||
unelevated | ||
color="background-light" | ||
text-color="display-dark" | ||
class="text-no-wrap q-mr-sm" | ||
@click="resetToDefault" | ||
/> | ||
</template> | ||
</q-input> | ||
</div> | ||
</div> | ||
<div class="text-body2 text-ellipsis"> | ||
出力例){{ previewFileName }} | ||
</div> | ||
<div class="row full-width q-my-md"> | ||
<q-btn | ||
v-for="tagString in tagStrings" | ||
:key="tagString" | ||
:label="`$${tagString}$`" | ||
unelevated | ||
color="background-light" | ||
text-color="display-dark" | ||
class="text-no-wrap q-mr-sm" | ||
@click="insertTagToCurrentPosition(`$${tagString}$`)" | ||
/> | ||
</div> | ||
<div class="row full-width justify-end"> | ||
<q-btn | ||
label="キャンセル" | ||
unelevated | ||
color="background-light" | ||
text-color="display-dark" | ||
class="text-no-wrap text-bold q-mr-sm col-2" | ||
@click="updateOpenDialog(false)" | ||
/> | ||
<q-btn | ||
label="確定" | ||
unelevated | ||
color="background-light" | ||
text-color="display-dark" | ||
class="text-no-wrap text-bold q-mr-sm col-2" | ||
:disable="hasError" | ||
@click="submit" | ||
/> | ||
</div> | ||
</q-card-actions> | ||
</q-card> | ||
</q-dialog> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent, computed, ref, nextTick } from "vue"; | ||
import { QInput } from "quasar"; | ||
import { useStore } from "@/store"; | ||
import { | ||
buildFileNameFromRawData, | ||
DEFAULT_FILE_NAME_TEMPLATE, | ||
replaceTagIdToTagString, | ||
sanitizeFileName, | ||
} from "@/store/utility"; | ||
export default defineComponent({ | ||
name: "FileNamePatternDialog", | ||
props: { | ||
openDialog: Boolean, | ||
}, | ||
emits: ["update:openDialog"], | ||
setup(props, context) { | ||
const updateOpenDialog = (isOpen: boolean) => | ||
context.emit("update:openDialog", isOpen); | ||
const store = useStore(); | ||
const patternInput = ref<QInput>(); | ||
const maxLength = 128; | ||
const tagStrings = Object.values(replaceTagIdToTagString); | ||
const savingSetting = computed(() => store.state.savingSetting); | ||
const currentFileNamePattern = ref(savingSetting.value.fileNamePattern); | ||
const sanitizedFileNamePattern = computed(() => | ||
sanitizeFileName(currentFileNamePattern.value) | ||
); | ||
const hasInvalidChar = computed( | ||
() => currentFileNamePattern.value !== sanitizedFileNamePattern.value | ||
); | ||
const hasNotIndexTagString = computed( | ||
() => | ||
!currentFileNamePattern.value.includes(replaceTagIdToTagString["index"]) | ||
); | ||
const invalidChar = computed(() => { | ||
if (!hasInvalidChar.value) return ""; | ||
const a = currentFileNamePattern.value; | ||
const b = sanitizedFileNamePattern.value; | ||
let diffAt = ""; | ||
for (let i = 0; i < a.length; i++) { | ||
if (b[i] !== a[i]) { | ||
diffAt = a[i]; | ||
break; | ||
} | ||
} | ||
return diffAt; | ||
}); | ||
const errorMessage = computed(() => { | ||
if (currentFileNamePattern.value.length === 0) { | ||
return "何か入力してください"; | ||
} | ||
const result: string[] = []; | ||
if (invalidChar.value !== "") { | ||
result.push( | ||
`使用できない文字が含まれています:「${invalidChar.value}」` | ||
); | ||
} | ||
if (previewFileName.value.includes("$")) { | ||
result.push(`不正なタグが存在するか、$が単体で含まれています`); | ||
} | ||
if (hasNotIndexTagString.value) { | ||
result.push(`$${replaceTagIdToTagString["index"]}$は必須です`); | ||
} | ||
return result.join(", "); | ||
}); | ||
const hasError = computed(() => errorMessage.value !== ""); | ||
const previewFileName = computed(() => | ||
buildFileNameFromRawData(currentFileNamePattern.value + ".wav") | ||
); | ||
const removeExtension = (str: string) => { | ||
return str.replace(/\.wav$/, ""); | ||
}; | ||
const initializeInput = () => { | ||
const pattern = savingSetting.value.fileNamePattern; | ||
currentFileNamePattern.value = removeExtension(pattern); | ||
if (currentFileNamePattern.value.length === 0) { | ||
currentFileNamePattern.value = removeExtension( | ||
DEFAULT_FILE_NAME_TEMPLATE | ||
); | ||
} | ||
}; | ||
const resetToDefault = () => { | ||
currentFileNamePattern.value = removeExtension( | ||
DEFAULT_FILE_NAME_TEMPLATE | ||
); | ||
patternInput.value?.focus(); | ||
}; | ||
const insertTagToCurrentPosition = (tag: string) => { | ||
const elem = patternInput.value?.getNativeElement() as HTMLInputElement; | ||
if (elem) { | ||
const text = elem.value; | ||
if (text.length + tag.length > maxLength) { | ||
return; | ||
} | ||
const from = elem.selectionStart ?? 0; | ||
const to = elem.selectionEnd ?? 0; | ||
const newText = text.substring(0, from) + tag + text.substring(to); | ||
currentFileNamePattern.value = newText; | ||
// キャレットの位置を挿入した後の位置にずらす | ||
nextTick(() => { | ||
elem.selectionStart = from + tag.length; | ||
elem.selectionEnd = from + tag.length; | ||
elem.focus(); | ||
}); | ||
} | ||
}; | ||
const submit = async () => { | ||
await store.dispatch("SET_SAVING_SETTING", { | ||
data: { | ||
...savingSetting.value, | ||
fileNamePattern: currentFileNamePattern.value + ".wav", | ||
}, | ||
}); | ||
updateOpenDialog(false); | ||
}; | ||
return { | ||
patternInput, | ||
tagStrings, | ||
maxLength, | ||
updateOpenDialog, | ||
resetToDefault, | ||
insertTagToCurrentPosition, | ||
submit, | ||
initializeInput, | ||
savingSetting, | ||
currentFileNamePattern, | ||
errorMessage, | ||
hasError, | ||
previewFileName, | ||
}; | ||
}, | ||
}); | ||
</script> | ||
|
||
<style scoped lang="scss"> | ||
@use '@/styles/colors' as colors; | ||
.setting-card { | ||
width: 100%; | ||
min-width: 475px; | ||
background: colors.$setting-item; | ||
} | ||
.text-ellipsis { | ||
white-space: nowrap; | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
} | ||
.dialog-card { | ||
width: 700px; | ||
max-width: 80vw; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.