Merge branch 'message_format' into develop

This commit is contained in:
Sakamoto Shiina
2025-08-26 17:41:02 +09:00
19 changed files with 786 additions and 48 deletions

View File

@@ -211,6 +211,7 @@ config_page:
others: others:
section_label_sounds: "Sounds" section_label_sounds: "Sounds"
section_label_message_formats: "Message Formats"
auto_clear_the_message_box: auto_clear_the_message_box:
label: "Auto Clear Message box" label: "Auto Clear Message box"
send_only_translated_messages: send_only_translated_messages:
@@ -231,6 +232,26 @@ config_page:
label: "Send Received Message To VRChat" label: "Send Received Message To VRChat"
desc: "Send the message you received from the speaker's voice to VRChat's chatbox." desc: "Send the message you received from the speaker's voice to VRChat's chatbox."
message_format_common:
example_view:
title: "Preview"
original_translated: "Original + Translated"
original_translated_multi: "Original + Translated (Multi-Translation)"
translated_only_multi: "Translated Only (Multi-Translation)"
translated_only: "Translated Only"
original_only: "Original Only"
settings:
title: "Settings"
original: "Original"
translated: "Translated"
for_multi_translation: For Multi-Translation
send_message_format:
label: "Message Format (Send)"
desc: "You can change the decoration of the message you want to send."
received_message_format:
label: "Message Format (Speaker2Chatbox)"
desc: "It will be used in Speaker2Chatbox for now."
hotkeys: hotkeys:
toggle_vrct_visibility: toggle_vrct_visibility:
label: "Toggle VRCT visibility" label: "Toggle VRCT visibility"

View File

@@ -211,6 +211,7 @@ config_page:
others: others:
section_label_sounds: "サウンド" section_label_sounds: "サウンド"
section_label_message_formats: "メッセージフォーマット"
auto_clear_the_message_box: auto_clear_the_message_box:
label: "送信後はメッセージ入力欄を空にする" label: "送信後はメッセージ入力欄を空にする"
send_only_translated_messages: send_only_translated_messages:
@@ -230,6 +231,25 @@ config_page:
send_received_message_to_vrc: send_received_message_to_vrc:
label: "受信したメッセージをVRChatに送信する" label: "受信したメッセージをVRChatに送信する"
desc: "スピーカーから聞き取り、文字起こしされたメッセージをVRChatに送信します。" desc: "スピーカーから聞き取り、文字起こしされたメッセージをVRChatに送信します。"
message_format_common:
example_view:
title: "プレビュー"
original_translated: "原文 + 翻訳"
original_translated_multi: "原文 + 翻訳(多言語)"
translated_only_multi: "翻訳のみ(多言語)"
translated_only: "翻訳のみ"
original_only: "原文のみ"
settings:
title: "設定"
original: "原文"
translated: "翻訳"
for_multi_translation: 多言語翻訳用
send_message_format:
label: メッセージフォーマット(送信)
desc: VRChatで相手に実際に見えるフォーマットを変更できます。
received_message_format:
label: メッセージフォーマットSpeaker2Chatbox
desc: 今のところ、Speaker2Chatboxで送信した時の表示に使われます。
hotkeys: hotkeys:
toggle_vrct_visibility: toggle_vrct_visibility:

View File

@@ -1462,7 +1462,7 @@ class Controller:
@staticmethod @staticmethod
def setSendMessageFormatParts(data, *args, **kwargs) -> dict: def setSendMessageFormatParts(data, *args, **kwargs) -> dict:
config.SEND_MESSAGE_FORMAT_PARTS = str(data) config.SEND_MESSAGE_FORMAT_PARTS = dict(data)
return {"status":200, "result":config.SEND_MESSAGE_FORMAT_PARTS} return {"status":200, "result":config.SEND_MESSAGE_FORMAT_PARTS}
@staticmethod @staticmethod
@@ -1471,7 +1471,7 @@ class Controller:
@staticmethod @staticmethod
def setReceivedMessageFormatParts(data, *args, **kwargs) -> dict: def setReceivedMessageFormatParts(data, *args, **kwargs) -> dict:
config.RECEIVED_MESSAGE_FORMAT_PARTS = str(data) config.RECEIVED_MESSAGE_FORMAT_PARTS = dict(data)
return {"status":200, "result":config.RECEIVED_MESSAGE_FORMAT_PARTS} return {"status":200, "result":config.RECEIVED_MESSAGE_FORMAT_PARTS}
@staticmethod @staticmethod

View File

