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 33 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
154 changes: 132 additions & 22 deletions src/components/AudioCell.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
<template>
<div class="audio-cell">
<div
class="audio-cell"
:class="{ active: isActiveAudioCell, selected: isSelectedAudioCell }"
:data-is-multi-select-enabled="isMultiSelectEnabled"
>
<!-- 複数選択用のヒットボックス -->
<div
v-if="(isCtrlOrCommandKeyDown || isShiftKeyDown) && isMultiSelectEnabled"
class="click-hitbox"
@click="onClickWithModifierKey"
/>
<q-icon
v-if="isActiveAudioCell"
name="arrow_right"
Expand All @@ -20,9 +30,13 @@
:loading="isInitializingSpeaker"
:show-engine-info="isMultipleEngine"
:ui-locked="uiLocked"
@focus="setActiveAudioKey()"
@focus="
if (activeAudioKey !== props.audioKey) {
selectAndSetActiveAudioKey();
}
"
/>
<!--
<!--
input.valueをスクリプトから変更した場合は@changeが発火しないため、
@blurと@keydown.prevent.enter.exactに分けている
-->
Expand All @@ -40,12 +54,16 @@
@update:model-value="setAudioTextBuffer"
@focus="
clearInputSelection();
setActiveAudioKey();
// focusは画面外から切り換えてきた場合にも発火するため、
// activeAudioKeyが同じなら何もしない
if (activeAudioKey !== props.audioKey) {
selectAndSetActiveAudioKey();
}
"
@blur="pushAudioTextIfNeeded()"
@paste="pasteOnAudioCell"
@keydown.prevent.up.exact="moveUpCell"
@keydown.prevent.down.exact="moveDownCell"
@keydown.prevent.up="moveUpCell"
@keydown.prevent.down="moveDownCell"
@keydown.prevent.enter.exact="pushAudioTextIfNeeded"
>
<template #error>
Expand Down Expand Up @@ -78,14 +96,15 @@
</template>

<script setup lang="ts">
import { computed, watch, ref, nextTick } from "vue";
import { computed, watch, ref, nextTick, onMounted, onUnmounted } from "vue";
import { QInput } from "quasar";
import CharacterButton from "./CharacterButton.vue";
import { MenuItemButton, MenuItemSeparator } from "./MenuBar.vue";
import ContextMenu from "./ContextMenu.vue";
import { useStore } from "@/store";
import { AudioKey, SplitTextWhenPasteType, Voice } from "@/type/preload";
import { SelectionHelperForQInput } from "@/helpers/SelectionHelperForQInput";
import { isOnCommandOrCtrlKeyDown } from "@/store/utility";

