From 7d94c31b7ef25d4c81b3c39e2e49d38b7db6791a Mon Sep 17 00:00:00 2001 From: Nanashi Date: Thu, 28 Mar 2024 10:12:37 +0900 Subject: [PATCH] =?UTF-8?q?buffer/2024-03-24=EF=BC=9A=E4=B8=8A=E6=B5=81?= =?UTF-8?q?=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=82=8B=20(#47)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: UNDOの引数を追従 * Change: ブラウザ版っぽく * Change: コメントを合わせる * Fix: 細かいところを修正 * Change: on Browser versionを消す * Fix: 型エラーを修正 * Delete: 使われてない定数を削除 --- android/app/build.gradle | 6 +- .../java/jp/hiroshiba/voicevox/CorePlugin.kt | 7 +- src/backend/mobile/capacitorConfig.ts | 52 +++ src/backend/mobile/electronMock.ts | 255 -------------- src/backend/mobile/engine/index.ts | 8 +- src/backend/mobile/index.ts | 3 - src/backend/mobile/plugin.ts | 11 +- src/backend/mobile/preload.ts | 8 + src/backend/mobile/sandbox.ts | 311 ++++++++++++++++++ src/components/MobileHeaderBar.vue | 4 +- src/index.html | 2 +- src/infrastructures/EngineConnector.ts | 2 +- src/main.ts | 10 - vite.config.ts | 30 +- 14 files changed, 395 insertions(+), 314 deletions(-) create mode 100644 src/backend/mobile/capacitorConfig.ts delete mode 100644 src/backend/mobile/electronMock.ts delete mode 100644 src/backend/mobile/index.ts create mode 100644 src/backend/mobile/preload.ts create mode 100644 src/backend/mobile/sandbox.ts diff --git a/android/app/build.gradle b/android/app/build.gradle index 7088395020..c2bcc3ce28 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -75,9 +75,9 @@ dependencies { // TODO: ちゃんと公開されたらそれに置き換える implementation urlZipFile( - "voicevoxcore-android_0.15.0-preview.15", - "jp/hiroshiba/voicevoxcore/voicevoxcore-android/0.15.0-preview.15/voicevoxcore-android-0.15.0-preview.15.aar", - "https://github.com/VOICEVOX/voicevox_core/releases/download/0.15.0-preview.15/java_packages.zip" + "voicevoxcore-android_0.15.0-preview.16", + "jp/hiroshiba/voicevoxcore/voicevoxcore-android/0.15.0-preview.16/voicevoxcore-android-0.15.0-preview.16.aar", + "https://github.com/VOICEVOX/voicevox_core/releases/download/0.15.0-preview.16/java_packages.zip" ) // https://mvnrepository.com/artifact/com.google.code.gson/gson diff --git a/android/app/src/main/java/jp/hiroshiba/voicevox/CorePlugin.kt b/android/app/src/main/java/jp/hiroshiba/voicevox/CorePlugin.kt index cfe4dde3ea..34f984dac0 100644 --- a/android/app/src/main/java/jp/hiroshiba/voicevox/CorePlugin.kt +++ b/android/app/src/main/java/jp/hiroshiba/voicevox/CorePlugin.kt @@ -89,7 +89,7 @@ class CorePlugin : Plugin() { return } vvms.sortWith(compareBy { - it.name.split(".")[0].length + it.name.split(".")[0].toInt() }) voiceModels = vvms.map { VoiceModel(it.absolutePath) @@ -102,6 +102,7 @@ class CorePlugin : Plugin() { tempDir.mkdirs() Os.setenv("TMPDIR", tempDir.absolutePath, true) + Log.i("CorePlugin", "Ready") call.resolve() } catch (e: Exception) { call.reject(e.message) @@ -287,6 +288,10 @@ class CorePlugin : Plugin() { try { val words = gson.fromJson(wordsJson, Array::class.java).asList() + if (words.isEmpty()) { + call.resolve() + return + } val userDict = UserDict() words.forEach { word -> userDict.addWord(word) diff --git a/src/backend/mobile/capacitorConfig.ts b/src/backend/mobile/capacitorConfig.ts new file mode 100644 index 0000000000..c9c2af8582 --- /dev/null +++ b/src/backend/mobile/capacitorConfig.ts @@ -0,0 +1,52 @@ +import AsyncLock from "async-lock"; +import { Preferences } from "@capacitor/preferences"; +import { defaultEngine } from "@/backend/browser/contract"; + +import { BaseConfigManager, Metadata } from "@/backend/common/ConfigManager"; +import { ConfigType, EngineId, engineSettingSchema } from "@/type/preload"; + +let configManager: MobileConfigManager | undefined; +const entryKey = `${import.meta.env.VITE_APP_NAME}-config`; + +const configManagerLock = new AsyncLock(); +const defaultEngineId = EngineId(defaultEngine.uuid); + +export async function getConfigManager() { + await configManagerLock.acquire("configManager", async () => { + if (!configManager) { + configManager = new MobileConfigManager(); + await configManager.initialize(); + } + }); + + if (!configManager) { + throw new Error("configManager is undefined"); + } + + return configManager; +} + +class MobileConfigManager extends BaseConfigManager { + protected getAppVersion() { + return import.meta.env.VITE_APP_VERSION; + } + protected async exists() { + return !!(await Preferences.get({ key: entryKey }).then((v) => v.value)); + } + protected async load(): Promise & Metadata> { + const db = await Preferences.get({ key: entryKey }); + return JSON.parse(db.value || "{}"); + } + + protected async save(data: ConfigType & Metadata) { + await Preferences.set({ key: entryKey, value: JSON.stringify(data) }); + } + + protected getDefaultConfig() { + const baseConfig = super.getDefaultConfig(); + baseConfig.engineSettings[defaultEngineId] ??= engineSettingSchema.parse( + {} + ); + return baseConfig; + } +} diff --git a/src/backend/mobile/electronMock.ts b/src/backend/mobile/electronMock.ts deleted file mode 100644 index f6fa449696..0000000000 --- a/src/backend/mobile/electronMock.ts +++ /dev/null @@ -1,255 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable no-console */ -import { SplashScreen } from "@capacitor/splash-screen"; -import { - defaultHotkeySettings, - defaultToolbarButtonSetting, - configSchema, - EngineId, - EngineInfo, - engineSettingSchema, - Sandbox, - ThemeConf, -} from "@/type/preload"; - -declare const __availableThemes: ThemeConf[]; - -const storeName = "voicevox"; - -const engineInfos: EngineInfo[] = [ - { - executionArgs: [], - executionEnabled: false, - executionFilePath: "", - host: "core", - name: "VOICEVOX Engine", - type: "default", - uuid: EngineId("074fc39e-678b-4c13-8916-ffca8d505d1d"), - }, -]; - -const loadMock = () => { - const electronMock: Sandbox = { - async getAppInfos() { - return { - name: "VOICEVOX Web", - version: "0.0.0", - }; - }, - async getHowToUseText() { - return await fetch("/howtouse.md").then((res) => res.text()); - }, - async getPolicyText() { - return await fetch("/policy.md").then((res) => res.text()); - }, - async getOssLicenses() { - return await fetch("/licenses.json").then((res) => res.json()); - }, - async getUpdateInfos() { - return await fetch("/updateInfos.json").then((res) => res.json()); - }, - async getOssCommunityInfos() { - return await fetch("/ossCommunityInfos.md").then((res) => res.text()); - }, - async getQAndAText() { - return await fetch("/qAndA.md").then((res) => res.text()); - }, - async getContactText() { - return await fetch("/contact.md").then((res) => res.text()); - }, - async getPrivacyPolicyText() { - return await fetch("/privacyPolicy.md").then((res) => res.text()); - }, - async showAudioSaveDialog(obj) { - throw new Error(`Not implemented: showAudioSaveDialog ${obj}`); - }, - async showTextSaveDialog(obj) { - throw new Error(`Not implemented: showTextSaveDialog ${obj}`); - }, - async showVvppOpenDialog(obj) { - throw new Error(`Not implemented: showVvppOpenDialog ${obj}`); - }, - async showOpenDirectoryDialog(obj) { - throw new Error(`Not implemented: showOpenDirectoryDialog ${obj}`); - }, - async showProjectSaveDialog(obj) { - throw new Error(`Not implemented: showProjectSaveDialog ${obj}`); - }, - async showProjectLoadDialog(obj) { - throw new Error(`Not implemented: showProjectLoadDialog ${obj}`); - }, - async showMessageDialog(obj) { - throw new Error(`Not implemented: showMessageDialog ${obj}`); - }, - async showQuestionDialog(obj) { - throw new Error(`Not implemented: showQuestionDialog ${obj}`); - }, - async showImportFileDialog(obj) { - throw new Error(`Not implemented: showImportFileDialog ${obj}`); - }, - showSaveDirectoryDialog(obj) { - throw new Error(`Not implemented: showSaveDirectoryDialog ${obj}`); - }, - async writeFile(obj) { - throw new Error(`Not implemented: writeFile ${obj}`); - }, - async readFile(obj) { - throw new Error(`Not implemented: readFile ${obj}`); - }, - async isAvailableGPUMode() { - return false; - }, - async isMaximizedWindow() { - return false; - }, - async onReceivedIPCMsg(channel, listener) { - window.addEventListener("message", (event) => { - if (event.data.channel === channel) { - listener(event.data.args); - } - }); - }, - async closeWindow() { - throw new Error("Not implemented: closeWindow"); - }, - async minimizeWindow() { - throw new Error("Not implemented: minimizeWindow"); - }, - async maximizeWindow() { - throw new Error("Not implemented: maximizeWindow"); - }, - async logError(...params) { - console.error(...params); - }, - async logWarn(...params) { - console.warn(...params); - }, - async logInfo(...params) { - console.info(...params); - }, - async engineInfos() { - return engineInfos; - }, - async restartEngine(engineId) { - throw new Error(`Not implemented: restartEngine ${engineId}`); - }, - async openEngineDirectory(engineId) { - throw new Error(`Not implemented: openEngineDirectory ${engineId}`); - }, - async hotkeySettings(newData) { - if (newData != undefined) { - const hotkeySettings = await this.getSetting("hotkeySettings"); - const hotkeySetting = hotkeySettings.find( - (hotkey) => hotkey.action == newData.action - ); - if (hotkeySetting != undefined) { - hotkeySetting.combination = newData.combination; - } - await this.setSetting("hotkeySettings", hotkeySettings); - } - return this.getSetting("hotkeySettings"); - }, - async checkFileExists(file) { - return false; - }, - async changePinWindow() { - throw new Error("Not implemented: changePinWindow"); - }, - async getDefaultHotkeySettings() { - return defaultHotkeySettings; - }, - async getDefaultToolbarSetting() { - return defaultToolbarButtonSetting; - }, - setNativeTheme(source) { - // 何もしない - }, - async theme(newData) { - if (newData) { - this.setSetting("currentTheme", newData); - return; - } - return { - currentTheme: await this.getSetting("currentTheme"), - availableThemes: __availableThemes, - }; - }, - vuexReady() { - requestAnimationFrame(() => { - // 1回だけだと文字の描画が終わっていないので2回待機する - requestAnimationFrame(() => { - SplashScreen.hide(); - }); - }); - }, - getSetting(key) { - const setting = configSchema.parse( - JSON.parse(localStorage.getItem(storeName) || "{}") - ); - // 同期でも使いたいので、async functionではなく手動でPromise.resolveを返す - return Promise.resolve(setting[key]); - }, - setSetting(key, newValue) { - const setting = configSchema.parse( - JSON.parse(localStorage.getItem(storeName) || "{}") - ); - setting[key] = newValue; - localStorage.setItem(storeName, JSON.stringify(setting)); - // 同期でも使いたいので、async functionではなく手動でPromise.resolveを返す - return Promise.resolve(setting[key]); - }, - async setEngineSetting(engineId, engineSetting) { - await this.setSetting("engineSettings", { - ...this.getSetting("engineSettings"), - [engineId]: engineSetting, - }); - return; - }, - async installVvppEngine(path) { - throw new Error(`Not implemented: installVvppEngine ${path}`); - }, - async uninstallVvppEngine(engineId) { - throw new Error(`Not implemented: uninstallVvppEngine ${engineId}`); - }, - async validateEngineDir(engineDir) { - throw new Error(`Not implemented: validateEngineDir ${engineDir}`); - }, - async reloadApp() { - window.location.reload(); - }, - async getAltPortInfos() { - return {}; - }, - async openLogDirectory() { - throw new Error("Not implemented: openLogDirectory"); - }, - }; - - try { - localStorage.setItem( - storeName, - JSON.stringify( - configSchema.parse(JSON.parse(localStorage.getItem(storeName) || "{}")) - ) - ); - } catch (e) { - console.warn("Failed to load store, reset store"); - localStorage.setItem(storeName, JSON.stringify(configSchema.parse({}))); - } - - const engineSettings = JSON.parse( - localStorage.getItem("voicevox_engineSettings") || "{}" - ); - for (const engineInfo of engineInfos) { - if (!engineSettings[engineInfo.uuid]) { - // 空オブジェクトをパースさせることで、デフォルト値を取得する - engineSettings[engineInfo.uuid] = engineSettingSchema.parse({}); - } - } - electronMock.setSetting("engineSettings", engineSettings); - - // @ts-expect-error readonlyなので代入できないが、モックのため問題ない - window.electron = electronMock; -}; - -export default loadMock; diff --git a/src/backend/mobile/engine/index.ts b/src/backend/mobile/engine/index.ts index 980cdf1194..bd61386478 100644 --- a/src/backend/mobile/engine/index.ts +++ b/src/backend/mobile/engine/index.ts @@ -1,4 +1,4 @@ -import { VoicevoxCorePlugin } from "../plugin"; +import { VoicevoxCorePlugin, corePlugin } from "../plugin"; import queryProvider from "./query"; import infoProvider from "./info"; import speakerProvider from "./speaker"; @@ -11,10 +11,8 @@ export type ApiProvider = (deps: { corePlugin: VoicevoxCorePlugin; }) => Partial; -const loadApi = () => { +export const loadApi = () => { api = new DefaultApi(); - const corePlugin = window.plugins?.voicevoxCore; - if (!corePlugin) throw new Error("assert: corePlugin != null"); let isCoreInitialized = false; (async () => { await corePlugin.initialize(); @@ -47,5 +45,3 @@ const loadApi = () => { } ) as DefaultApiInterface; }; - -export default loadApi; diff --git a/src/backend/mobile/index.ts b/src/backend/mobile/index.ts deleted file mode 100644 index 8675796f09..0000000000 --- a/src/backend/mobile/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { default as loadPlugin } from "./plugin"; -export { default as loadMock } from "./electronMock"; -export { default as loadCoreBasedApi, coreBasedApi } from "./engine"; diff --git a/src/backend/mobile/plugin.ts b/src/backend/mobile/plugin.ts index 826f8ce14e..3f74654058 100644 --- a/src/backend/mobile/plugin.ts +++ b/src/backend/mobile/plugin.ts @@ -41,13 +41,4 @@ export type VoicevoxCorePlugin = { useUserDict: (obj: { wordsJson: string }) => Promise; }; -const loadPlugin = () => { - const corePlugin = registerPlugin("VoicevoxCore"); - - // @ts-expect-error 定義時だけは無視する - window.plugins = { - voicevoxCore: corePlugin, - }; -}; - -export default loadPlugin; +export const corePlugin = registerPlugin("VoicevoxCore"); diff --git a/src/backend/mobile/preload.ts b/src/backend/mobile/preload.ts new file mode 100644 index 0000000000..d65bca39b1 --- /dev/null +++ b/src/backend/mobile/preload.ts @@ -0,0 +1,8 @@ +import { loadApi } from "./engine"; +import { api } from "./sandbox"; +import { SandboxKey, Sandbox } from "@/type/preload"; + +const sandbox: Sandbox = api; +// @ts-expect-error readonlyになっているが、初期化処理はここで行うので問題ない +window[SandboxKey] = sandbox; +loadApi(); diff --git a/src/backend/mobile/sandbox.ts b/src/backend/mobile/sandbox.ts new file mode 100644 index 0000000000..5f516ce132 --- /dev/null +++ b/src/backend/mobile/sandbox.ts @@ -0,0 +1,311 @@ +import { SplashScreen } from "@capacitor/splash-screen"; +import { getConfigManager } from "./capacitorConfig"; +import { defaultEngine } from "@/backend/browser/contract"; + +import { IpcSOData } from "@/type/ipc"; +import { + defaultHotkeySettings, + defaultToolbarButtonSetting, + configSchema, + EngineId, + EngineSettingType, + EngineSettings, + HotkeySettingType, + Sandbox, + ThemeConf, +} from "@/type/preload"; +import { + ContactTextFileName, + HowToUseTextFileName, + OssCommunityInfosFileName, + OssLicensesJsonFileName, + PolicyTextFileName, + PrivacyPolicyTextFileName, + QAndATextFileName, + UpdateInfosJsonFileName, +} from "@/type/staticResources"; + +// TODO: base pathを設定できるようにするか、ビルド時埋め込みにする +const toStaticPath = (fileName: string) => `/${fileName}`; + +/** + * スマホ版のSandBox実装 + * src/type/preload.tsのSandboxを変更した場合は、interfaceに追従した変更が必要 + * スマホ版では利用しない場合は、メソッドを追加して throw new Error() する + */ +export const api: Sandbox = { + getAppInfos() { + const appInfo = { + name: import.meta.env.VITE_APP_NAME, + version: import.meta.env.VITE_APP_VERSION, + }; + return Promise.resolve(appInfo); + }, + getHowToUseText() { + return fetch(toStaticPath(HowToUseTextFileName)).then((v) => v.text()); + }, + getPolicyText() { + return fetch(toStaticPath(PolicyTextFileName)).then((v) => v.text()); + }, + getOssLicenses() { + return fetch(toStaticPath(OssLicensesJsonFileName)).then((v) => v.json()); + }, + getUpdateInfos() { + return fetch(toStaticPath(UpdateInfosJsonFileName)).then((v) => v.json()); + }, + getOssCommunityInfos() { + return fetch(toStaticPath(OssCommunityInfosFileName)).then((v) => v.text()); + }, + getQAndAText() { + return fetch(toStaticPath(QAndATextFileName)).then((v) => v.text()); + }, + getContactText() { + return fetch(toStaticPath(ContactTextFileName)).then((v) => v.text()); + }, + getPrivacyPolicyText() { + return fetch(toStaticPath(PrivacyPolicyTextFileName)).then((v) => v.text()); + }, + getAltPortInfos() { + // NOTE: ブラウザ版ではサポートされていません + return Promise.resolve({}); + }, + showAudioSaveDialog(obj: { title: string; defaultPath?: string }) { + return new Promise((resolve, reject) => { + if (obj.defaultPath == undefined) { + reject( + // storeやvue componentからdefaultPathを設定していなかったらrejectされる + new Error( + "ブラウザ版ではファイルの保存機能が一部サポートされていません。" + ) + ); + } else { + resolve(obj.defaultPath); + } + }); + }, + showTextSaveDialog(obj: { title: string; defaultPath?: string }) { + return new Promise((resolve, reject) => { + if (obj.defaultPath == undefined) { + reject( + // storeやvue componentからdefaultPathを設定していなかったらrejectされる + new Error( + "ブラウザ版ではファイルの保存機能が一部サポートされていません。" + ) + ); + } else { + resolve(obj.defaultPath); + } + }); + }, + showSaveDirectoryDialog() { + throw new Error("Not supported: showSaveDirectoryDialog"); + }, + showVvppOpenDialog(obj: { title: string; defaultPath?: string }) { + throw new Error( + `not implemented: showVvppOpenDialog, request: ${JSON.stringify(obj)}` + ); + }, + showOpenDirectoryDialog() { + throw new Error("Not supported: showOpenDirectoryDialog"); + }, + showProjectSaveDialog(obj: { title: string; defaultPath?: string }) { + return new Promise((resolve, reject) => { + if (obj.defaultPath == undefined) { + reject( + // storeやvue componentからdefaultPathを設定していなかったらrejectされる + new Error( + "スマホ版ではファイルの保存機能が一部サポートされていません。" + ) + ); + } else { + resolve(obj.defaultPath); + } + }); + }, + showProjectLoadDialog(/* obj: { title: string } */) { + throw new Error("スマホ版では現在ファイルの読み込みをサポートしていません"); + }, + showMessageDialog(obj: { + type: "none" | "info" | "error" | "question" | "warning"; + title: string; + message: string; + }) { + window.alert(`${obj.title}\n${obj.message}`); + // NOTE: どの呼び出し元も、return valueを使用していないので雑に対応している + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return Promise.resolve({} as any); + }, + showQuestionDialog(obj: { + type: "none" | "info" | "error" | "question" | "warning"; + title: string; + message: string; + buttons: string[]; + cancelId?: number; + defaultId?: number; + }) { + // FIXME + // TODO: 例えば動的にdialog要素をDOMに生成して、それを表示させるみたいのはあるかもしれない + throw new Error( + `Not implemented: showQuestionDialog, request: ${JSON.stringify(obj)}` + ); + }, + showImportFileDialog(/* obj: { title: string } */) { + throw new Error("スマホ版では現在ファイルの読み込みをサポートしていません"); + }, + writeFile(obj: { filePath: string; buffer: ArrayBuffer }) { + throw new Error( + "Not implemented: writeFile, request: " + JSON.stringify(obj) + ); + }, + readFile(/* obj: { filePath: string } */) { + throw new Error("Not implemented: readFile"); + }, + isAvailableGPUMode() { + // TODO: WebAssembly版をサポートする時に実装する + // FIXME: canvasでWebGLから調べたり、WebGPUがサポートされているかを調べたりで判断は出来そう + return Promise.resolve(false); + }, + isMaximizedWindow() { + // NOTE: UIの表示状態の制御のためだけなので固定値を返している + return Promise.resolve(true); + }, + onReceivedIPCMsg( + channel: T, + listener: (_: unknown, ...args: IpcSOData[T]["args"]) => void + ) { + window.addEventListener("message", (event) => { + if (event.data.channel == channel) { + listener(event.data.args); + } + }); + }, + closeWindow() { + throw new Error(`Not supported: closeWindow`); + }, + minimizeWindow() { + throw new Error(`Not supported: minimizeWindow`); + }, + maximizeWindow() { + throw new Error(`Not supported: maximizeWindow`); + }, + /* eslint-disable no-console */ // ログの吐き出し先は console ぐらいしかないので、ここでは特例で許可している + logError(...params: unknown[]) { + console.error(...params); + return; + }, + logWarn(...params: unknown[]) { + console.warn(...params); + return; + }, + logInfo(...params: unknown[]) { + console.info(...params); + return; + }, + openLogDirectory() { + throw new Error(`Not supported: openLogDirectory`); + }, + /* eslint-enable no-console */ + engineInfos() { + return Promise.resolve([defaultEngine]); + }, + restartEngine(/* engineId: EngineId */) { + throw new Error(`Not supported: restartEngine`); + }, + openEngineDirectory(/* engineId: EngineId */) { + throw new Error(`Not supported: openEngineDirectory`); + }, + async hotkeySettings(newData?: HotkeySettingType) { + type HotkeySettingType = ReturnType< + typeof configSchema["parse"] + >["hotkeySettings"]; + if (newData != undefined) { + const hotkeySettings = (await this.getSetting( + "hotkeySettings" + )) as HotkeySettingType; + const hotkeySetting = hotkeySettings.find( + (hotkey) => hotkey.action == newData.action + ); + if (hotkeySetting != undefined) { + hotkeySetting.combination = newData.combination; + } + await this.setSetting("hotkeySettings", hotkeySettings); + } + return this.getSetting("hotkeySettings") as Promise; + }, + checkFileExists() { + throw new Error(`Not supported: checkFileExists`); + }, + changePinWindow() { + throw new Error(`Not supported: changePinWindow`); + }, + getDefaultHotkeySettings() { + return Promise.resolve(defaultHotkeySettings); + }, + getDefaultToolbarSetting() { + return Promise.resolve(defaultToolbarButtonSetting); + }, + setNativeTheme(/* source: NativeThemeType */) { + // TODO: Impl + return; + }, + async theme(newData?: string) { + if (newData != undefined) { + await this.setSetting("currentTheme", newData); + return; + } + // NOTE: Electron版では起動時にテーマ情報が必要なので、 + // この実装とは違って起動時に読み込んだキャッシュを返すだけになっている。 + return Promise.all( + // FIXME: themeファイルのいい感じのパスの設定 + ["/themes/default.json", "/themes/dark.json"].map((url) => + fetch(url).then((res) => res.json()) + ) + ) + .then((v) => ({ + currentTheme: "Default", + availableThemes: v, + })) + .then((v) => + this.getSetting("currentTheme").then( + (currentTheme) => + ({ + ...v, + currentTheme, + } as { currentTheme: string; availableThemes: ThemeConf[] }) + ) + ); + }, + vuexReady() { + SplashScreen.hide(); + }, + async getSetting(key) { + const configManager = await getConfigManager(); + return configManager.get(key); + }, + async setSetting(key, newValue) { + const configManager = await getConfigManager(); + configManager.set(key, newValue); + return newValue; + }, + async setEngineSetting(engineId: EngineId, engineSetting: EngineSettingType) { + const engineSettings = (await this.getSetting( + "engineSettings" + )) as EngineSettings; + engineSettings[engineId] = engineSetting; + await this.setSetting("engineSettings", engineSettings); + return; + }, + installVvppEngine(/* path: string */) { + throw new Error(`Not supported: installVvppEngine`); + }, + uninstallVvppEngine(/* engineId: EngineId */) { + throw new Error(`Not supported: uninstallVvppEngine`); + }, + validateEngineDir(/* engineDir: string */) { + throw new Error(`Not supported: validateEngineDir`); + }, + async reloadApp(obj: { isMultiEngineOffMode: boolean }) { + location.search = `?isMultiEngineOffMode=${obj.isMultiEngineOffMode}`; + location.reload(); + }, +}; diff --git a/src/components/MobileHeaderBar.vue b/src/components/MobileHeaderBar.vue index a1eaf1e76b..ad60c87c9d 100644 --- a/src/components/MobileHeaderBar.vue +++ b/src/components/MobileHeaderBar.vue @@ -57,14 +57,14 @@ const headerButtons = computed(() => [ { icon: "undo", onClick: () => { - store.dispatch("UNDO"); + store.dispatch("UNDO", { editor: "talk" }); }, disable: !canUndo.value, }, { icon: "redo", onClick: () => { - store.dispatch("REDO"); + store.dispatch("REDO", { editor: "talk" }); }, disable: !canRedo.value, }, diff --git a/src/index.html b/src/index.html index 79c0fd92ec..074aeafbe9 100644 --- a/src/index.html +++ b/src/index.html @@ -18,7 +18,7 @@ import { Buffer } from "buffer"; window.Buffer = Buffer; - + diff --git a/src/infrastructures/EngineConnector.ts b/src/infrastructures/EngineConnector.ts index 0f4e36f970..5ef201af07 100644 --- a/src/infrastructures/EngineConnector.ts +++ b/src/infrastructures/EngineConnector.ts @@ -1,4 +1,4 @@ -import { coreBasedApi } from "@/backend/mobile"; +import { coreBasedApi } from "@/backend/mobile/engine"; import { Configuration, DefaultApi, DefaultApiInterface } from "@/openapi"; export interface IEngineConnectorFactory { diff --git a/src/main.ts b/src/main.ts index 51e3e6e770..0cf3e49ad1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,10 +2,8 @@ import { createApp } from "vue"; import { createGtm } from "@gtm-support/vue-gtm"; import { Quasar, Dialog, Loading, Notify } from "quasar"; import iconSet from "quasar/icon-set/material-icons"; -import { Capacitor } from "@capacitor/core"; import { store, storeKey } from "./store"; import { ipcMessageReceiver } from "./plugins/ipcMessageReceiverPlugin"; -import * as mobile from "./backend/mobile"; import { hotkeyPlugin } from "./plugins/hotkeyPlugin"; import App from "@/components/App.vue"; import { markdownItPlugin } from "@/plugins/markdownItPlugin"; @@ -18,14 +16,6 @@ import "./styles/_index.scss"; // ため、それを防止するため自前でdataLayerをあらかじめ用意する window.dataLayer = []; -if (Capacitor.isNativePlatform()) { - // eslint-disable-next-line no-console - console.log("Running in Capacitor"); - mobile.loadMock(); - mobile.loadPlugin(); - mobile.loadCoreBasedApi(); -} - document.body.setAttribute("data-target", import.meta.env.VITE_TARGET); createApp(App) diff --git a/vite.config.ts b/vite.config.ts index 01096c8f3f..e908934820 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,6 @@ /// import path from "path"; -import { rm, readdir, readFile } from "fs/promises"; +import { rm } from "fs/promises"; import treeKill from "tree-kill"; import electron from "vite-plugin-electron"; @@ -13,6 +13,7 @@ import { quasar } from "@quasar/vite-plugin"; const isElectron = process.env.VITE_TARGET === "electron"; const isBrowser = process.env.VITE_TARGET === "browser"; +const isMobile = process.env.VITE_TARGET === "mobile"; export default defineConfig(async (options) => { const packageName = process.env.npm_package_name; @@ -36,19 +37,6 @@ export default defineConfig(async (options) => { const sourcemap: BuildOptions["sourcemap"] = shouldEmitSourcemap ? "inline" : false; - const themes = await readdir(path.resolve(__dirname, "public/themes")).then( - (files) => - Promise.all( - files.map(async (themeFile: string) => { - return JSON.parse( - await readFile( - path.resolve(__dirname, "public/themes", themeFile), - "utf-8" - ) - ); - }) - ) - ); return { root: path.resolve(__dirname, "src"), envDir: __dirname, @@ -85,9 +73,6 @@ export default defineConfig(async (options) => { ], globals: true, }, - define: { - __availableThemes: JSON.stringify(themes), - }, plugins: [ vue(), @@ -129,7 +114,8 @@ export default defineConfig(async (options) => { }, }), ], - isBrowser && injectBrowserPreloadPlugin(), + isBrowser && injectPreloadPlugin("browser"), + isMobile && injectPreloadPlugin("mobile"), ], }; }); @@ -147,15 +133,15 @@ const cleanDistPlugin = (): Plugin => { }; }; -const injectBrowserPreloadPlugin = (): Plugin => { +const injectPreloadPlugin = (backend: string): Plugin => { return { - name: "inject-browser-preload", + name: "inject-preload", transformIndexHtml: { enforce: "pre" as const, transform: (html: string) => html.replace( - "", - `` + "", + `` ), }, };