@@ -21,11 +21,11 @@ const _Entry = forwardRef((props, ref) => {
}); });
return ( return (
<div className={styles.entry_container}> <div
<div className={styles.entry_container}
className={input_wrapper_class_names} style={{width: props.width || "100%" }}
style={{width: props.width || "20rem" }} >
> <div className={input_wrapper_class_names}>
<input <input
ref={inputRef} ref={inputRef}
text={props.text ? props.text : "text"} text={props.text ? props.text : "text"}

View File

@@ -3,7 +3,6 @@
} }
.entry_wrapper { .entry_wrapper {
width: 10rem;
height: 100%; height: 100%;
padding: 0.6rem; padding: 0.6rem;
background-color: var(--dark_875_color); background-color: var(--dark_875_color);
@@ -19,6 +18,7 @@
height: 100%; height: 100%;
font-size: 1.4rem; font-size: 1.4rem;
resize: none; resize: none;
font-family: Arial, sans-serif;
&.is_disabled { &.is_disabled {
color: var(--dark_500_color); color: var(--dark_500_color);
pointer-events: none; pointer-events: none;

View File

@@ -12,4 +12,5 @@ export { Slider } from "./slider/Slider";
export { SwitchBox } from "./switch_box/SwitchBox"; export { SwitchBox } from "./switch_box/SwitchBox";
export { ThresholdComponent } from "./threshold_component/ThresholdComponent"; export { ThresholdComponent } from "./threshold_component/ThresholdComponent";
export { WordFilter, WordFilterListToggleComponent } from "./word_filter/WordFilter"; export { WordFilter, WordFilterListToggleComponent } from "./word_filter/WordFilter";
export { DownloadModels } from "./download_models/DownloadModels"; export { DownloadModels } from "./download_models/DownloadModels";
export { MessageFormat } from "./message_format/MessageFormat";

View File

@@ -0,0 +1,295 @@
import styles from "./MessageFormat.module.scss";
import { useTranslation } from "react-i18next";
import { _Entry } from "../_atoms/_entry/_Entry";
import SwapImg from "@images/swap_icon.png";
import ArrowLeftSvg from "@images/arrow_left.svg?react";
import { useStore_IsBreakPoint } from "@store";
import { useAppearance } from "@logics_configs";
import { ui_configs } from "@ui_configs";
import { ResetButton } from "@common_components";
const ENTRY_WIDTH = "8rem";
const EXAMPLE_TEXTS = {
en: "Hello",
ja: "こんにちは",
ko: "안녕하세요",
fr: "Bonjour",
};
export const MessageFormat = (props) => {
const { currentIsBreakPoint } = useStore_IsBreakPoint();
const message_format_container_class = clsx(styles.container, {
[styles.is_break_point]: currentIsBreakPoint.data,
});
return (
<div className={message_format_container_class}>
<ExampleComponent
format={props.variable.data}
example_view_filter_variable={props.example_view_filter_variable}
exampleViewFilterToggleFunction={props.exampleViewFilterToggleFunction}
format_id={props.format_id}
/>
<div className={styles.border}></div>
<InputComponent
variable={props.variable.data}
setFunction={props.setFunction}
format_id={props.format_id}
/>
</div>
);
};
const ExampleComponent = ({ format, example_view_filter_variable, exampleViewFilterToggleFunction, format_id }) => {
const { currentUiLanguage } = useAppearance();
const { t } = useTranslation();
const locale_base_path = "config_page.others.message_format_common.example_view.";
const label_title = t(locale_base_path + "title");
const label_original_translated = t(locale_base_path + "original_translated");
const label_original_translated_multi = t(locale_base_path + "original_translated_multi");
const label_translated_only_multi = t(locale_base_path + "translated_only_multi");
const label_translated_only = t(locale_base_path + "translated_only");
const label_original_only = t(locale_base_path + "original_only");
const createExampleMessage = (id) => {
// 言語順序を決定
let example_text_order = [];
switch (currentUiLanguage.data) {
case "ja":
example_text_order = ["ja", "en", "ko", "fr"];
break;
case "ko":
example_text_order = ["ko", "ja", "en", "fr"];
break;
default: // en
example_text_order = ["en", "ja", "ko", "fr"];
break;
}
const original = EXAMPLE_TEXTS[example_text_order[0]];
const translations = example_text_order.slice(1).map(lang => EXAMPLE_TEXTS[lang]);
const originalPart = `${format.message.prefix}${original}${format.message.suffix}`;
const translationSingle = `${format.translation.prefix}${translations[0]}${format.translation.suffix}`;
const translationMulti = `${format.translation.prefix}${translations.join(format.translation.separator)}${format.translation.suffix}`;
switch (id) {
case "original_translated":
return format.translation_first
? `${translationSingle}${format.separator}${originalPart}`
: `${originalPart}${format.separator}${translationSingle}`;
case "original_only":
return originalPart;
case "translated_only":
return translationSingle;
case "translated_only_multi":
return translationMulti;
case "original_translated_multi":
return format.translation_first
? `${translationMulti}${format.separator}${originalPart}`
: `${originalPart}${format.separator}${translationMulti}`;
default:
throw new Error(`Unexpected id: ${id}`);
}
};
const ExampleBox = ({label, example_text_id}) => {
return (
<div className={styles.example_wrapper}>
<p className={styles.example_label}>{label}</p>
<div className={styles.example_chatbox}>
<p className={styles.example_text}>{createExampleMessage(example_text_id)}</p>
</div>
</div>
);
};
const svg_class_names = clsx(styles.arrow_left_svg, {
[styles.to_down]: example_view_filter_variable[format_id] === "Simplified",
[styles.to_up]: example_view_filter_variable[format_id] === "All"
});
const FilteredExampleBox = ({format_id, id}) => {
if (format_id === "send" && id === "Simplified") {
return (
<>
<ExampleBox label={label_original_translated} example_text_id="original_translated" />
<ExampleBox label={label_original_translated_multi} example_text_id="original_translated_multi" />
</>
);
} else if ( format_id === "send" && id === "All") {
return (
<>
<ExampleBox label={label_original_translated} example_text_id="original_translated" />
<ExampleBox label={label_original_translated_multi} example_text_id="original_translated_multi" />
<ExampleBox label={label_translated_only_multi} example_text_id="translated_only_multi" />
<ExampleBox label={label_translated_only} example_text_id="translated_only" />
<ExampleBox label={label_original_only} example_text_id="original_only" />
</>
);
} else if (format_id === "received") {
return (
<>
<ExampleBox label={label_original_translated} example_text_id="original_translated" />
<ExampleBox label={label_original_only} example_text_id="original_only" />
<ExampleBox label={label_translated_only} example_text_id="translated_only" />
</>
);
}
};
return (
<div className={styles.example_container}>
<p className={styles.section_title}>{label_title}</p>
<div className={styles.example_view_container}>
<FilteredExampleBox format_id={format_id} id={example_view_filter_variable[format_id]} />
</div>
{ format_id === "send" &&
<div className={styles.show_more_container} onClick={() => exampleViewFilterToggleFunction(format_id)}>
<ArrowLeftSvg className={svg_class_names}/>
</div>
}
</div>
);
};
const InputComponent = ({id, variable, setFunction, format_id }) => {
const { t } = useTranslation();
const locale_base_path = "config_page.others.message_format_common.settings.";
const label_title = t(locale_base_path + "title");
const LABEL_ORIGINAL = t(locale_base_path + "original");
const LABEL_TRANSLATED = t(locale_base_path + "translated");
const LABEL_FOR_MULTI_TRANSLATION = t(locale_base_path + "for_multi_translation");
const replaceValue = (value) => {
if (value === "") return "";
const replaced = value.replace(/\\n/g, "\n");
return replaced;
};
const handleChange = (parent_key, child_key) => (e) => {
const rawValue = e.target.value;
const parsedValue = replaceValue(rawValue);
if (child_key !== undefined) {
setFunction({
...variable,
[parent_key]: {
...variable[parent_key],
[child_key]: parsedValue
}
});
} else {
setFunction({
...variable,
[parent_key]: parsedValue
});
}
};
const toUiValue = (v) => {
if (typeof v === "string") {
return v.replace(/\n/g, "\\n");
}
console.log("Empty");
return v ?? "";
};
const resetFunction = () => {
if (format_id === "send") {
setFunction(ui_configs.send_message_format_parts);
} else if (format_id === "received") {
setFunction(ui_configs.received_message_format_parts);
}
};
const SwapButton = ({ variable, setFunction }) => {
const swapMessageAndTranslate = () => {
setFunction({ ...variable, translation_first: !variable.translation_first });
};
return (
<div className={styles.swap_button_wrapper} onClick={swapMessageAndTranslate}>
<p className={styles.swap_text}>{variable.translation_first ? LABEL_TRANSLATED : LABEL_ORIGINAL}</p>
<img className={styles.swap_img} src={SwapImg} alt="Swap Icon" />
<p className={styles.swap_text}>{variable.translation_first ? LABEL_ORIGINAL : LABEL_TRANSLATED}</p>
</div>
);
};
return (
<div className={styles.message_format_settings_container}>
<p className={styles.section_title}>{label_title}</p>
<div className={styles.message_format_settings_wrapper}>
<div className={styles.swap_button_container}>
<SwapButton variable={variable} setFunction={setFunction} />
</div>
{ !variable.translation_first ?
<div className={styles.input_wrapper}>
<div className={styles.input_contents}>
<_Entry ui_variable={toUiValue(variable.message.prefix)} width={ENTRY_WIDTH} onChange={handleChange("message", "prefix")} />
<p className={styles.preset_text}>{LABEL_ORIGINAL}</p>
<_Entry ui_variable={toUiValue(variable.message.suffix)} width={ENTRY_WIDTH} onChange={handleChange("message", "suffix")} />
</div>
<div className={styles.input_contents}>
<_Entry ui_variable={toUiValue(variable.separator)} width={ENTRY_WIDTH} onChange={handleChange("separator")} />
</div>
<div className={styles.input_contents}>
<_Entry ui_variable={toUiValue(variable.translation.prefix)} width={ENTRY_WIDTH} onChange={handleChange("translation", "prefix")} />
<p className={styles.preset_text}>{LABEL_TRANSLATED}</p>
<_Entry ui_variable={toUiValue(variable.translation.suffix)} width={ENTRY_WIDTH} onChange={handleChange("translation", "suffix")} />
</div>
</div>
:
<div className={styles.input_wrapper}>
<div className={styles.input_contents}>
<_Entry ui_variable={toUiValue(variable.translation.prefix)} width={ENTRY_WIDTH} onChange={handleChange("translation", "prefix")} />
<p className={styles.preset_text}>{LABEL_TRANSLATED}</p>
<_Entry ui_variable={toUiValue(variable.translation.suffix)} width={ENTRY_WIDTH} onChange={handleChange("translation", "suffix")} />
</div>
<div className={styles.input_contents}>
<_Entry ui_variable={toUiValue(variable.separator)} width={ENTRY_WIDTH} onChange={handleChange("separator")} />
</div>
<div className={styles.input_contents}>
<_Entry ui_variable={toUiValue(variable.message.prefix)} width={ENTRY_WIDTH} onChange={handleChange("message", "prefix")} />
<p className={styles.preset_text}>{LABEL_ORIGINAL}</p>
<_Entry ui_variable={toUiValue(variable.message.suffix)} width={ENTRY_WIDTH} onChange={handleChange("message", "suffix")} />
</div>
</div>
}
{ format_id === "send" &&
<div className={styles.multi_translation_input_wrapper}>
<p className={styles.multi_translation_title}>{LABEL_FOR_MULTI_TRANSLATION}</p>
<div className={styles.input_contents}>
<p className={styles.preset_text}>{LABEL_TRANSLATED}</p>
<_Entry ui_variable={toUiValue(variable.translation.separator)} width={ENTRY_WIDTH} onChange={handleChange("translation", "separator")} />
<p className={styles.preset_text}>{LABEL_TRANSLATED}</p>
</div>
</div>
}
<div className={styles.reset_button_wrapper}>
<ResetButton onClickFunction={resetFunction}/>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,200 @@
.container {
display: flex;
justify-content: center;
width: 100%;
gap: 2.6rem;
padding-bottom: 2rem;
margin: 1rem 0;
&.is_break_point {
flex-direction: column;
gap: 2.2rem;
align-items: center;
.border {
height: 0.1rem;
width: 60%;
margin: 0;
flex-shrink: 0;
}
.show_more_container {
margin-top: 2rem;
}
.example_container {
gap: 0;
}
.message_format_settings_container {
gap: 0;
}
}
}
.border {
height: auto;
width: 0.1rem;
background-color: var(--dark_800_color);
margin: 3.2rem 0;
flex-shrink: 0;
}
.section_title {
font-size: 1.4rem;
text-align: center;
width: 100%;
}
.example_container {
display: flex;
flex-direction: column;
gap: 2rem;
min-width: 14rem;
max-width: 34rem;
width: 100%;
}
.example_view_container {
display: flex;
flex-direction: column;
gap: 1.2rem;
}
.example_wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.4rem;
}
.example_label {
font-size: 1.4rem;
text-align: start;
width: 100%;
color: var(--dark_basic_text_color);
}
.example_chatbox {
padding: 0.6rem;
background-color: #3A4554;
border-radius: 1rem;
width: 100%;
text-align: center;
}
.example_text {
font-size: 1.2rem;
}
.show_more_container {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 0.6rem 0;
cursor: pointer;
border-radius: 0.6rem;
&:hover {
background-color: var(--dark_850_color);
}
&:active {
background-color: var(--dark_875_color);
}
}
.arrow_left_svg {
width: 2rem;
color: var(--dark_450_color);
&.to_down {
transform: rotate(-90deg);
}
&.to_up {
transform: rotate(90deg);
}
}
.message_format_settings_container {
flex-direction: column;
display: flex;
align-items: center;
flex-shrink: 0;
gap: 2rem;
}
.message_format_settings_wrapper {
display: flex;
flex-direction: column;
gap: 3.8rem;
width: 34rem;
flex-shrink: 0;
}
.swap_button_container {
width: 100%;
display: flex;
justify-content: end;
}
.swap_button_wrapper {
display: flex;
justify-content: center;
align-items: center;
gap: 0.8rem;
padding: 0.6rem 1.2rem;
border-radius: 0.4rem;
background-color: var(--dark_850_color);
cursor: pointer;
&:hover {
background-color: var(--dark_800_color);
}
&:active {
background-color: var(--dark_900_color);
}
}
.swap_text {
font-size: 1.4rem;
}
.swap_img {
width: 2rem;
}
.input_wrapper {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 1.8rem;
}
.input_contents {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
color: var(--dark_basic_text_color);
}
.preset_text {
font-size: 1.6rem;
text-align: center;
flex-shrink: 0;
color: var(--dark_basic_text_color);
}
.multi_translation_input_wrapper {
display: flex;
flex-direction: column;
gap: 1rem;
justify-content: center;
color: var(--dark_basic_text_color);
}
.multi_translation_title {
font-size: 1.2rem;
}
.reset_button_wrapper {
width: 100%;
display: flex;
justify-content: end;
align-items: center;
}

View File

@@ -18,6 +18,7 @@ import {
WordFilter, WordFilter,
WordFilterListToggleComponent, WordFilterListToggleComponent,
DownloadModels, DownloadModels,
MessageFormat,
} from "../_components/"; } from "../_components/";
import { Checkbox } from "@common_components"; import { Checkbox } from "@common_components";
@@ -129,4 +130,17 @@ export const WordFilterContainer = (props) => (
export const DownloadModelsContainer = (props) => ( export const DownloadModelsContainer = (props) => (
<CommonContainer Component={DownloadModels} {...props} /> <CommonContainer Component={DownloadModels} {...props} />
); );
export const MessageFormatContainer = (props) => {
return (
<div className={clsx(styles.container, styles.flex_column)}>
<div className={styles.label_only_section}>
<LabelComponent label={props.label} desc={props.desc} />
</div>
<div className={styles.message_format_section}>
<MessageFormat {...props}/>
</div>
</div>
);
};

View File

@@ -8,6 +8,7 @@ import {
import { import {
CheckboxContainer, CheckboxContainer,
MessageFormatContainer,
} from "../_templates/Templates"; } from "../_templates/Templates";
import { import {
@@ -39,6 +40,11 @@ export const Others = () => {
<SectionLabelComponent label="Speaker2Chatbox" /> <SectionLabelComponent label="Speaker2Chatbox" />
<SendReceivedMessageToVrcContainer /> <SendReceivedMessageToVrcContainer />
</div> </div>
<div>
<SectionLabelComponent label={t("config_page.others.section_label_message_formats")} />
<SendMessageFormatPartsContainer />
<ReceivedMessageFormatPartsContainer />
</div>
</div> </div>
); );
}; };
@@ -151,4 +157,48 @@ const SendReceivedMessageToVrcContainer = () => {
toggleFunction={toggleEnableSendReceivedMessageToVrc} toggleFunction={toggleEnableSendReceivedMessageToVrc}
/> />
); );
};
const SendMessageFormatPartsContainer = () => {
const { t } = useI18n();
const {
currentSendMessageFormatParts,
setSendMessageFormatParts,
currentMessageFormat_ExampleViewFilter,
toggleMessageFormat_ExampleViewFilter,
} = useOthers();
return (
<MessageFormatContainer
label={t("config_page.others.send_message_format.label")}
desc={t("config_page.others.send_message_format.desc")}
variable={currentSendMessageFormatParts}
setFunction={setSendMessageFormatParts}
example_view_filter_variable={currentMessageFormat_ExampleViewFilter.data}
exampleViewFilterToggleFunction={toggleMessageFormat_ExampleViewFilter}
format_id="send"
/>
);
};
const ReceivedMessageFormatPartsContainer = () => {
const { t } = useI18n();
const {
currentReceivedMessageFormatParts,
setReceivedMessageFormatParts,
currentMessageFormat_ExampleViewFilter,
toggleMessageFormat_ExampleViewFilter,
} = useOthers();
return (
<MessageFormatContainer
label={t("config_page.others.received_message_format.label")}
desc={t("config_page.others.received_message_format.desc")}
variable={currentReceivedMessageFormatParts}
setFunction={setReceivedMessageFormatParts}
example_view_filter_variable={currentMessageFormat_ExampleViewFilter.data}
exampleViewFilterToggleFunction={toggleMessageFormat_ExampleViewFilter}
format_id="received"
/>
);
}; };

View File

@@ -14,12 +14,12 @@ import {
SectionLabelComponent, SectionLabelComponent,
} from "../_components/"; } from "../_components/";
import { ResetButton } from "@common_components";
import { import {
useVr, useVr,
} from "@logics_configs"; } from "@logics_configs";
import RedoSvg from "@images/redo.svg?react";
import SquareSvg from "@images/square.svg?react"; import SquareSvg from "@images/square.svg?react";
import TriangleSvg from "@images/triangle.svg?react"; import TriangleSvg from "@images/triangle.svg?react";
import { randomIntMinMax } from "@utils"; import { randomIntMinMax } from "@utils";
@@ -528,13 +528,6 @@ const CommonSettingsContainer = () => {
); );
}; };
const ResetButton = ({onClickFunction}) => {
return (
<button className={styles.slider_reset_button} onClick={onClickFunction}>
<RedoSvg className={styles.slider_reset_svg}/>
</button>
);
};
const SendSampleTextToggleButton = () => { const SendSampleTextToggleButton = () => {
const { t } = useI18n(); const { t } = useI18n();

View File

@@ -301,34 +301,6 @@
@include variable-button-wrapper(bottom, 50%, right, -60%, -45deg); @include variable-button-wrapper(bottom, 50%, right, -60%, -45deg);
} }
.slider_reset_button {
background-color: var(--dark_875_color);
padding: 0.6rem;
display: flex;
justify-content: center;
align-items: center;
border-radius: 0.4rem;
flex-shrink: 0;
&:hover {
background-color: var(--dark_825_color);
& .slider_reset_svg {
color: var(--dark_200_color);
}
}
&:active {
background-color: var(--dark_925_color);
}
}
.slider_reset_svg {
width: 1.4rem;
color: var(--dark_550_color);
}
.other_controls { .other_controls {
margin-top: 6rem; margin-top: 6rem;
display: flex; display: flex;

View File

@@ -1,2 +1,3 @@
export { Checkbox } from "./checkbox/Checkbox"; export { Checkbox } from "./checkbox/Checkbox";
export { HomepageLinkButton } from "./homepage_link_button/HomepageLinkButton"; export { HomepageLinkButton } from "./homepage_link_button/HomepageLinkButton";
export { ResetButton } from "./reset_button/ResetButton";

View File

@@ -0,0 +1,10 @@
import styles from "./ResetButton.module.scss";
import RedoSvg from "@images/redo.svg?react";
export const ResetButton = ({ onClickFunction }) => {
return (
<button className={styles.reset_button} onClick={onClickFunction}>
<RedoSvg className={styles.reset_svg}/>
</button>
);
};

View File

@@ -0,0 +1,24 @@
.reset_button {
width: fit-content;
background-color: var(--dark_875_color);
padding: 0.6rem;
display: flex;
justify-content: center;
align-items: center;
border-radius: 0.4rem;
flex-shrink: 0;
&:hover {
background-color: var(--dark_825_color);
& .reset_svg {
color: var(--dark_200_color);
}
}
&:active {
background-color: var(--dark_925_color);
}
}
.reset_svg {
width: 1.4rem;
color: var(--dark_550_color);
}

View File

@@ -6,6 +6,9 @@ import {
useStore_EnableSendMessageToVrc, useStore_EnableSendMessageToVrc,
useStore_EnableNotificationVrcSfx, useStore_EnableNotificationVrcSfx,
useStore_EnableSendReceivedMessageToVrc, useStore_EnableSendReceivedMessageToVrc,
useStore_MessageFormat_ExampleViewFilter,
useStore_SendMessageFormatParts,
useStore_ReceivedMessageFormatParts,
} from "@store"; } from "@store";
import { useStdoutToPython } from "@useStdoutToPython"; import { useStdoutToPython } from "@useStdoutToPython";
import { useNotificationStatus } from "@logics_common"; import { useNotificationStatus } from "@logics_common";
@@ -29,6 +32,12 @@ export const useOthers = () => {
// Speaker2Chatbox // Speaker2Chatbox
// Send Received Message To VRC // Send Received Message To VRC
const { currentEnableSendReceivedMessageToVrc, updateEnableSendReceivedMessageToVrc, pendingEnableSendReceivedMessageToVrc } = useStore_EnableSendReceivedMessageToVrc(); const { currentEnableSendReceivedMessageToVrc, updateEnableSendReceivedMessageToVrc, pendingEnableSendReceivedMessageToVrc } = useStore_EnableSendReceivedMessageToVrc();
// Message Formats
const { currentMessageFormat_ExampleViewFilter, updateMessageFormat_ExampleViewFilter, pendingMessageFormat_ExampleViewFilter } = useStore_MessageFormat_ExampleViewFilter();
// Send
const { currentSendMessageFormatParts, updateSendMessageFormatParts, pendingSendMessageFormatParts } = useStore_SendMessageFormatParts();
// Received
const { currentReceivedMessageFormatParts, updateReceivedMessageFormatParts, pendingReceivedMessageFormatParts } = useStore_ReceivedMessageFormatParts();
const { showNotification_SaveSuccess } = useNotificationStatus(); const { showNotification_SaveSuccess } = useNotificationStatus();
@@ -178,6 +187,53 @@ export const useOthers = () => {
showNotification_SaveSuccess(); showNotification_SaveSuccess();
}; };
// Message Formats
// Send
const getSendMessageFormatParts = () => {
pendingSendMessageFormatParts();
asyncStdoutToPython("/get/data/send_message_format_parts");
};
const setSendMessageFormatParts = (message_format_parts) => {
pendingSendMessageFormatParts();
asyncStdoutToPython("/set/data/send_message_format_parts", message_format_parts);
};
const setSuccessSendMessageFormatParts = (message_format_parts) => {
updateSendMessageFormatParts(message_format_parts);
showNotification_SaveSuccess();
};
// Received
const getReceivedMessageFormatParts = () => {
pendingReceivedMessageFormatParts();
asyncStdoutToPython("/get/data/received_message_format_parts");
};
const setReceivedMessageFormatParts = (message_format_parts) => {
pendingReceivedMessageFormatParts();
asyncStdoutToPython("/set/data/received_message_format_parts", message_format_parts);
};
const setSuccessReceivedMessageFormatParts = (message_format_parts) => {
updateReceivedMessageFormatParts(message_format_parts);
showNotification_SaveSuccess();
};
const toggleMessageFormat_ExampleViewFilter = (id) => {
pendingMessageFormat_ExampleViewFilter();
if (["send", "received"].includes(id) === false) return console.error(`id should be small case 'send' or 'received'. got id: ${id}`);
updateMessageFormat_ExampleViewFilter({
...currentMessageFormat_ExampleViewFilter.data,
[id]: currentMessageFormat_ExampleViewFilter.data[id] === "Simplified"
? "All"
: "Simplified"
});
};
return { return {
// Auto Clear Message Input Box // Auto Clear Message Input Box
currentEnableAutoClearMessageInputBox, currentEnableAutoClearMessageInputBox,
@@ -230,5 +286,22 @@ export const useOthers = () => {
toggleEnableSendReceivedMessageToVrc, toggleEnableSendReceivedMessageToVrc,
updateEnableSendReceivedMessageToVrc, updateEnableSendReceivedMessageToVrc,
setSuccessEnableSendReceivedMessageToVrc, setSuccessEnableSendReceivedMessageToVrc,
// Message Formats
currentMessageFormat_ExampleViewFilter,
toggleMessageFormat_ExampleViewFilter,
// Send
currentSendMessageFormatParts,
updateSendMessageFormatParts,
getSendMessageFormatParts,
setSendMessageFormatParts,
setSuccessSendMessageFormatParts,
// Received
currentReceivedMessageFormatParts,
updateReceivedMessageFormatParts,
getReceivedMessageFormatParts,
setReceivedMessageFormatParts,
setSuccessReceivedMessageFormatParts,
}; };
}; };

