diff --git a/src-ui/app/App.jsx b/src-ui/app/App.jsx index 80809c7d..d335ea68 100644 --- a/src-ui/app/App.jsx +++ b/src-ui/app/App.jsx @@ -34,6 +34,7 @@ import { useUiLanguage } from "@logics_configs/useUiLanguage"; import { useIsMainPageCompactMode } from "@logics_main/useIsMainPageCompactMode"; import { useLanguageSettings } from "@logics_main/useLanguageSettings"; import { useSelectableLanguageList } from "@logics_main/useSelectableLanguageList"; +import { useMessageInputBoxRatio } from "@logics_main/useMessageInputBoxRatio"; const StartPythonFacadeComponent = () => { const { asyncStartPython } = useStartPython(); @@ -62,6 +63,7 @@ const StartPythonFacadeComponent = () => { getSelectedTranslationEngines, } = useLanguageSettings(); const { getSelectableLanguageList } = useSelectableLanguageList(); + const { getMessageInputBoxRatio } = useMessageInputBoxRatio(); useEffect(() => { @@ -70,6 +72,7 @@ const StartPythonFacadeComponent = () => { asyncStartPython().then((result) => { getUiLanguage(); getIsMainPageCompactMode(); + getMessageInputBoxRatio(); getSoftwareVersion(); diff --git a/src-ui/app/main_page/main_section/message_container/MessageContainer.jsx b/src-ui/app/main_page/main_section/message_container/MessageContainer.jsx index 68e8875f..45693788 100644 --- a/src-ui/app/main_page/main_section/message_container/MessageContainer.jsx +++ b/src-ui/app/main_page/main_section/message_container/MessageContainer.jsx @@ -1,34 +1,97 @@ import { useResizable } from "react-resizable-layout"; - +import { useRef, useEffect, useState } from "react"; import styles from "./MessageContainer.module.scss"; - +import { appWindow } from "@tauri-apps/api/window"; // Tauriのwindow APIをインポート import { LogBox } from "./log_box/LogBox"; import { MessageInputBox } from "./message_input_box/MessageInputBox"; +import { useMessageInputBoxRatio } from "@logics_main/useMessageInputBoxRatio"; export const MessageContainer = () => { + const { currentMessageInputBoxRatio, setMessageInputBoxRatio } = useMessageInputBoxRatio(); + const [message_box_height_in_rem, setMessageBoxHeightInRem] = useState(10); + + const container_ref = useRef(null); + const log_box_ref = useRef(null); + const message_box_wrapper_ref = useRef(null); + + const calculateMessageBoxRatioAndHeight = () => { + if (log_box_ref.current && message_box_wrapper_ref.current) { + const container_height = container_ref.current.offsetHeight; + const container_padding_bottom = parseFloat(window.getComputedStyle(container_ref.current).paddingBottom); + const total_height = container_height - container_padding_bottom; + + const message_box_height = message_box_wrapper_ref.current.offsetHeight; + const message_box_ratio = (message_box_height / total_height) * 100; + + setMessageInputBoxRatio(message_box_ratio); + + const height_in_rem = convertRatioToRem(message_box_ratio); + setMessageBoxHeightInRem(height_in_rem); + } + }; + const { position, separatorProps } = useResizable({ axis: "y", - reverse: true + reverse: true, + onResizeEnd: calculateMessageBoxRatioAndHeight, }); + useEffect(() => { + setMessageBoxHeightInRem((position / 10) - 1.5); + }, [position]); + + + useEffect(() => { + setMessageBoxHeightInRem(convertRatioToRem(currentMessageInputBoxRatio.data)); + }, [currentMessageInputBoxRatio.data]); + + const convertRatioToRem = (ratio) => { + const container_height = container_ref.current.offsetHeight; + const container_padding_bottom = parseFloat(window.getComputedStyle(container_ref.current).paddingBottom); + const total_height = container_height - container_padding_bottom; + + return ((ratio / 100) * total_height / 10) | 0; // 10px = 1rem + }; + + + // Tauriのwindow resizeイベントをリッスン + useEffect(() => { + let resizeTimeout; + + // イベントのリスナーを設定 + const unlisten = appWindow.onResized(() => { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => { + calculateMessageBoxRatioAndHeight(); // リサイズが終了した後に実行 + }, 200); // ドラッグが終了したと見なすまでの遅延(200ms程度) + }); + + return () => { + unlisten.then((dispose) => dispose()); // イベントリスナーを解除 + }; + }, []); + return ( -
- - -
+
+
+ +
+ +
); }; -const Separator = ({ ...props }) => { +const Separator = ({ onDragStart, ...props }) => { return ( -
+
); -}; \ No newline at end of file +}; diff --git a/src-ui/app/main_page/main_section/message_container/MessageContainer.module.scss b/src-ui/app/main_page/main_section/message_container/MessageContainer.module.scss index 2bd2288b..27763d9c 100644 --- a/src-ui/app/main_page/main_section/message_container/MessageContainer.module.scss +++ b/src-ui/app/main_page/main_section/message_container/MessageContainer.module.scss @@ -6,6 +6,11 @@ padding: 0 1.6rem 1rem 1.6rem; } +.log_box_resize_wrapper { + flex: 1; + overflow: auto; +} + .separator { position: relative; width: 100%; @@ -32,5 +37,5 @@ .message_box_resize_wrapper { height: 10rem; min-height: 3.8rem; - max-height: 80%; + max-height: 90%; } \ No newline at end of file diff --git a/src-ui/logics/main/useMessageInputBoxRatio.js b/src-ui/logics/main/useMessageInputBoxRatio.js new file mode 100644 index 00000000..61a36706 --- /dev/null +++ b/src-ui/logics/main/useMessageInputBoxRatio.js @@ -0,0 +1,24 @@ +import { useStore_MessageInputBoxRatio } from "@store"; +import { useStdoutToPython } from "@logics/useStdoutToPython"; +import { clampMinMax } from "@utils/clampMinMax"; +export const useMessageInputBoxRatio = () => { + const { asyncStdoutToPython } = useStdoutToPython(); + const { currentMessageInputBoxRatio, updateMessageInputBoxRatio } = useStore_MessageInputBoxRatio(); + + const getMessageInputBoxRatio = () => { + asyncStdoutToPython("/get/data/message_box_ratio"); + }; + + const setMessageInputBoxRatio = (ratio) => { + const parsed = parseFloat(ratio.toFixed(2)); + const valid_ratio = clampMinMax(parsed, 1, 99); + asyncStdoutToPython("/set/data/message_box_ratio", valid_ratio); + }; + + return { + currentMessageInputBoxRatio, + getMessageInputBoxRatio, + updateMessageInputBoxRatio, + setMessageInputBoxRatio, + }; +}; \ No newline at end of file diff --git a/src-ui/logics/useReceiveRoutes.js b/src-ui/logics/useReceiveRoutes.js index 45bb2cbd..406a7b31 100644 --- a/src-ui/logics/useReceiveRoutes.js +++ b/src-ui/logics/useReceiveRoutes.js @@ -1,12 +1,15 @@ import { translator_status } from "@data"; import { arrayToObject } from "@utils/arrayToObject"; -import { useMainFunction } from "@logics_main/useMainFunction"; + import { useMessage } from "@logics_common/useMessage"; +import { useVolume } from "@logics_common/useVolume"; + +import { useMainFunction } from "@logics_main/useMainFunction"; import { useSelectableLanguageList } from "@logics_main/useSelectableLanguageList"; import { useLanguageSettings } from "@logics_main/useLanguageSettings"; import { useIsMainPageCompactMode } from "@logics_main/useIsMainPageCompactMode"; -import { useVolume } from "@logics_common/useVolume"; +import { useMessageInputBoxRatio } from "@logics_main/useMessageInputBoxRatio"; import { useSoftwareVersion } from "@logics_configs/useSoftwareVersion"; @@ -67,6 +70,9 @@ export const useReceiveRoutes = () => { updateSpeakerThresholdCheckStatus, } = useVolume(); + const { updateMessageInputBoxRatio } = useMessageInputBoxRatio(); + + const routes = { // Main Page // Page Controls @@ -118,6 +124,10 @@ export const useReceiveRoutes = () => { "/run/transcription_send_mic_message": addSentMessageLog, "/run/transcription_receive_speaker_message": addReceivedMessageLog, + // Message Box + "/get/data/message_box_ratio": updateMessageInputBoxRatio, + "/set/data/message_box_ratio": updateMessageInputBoxRatio, + // Config Page // Common diff --git a/src-ui/logics/useStdoutToPython.js b/src-ui/logics/useStdoutToPython.js index 3c852726..fa7c0e35 100644 --- a/src-ui/logics/useStdoutToPython.js +++ b/src-ui/logics/useStdoutToPython.js @@ -2,9 +2,9 @@ import { store } from "@store"; import { encode } from "js-base64"; export const useStdoutToPython = () => { - const asyncStdoutToPython = async (path, value) => { + const asyncStdoutToPython = async (path, value = undefined) => { let send_object = { endpoint: path }; - if (value) send_object.data = encode(JSON.stringify(value)); + if (value !== undefined) send_object.data = encode(JSON.stringify(value)); // send to python const backend_subprocess = store.backend_subprocess; diff --git a/src-ui/store.js b/src-ui/store.js index 56e1f9ea..363c21fb 100644 --- a/src-ui/store.js +++ b/src-ui/store.js @@ -101,18 +101,15 @@ const createAtomWithHook = (initialValue, base_name, options) => { export const { atomInstance: Atom_SoftwareVersion, useHook: useStore_SoftwareVersion } = createAtomWithHook("-", "SoftwareVersion"); +// Main Page +// Functions export const { atomInstance: Atom_TranslationStatus, useHook: useStore_TranslationStatus } = createAtomWithHook(false, "TranslationStatus", {is_state_ok: true}); export const { atomInstance: Atom_TranscriptionSendStatus, useHook: useStore_TranscriptionSendStatus } = createAtomWithHook(false, "TranscriptionSendStatus", {is_state_ok: true}); export const { atomInstance: Atom_TranscriptionReceiveStatus, useHook: useStore_TranscriptionReceiveStatus } = createAtomWithHook(false, "TranscriptionReceiveStatus", {is_state_ok: true}); export const { atomInstance: Atom_ForegroundStatus, useHook: useStore_ForegroundStatus } = createAtomWithHook(false, "ForegroundStatus", {is_state_ok: true}); export const { atomInstance: Atom_MessageLogs, useHook: useStore_MessageLogs } = createAtomWithHook(generateTestData(20), "MessageLogs"); -export const { atomInstance: Atom_IsMainPageCompactMode, useHook: useStore_IsMainPageCompactMode } = createAtomWithHook(false, "IsMainPageCompactMode"); -export const { atomInstance: Atom_IsOpenedLanguageSelector, useHook: useStore_IsOpenedLanguageSelector } = createAtomWithHook( - { your_language: false, target_language: false }, - "IsOpenedLanguageSelector" -); export const { atomInstance: Atom_SelectableLanguageList, useHook: useStore_SelectableLanguageList } = createAtomWithHook([], "SelectableLanguageList"); export const { atomInstance: Atom_SelectedPresetTabNumber, useHook: useStore_SelectedPresetTabNumber } = createAtomWithHook("", "SelectedPresetTabNumber"); @@ -127,10 +124,21 @@ export const { atomInstance: Atom_SelectedTranslationEngines, useHook: useStore_ export const { atomInstance: Atom_IsOpenedConfigPage, useHook: useStore_IsOpenedConfigPage } = createAtomWithHook(false, "IsOpenedConfigPage"); export const { atomInstance: Atom_SelectedConfigTabId, useHook: useStore_SelectedConfigTabId } = createAtomWithHook("device", "SelectedConfigTabId"); -export const { atomInstance: Atom_IsOpenedDropdownMenu, useHook: useStore_IsOpenedDropdownMenu } = createAtomWithHook("", "IsOpenedDropdownMenu"); + +// Designs +export const { atomInstance: Atom_IsMainPageCompactMode, useHook: useStore_IsMainPageCompactMode } = createAtomWithHook(false, "IsMainPageCompactMode"); +export const { atomInstance: Atom_MessageInputBoxRatio, useHook: useStore_MessageInputBoxRatio } = createAtomWithHook(20, "MessageInputBoxRatio"); +export const { atomInstance: Atom_IsOpenedLanguageSelector, useHook: useStore_IsOpenedLanguageSelector } = createAtomWithHook( + { your_language: false, target_language: false }, + "IsOpenedLanguageSelector" +); // Config Page +// Designs +export const { atomInstance: Atom_IsOpenedDropdownMenu, useHook: useStore_IsOpenedDropdownMenu } = createAtomWithHook("", "IsOpenedDropdownMenu"); + +// Device export const { atomInstance: Atom_EnableAutoMicSelect, useHook: useStore_EnableAutoMicSelect } = createAtomWithHook(true, "EnableAutoMicSelect"); export const { atomInstance: Atom_EnableAutoSpeakerSelect, useHook: useStore_EnableAutoSpeakerSelect } = createAtomWithHook(true, "EnableAutoSpeakerSelect"); diff --git a/src-ui/utils/clampMinMax.js b/src-ui/utils/clampMinMax.js new file mode 100644 index 00000000..1eced3f0 --- /dev/null +++ b/src-ui/utils/clampMinMax.js @@ -0,0 +1,8 @@ +export const clampMinMax = (value, min, max) => { + return Math.min(Math.max(value, min), max); +}; + +// console.log(clamp(5, 1, 10)); // 5 (範囲内) +// console.log(clamp(-3, 0, 10)); // 0 (minより小さい) +// console.log(clamp(15, 1, 10)); // 10 (maxより大きい) +// console.log(clamp(7.5, 1, 10)); // 7.5 (範囲内、少数) \ No newline at end of file