Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ブラウザでEditorが動作する状態にする #1345

Merged
merged 76 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
50e9842
browser版のベースラインのコードを追加
yamachu Apr 6, 2023
b8092b3
Worker間のmessagingの型を定義
yamachu Jun 10, 2023
991f26d
一旦electronと同様の実装でsandboxのinvoke部分を実装
yamachu Jun 10, 2023
13d92d0
moduleのimportが出来なかったので、Workerの生成方法を変更
yamachu Jun 10, 2023
e96870d
tempファイルは現在使っていなそうなので、もともとあった実装を削除する
yamachu Jun 12, 2023
6b1c904
BrowserでMessagingをする第一歩みたいな実装を入れる
yamachu Jun 12, 2023
bb81c5d
resourcesを取得する処理の実装
yamachu Jun 12, 2023
f215889
呼ばれないものはconsole.error吐き出しておく
yamachu Jun 12, 2023
b280768
固定値を含むThemeの処理の実装
yamachu Jun 12, 2023
ee3eaf9
IndexedDBにsettingsを保存して読めるように
yamachu Jun 12, 2023
2b4fcc1
Themeはimplの方でsettingsから引っ張って反映させる
yamachu Jun 13, 2023
777e2f8
IndexedDBでの値の持ち方間違えた
yamachu Jun 14, 2023
6b90d5f
postMessageするmessageの型間違えてた
yamachu Jun 14, 2023
a865dd2
使用しない関数もexportしておく
yamachu Jun 14, 2023
b316bca
impl getAltPortInfos
yamachu Jun 14, 2023
ba05df6
IndexedDB直した2
yamachu Jun 14, 2023
a6e3771
openTextEditContextMenuImplは不要そう
yamachu Jun 14, 2023
d2f96ad
DummyでEngineInfoを入れておく
yamachu Jun 14, 2023
0c3d5c2
engineSettingsのmigration
yamachu Jun 14, 2023
3d8b048
Fix Theme impl
yamachu Jun 14, 2023
d993cec
impl HotkeySettings
yamachu Jun 14, 2023
ab044ba
impl getDefault~
yamachu Jun 14, 2023
ffe6e2f
impl log
yamachu Jun 15, 2023
3e47456
impl setEngineSetting
yamachu Jun 15, 2023
152ac30
実装しないもののマークとテンプレートの追加
yamachu Jun 15, 2023
7c9d4fe
schema不整合が起こる入力も可能なことに気づいたので今後対処する
yamachu Jun 15, 2023
3591ddb
window.showSaveFilePickerを使うには型定義が必要だったので追加
yamachu Jun 15, 2023
bc2a811
tsconfigでDialog関係の型を読ませる
yamachu Jun 15, 2023
c992283
dialogやFileIOに関する処理はWebWorkerで行うにはかなり面倒そうだったので、一旦ベタにsandboxに実装
yamachu Jun 15, 2023
dad2a65
IndexedDBへの処理をWorker以外でも行いたいので切り出す
yamachu Jun 15, 2023
a2caf29
Fixedなんちゃらにも対応できるように、directoryHandlerをIndexedDBに保存しておく
yamachu Jun 15, 2023
ce9b626
db周りのお気持ち変えとく
yamachu Jun 15, 2023
5d47d4f
File I/Oに関するものを一旦Workerから排除
yamachu Jun 15, 2023
4839039
実はdbのopenのcostそこまでないんじゃないか読みでインスタンス保持をしないでみる
yamachu Jun 15, 2023
d670c72
debug用途として残してたけど、実装は必要になったタイミングで行うので一旦logの出力を止める
yamachu Jun 15, 2023
d067061
コピペでとりあえず必要になるものを生やす
yamachu Jun 15, 2023
07b54d7
文字列を再度stringifyしていたので、二重に " が出現してしまっていた
yamachu Jun 15, 2023
e577ad6
またコピペでI/O周りが生えた
yamachu Jun 15, 2023
a686ea2
handler -> handle
yamachu Jun 19, 2023
76b6c0f
FileIOに関する処理を別ファイルに切り出し
yamachu Jun 19, 2023
3479b34
いつかやる
yamachu Jun 19, 2023
46158a9
docsにそってメッセージをユーザ向けにする
yamachu Jun 19, 2023
f0f3185
WIP: docs
yamachu Jun 19, 2023
2cc3b36
使用していないSandboxAPIの削除
yamachu Jun 20, 2023
2e403e6
bye debug console.~
yamachu Jun 21, 2023
eecb9fc
logのImplのみconsoleは許容する
yamachu Jun 21, 2023
ae23220
全て握り潰しているのと一緒なので、そのままErrorに落とす
yamachu Jun 21, 2023
7b377bf
実装しないものはcaseから排除する
yamachu Jun 23, 2023
9f01ec8
NativeThemeは対応方法がありそうだったら今後実装する
yamachu Jun 23, 2023
f30d86c
bye worker
yamachu Jun 23, 2023
0df3959
bye backgroundImpl
yamachu Jun 23, 2023
2d40fef
store -> storeImpl
yamachu Jun 23, 2023
fc4561d
設定周りの処理はstoreImplに切り出す
yamachu Jun 23, 2023
efaac6d
serveアクションの統一感を持たせるために、browserをprefixにつける
yamachu Jun 28, 2023
f133472
Sandbox interfaceから参照を探しやすいように、型を直接参照する
yamachu Jul 1, 2023
a783205
window.SandboxKey に代入している値がSandboxの実装であることを明示するために、部分型であること確認する
yamachu Jul 1, 2023
124cd26
わからなかったらとりあえずthrowしてくれを書いておく
yamachu Jul 1, 2023
57203a3
@types/wicg-file-system-access をtsconfigで読ませないで、アプリケーションのglobal型定義ファイ…
yamachu Jul 2, 2023
a3c3fe6
マイグレーション忘れに気づけるようにconsole.errorを吐いておく
yamachu Jul 2, 2023
20d1c1a
migrationとかを行う際にトランザクション的にも単一のjsonObjectとして扱えばやりやすそうかなとなったので、一つのstore…
yamachu Jul 2, 2023
cfe3e3f
directoryHandleを保持する背景を書いておく
yamachu Jul 2, 2023
f038630
write周りの処理はaudio.tsのactionから渡されるfilePathに依存しているので、その旨をコメントに残す
yamachu Jul 2, 2023
d40d430
Update src/browser/fileImpl.ts
yamachu Jul 2, 2023
8f96446
ディレクトリを特定できない単体ファイルに対する処理は一旦サポートしないように
yamachu Jul 3, 2023
e2adadf
readFileは基本的にディレクトリを指定しないので実装ごと一旦消す
yamachu Jul 3, 2023
03cb8d2
意味単位で関数の場所まとめよう
yamachu Jul 3, 2023
30aaa8f
握りつぶす意味なかった
yamachu Jul 3, 2023
d123155
コピペで増やしていたDirectoryHandle周りの処理をまとめる
yamachu Jul 3, 2023
04084e8
rename const name
yamachu Jul 3, 2023
50bd37c
単一ファイルはaタグでダウンロードさせる
yamachu Jul 4, 2023
65d91da
単体ファイルの書き出しは、共通コードで生成されるファイル名を使う
yamachu Jul 8, 2023
b5d9294
実行方法をREADMEに追記
yamachu Jul 8, 2023
34177c4
Merge branch 'main' into browser-compat
yamachu Jul 8, 2023
2db5841
writeFileの型が変わったことへの追従
yamachu Jul 8, 2023
4c0aefe
ファイルの読み込みはサポートしてないし、関連するダイアログは一旦消してしまおう
yamachu Jul 10, 2023
8d45eec
directoryHandleMapって何のために存在するの?を明らかにしておく
yamachu Jul 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ npm run electron:serve

