Merge branch 'feature_hotkey' into develop
This commit is contained in:
@@ -94,6 +94,7 @@ config_page:
|
||||
transcription: Transcription
|
||||
vr: VR
|
||||
others: Others
|
||||
hotkeys: Hotkeys
|
||||
advanced_settings: Advanced Settings
|
||||
supporters: Supporters
|
||||
about_vrct: About VRCT
|
||||
@@ -257,6 +258,16 @@ config_page:
|
||||
label: Send Received Message To VRChat
|
||||
desc: Send the message you received from the speaker's sound to VRChat's chatbox.
|
||||
|
||||
hotkeys:
|
||||
toggle_vrct_visibility:
|
||||
label: Toggle VRCT Visibility
|
||||
toggle_translation:
|
||||
label: Toggle {{translation}}
|
||||
toggle_transcription_send:
|
||||
label: Toggle {{transcription_send}}
|
||||
toggle_transcription_receive:
|
||||
label: Toggle {{transcription_receive}}
|
||||
|
||||
advanced_settings:
|
||||
osc_ip_address:
|
||||
label: OSC IP Address
|
||||
|
||||
@@ -92,6 +92,7 @@ config_page:
|
||||
translation: 翻訳
|
||||
transcription: 音声認識
|
||||
others: その他
|
||||
hotkeys: ホットキー
|
||||
advanced_settings: 高度な設定
|
||||
|
||||
device:
|
||||
@@ -255,6 +256,16 @@ config_page:
|
||||
label: 受信したメッセージをVRChatに送信する
|
||||
desc: スピーカーから聞き取り、文字起こしされたメッセージをVRChatに送信します。
|
||||
|
||||
hotkeys:
|
||||
toggle_vrct_visibility:
|
||||
label: VRCTの最小化/アクティブ化の切り替え
|
||||
toggle_translation:
|
||||
label: '{{translation}}機能切り替え'
|
||||
toggle_transcription_send:
|
||||
label: '{{transcription_send}}機能切り替え'
|
||||
toggle_transcription_receive:
|
||||
label: '{{transcription_receive}}機能切り替え'
|
||||
|
||||
advanced_settings:
|
||||
osc_ip_address:
|
||||
label: OSC IP Address
|
||||
|
||||
@@ -533,6 +533,19 @@ class Config:
|
||||
self._MIC_WORD_FILTER = sorted(set(value), key=value.index)
|
||||
self.saveConfig(inspect.currentframe().f_code.co_name, value)
|
||||
|
||||
@property
|
||||
@json_serializable('HOTKEYS')
|
||||
def HOTKEYS(self):
|
||||
return self._HOTKEYS
|
||||
|
||||
@HOTKEYS.setter
|
||||
def HOTKEYS(self, value):
|
||||
if isinstance(value, dict) and set(value.keys()) == set(self.HOTKEYS.keys()):
|
||||
for key, value in value.items():
|
||||
if isinstance(value, list) or value is None:
|
||||
self._HOTKEYS[key] = value
|
||||
self.saveConfig(inspect.currentframe().f_code.co_name, self.HOTKEYS, immediate_save=True)
|
||||
|
||||
@property
|
||||
@json_serializable('MIC_AVG_LOGPROB')
|
||||
def MIC_AVG_LOGPROB(self):
|
||||
@@ -1026,6 +1039,12 @@ class Config:
|
||||
self._MIC_PHRASE_TIMEOUT = 3
|
||||
self._MIC_MAX_PHRASES = 10
|
||||
self._MIC_WORD_FILTER = []
|
||||
self._HOTKEYS = {
|
||||
"toggle_vrct_visibility": None,
|
||||
"toggle_translation": None,
|
||||
"toggle_transcription_send": None,
|
||||
"toggle_transcription_receive": None,
|
||||
}
|
||||
self._MIC_AVG_LOGPROB = -0.8
|
||||
self._MIC_NO_SPEECH_PROB = 0.6
|
||||
self._AUTO_SPEAKER_SELECT = True
|
||||
|
||||
@@ -996,6 +996,15 @@ class Controller:
|
||||
response = {"status":200, "result":config.SPEAKER_MAX_PHRASES}
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def getHotkeys(*args, **kwargs) -> dict:
|
||||
return {"status":200, "result":config.HOTKEYS}
|
||||
|
||||
@staticmethod
|
||||
def setHotkeys(data, *args, **kwargs) -> dict:
|
||||
config.HOTKEYS = data
|
||||
return {"status":200, "result":config.HOTKEYS}
|
||||
|
||||
@staticmethod
|
||||
def getSpeakerAvgLogprob(*args, **kwargs) -> dict:
|
||||
return {"status":200, "result":config.SPEAKER_AVG_LOGPROB}
|
||||
|
||||
@@ -183,6 +183,9 @@ mapping = {
|
||||
"/get/data/mic_max_phrases": {"status": True, "variable":controller.getMicMaxPhrases},
|
||||
"/set/data/mic_max_phrases": {"status": True, "variable":controller.setMicMaxPhrases},
|
||||
|
||||
"/get/data/hotkeys": {"status": True, "variable":controller.getHotkeys},
|
||||
"/set/data/hotkeys": {"status": True, "variable":controller.setHotkeys},
|
||||
|
||||
"/get/data/mic_avg_logprob": {"status": True, "variable":controller.getMicAvgLogprob},
|
||||
"/set/data/mic_avg_logprob": {"status": True, "variable":controller.setMicAvgLogprob},
|
||||
|
||||
|
||||
1070
src-tauri/Cargo.lock
generated
1070
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,12 +11,13 @@ edition = "2021"
|
||||
tauri-build = { version = "1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "1", features = [ "window-set-size", "window-set-position", "window-unmaximize", "window-close", "window-maximize", "window-minimize", "window-unminimize", "window-start-dragging", "window-set-decorations", "window-set-always-on-top", "shell-sidecar", "shell-open", "devtools"] }
|
||||
tauri = { version = "1", features = [ "window-hide", "window-set-focus", "global-shortcut-all", "window-set-size", "window-set-position", "window-unmaximize", "window-close", "window-maximize", "window-minimize", "window-unminimize", "window-start-dragging", "window-set-decorations", "window-set-always-on-top", "shell-sidecar", "shell-open", "devtools"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
font-kit = "0.14.2"
|
||||
window-shadows = { git = "https://github.com/tauri-apps/window-shadows.git" }
|
||||
|
||||
|
||||
[features]
|
||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
"window": {
|
||||
"all": false,
|
||||
"setAlwaysOnTop": true,
|
||||
"setFocus": true,
|
||||
"setDecorations": true,
|
||||
"close": true,
|
||||
"hide": true,
|
||||
"setPosition": true,
|
||||
"setSize": true,
|
||||
"maximize": true,
|
||||
@@ -25,6 +27,9 @@
|
||||
"unminimize": true,
|
||||
"startDragging": true
|
||||
},
|
||||
"globalShortcut": {
|
||||
"all": true
|
||||
},
|
||||
"shell": {
|
||||
"all": false,
|
||||
"open": true,
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
useWindow,
|
||||
} from "@logics_common";
|
||||
|
||||
// import React from "react";
|
||||
|
||||
import {
|
||||
KeyEventController,
|
||||
StartPythonController,
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
useMainFunction,
|
||||
} from "@logics_main";
|
||||
|
||||
import { useHotkeys } from "@logics_configs";
|
||||
|
||||
import { useStore_MainFunctionsStateMemory } from "@store";
|
||||
|
||||
export const ConfigPageCloseTriggerController = () => {
|
||||
@@ -27,6 +29,8 @@ export const ConfigPageCloseTriggerController = () => {
|
||||
volumeCheckStop_Speaker,
|
||||
} = useVolume();
|
||||
|
||||
const { registerShortcuts, unregisterAll } = useHotkeys();
|
||||
|
||||
|
||||
const memorizeLatestMainFunctionsState = () => {
|
||||
updateMainFunctionsStateMemory({
|
||||
@@ -43,9 +47,11 @@ export const ConfigPageCloseTriggerController = () => {
|
||||
useEffect(() => {
|
||||
if (currentIsOpenedConfigPage.data === true) { // When config page is opened.
|
||||
memorizeLatestMainFunctionsState();
|
||||
unregisterAll();
|
||||
if (currentTranscriptionSendStatus.data === true) setTranscriptionSend(false);
|
||||
if (currentTranscriptionReceiveStatus.data === true) setTranscriptionReceive(false);
|
||||
} else if (currentIsOpenedConfigPage.data === false) { // When config page is closed.
|
||||
registerShortcuts();
|
||||
if (currentMicThresholdCheckStatus.data === true) volumeCheckStop_Mic();
|
||||
if (currentSpeakerThresholdCheckStatus.data === true) volumeCheckStop_Speaker();
|
||||
restoreMainFunctionState();
|
||||
|
||||
@@ -3,8 +3,11 @@ import { useEffect } from "react";
|
||||
export const KeyEventController = () => {
|
||||
useEffect(() => {
|
||||
const handleKeydown = (event) => {
|
||||
if (event.key === "F5" || (event.ctrlKey && event.key === "r") ||
|
||||
(event.metaKey && event.key === "r")) {
|
||||
if (
|
||||
event.key === "F5" ||
|
||||
(event.ctrlKey && event.key === "r") ||
|
||||
(event.metaKey && event.key === "r")
|
||||
) {
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Others,
|
||||
AdvancedSettings,
|
||||
Vr,
|
||||
Hotkeys,
|
||||
Supporters,
|
||||
AboutVrct,
|
||||
} from "@setting_box";
|
||||
@@ -27,6 +28,8 @@ export const SettingBox = () => {
|
||||
return <Others />;
|
||||
case "vr":
|
||||
return <Vr />;
|
||||
case "hotkeys":
|
||||
return <Hotkeys />;
|
||||
case "advanced_settings":
|
||||
return <AdvancedSettings />;
|
||||
case "supporters":
|
||||
|
||||
@@ -8,23 +8,36 @@ const _Entry = forwardRef((props, ref) => {
|
||||
useImperativeHandle(ref, () => ({
|
||||
focus: () => {
|
||||
inputRef.current.focus();
|
||||
},
|
||||
blur: () => {
|
||||
inputRef.current.blur();
|
||||
}
|
||||
}));
|
||||
const input_class_names = clsx(styles.entry_input_area, {
|
||||
[styles.is_disabled]: props.is_disabled
|
||||
[styles.is_disabled]: props.is_disabled,
|
||||
});
|
||||
const input_wrapper_class_names = clsx(styles.entry_wrapper, {
|
||||
[styles.is_activated]: props.is_activated,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.entry_container}>
|
||||
<div
|
||||
className={styles.entry_wrapper}
|
||||
className={input_wrapper_class_names}
|
||||
style={{width: props.width }}
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
text={props.text ? props.text : "text"}
|
||||
placeholder={props.placeholder ? props.placeholder : ""}
|
||||
className={input_class_names}
|
||||
value={props.ui_variable === null ? "" : props.ui_variable}
|
||||
onChange={(e) => props.onChange(e)}
|
||||
onChange={(e) => props.onChange?.(e)}
|
||||
onFocus={(e) => props.onFocus?.(e)}
|
||||
onBlur={(e) => props.onBlur?.(e)}
|
||||
onKeyDown={(e) => props.onKeyDown?.(e)}
|
||||
onKeyUp={(e) => props.onKeyUp?.(e)}
|
||||
readOnly={props.readOnly === true ? true : false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
background-color: var(--dark_875_color);
|
||||
border: 0.1rem solid var(--dark_750_color);
|
||||
border-radius: 0.4rem;
|
||||
&.is_activated {
|
||||
border: 0.1rem solid var(--primary_400_color);
|
||||
}
|
||||
}
|
||||
|
||||
.entry_input_area {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import styles from "./HotkeysEntry.module.scss";
|
||||
import { _Entry } from "../_atoms/_entry/_Entry";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import DeleteSvg from "@images/cancel.svg?react";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
export const HotkeysEntry = (props) => {
|
||||
const [isAcceptingInput, setIsAcceptingInput] = useState(false);
|
||||
const [displayValue, setDisplayValue] = useState("");
|
||||
const lastKeyRef = useRef(null);
|
||||
const isModifierOnlyRef = useRef(false);
|
||||
const entryRef = useRef(null);
|
||||
const pressedKeys = useRef(new Set());
|
||||
const keysRef = useRef([]);
|
||||
|
||||
useEffect(() => {
|
||||
const init_display_value = props.value[props.hotkey_id] ? props.value[props.hotkey_id].join(" + ") : "";
|
||||
setDisplayValue(init_display_value);
|
||||
}, []);
|
||||
|
||||
const updateHotkeys = (keys) => {
|
||||
entryRef.current.blur();
|
||||
const result = props.setHotkeys({ [props.hotkey_id]: keys });
|
||||
if (result === false) setDisplayValue("");
|
||||
};
|
||||
|
||||
const processKey = (key) => {
|
||||
if (/^[a-zA-Z]$/.test(key)) return key.toUpperCase();
|
||||
if (key === "Meta") return "Super";
|
||||
return key;
|
||||
};
|
||||
|
||||
const handleKeyInput = (event) => {
|
||||
const keys = [];
|
||||
const nonModifierKeys = [];
|
||||
|
||||
["Ctrl", "Shift", "Alt", "Meta"].forEach((modKey) => {
|
||||
if (event[`${modKey.toLowerCase()}Key`] && !keys.includes(modKey)) {
|
||||
let register_mod_key = (modKey === "Meta") ? "Super" : modKey;
|
||||
keys.push(register_mod_key);
|
||||
}
|
||||
});
|
||||
|
||||
const key = processKey(event.key);
|
||||
if (!["Control", "Shift", "Alt", "Meta"].includes(event.key)) {
|
||||
keys.push(key);
|
||||
nonModifierKeys.push(key);
|
||||
}
|
||||
|
||||
if (!pressedKeys.current.has(key)) {
|
||||
pressedKeys.current.add(key);
|
||||
}
|
||||
|
||||
keysRef.current = keys;
|
||||
setDisplayValue(keys.join(" + "));
|
||||
isModifierOnlyRef.current = nonModifierKeys.length === 0;
|
||||
};
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
event.preventDefault();
|
||||
if (lastKeyRef.current === event.key) return;
|
||||
|
||||
lastKeyRef.current = event.key;
|
||||
handleKeyInput(event);
|
||||
};
|
||||
|
||||
const handleKeyUp = (event) => {
|
||||
lastKeyRef.current = null;
|
||||
|
||||
const key = processKey(event.key);
|
||||
pressedKeys.current.delete(key);
|
||||
|
||||
if (isModifierOnlyRef.current) {
|
||||
setDisplayValue("");
|
||||
}
|
||||
|
||||
if (pressedKeys.current.size === 0) {
|
||||
const hasNonModifierKeys = keysRef.current.some(
|
||||
(key) => !["Ctrl", "Shift", "Alt", "Super"].includes(key)
|
||||
);
|
||||
if (hasNonModifierKeys) {
|
||||
updateHotkeys(keysRef.current);
|
||||
} else {
|
||||
const display_value = props.value[props.hotkey_id] ? props.value[props.hotkey_id].join(" + ") : "";
|
||||
setDisplayValue(display_value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
setIsAcceptingInput(false);
|
||||
pressedKeys.current.clear();
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
updateHotkeys(null);
|
||||
setDisplayValue("");
|
||||
};
|
||||
|
||||
const is_pending = props.state === "pending";
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{is_pending && <span className={styles.loader}></span>}
|
||||
<_Entry
|
||||
ref={entryRef}
|
||||
type="text"
|
||||
onFocus={() => setIsAcceptingInput(true)}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyUp={handleKeyUp}
|
||||
ui_variable={displayValue}
|
||||
width="20rem"
|
||||
is_activated={isAcceptingInput}
|
||||
is_disabled={is_pending}
|
||||
readOnly
|
||||
/>
|
||||
<button className={clsx(styles.delete_button, { [styles.is_pending]: is_pending })} onClick={handleDelete}>
|
||||
<DeleteSvg className={styles.delete_svg}/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.delete_button {
|
||||
padding: 0.4rem;
|
||||
font-size: 1.4rem;
|
||||
// background-color: var(--dark_800_color);
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 0.2rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
&.is_pending {
|
||||
pointer-events: none;
|
||||
& .delete_svg {
|
||||
color: var(--dark_600_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.delete_svg {
|
||||
width: 2.2rem;
|
||||
color: var(--error_bc_color);
|
||||
}
|
||||
|
||||
.loader {
|
||||
@include loader(2rem, 0.2rem, left, -2.2rem);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ export { ComputeDevice } from "./compute_device/ComputeDevice";
|
||||
export { DeeplAuthKey, OpenWebpage_DeeplAuthKey } from "./deepl_auth_key/DeeplAuthKey";
|
||||
export { DropdownMenu } from "./dropdown_menu/DropdownMenu";
|
||||
export { Entry } from "./entry/Entry";
|
||||
export { HotkeysEntry } from "./hotkeys_entry/HotkeysEntry";
|
||||
export { LabelComponent } from "./label_component/LabelComponent";
|
||||
export { RadioButton } from "./radio_button/RadioButton";
|
||||
export { SectionLabelComponent } from "./section_label_component/SectionLabelComponent";
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Slider,
|
||||
SwitchBox,
|
||||
Entry,
|
||||
HotkeysEntry,
|
||||
RadioButton,
|
||||
OpenWebpage_DeeplAuthKey,
|
||||
DeeplAuthKey,
|
||||
@@ -75,6 +76,10 @@ export const EntryContainer = (props) => (
|
||||
<CommonContainer Component={Entry} {...props} add_break_point={false} />
|
||||
);
|
||||
|
||||
export const HotkeysEntryContainer = (props) => (
|
||||
<CommonContainer Component={HotkeysEntry} {...props} />
|
||||
);
|
||||
|
||||
export const RadioButtonContainer = (props) => (
|
||||
<CommonContainer Component={RadioButton} {...props} />
|
||||
);
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useHotkeys } from "@logics_configs";
|
||||
import styles from "./Hotkeys.module.scss";
|
||||
import { HotkeysEntryContainer } from "../_templates/Templates";
|
||||
import { useTranslation } from "react-i18next";
|
||||
export const Hotkeys = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<HotkeysBoxContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const HotkeysBoxContainer = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentHotkeys, setHotkeys } = useHotkeys();
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<HotkeysEntryContainer
|
||||
label={t("config_page.hotkeys.toggle_vrct_visibility.label")}
|
||||
hotkey_id="toggle_vrct_visibility"
|
||||
value={currentHotkeys.data}
|
||||
state={currentHotkeys.state}
|
||||
setHotkeys={setHotkeys}
|
||||
/>
|
||||
<HotkeysEntryContainer
|
||||
label={t("config_page.hotkeys.toggle_translation.label", {translation: t("main_page.translation")})}
|
||||
hotkey_id="toggle_translation"
|
||||
value={currentHotkeys.data}
|
||||
state={currentHotkeys.state}
|
||||
setHotkeys={setHotkeys}
|
||||
/>
|
||||
<HotkeysEntryContainer
|
||||
label={t("config_page.hotkeys.toggle_transcription_send.label", {transcription_send: t("main_page.transcription_send")})}
|
||||
hotkey_id="toggle_transcription_send"
|
||||
value={currentHotkeys.data}
|
||||
state={currentHotkeys.state}
|
||||
setHotkeys={setHotkeys}
|
||||
/>
|
||||
<HotkeysEntryContainer
|
||||
label={t("config_page.hotkeys.toggle_transcription_receive.label", {transcription_receive: t("main_page.transcription_receive")})}
|
||||
hotkey_id="toggle_transcription_receive"
|
||||
value={currentHotkeys.data}
|
||||
state={currentHotkeys.state}
|
||||
setHotkeys={setHotkeys}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
.container {
|
||||
display: flex;
|
||||
// gap: 6.4rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -5,5 +5,6 @@ export { Transcription } from "./transcription/Transcription";
|
||||
export { Others, VrcMicMuteSyncContainer } from "./others/Others";
|
||||
export { AdvancedSettings } from "./advanced_settings/AdvancedSettings";
|
||||
export { Vr } from "./vr/Vr";
|
||||
export { Hotkeys } from "./hotkeys/Hotkeys";
|
||||
export { AboutVrct } from "./about_vrct/AboutVrct";
|
||||
export { Supporters } from "./supporters/Supporters";
|
||||
@@ -10,6 +10,7 @@ export const SidebarSection = () => {
|
||||
<Tab tab_id="transcription" />
|
||||
<Tab tab_id="vr" />
|
||||
<Tab tab_id="others" />
|
||||
<Tab tab_id="hotkeys" />
|
||||
<Tab tab_id="advanced_settings" />
|
||||
</div>
|
||||
<div className={styles.separated_tabs_wrapper}>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useLayoutEffect, useRef } from "react";
|
||||
import styles from "./MessageInputBox.module.scss";
|
||||
import SendMessageSvg from "@images/send_message.svg?react";
|
||||
import { useMessage } from "@logics_common";
|
||||
import { useSendMessageButtonType, useEnableAutoClearMessageInputBox } from "@logics_configs";
|
||||
import { useMessageLogScroll } from "@logics_main";
|
||||
import { store } from "@store";
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
|
||||
export const MessageInputBox = () => {
|
||||
const [message_history, setMessageHistory] = useState([]);
|
||||
@@ -22,6 +24,12 @@ export const MessageInputBox = () => {
|
||||
|
||||
const { scrollToBottom } = useMessageLogScroll();
|
||||
|
||||
const log_box_ref = useRef(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
store.text_area_ref = log_box_ref;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentMessageLogs.data) {
|
||||
const sentMessages = currentMessageLogs.data
|
||||
@@ -33,6 +41,7 @@ export const MessageInputBox = () => {
|
||||
|
||||
const onSubmitFunction = (e) => {
|
||||
e.preventDefault();
|
||||
// appWindow.minimize();
|
||||
|
||||
if (!currentMessageInputValue.data.trim()) return updateMessageInputValue("");
|
||||
|
||||
@@ -90,6 +99,7 @@ export const MessageInputBox = () => {
|
||||
<div className={styles.container}>
|
||||
<div className={styles.message_box_wrapper}>
|
||||
<textarea
|
||||
ref={log_box_ref}
|
||||
className={styles.message_box_input_area}
|
||||
onChange={onChangeFunction}
|
||||
onBlur={stopTyping}
|
||||
|
||||
131
src-ui/logics/configs/hotkeys/useHotkeys.js
Normal file
131
src-ui/logics/configs/hotkeys/useHotkeys.js
Normal file
@@ -0,0 +1,131 @@
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
|
||||
import { store, useStore_Hotkeys } from "@store";
|
||||
import { useStdoutToPython } from "@logics/useStdoutToPython";
|
||||
import { useNotificationStatus } from "@logics_common";
|
||||
import { useMainFunction } from "@logics_main";
|
||||
import { register, unregisterAll, isRegistered } from "@tauri-apps/api/globalShortcut";
|
||||
|
||||
export const useHotkeys = () => {
|
||||
const { asyncStdoutToPython } = useStdoutToPython();
|
||||
const { currentHotkeys, updateHotkeys, pendingHotkeys } = useStore_Hotkeys();
|
||||
const {
|
||||
toggleTranslation,
|
||||
toggleTranscriptionSend,
|
||||
toggleTranscriptionReceive,
|
||||
} = useMainFunction();
|
||||
|
||||
|
||||
const getHotkeys = () => {
|
||||
pendingHotkeys();
|
||||
asyncStdoutToPython("/get/data/hotkeys");
|
||||
};
|
||||
const { showNotification_Success, showNotification_Error, closeNotification } = useNotificationStatus();
|
||||
|
||||
const setHotkeys = (hotkeys) => {
|
||||
pendingHotkeys();
|
||||
|
||||
const updatedHotkeys = { ...currentHotkeys.data, ...hotkeys };
|
||||
const usedShortcuts = new Set();
|
||||
const conflictingKeys = [];
|
||||
|
||||
for (const [actionKey, hotkey] of Object.entries(updatedHotkeys)) {
|
||||
if (!hotkey) continue;
|
||||
|
||||
const shortcut = parseHotkey(hotkey);
|
||||
if (usedShortcuts.has(shortcut)) {
|
||||
showNotification_Error(`The hotkey ${shortcut} is already in use.`);
|
||||
updatedHotkeys[actionKey] = null;
|
||||
conflictingKeys.push(actionKey);
|
||||
} else {
|
||||
usedShortcuts.add(shortcut);
|
||||
}
|
||||
}
|
||||
|
||||
updateHotkeys(updatedHotkeys);
|
||||
|
||||
if (conflictingKeys.length === 0) {
|
||||
asyncStdoutToPython("/set/data/hotkeys", updatedHotkeys);
|
||||
closeNotification();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const registerShortcuts = async () => {
|
||||
try {
|
||||
await unregisterAll();
|
||||
|
||||
const hotkeyEntries = Object.entries(currentHotkeys.data);
|
||||
|
||||
for (const [actionKey, hotkeyRaw] of hotkeyEntries) {
|
||||
if (!hotkeyRaw) continue;
|
||||
|
||||
const shortcut = parseHotkey(hotkeyRaw);
|
||||
const isAlreadyRegistered = await isRegistered(shortcut);
|
||||
|
||||
if (!isAlreadyRegistered) {
|
||||
await register(shortcut, async () => {
|
||||
switch (actionKey) {
|
||||
case "toggle_vrct_visibility": {
|
||||
const minimized = await appWindow.isMinimized();
|
||||
if (minimized) {
|
||||
appWindow.unminimize();
|
||||
await appWindow.setFocus();
|
||||
store.text_area_ref.current?.focus();
|
||||
} else {
|
||||
appWindow.minimize();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "toggle_translation": {
|
||||
toggleTranslation();
|
||||
break;
|
||||
}
|
||||
case "toggle_transcription_send": {
|
||||
toggleTranscriptionSend();
|
||||
break;
|
||||
}
|
||||
case "toggle_transcription_receive": {
|
||||
toggleTranscriptionReceive();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.warn(`No handler defined for action: ${actionKey}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to register global shortcuts:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
currentHotkeys,
|
||||
getHotkeys,
|
||||
updateHotkeys,
|
||||
setHotkeys,
|
||||
registerShortcuts,
|
||||
unregisterAll,
|
||||
};
|
||||
};
|
||||
|
||||
// 修飾キーのパースを行う関数
|
||||
const parseHotkey = (hotkeyString) => {
|
||||
const keyMap = {
|
||||
Ctrl: "Control",
|
||||
Alt: "Alt",
|
||||
Shift: "Shift",
|
||||
Meta: "Super",
|
||||
};
|
||||
|
||||
|
||||
return hotkeyString
|
||||
.map((key) => keyMap[key] || key)
|
||||
.join("+");
|
||||
};
|
||||
@@ -51,6 +51,8 @@ export { useOverlayShowOnlyTranslatedMessages } from "./vr/useOverlayShowOnlyTra
|
||||
export { useOverlayLargeLogSettings } from "./vr/useOverlayLargeLogSettings";
|
||||
export { useSendTextToOverlay } from "./vr/useSendTextToOverlay";
|
||||
|
||||
export { useHotkeys } from "./hotkeys/useHotkeys";
|
||||
|
||||
export { useOscIpAddress } from "./advanced_settings/useOscIpAddress";
|
||||
export { useOscPort } from "./advanced_settings/useOscPort";
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useStore_TranscriptionReceiveStatus,
|
||||
useStore_ForegroundStatus,
|
||||
} from "@store";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useStdoutToPython } from "@logics/useStdoutToPython";
|
||||
|
||||
export const useMainFunction = () => {
|
||||
@@ -40,7 +40,11 @@ export const useMainFunction = () => {
|
||||
asyncStdoutToPython("/set/disable/translation");
|
||||
}
|
||||
};
|
||||
const toggleTranslation = () => setTranslation(!currentTranslationStatus.data);
|
||||
const toggleTranslation = () => {
|
||||
updateTranslationStatus(prev_state => {
|
||||
if (prev_state.state === "ok") setTranslation(!prev_state.data);
|
||||
}, { set_state: "pending" });
|
||||
};
|
||||
|
||||
const setTranscriptionSend = (to_enable) => {
|
||||
pendingTranscriptionSendStatus();
|
||||
@@ -50,7 +54,11 @@ export const useMainFunction = () => {
|
||||
asyncStdoutToPython("/set/disable/transcription_send");
|
||||
}
|
||||
};
|
||||
const toggleTranscriptionSend = () => setTranscriptionSend(!currentTranscriptionSendStatus.data);
|
||||
const toggleTranscriptionSend = () => {
|
||||
updateTranscriptionSendStatus(prev_state => {
|
||||
if (prev_state.state === "ok") setTranscriptionSend(!prev_state.data);
|
||||
}, { set_state: "pending" });
|
||||
};
|
||||
|
||||
const setTranscriptionReceive = (to_enable) => {
|
||||
pendingTranscriptionReceiveStatus();
|
||||
@@ -60,7 +68,11 @@ export const useMainFunction = () => {
|
||||
asyncStdoutToPython("/set/disable/transcription_receive");
|
||||
}
|
||||
};
|
||||
const toggleTranscriptionReceive = () => setTranscriptionReceive(!currentTranscriptionReceiveStatus.data);
|
||||
const toggleTranscriptionReceive = () => {
|
||||
updateTranscriptionReceiveStatus(prev_state => {
|
||||
if (prev_state.state === "ok") setTranscriptionReceive(!prev_state.data);
|
||||
}, { set_state: "pending" });
|
||||
};
|
||||
|
||||
|
||||
const toggleForeground = () => {
|
||||
|
||||
@@ -68,6 +68,7 @@ import {
|
||||
useIsEnabledOverlayLargeLog,
|
||||
useOverlayLargeLogSettings,
|
||||
useOverlayShowOnlyTranslatedMessages,
|
||||
useHotkeys,
|
||||
useOscIpAddress,
|
||||
useOscPort,
|
||||
} from "@logics_configs";
|
||||
@@ -168,6 +169,8 @@ export const useReceiveRoutes = () => {
|
||||
const { updateIsEnabledOverlayLargeLog } = useIsEnabledOverlayLargeLog();
|
||||
const { updateOverlayShowOnlyTranslatedMessages } = useOverlayShowOnlyTranslatedMessages();
|
||||
|
||||
const { updateHotkeys } = useHotkeys();
|
||||
|
||||
const { updateOscIpAddress } = useOscIpAddress();
|
||||
const { updateOscPort } = useOscPort();
|
||||
|
||||
@@ -458,6 +461,10 @@ export const useReceiveRoutes = () => {
|
||||
"/set/enable/send_received_message_to_vrc": updateEnableSendReceivedMessageToVrc,
|
||||
"/set/disable/send_received_message_to_vrc": updateEnableSendReceivedMessageToVrc,
|
||||
|
||||
// Hotkeys
|
||||
"/get/data/hotkeys": updateHotkeys,
|
||||
"/set/data/hotkeys": updateHotkeys,
|
||||
|
||||
// Advanced Settings
|
||||
"/get/data/osc_ip_address": updateOscIpAddress,
|
||||
"/set/data/osc_ip_address": updateOscIpAddress,
|
||||
|
||||
@@ -18,6 +18,7 @@ export const store = {
|
||||
config_page: null,
|
||||
setting_box_scroll_container: null,
|
||||
log_box_ref: null,
|
||||
text_area_ref: null,
|
||||
is_applied_init_message_box_height: false,
|
||||
};
|
||||
|
||||
@@ -54,20 +55,20 @@ const createAtomWithHook = (initialValue, base_name, options) => {
|
||||
});
|
||||
};
|
||||
|
||||
const updateAtom = (payload) => {
|
||||
const updateAtom = (payload, options = {}) => {
|
||||
const { remain_state = false, set_state } = options;
|
||||
|
||||
setAtom((currentValue) => {
|
||||
if (typeof payload === "function") {
|
||||
const updated_data = payload(currentValue);
|
||||
const new_state = set_state ?? (remain_state ? currentValue.state : "ok");
|
||||
|
||||
const updated_data = typeof payload === "function"
|
||||
? payload(currentValue)
|
||||
: payload;
|
||||
|
||||
return {
|
||||
state: "ok",
|
||||
state: new_state,
|
||||
data: updated_data,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
state: "ok",
|
||||
data: payload,
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -263,6 +264,14 @@ export const { atomInstance: Atom_EnableVrcMicMuteSync, useHook: useStore_Enable
|
||||
export const { atomInstance: Atom_EnableSendMessageToVrc, useHook: useStore_EnableSendMessageToVrc } = createAtomWithHook(true, "EnableSendMessageToVrc");
|
||||
export const { atomInstance: Atom_EnableSendReceivedMessageToVrc, useHook: useStore_EnableSendReceivedMessageToVrc } = createAtomWithHook(false, "EnableSendReceivedMessageToVrc");
|
||||
|
||||
// Hotkeys
|
||||
export const { atomInstance: Atom_Hotkeys, useHook: useStore_Hotkeys } = createAtomWithHook({
|
||||
toggle_vrct_visibility: null,
|
||||
toggle_translation: null,
|
||||
toggle_transcription_send: null,
|
||||
toggle_transcription_receive: null,
|
||||
}, "Hotkeys");
|
||||
|
||||
// Advanced Settings
|
||||
export const { atomInstance: Atom_OscIpAddress, useHook: useStore_OscIpAddress } = createAtomWithHook("127.0.0.1", "OscIpAddress");
|
||||
export const { atomInstance: Atom_OscPort, useHook: useStore_OscPort } = createAtomWithHook("9000", "OscPort");
|
||||
|
||||
Reference in New Issue
Block a user