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

複数選択:選択だけ実装 #1470

Merged
merged 59 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
30b055d
(add/multiple-selection)
sevenc-nanashi Aug 4, 2023
0ad4d77
Add: 選択を実装
sevenc-nanashi Aug 5, 2023
c46a2fd
Add: 単体クリックのテストを追加
sevenc-nanashi Aug 5, 2023
fb5788b
Add: focusでも良い感じになるように
sevenc-nanashi Aug 5, 2023
f122e7c
Add: テストを追加
sevenc-nanashi Aug 5, 2023
dc13b41
Merge: main -> add/multiple-selection
sevenc-nanashi Aug 5, 2023
ec906e1
Fix: 間を押したときの挙動を修正
sevenc-nanashi Aug 5, 2023
ac15bbd
Fix: ウィンドウのフォーカスを外したときの挙動を修正
sevenc-nanashi Aug 5, 2023
1223e63
Add: 複数ある物を削除
sevenc-nanashi Aug 5, 2023
847c89c
Fix: Mac周りを修正
sevenc-nanashi Aug 5, 2023
8068fad
Add: test.failを追加
sevenc-nanashi Aug 5, 2023
3980e97
Change: 当たり判定用のdivを召喚するように
sevenc-nanashi Aug 12, 2023
e808521
Update: テストを更新
sevenc-nanashi Aug 12, 2023
79836e4
Add: Shift+カーソルの選択を追加
sevenc-nanashi Aug 12, 2023
4b03835
Change: 実験的機能に隔離
sevenc-nanashi Aug 14, 2023
6e3baba
Fix: ハイライトを修正
sevenc-nanashi Aug 14, 2023
aedd4a4
Delete: skipFocusEvent周りの処理を削除
sevenc-nanashi Aug 15, 2023
b45efb9
Revert: onInputFocus -> setActiveAudioKey
sevenc-nanashi Aug 15, 2023
aae65a4
Revert: 不要な変更をRevert
sevenc-nanashi Aug 15, 2023
ea8453e
Change: 選択食を変更
sevenc-nanashi Aug 15, 2023
0f78045
Change: 少し濃く
sevenc-nanashi Aug 15, 2023
6fe825a
Change: enableSetting -> toggleSetting
sevenc-nanashi Aug 15, 2023
9176b38
Add: Shift+上下のテストを追加
sevenc-nanashi Aug 15, 2023
f001f83
Fix: hostの値を修正
sevenc-nanashi Aug 16, 2023
3942e2b
Fix: playwrightのサーバー起動を無効化していたのを修正
sevenc-nanashi Aug 16, 2023
d67e8a3
Change: selectedではないactiveなAudioCellができるように
sevenc-nanashi Aug 16, 2023
6453ff8
Merge: main -> add/multiple-selection
sevenc-nanashi Aug 16, 2023
4e910d1
Add: コメントを追加
sevenc-nanashi Aug 16, 2023
0ae8237
Revert: 不要な変更をRevert
sevenc-nanashi Aug 16, 2023
bbec109
Refactor: onClickWithModifierKey周りをリファクタ
sevenc-nanashi Aug 16, 2023
f61e3f4
Change: e2eテストをまとめる
sevenc-nanashi Aug 17, 2023
4ef7cd6
Add: コメントを追加
sevenc-nanashi Aug 17, 2023
76e0435
Fix: hostを修正
sevenc-nanashi Aug 17, 2023
3170f53
Merge: main -> add/multiple-selection
sevenc-nanashi Aug 19, 2023
d406a4b
Delete: Macでの分岐を削除
sevenc-nanashi Aug 19, 2023
998d165
Change: selectedは常にactiveを含むように
sevenc-nanashi Aug 19, 2023
0da07d2
Update: テストを更新
sevenc-nanashi Aug 19, 2023
877f6bf
Change: prepareAudioCells -> addAudioCells
sevenc-nanashi Aug 19, 2023
8dad306
Delete: Macでテストを無効化
sevenc-nanashi Aug 19, 2023
ee08b65
Delete: data-is-multi-select-enabledを削除
sevenc-nanashi Aug 19, 2023
9b29af9
Add: filterを追加
sevenc-nanashi Aug 19, 2023
c37df7e
Refactor: onKeyup/downをkeyEventListenerに
sevenc-nanashi Aug 19, 2023
b2019a7
Change: selectedじゃない時だけactiveを移動させるように
sevenc-nanashi Aug 20, 2023
a0e98eb
Improve: コメント回りを改善
sevenc-nanashi Aug 21, 2023
96a46ed
Change: focus周りの仕様を変更
sevenc-nanashi Aug 22, 2023
072093a
Change: 上下移動はテキスト欄にfocusするように
sevenc-nanashi Aug 22, 2023
19e6810
Add: :focusの理由を追加
sevenc-nanashi Aug 22, 2023
16966b5
Change: focusTextField -> focusTarget
sevenc-nanashi Aug 23, 2023
9db22ad
Fix: フォーカス対象を修正
sevenc-nanashi Aug 24, 2023
31b6d42
Add: focusCellのドキュメントを追加
sevenc-nanashi Aug 24, 2023
2f7e79b
Add: テストを追加
sevenc-nanashi Aug 24, 2023
5adcea8
Change: throwするように
sevenc-nanashi Aug 24, 2023
6e6e864
Improve: ドキュメントのわかりやすさを向上
sevenc-nanashi Aug 24, 2023
71393a7
Change: エラーメッセージを変更
sevenc-nanashi Aug 25, 2023
e6838eb
Merge: remove -> local
sevenc-nanashi Aug 25, 2023
1512a5f
Change: AudioCellのfocusCellのfocusTargetをOptionalに
sevenc-nanashi Aug 25, 2023
bc0472b
Delete: Macの処理変更を削除
sevenc-nanashi Aug 25, 2023
f15093d
Fix: Macで落ちることを追求
sevenc-nanashi Aug 26, 2023
60453aa
Update tests/e2e/browser/複数選択.spec.ts
Hiroshiba Aug 26, 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
243 changes: 197 additions & 46 deletions src/components/AudioCell.vue

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions src/components/SettingDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,33 @@
>
</q-toggle>
</q-card-actions>
<q-card-actions
v-if="!isProduction"
class="q-px-md q-py-none bg-surface"
>
<div>複数選択</div>
<div aria-label="複数のテキスト欄を選択できるようにします。">
<q-icon name="help_outline" size="sm" class="help-hover-icon">
<q-tooltip
:delay="500"
anchor="center right"
self="center left"
transition-show="jump-right"
transition-hide="jump-left"
>
複数のテキスト欄を選択できるようにします。
</q-tooltip>
</q-icon>
</div>
<q-space />
<q-toggle
:model-value="experimentalSetting.enableMultiSelect"
@update:model-value="
changeExperimentalSetting('enableMultiSelect', $event)
"
>
</q-toggle>
</q-card-actions>
</q-card>
<q-card flat class="setting-card">
<q-card-actions>
Expand Down Expand Up @@ -895,6 +922,7 @@ import { computed, ref } from "vue";
import FileNamePatternDialog from "./FileNamePatternDialog.vue";
import { useStore } from "@/store";
import {
isProduction,
SavingSetting,
EngineSetting,
ExperimentalSetting,
Expand Down
30 changes: 30 additions & 0 deletions src/store/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,17 @@ export const audioStore = createPartialStore<AudioStoreTypes>({
},
},

SELECTED_AUDIO_KEYS: {
getter(state) {
return (
// undo/redoで消えていることがあるためフィルタする
state._selectedAudioKeys?.filter((audioKey) =>
state.audioKeys.includes(audioKey)
) || []
);
},
},

HAVE_AUDIO_QUERY: {
getter: (state) => (audioKey: AudioKey) => {
return state.audioItems[audioKey]?.query != undefined;
Expand Down Expand Up @@ -505,6 +516,25 @@ export const audioStore = createPartialStore<AudioStoreTypes>({
},
},

SET_SELECTED_AUDIO_KEYS: {
mutation(state, { audioKeys }: { audioKeys?: AudioKey[] }) {
state._selectedAudioKeys = audioKeys;
},
action(
{ state, commit, getters },
{ audioKeys }: { audioKeys?: AudioKey[] }
) {
const uniqueAudioKeys = new Set(audioKeys);
if (getters.ACTIVE_AUDIO_KEY) {
uniqueAudioKeys.add(getters.ACTIVE_AUDIO_KEY);
}
const sortedAudioKeys = state.audioKeys.filter((audioKey) =>
uniqueAudioKeys.has(audioKey)
);
commit("SET_SELECTED_AUDIO_KEYS", { audioKeys: sortedAudioKeys });
},
},

SET_AUDIO_PLAY_START_POINT: {
mutation(state, { startPoint }: { startPoint?: number }) {
state.audioPlayStartPoint = startPoint;
Expand Down
1 change: 1 addition & 0 deletions src/store/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const settingStoreState: SettingStoreState = {
enableInterrogativeUpspeak: false,
enableMorphing: false,
enableMultiEngine: false,
enableMultiSelect: false,
},
splitTextWhenPaste: "PERIOD_AND_NEW_LINE",
splitterPosition: {
Expand Down
10 changes: 10 additions & 0 deletions src/store/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export type AudioStoreState = {
audioKeys: AudioKey[];
audioStates: Record<AudioKey, AudioState>;
_activeAudioKey?: AudioKey;
_selectedAudioKeys?: AudioKey[];
audioPlayStartPoint?: number;
nowPlayingContinuously: boolean;
};
Expand All @@ -139,6 +140,10 @@ export type AudioStoreTypes = {
getter: AudioKey | undefined;
};

SELECTED_AUDIO_KEYS: {
getter: AudioKey[];
};

HAVE_AUDIO_QUERY: {
getter(audioKey: AudioKey): boolean;
};
Expand Down Expand Up @@ -192,6 +197,11 @@ export type AudioStoreTypes = {
action(payload: { audioKey?: AudioKey }): void;
};

SET_SELECTED_AUDIO_KEYS: {
mutation: { audioKeys?: AudioKey[] };
action(payload: { audioKeys?: AudioKey[] }): void;
};

SET_AUDIO_PLAY_START_POINT: {
mutation: { startPoint?: number };
action(payload: { startPoint?: number }): void;
Expand Down
2 changes: 2 additions & 0 deletions src/type/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IpcSOData } from "./ipc";
import { AltPortInfos } from "@/store/type";
import { Result } from "@/type/result";

export const isProduction = import.meta.env.MODE === "production";
export const isElectron = import.meta.env.VITE_TARGET === "electron";
export const isBrowser = import.meta.env.VITE_TARGET === "browser";

Expand Down Expand Up @@ -498,6 +499,7 @@ export const experimentalSettingSchema = z.object({
enableInterrogativeUpspeak: z.boolean().default(false),
enableMorphing: z.boolean().default(false),
enableMultiEngine: z.boolean().default(false),
enableMultiSelect: z.boolean().default(false),
});

export type ExperimentalSetting = z.infer<typeof experimentalSettingSchema>;
Expand Down
18 changes: 12 additions & 6 deletions src/views/EditorHome.vue
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ const hotkeyMap = new Map<HotkeyAction, () => HotkeyReturnType>([
"テキスト欄にフォーカスを戻す",
() => {
if (activeAudioKey.value !== undefined) {
focusCell({ audioKey: activeAudioKey.value });
focusCell({ audioKey: activeAudioKey.value, focusTarget: "textField" });
}
return false; // this is the same with event.preventDefault()
},
Expand Down Expand Up @@ -406,7 +406,7 @@ const addAudioItem = async () => {
audioItem,
prevAudioKey: activeAudioKey.value,
});
audioCellRefs[newAudioKey].focusTextField();
audioCellRefs[newAudioKey].focusCell({ focusTarget: "textField" });
};
const duplicateAudioItem = async () => {
const prevAudioKey = activeAudioKey.value;
Expand All @@ -420,7 +420,7 @@ const duplicateAudioItem = async () => {
audioItem: cloneDeep(prevAudioItem),
prevAudioKey: activeAudioKey.value,
});
audioCellRefs[newAudioKey].focusTextField();
audioCellRefs[newAudioKey].focusCell({ focusTarget: "textField" });
};

// Pane
Expand Down Expand Up @@ -472,8 +472,14 @@ watch(shouldShowPanes, (val, old) => {
});

// セルをフォーカス
const focusCell = ({ audioKey }: { audioKey: AudioKey }) => {
audioCellRefs[audioKey].focusTextField();
const focusCell = ({
audioKey,
focusTarget,
}: {
audioKey: AudioKey;
focusTarget: "root" | "textField";
}) => {
audioCellRefs[audioKey].focusCell({ focusTarget });
};

// Electronのデフォルトのundo/redoを無効化
Expand Down Expand Up @@ -562,7 +568,7 @@ onMounted(async () => {
const newAudioKey = await store.dispatch("REGISTER_AUDIO_ITEM", {
audioItem,
});
focusCell({ audioKey: newAudioKey });
focusCell({ audioKey: newAudioKey, focusTarget: "textField" });

// 最初の話者を初期化
store.dispatch("SETUP_SPEAKER", {
Expand Down
148 changes: 148 additions & 0 deletions tests/e2e/browser/複数選択.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { test, expect, Page } from "@playwright/test";
import { toggleSetting, navigateToMain } from "../navigators";

test.beforeEach(async ({ page }) => {
const BASE_URL = "http://localhost:5173/#/home";
await page.setViewportSize({ width: 800, height: 600 });
await page.goto(BASE_URL);

await navigateToMain(page);
await page.waitForTimeout(100);
await toggleSetting(page, "複数選択");

await addAudioCells(page, 3);
});

const ctrlLike = process.platform === "darwin" ? "Meta" : "Control";
/**
* アクティブなAudioCellと選択されているAudioCellを取得する。
* 戻り値のインデックスは1から始まる。(nth-childのインデックスと揃えるため)
*/
async function getSelectedStatus(page: Page): Promise<{
active: number;
selected: number[];
}> {
const selectedAudioKeys = await page.evaluate(() => {
const audioCells = [...document.querySelectorAll(".audio-cell")];
let active: number | undefined;
const selected: number[] = [];
for (let i = 0; i < audioCells.length; i++) {
const audioCell = audioCells[i];
if (audioCell.classList.contains("active")) {
active = i + 1;
}
if (audioCell.classList.contains("selected")) {
selected.push(i + 1);
}
}
if (active === undefined) {
throw new Error("No active audio cell");
}

return { active, selected };
});
return selectedAudioKeys;
}

async function addAudioCells(page: Page, count: number) {
for (let i = 0; i < count; i++) {
await page.getByRole("button", { name: "テキストを追加" }).click();
await page.waitForTimeout(100);
}
}

test("複数選択:マウス周り", async ({ page }) => {
// Shift+クリックは前回選択していたAudioCellから今回クリックしたAudioCellまでを選択する
await page.locator(".audio-cell:nth-child(2)").click();
await page.keyboard.down("Shift");
await page.locator(".audio-cell:nth-child(4)").click();
await page.keyboard.up("Shift");

await page.waitForTimeout(100);
const selectedStatus1 = await getSelectedStatus(page);
expect(selectedStatus1.active).toBe(4);
expect(selectedStatus1.selected).toEqual([2, 3, 4]);

// ただのクリックはactiveAudioKeyとselectedAudioKeysをクリックしたAudioCellだけにする
await page.locator(".audio-cell:nth-child(2)").click();

await page.waitForTimeout(100);
const selectedStatus2 = await getSelectedStatus(page);
expect(selectedStatus2.active).toBe(2);
expect(selectedStatus2.selected).toEqual([2]);

if (process.platform === "darwin") {
// なぜかCmd(Meta)+クリックが動かないのでスキップする
return;
}

// Ctrl+クリックは選択範囲を追加する
await page.keyboard.down(ctrlLike);
await page.locator(".audio-cell:nth-child(4)").click();
await page.keyboard.up(ctrlLike);
await page.waitForTimeout(100);

const selectedStatus3 = await getSelectedStatus(page);
expect(selectedStatus3.active).toBe(4);
expect(selectedStatus3.selected).toEqual([2, 4]);

// Ctrl+クリックは選択範囲から削除する
await page.keyboard.down(ctrlLike);
await page.locator(".audio-cell:nth-child(2)").click();
await page.keyboard.up(ctrlLike);
await page.waitForTimeout(100);

const selectedStatus4 = await getSelectedStatus(page);
expect(selectedStatus4.active).toBe(4);
expect(selectedStatus4.selected).toEqual([4]);

// activeのAudioCellをCtrl+クリックすると選択範囲から削除して次のselectedのAudioCellをactiveにする
await page.keyboard.down(ctrlLike);
await page.locator(".audio-cell:nth-child(2)").click();
await page.locator(".audio-cell:nth-child(2)").click();
await page.keyboard.up(ctrlLike);
await page.waitForTimeout(100);

const selectedStatus5 = await getSelectedStatus(page);
expect(selectedStatus5.active).toBe(4);
expect(selectedStatus5.selected).toEqual([4]);
});

test("複数選択:キーボード", async ({ page }) => {
// Shift+下で下方向を選択範囲にする
await page.locator(".audio-cell:nth-child(2)").click();
await page.keyboard.down("Shift");
await page.keyboard.press("ArrowDown");
await page.keyboard.up("Shift");
await page.waitForTimeout(100);

const selectedStatus1 = await getSelectedStatus(page);
expect(selectedStatus1.active).toBe(3);
expect(selectedStatus1.selected).toEqual([2, 3]);

// ただの下で下方向をactiveにして他の選択を解除する
await page.keyboard.press("ArrowDown");

await page.waitForTimeout(100);
const selectedStatus2 = await getSelectedStatus(page);
expect(selectedStatus2.active).toBe(4);
expect(selectedStatus2.selected).toEqual([4]);

// Shift+上で上方向を選択範囲にする
await page.keyboard.down("Shift");
await page.keyboard.press("ArrowUp");
await page.keyboard.up("Shift");
await page.waitForTimeout(100);

const selectedStatus3 = await getSelectedStatus(page);
expect(selectedStatus3.active).toBe(3);
expect(selectedStatus3.selected).toEqual([3, 4]);

// ただの上で上方向をactiveにして他の選択を解除する
await page.keyboard.press("ArrowUp");
await page.waitForTimeout(100);

const selectedStatus4 = await getSelectedStatus(page);
expect(selectedStatus4.active).toBe(2);
expect(selectedStatus4.selected).toEqual([2]);
});
21 changes: 21 additions & 0 deletions tests/e2e/navigators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,27 @@ export async function navigateToMain(page: Page) {
await page.getByRole("button", { name: "許可" }).click();
}

/**
* 特定の設定をトグルする
*/
export async function toggleSetting(page: Page, settingName: string) {
await page.getByRole("button", { name: "設定" }).click();
await page.waitForTimeout(100);
// FIXME: なぜかariaで取得できない
// await page.getByRole("listitem", { name: "オプション" }).click();
await page.getByText("オプション").click();
await page.waitForTimeout(100);
await page
.locator(".q-card__actions", {
has: page.getByText(settingName),
})
.locator(".q-toggle")
.click();
await page.waitForTimeout(100);
await page.getByRole("button", { name: "設定を閉じる" }).click();
await page.waitForTimeout(100);
}

/**
* ヘルプダイアログの表示まで移動
*/
Expand Down
1 change: 1 addition & 0 deletions tests/unit/store/Vuex.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ describe("store/vuex.js test", () => {
enableInterrogativeUpspeak: false,
enableMorphing: false,
enableMultiEngine: false,
enableMultiSelect: false,
},
splitTextWhenPaste: "PERIOD_AND_NEW_LINE",
splitterPosition: {
Expand Down