音声合成エンジンのリポジトリはこちらです <https://github.com/VOICEVOX/voicevox_engine>

### ブラウザ版の実行(開発中)

別途音声合成エンジンを起動し、以下を実行して表示された localhost へアクセスします。

```bash
npm run browser:serve
```

## ビルド

```bash
Expand Down
3 changes: 2 additions & 1 deletion docs/コードの歩き方.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,14 @@ TODO
## ソースコードのディレクトリ構成

- src
- background.ts ・・・ 最初に実行されるコード。ウィンドウを表示したり、エンジンを起動したりする。Electron のメインプロセス。
- background.ts ・・・ Electron 版で最初に実行されるコード。ウィンドウを表示したり、エンジンを起動したりする。Electron のメインプロセス。
- main.ts ・・・ ウィンドウを表示するために最初に実行されるコード。ここで Vue や Vuex を組み込む。
- App.vue ・・・ Vue のルートになるコンポーネント。他の全てのコンポーネントの親。
- views ディレクトリ ・・・ 画面全体を覆うような Vue コンポーネントのディレクトリ。
- components ディレクトリ ・・・ UI のパーツになる Vue コンポーネントディレクトリ。
- store ディレクトリ ・・・ Vuex のストアのディレクトリ。アプリのロジックの大半はここに書かれる。
- electron ディレクトリ ・・・ Electron の ipc 通信などのコードが置かれるディレクトリ。
- browser ディレクトリ ・・・ WIP
- type ディレクトリ ・・・ TypeScript 用の型定義などが入るディレクトリ。
- styles ディレクトリ ・・・ CSS や SCSS などのディレクトリ。
- infrastructures ディレクトリ ・・・ UI 用のコードと UI 以外のコードを跨ぐときに一枚かませたいときのためのコードのディレクトリ。
Expand Down
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"electron:build_pnever": "cross-env VITE_IS_ELECTRON=true vite build && electron-builder --config electron-builder.config.js --publish never",
"electron:build_pnever_prepackaged": "cross-var electron-builder --config electron-builder.config.js --publish never --prepackaged $PREPACKAGED",
"electron:serve": "cross-env VITE_IS_ELECTRON=true vite",
"browser:serve": "cross-env VITE_IS_ELECTRON=false VITE_IS_BROWSER=true vite",
"postinstall": "electron-builder install-app-deps && node build/download7z.js",
"postuninstall": "electron-builder install-app-deps",
"license:generate": "node build/generateLicenses.js",
Expand Down Expand Up @@ -82,6 +83,7 @@
"@types/semver": "7.3.9",
"@types/unzipper": "0.10.5",
"@types/uuid": "8.3.4",
"@types/wicg-file-system-access": "2020.9.6",
"@typescript-eslint/eslint-plugin": "5.38.1",
"@typescript-eslint/parser": "5.38.1",
"@vitejs/plugin-vue": "4.0.0",
Expand Down
14 changes: 14 additions & 0 deletions src/browser/contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { EngineInfo, EngineId } from "@/type/preload";

export const defaultEngine: EngineInfo = {
uuid: EngineId("074fc39e-678b-4c13-8916-ffca8d505d1d"),
host: "http://127.0.0.1:50021",
name: "VOICEVOX Engine",
path: undefined,
executionEnabled: false,
executionFilePath: "",
executionArgs: [],
type: "default",
};

export const directoryHandleStoreKey = "directoryHandle";
178 changes: 178 additions & 0 deletions src/browser/fileImpl.ts
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { sep } from "path";
import { directoryHandleStoreKey } from "./contract";
import { openDB } from "./storeImpl";
import { SandboxKey } from "@/type/preload";
import { failure, success } from "@/type/result";

const storeDirectoryHandle = async (
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved
directoryHandle: FileSystemDirectoryHandle
): Promise<void> => {
const db = await openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(directoryHandleStoreKey, "readwrite");
const store = transaction.objectStore(directoryHandleStoreKey);
const request = store.put(directoryHandle, directoryHandle.name);
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
reject(request.error);
};
});
};

const fetchStoredDirectoryHandle = async (maybeDirectoryHandleName: string) => {
const db = await openDB();
return new Promise<FileSystemDirectoryHandle | undefined>(
(resolve, reject) => {
const transaction = db.transaction(directoryHandleStoreKey, "readonly");
const store = transaction.objectStore(directoryHandleStoreKey);
const request = store.get(maybeDirectoryHandleName);
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = () => {
reject(request.error);
};
}
);
};

// ディレクトリ名をキーとして、Write権限を持ったFileSystemDirectoryHandleを保持する
// writeFileに`ディレクトリ名/ファイル名`の形式でfilePathが渡ってくるため、
// `/`で区切ってディレクトリ名を取得し、そのディレクトリ名をキーとしてdirectoryHandleMapからハンドラを取得し、fileWriteを可能にする
const directoryHandleMap: Map<string, FileSystemDirectoryHandle> = new Map();
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved

const showWritableDirectoryPicker = async (): Promise<
FileSystemDirectoryHandle | undefined
> =>
window
.showDirectoryPicker({
mode: "readwrite",
})
// キャンセルするとエラーが投げられる
.catch(() => undefined); // FIXME: このままだとダイアログ表示エラーと見分けがつかない

export const showOpenDirectoryDialogImpl: typeof window[typeof SandboxKey]["showOpenDirectoryDialog"] =
async () => {
const _directoryHandler = await showWritableDirectoryPicker();
if (_directoryHandler === undefined) {
return undefined;
}

await storeDirectoryHandle(_directoryHandler);

// NOTE: 同一のディレクトリ名だった場合、後で選択されたディレクトリがそれ移行の処理で使用されるため、意図しない保存が発生するかもしれない
directoryHandleMap.set(_directoryHandler.name, _directoryHandler);
return _directoryHandler.name;
};

// separator 以前の文字列はディレクトリ名として扱う
const resolveDirectoryName = (path: string) => path.split(sep)[0];

// FileSystemDirectoryHandle.getFileHandle では / のような separator が含まれるとエラーになるため、ファイル名のみを抽出している
const resolveFileName = (path: string) => {
const maybeDirectoryHandleName = resolveDirectoryName(path);
return path.slice(maybeDirectoryHandleName.length + sep.length);
};

// FileSystemDirectoryHandle.getFileHandle では / のような separator が含まれるとエラーになるため、以下の if 文で separator を除去している
// また separator 以前の文字列はディレクトリ名として扱われ、それを key として directoryHandleMap からハンドラを取得したり、set している
const getDirectoryHandleFromDirectoryPath = async (
maybeDirectoryPathKey: string
): Promise<FileSystemDirectoryHandle> => {
const maybeHandle = directoryHandleMap.get(maybeDirectoryPathKey);

if (maybeHandle !== undefined) {
return maybeHandle;
} else {
// NOTE: fixedDirectoryの場合こっちに落ちる場合がある
const maybeFixedDirectory = await fetchStoredDirectoryHandle(
maybeDirectoryPathKey
);

if (maybeFixedDirectory === undefined) {
throw new Error(
`フォルダへのアクセス許可がありません。アクセスしようとしたフォルダ名: ${maybeDirectoryPathKey}`
);
}

if (!(await maybeFixedDirectory.requestPermission({ mode: "readwrite" }))) {
throw new Error(
"フォルダへのアクセス許可がありません。ファイルの読み書きのためにアクセス許可が必要です。"
);
}

return maybeFixedDirectory;
}
};

// NOTE: fixedExportEnabled が有効になっている GENERATE_AND_SAVE_AUDIO action では、ファイル名に加えディレクトリ名も指定された状態でfilePathが渡ってくる
// また GENERATE_AND_SAVE_ALL_AUDIO action では fixedExportEnabled の有効の有無に関わらず、ディレクトリ名も指定された状態でfilePathが渡ってくる
export const writeFileImpl: typeof window[typeof SandboxKey]["writeFile"] =
async (obj: { filePath: string; buffer: ArrayBuffer }) => {
const path = obj.filePath;

if (path.indexOf(sep) === -1) {
const aTag = document.createElement("a");
const blob = URL.createObjectURL(new Blob([obj.buffer]));
aTag.href = blob;
aTag.download = path;
document.body.appendChild(aTag);
aTag.click();
document.body.removeChild(aTag);
URL.revokeObjectURL(blob);
return success(undefined);
}

const fileName = resolveFileName(path);
const maybeDirectoryHandleName = resolveDirectoryName(path);

const directoryHandle = await getDirectoryHandleFromDirectoryPath(
maybeDirectoryHandleName
);

directoryHandleMap.set(maybeDirectoryHandleName, directoryHandle);

return directoryHandle
.getFileHandle(fileName, { create: true })
.then(async (fileHandle) => {
const writable = await fileHandle.createWritable();
await writable.write(obj.buffer);
return writable.close();
})
.then(() => success(undefined))
.catch((e) => {
return failure(e);
});
};

export const checkFileExistsImpl: typeof window[typeof SandboxKey]["checkFileExists"] =
async (file) => {
const path = file;

if (path.indexOf(sep) === -1) {
return Promise.resolve(false);
}

const fileName = resolveFileName(path);
const maybeDirectoryHandleName = resolveDirectoryName(path);

const directoryHandle = await getDirectoryHandleFromDirectoryPath(
maybeDirectoryHandleName
);

directoryHandleMap.set(maybeDirectoryHandleName, directoryHandle);

const fileEntries = [];
for await (const [
fileOrDirectoryName,
entry,
] of directoryHandle.entries()) {
if (entry.kind === "file") {
fileEntries.push(fileOrDirectoryName);
}
}

return Promise.resolve(fileEntries.includes(fileName));
};
6 changes: 6 additions & 0 deletions src/browser/preload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { SandboxKey, Sandbox } from "../type/preload";
import { api } from "./sandbox";

const sandbox: Sandbox = api;
// @ts-expect-error readonlyになっているが、初期化処理はここで行うので問題ない
window[SandboxKey] = sandbox;
Loading