View File

@@ -275,6 +275,12 @@ export const ROUTE_META_LIST = [
{ endpoint: "/get/data/notification_vrc_sfx", ns: configs, hook_name: "useOthers", method_name: "updateEnableNotificationVrcSfx" }, { endpoint: "/get/data/notification_vrc_sfx", ns: configs, hook_name: "useOthers", method_name: "updateEnableNotificationVrcSfx" },
{ endpoint: "/set/enable/notification_vrc_sfx", ns: configs, hook_name: "useOthers", method_name: "setSuccessEnableNotificationVrcSfx" }, { endpoint: "/set/enable/notification_vrc_sfx", ns: configs, hook_name: "useOthers", method_name: "setSuccessEnableNotificationVrcSfx" },
{ endpoint: "/set/disable/notification_vrc_sfx", ns: configs, hook_name: "useOthers", method_name: "setSuccessEnableNotificationVrcSfx" }, { endpoint: "/set/disable/notification_vrc_sfx", ns: configs, hook_name: "useOthers", method_name: "setSuccessEnableNotificationVrcSfx" },
{ endpoint: "/set/disable/notification_vrc_sfx", ns: configs, hook_name: "useOthers", method_name: "setSuccessEnableNotificationVrcSfx" },
{ endpoint: "/get/data/send_message_format_parts", ns: configs, hook_name: "useOthers", method_name: "updateSendMessageFormatParts" },
{ endpoint: "/set/data/send_message_format_parts", ns: configs, hook_name: "useOthers", method_name: "setSuccessSendMessageFormatParts" },
{ endpoint: "/get/data/received_message_format_parts", ns: configs, hook_name: "useOthers", method_name: "updateReceivedMessageFormatParts" },
{ endpoint: "/set/data/received_message_format_parts", ns: configs, hook_name: "useOthers", method_name: "setSuccessReceivedMessageFormatParts" },
// Hotkeys // Hotkeys
{ endpoint: "/get/data/hotkeys", ns: configs, hook_name: "useHotkeys", method_name: "updateHotkeys" }, { endpoint: "/get/data/hotkeys", ns: configs, hook_name: "useHotkeys", method_name: "updateHotkeys" },

View File

@@ -274,6 +274,37 @@ export const { atomInstance: Atom_EnableVrcMicMuteSync, useHook: useStore_Enable
export const { atomInstance: Atom_EnableSendMessageToVrc, useHook: useStore_EnableSendMessageToVrc } = createAtomWithHook(true, "EnableSendMessageToVrc"); export const { atomInstance: Atom_EnableSendMessageToVrc, useHook: useStore_EnableSendMessageToVrc } = createAtomWithHook(true, "EnableSendMessageToVrc");
export const { atomInstance: Atom_EnableSendReceivedMessageToVrc, useHook: useStore_EnableSendReceivedMessageToVrc } = createAtomWithHook(false, "EnableSendReceivedMessageToVrc"); export const { atomInstance: Atom_EnableSendReceivedMessageToVrc, useHook: useStore_EnableSendReceivedMessageToVrc } = createAtomWithHook(false, "EnableSendReceivedMessageToVrc");
export const { atomInstance: Atom_EnableNotificationVrcSfx, useHook: useStore_EnableNotificationVrcSfx } = createAtomWithHook(true, "EnableNotificationVrcSfx"); export const { atomInstance: Atom_EnableNotificationVrcSfx, useHook: useStore_EnableNotificationVrcSfx } = createAtomWithHook(true, "EnableNotificationVrcSfx");
export const { atomInstance: Atom_MessageFormat_ExampleViewFilter, useHook: useStore_MessageFormat_ExampleViewFilter } = createAtomWithHook({
send: "Simplified",
received: "Simplified",
}, "MessageFormat_ExampleViewFilter");
export const { atomInstance: Atom_SendMessageFormatParts, useHook: useStore_SendMessageFormatParts } = createAtomWithHook({
message: {
prefix: "",
suffix: ""
},
separator: "\n",
translation: {
prefix: "",
separator: "\n",
suffix: ""
},
translation_first: false,
}, "SendMessageFormatParts");
export const { atomInstance: Atom_ReceivedMessageFormatParts, useHook: useStore_ReceivedMessageFormatParts } = createAtomWithHook({
message: {
prefix: "",
suffix: ""
},
separator: "\n",
translation: {
prefix: "",
separator: "\n",
suffix: ""
},
translation_first: false,
}, "ReceivedMessageFormatParts");
// Hotkeys // Hotkeys
export const { atomInstance: Atom_Hotkeys, useHook: useStore_Hotkeys } = createAtomWithHook({ export const { atomInstance: Atom_Hotkeys, useHook: useStore_Hotkeys } = createAtomWithHook({

View File

@@ -49,6 +49,33 @@ export const ui_configs = {
tracker: "LeftHand", tracker: "LeftHand",
}, },
send_message_format_parts: {
message: {
prefix: "",
suffix: ""
},
separator: "\n",
translation: {
prefix: "",
separator: "\n",
suffix: ""
},
translation_first: false,
},
received_message_format_parts: {
message: {
prefix: "",
suffix: ""
},
separator: "\n",
translation: {
prefix: "",
separator: "\n",
suffix: ""
},
translation_first: false,
},
selectable_ui_languages: [ selectable_ui_languages: [
{id: "en", label: "English"}, {id: "en", label: "English"},
{id: "ja", label: "日本語"}, {id: "ja", label: "日本語"},