Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
? ["error", "unix"]
: "off",
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-constant-condition": ["error", { checkLoops: false }], // while(true) などを許可
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"prettier/prettier": [
"error",
Expand Down
3 changes: 3 additions & 0 deletions src/backend/browser/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ export const api: Sandbox = {
}
});
},
showSaveDirectoryDialog(obj: { title: string }) {
return showOpenDirectoryDialogImpl(obj);
},
showVvppOpenDialog(obj: { title: string; defaultPath?: string }) {
// NOTE: 今後接続先を変える手段としてVvppが使われるかもしれないので、そのタイミングで実装する
throw new Error(
Expand Down
132 changes: 114 additions & 18 deletions src/backend/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,26 +618,116 @@ ipcMainHandle("GET_ALT_PORT_INFOS", () => {
return engineManager.altPortInfo;
});

/**
* 保存に適した場所を選択するかキャンセルするまでダイアログを繰り返し表示する。
* アンインストール等で消えうる場所などを避ける。
* @param showDialogFunction ダイアログを表示する関数
*/
const retryShowSaveDialogWhileSafeDir = async <
T extends Electron.OpenDialogReturnValue | Electron.SaveDialogReturnValue
>(
showDialogFunction: () => Promise<T>
): Promise<T> => {
/**
* 指定されたパスが安全でないかどうかを判断する
*/
const isUnsafePath = (filePath: string) => {
const unsafeSaveDirs = [appDirPath, app.getPath("userData")]; // アンインストールで消えうるフォルダ
return unsafeSaveDirs.some((unsafeDir) => {
const relativePath = path.relative(unsafeDir, filePath);
return !(
path.isAbsolute(relativePath) ||
relativePath.startsWith(`..${path.sep}`) ||
relativePath === ".."
);
});
};

/**
* 警告ダイアログを表示し、ユーザーが再試行を選択したかどうかを返す
*/
const showWarningDialog = async () => {
const productName = app.getName().toUpperCase();
const warningResult = await dialog.showMessageBox(win, {
message: `指定された保存先は${productName}により自動的に削除される可能性があります。\n他の場所に保存することをおすすめします。`,
type: "warning",
buttons: ["保存場所を変更", "無視して保存"],
defaultId: 0,
title: "警告",
cancelId: 0,
});
return warningResult.response === 0 ? "retry" : "forceSave";
};

while (true) {
const result = await showDialogFunction();
// キャンセルされた場合、結果を直ちに返す
if (result.canceled) return result;

// 選択されたファイルパスを取得
const filePath =
"filePaths" in result ? result.filePaths[0] : result.filePath;

// filePathが未定義の場合、エラーを返す
if (filePath == undefined) {
throw new Error(
`canseld == ${result.canceled} but filePath == ${filePath}`
);
}

// 選択されたパスが安全かどうかを確認
if (isUnsafePath(filePath)) {
const result = await showWarningDialog();
if (result === "retry") continue; // ユーザーが保存場所を変更を選択した場合
}
return result; // 安全なパスが選択された場合
}
};

ipcMainHandle("SHOW_AUDIO_SAVE_DIALOG", async (_, { title, defaultPath }) => {
const result = await dialog.showSaveDialog(win, {
title,
defaultPath,
filters: [{ name: "Wave File", extensions: ["wav"] }],
properties: ["createDirectory"],
});
const result = await retryShowSaveDialogWhileSafeDir(() =>
dialog.showSaveDialog(win, {
title,
defaultPath,
filters: [{ name: "Wave File", extensions: ["wav"] }],
properties: ["createDirectory"],
})
);
return result.filePath;
});

ipcMainHandle("SHOW_TEXT_SAVE_DIALOG", async (_, { title, defaultPath }) => {
const result = await dialog.showSaveDialog(win, {
title,
defaultPath,
filters: [{ name: "Text File", extensions: ["txt"] }],
properties: ["createDirectory"],
});
const result = await retryShowSaveDialogWhileSafeDir(() =>
dialog.showSaveDialog(win, {
title,
defaultPath,
filters: [{ name: "Text File", extensions: ["txt"] }],
properties: ["createDirectory"],
})
);
return result.filePath;
});

/**
* 保存先になるディレクトリを選ぶダイアログを表示する。
*/
ipcMainHandle("SHOW_SAVE_DIRECTORY_DIALOG", async (_, { title }) => {
const result = await retryShowSaveDialogWhileSafeDir(() =>
dialog.showOpenDialog(win, {
title,
properties: [
"openDirectory",
"createDirectory",
"treatPackageAsDirectory",
],
})
);
if (result.canceled) {
return undefined;
}
return result.filePaths[0];
});

ipcMainHandle("SHOW_VVPP_OPEN_DIALOG", async (_, { title, defaultPath }) => {
const result = await dialog.showOpenDialog(win, {
title,
Expand All @@ -650,6 +740,10 @@ ipcMainHandle("SHOW_VVPP_OPEN_DIALOG", async (_, { title, defaultPath }) => {
return result.filePaths[0];
});

/**
* ディレクトリ選択ダイアログを表示する。
* 保存先として選ぶ場合は SHOW_SAVE_DIRECTORY_DIALOG を使うべき。
*/
ipcMainHandle("SHOW_OPEN_DIRECTORY_DIALOG", async (_, { title }) => {
const result = await dialog.showOpenDialog(win, {
title,
Expand All @@ -662,12 +756,14 @@ ipcMainHandle("SHOW_OPEN_DIRECTORY_DIALOG", async (_, { title }) => {
});

ipcMainHandle("SHOW_PROJECT_SAVE_DIALOG", async (_, { title, defaultPath }) => {
const result = await dialog.showSaveDialog(win, {
title,
defaultPath,
filters: [{ name: "VOICEVOX Project file", extensions: ["vvproj"] }],
properties: ["showOverwriteConfirmation"],
});
const result = await retryShowSaveDialogWhileSafeDir(() =>
dialog.showSaveDialog(win, {
title,
defaultPath,
filters: [{ name: "VOICEVOX Project file", extensions: ["vvproj"] }],
properties: ["showOverwriteConfirmation"],
})
);
if (result.canceled) {
return undefined;
}
Expand Down
4 changes: 4 additions & 0 deletions src/backend/electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ const api: Sandbox = {
return ipcRendererInvoke("SHOW_TEXT_SAVE_DIALOG", { title, defaultPath });
},

showSaveDirectoryDialog: ({ title }) => {
return ipcRendererInvoke("SHOW_SAVE_DIRECTORY_DIALOG", { title });
},

showVvppOpenDialog: ({ title, defaultPath }) => {
return ipcRendererInvoke("SHOW_VVPP_OPEN_DIALOG", { title, defaultPath });
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/Dialog/SettingDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1290,7 +1290,7 @@ const outputSamplingRate = computed({
});

const openFileExplore = async () => {
const path = await window.backend.showOpenDirectoryDialog({
const path = await window.backend.showSaveDirectoryDialog({
title: "書き出し先のフォルダを選択",
});
if (path) {
Expand Down
2 changes: 1 addition & 1 deletion src/store/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1395,7 +1395,7 @@ export const audioStore = createPartialStore<AudioStoreTypes>({
if (state.savingSetting.fixedExportEnabled) {
dirPath = state.savingSetting.fixedExportDir;
} else {
dirPath ??= await window.backend.showOpenDirectoryDialog({
dirPath ??= await window.backend.showSaveDirectoryDialog({
title: "音声を保存",
});
}
Expand Down
5 changes: 5 additions & 0 deletions src/type/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ export type IpcIHData = {
return?: string;
};

SHOW_SAVE_DIRECTORY_DIALOG: {
args: [obj: { title: string }];
return?: string;
};

SHOW_VVPP_OPEN_DIALOG: {
args: [obj: { title: string; defaultPath?: string }];
return?: string;
Expand Down
1 change: 1 addition & 0 deletions src/type/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export interface Sandbox {
title: string;
defaultPath?: string;
}): Promise<string | undefined>;
showSaveDirectoryDialog(obj: { title: string }): Promise<string | undefined>;
showVvppOpenDialog(obj: {
title: string;
defaultPath?: string;
Expand Down
1 change: 0 additions & 1 deletion tests/e2e/browser/スクリーンショット.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ test("メイン画面の表示", async ({ page }) => {
test.skip(process.platform !== "win32", "Windows以外のためスキップします");
await navigateToMain(page);

// eslint-disable-next-line no-constant-condition
while (true) {
await page.locator(".audio-cell:nth-child(1) .q-field").click();
await page.waitForTimeout(100);
Expand Down
1 change: 0 additions & 1 deletion tests/e2e/electron/example.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ test.beforeAll(async () => {
dotenv.config(); // FIXME: エンジンの設定直読み

console.log("Waiting for main.js to be built...");
// eslint-disable-next-line no-constant-condition
while (true) {
try {
await fs.access("./dist/main.js");
Expand Down