const props =
defineProps<{
Expand Down Expand Up @@ -119,8 +138,80 @@ const isInitializingSpeaker = computed(
);
const audioItem = computed(() => store.state.audioItems[props.audioKey]);

const activeAudioKey = computed(() => store.getters.ACTIVE_AUDIO_KEY);

const uiLocked = computed(() => store.getters.UI_LOCKED);

const isMultiSelectEnabled = computed(
() => store.state.experimentalSetting.enableMultiSelect
);

const selectAndSetActiveAudioKey = () => {
store.dispatch("SET_ACTIVE_AUDIO_KEY", { audioKey: props.audioKey });
store.dispatch("SET_SELECTED_AUDIO_KEYS", { audioKeys: [props.audioKey] });
};
// 複数選択のクリック判定
const onClickWithModifierKey = (event: MouseEvent) => {
if (uiLocked.value) return;
const currentActiveAudioKey = store.getters.ACTIVE_AUDIO_KEY;
const currentSelectedAudioKeys = store.getters.SELECTED_AUDIO_KEYS;
let newActiveAudioKey: AudioKey | undefined = currentActiveAudioKey;
let newSelectedAudioKeys: AudioKey[] = [...currentSelectedAudioKeys];
if (event.shiftKey) {
if (currentActiveAudioKey) {
const currentAudioIndex = store.state.audioKeys.indexOf(
currentActiveAudioKey
);
const clickedAudioIndex = store.state.audioKeys.indexOf(props.audioKey);
const minIndex = Math.min(currentAudioIndex, clickedAudioIndex);
const maxIndex = Math.max(currentAudioIndex, clickedAudioIndex);
const audioKeysBetween = store.state.audioKeys.slice(
minIndex,
maxIndex + 1
);
newActiveAudioKey = props.audioKey;
newSelectedAudioKeys = [...currentSelectedAudioKeys, ...audioKeysBetween];
}
} else if (isOnCommandOrCtrlKeyDown(event)) {
// Ctrlキーを押しながらクリックしたとき:
// 選択していないAudioCellならactiveを移動し、以前の選択をselectedに追加する。
// 選択しているAudioCellならselectedから除外する。activeは変更しない。
if (currentSelectedAudioKeys.includes(props.audioKey)) {
newActiveAudioKey = currentActiveAudioKey;
newSelectedAudioKeys = currentSelectedAudioKeys.filter(
(audioKey) => audioKey !== props.audioKey
);
} else {
newActiveAudioKey = props.audioKey;
newSelectedAudioKeys = [...currentSelectedAudioKeys, props.audioKey];
}
}
store.dispatch("SET_ACTIVE_AUDIO_KEY", { audioKey: newActiveAudioKey });
store.dispatch("SET_SELECTED_AUDIO_KEYS", {
audioKeys: newSelectedAudioKeys,
});
};

const isCtrlOrCommandKeyDown = ref(false);
const isShiftKeyDown = ref(false);

const onKeydown = (e: KeyboardEvent) => {
isCtrlOrCommandKeyDown.value = isOnCommandOrCtrlKeyDown(e);
isShiftKeyDown.value = e.shiftKey;
};
const onKeyup = (e: KeyboardEvent) => {
isCtrlOrCommandKeyDown.value = isOnCommandOrCtrlKeyDown(e);
isShiftKeyDown.value = e.shiftKey;
};
onMounted(() => {
window.addEventListener("keydown", onKeydown);
window.addEventListener("keyup", onKeyup);
});
onUnmounted(() => {
window.removeEventListener("keydown", onKeydown);
window.removeEventListener("keyup", onKeyup);
});

const selectedVoice = computed<Voice | undefined>({
get() {
const { engineId, styleId } = audioItem.value.voice;
Expand Down Expand Up @@ -150,6 +241,9 @@ const selectedVoice = computed<Voice | undefined>({
const isActiveAudioCell = computed(
() => props.audioKey === store.getters.ACTIVE_AUDIO_KEY
);
const isSelectedAudioCell = computed(() =>
store.getters.SELECTED_AUDIO_KEYS.includes(props.audioKey)
);

const audioTextBuffer = ref(audioItem.value.text);
const isChangeFlag = ref(false);
Expand Down Expand Up @@ -188,10 +282,6 @@ const clearInputSelection = () => {
}
};

const setActiveAudioKey = () => {
store.dispatch("SET_ACTIVE_AUDIO_KEY", { audioKey: props.audioKey });
};

// コピペしたときに句点と改行で区切る
const textSplitType = computed(() => store.state.splitTextWhenPaste);
const pasteOnAudioCell = async (event: ClipboardEvent) => {
Expand Down Expand Up @@ -273,20 +363,25 @@ const textLineNumberWidth = computed(() => {

// 上下に移動
const audioKeys = computed(() => store.state.audioKeys);
const moveUpCell = (e?: KeyboardEvent) => {
const moveCell = (offset: number) => (e?: KeyboardEvent) => {
if (e && e.isComposing) return;
const index = audioKeys.value.indexOf(props.audioKey) - 1;
if (index >= 0) {
emit("focusCell", { audioKey: audioKeys.value[index] });
}
};
const moveDownCell = (e?: KeyboardEvent) => {
if (e && e.isComposing) return;
const index = audioKeys.value.indexOf(props.audioKey) + 1;
if (index < audioKeys.value.length) {
const index = audioKeys.value.indexOf(props.audioKey) + offset;
if (index >= 0 && index < audioKeys.value.length) {
const selectedAudioKeys = store.getters.SELECTED_AUDIO_KEYS;
emit("focusCell", { audioKey: audioKeys.value[index] });
if (isMultiSelectEnabled.value && e?.shiftKey) {
store.dispatch("SET_SELECTED_AUDIO_KEYS", {
audioKeys: [
...selectedAudioKeys,
props.audioKey,
audioKeys.value[index],
],
});
}
}
};
const moveUpCell = moveCell(-1);
const moveDownCell = moveCell(1);

// 消去
const willRemove = ref(false);
Expand Down Expand Up @@ -460,8 +555,12 @@ const isMultipleEngine = computed(() => store.state.engineIds.length > 1);

.audio-cell {
display: flex;
position: relative;
padding: 0.4rem 0.5rem;
margin: 0.2rem 0.5rem;
&[data-is-multi-select-enabled="true"].selected {
background-color: rgba(colors.$active-point-focus-rgb, 0.5);
}

&:first-child {
margin-top: 0.6rem;
Expand All @@ -474,7 +573,7 @@ const isMultipleEngine = computed(() => store.state.engineIds.length > 1);
gap: 0px 1rem;

.active-arrow {
left: -5px;
left: -1rem;
height: 2rem;
}

Expand Down Expand Up @@ -523,4 +622,15 @@ const isMultipleEngine = computed(() => store.state.engineIds.length > 1);
color: colors.$display;
}
}

.click-hitbox {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: none;
z-index: 1;
cursor: default;
}
</style>
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
16 changes: 16 additions & 0 deletions src/store/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,13 @@ export const audioStore = createPartialStore<AudioStoreTypes>({
},
},

SELECTED_AUDIO_KEYS: {
getter(state) {
const base = new Set(state._selectedAudioKeys || []);
return Array.from(base);
},
},

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

SET_SELECTED_AUDIO_KEYS: {
mutation(state, { audioKeys }: { audioKeys?: AudioKey[] }) {
state._selectedAudioKeys = audioKeys;
},
action({ commit }, { audioKeys }: { audioKeys?: AudioKey[] }) {
commit("SET_SELECTED_AUDIO_KEYS", { audioKeys });
},
Copy link
Member

@Hiroshiba Hiroshiba Aug 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(これは好みかもしれませんが)複数選択がオンになってなかった場合にこの関数を実行するとエラーにしてあげると、異常が起きた時に分かりやすいかも?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

選択処理のところにフラグチェック入れるよりもUnused parameter的な扱いをした方が楽かなぁと思います

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あれ、これってどういう感じでしょうか。値を使うところでフラグチェックしてエラーを出す・・・?

},

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";
export const isMac =
Expand Down Expand Up @@ -482,6 +483,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
Loading