[Refactor] Move to src-ui/views and src-ui/logics structure.
This commit is contained in:
21
src-ui/views/app/config_page/ConfigPage.jsx
Normal file
21
src-ui/views/app/config_page/ConfigPage.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import styles from "./ConfigPage.module.scss";
|
||||
|
||||
import { Topbar } from "./topbar/Topbar.jsx";
|
||||
import { SidebarSection } from "./sidebar_section/SidebarSection.jsx";
|
||||
import { SettingSection } from "./setting_section/SettingSection.jsx";
|
||||
import { VersionLabel } from "./version_label/VersionLabel.jsx";
|
||||
|
||||
export const ConfigPage = () => {
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<div className={styles.container}>
|
||||
<Topbar />
|
||||
<div className={styles.main_container}>
|
||||
<SidebarSection />
|
||||
<SettingSection />
|
||||
</div>
|
||||
<VersionLabel />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
26
src-ui/views/app/config_page/ConfigPage.module.scss
Normal file
26
src-ui/views/app/config_page/ConfigPage.module.scss
Normal file
@@ -0,0 +1,26 @@
|
||||
.page {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
background-color: var(--dark_900_color);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main_container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
padding-top: var(--config_page_topbar_height);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useRef, useLayoutEffect, useEffect } from "react";
|
||||
|
||||
import styles from "./SettingSection.module.scss";
|
||||
import { SettingBox } from "./setting_box/SettingBox";
|
||||
import { store, useStore_SelectedConfigTabId } from "@store";
|
||||
import { useSettingBoxScrollPosition } from "@logics_configs";
|
||||
|
||||
export const SettingSection = () => {
|
||||
const { currentSelectedConfigTabId } = useStore_SelectedConfigTabId();
|
||||
const { resetScrollPosition } = useSettingBoxScrollPosition();
|
||||
const scrollContainerRef = useRef(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
store.setting_box_scroll_container = scrollContainerRef;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
resetScrollPosition();
|
||||
}, [currentSelectedConfigTabId.data]);
|
||||
|
||||
return (
|
||||
<div ref={scrollContainerRef} className={styles.scroll_container}>
|
||||
<div className={styles.container}>
|
||||
<SettingBox />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
.scroll_container {
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0rem 4rem 16rem 0.6rem;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useStore_SelectedConfigTabId } from "@store";
|
||||
|
||||
import {
|
||||
Device,
|
||||
Appearance,
|
||||
Translation,
|
||||
Transcription,
|
||||
Others,
|
||||
AdvancedSettings,
|
||||
Vr,
|
||||
Hotkeys,
|
||||
Plugins,
|
||||
Supporters,
|
||||
AboutVrct,
|
||||
} from "@setting_box";
|
||||
|
||||
export const SettingBox = () => {
|
||||
const { currentSelectedConfigTabId } = useStore_SelectedConfigTabId();
|
||||
switch (currentSelectedConfigTabId.data) {
|
||||
case "device":
|
||||
return <Device />;
|
||||
case "appearance":
|
||||
return <Appearance />;
|
||||
case "translation":
|
||||
return <Translation />;
|
||||
case "transcription":
|
||||
return <Transcription />;
|
||||
case "others":
|
||||
return <Others />;
|
||||
case "vr":
|
||||
return <Vr />;
|
||||
case "hotkeys":
|
||||
return <Hotkeys />;
|
||||
case "advanced_settings":
|
||||
return <AdvancedSettings />;
|
||||
case "plugins":
|
||||
return <Plugins />;
|
||||
case "supporters":
|
||||
return <Supporters />;
|
||||
case "about_vrct":
|
||||
return <AboutVrct />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import styles from "./_DownloadButton.module.scss";
|
||||
|
||||
export const _DownloadButton = ({option, ...props}) => {
|
||||
const { t } = useI18n();
|
||||
|
||||
const renderContent = () => {
|
||||
const circular_progress = Math.floor(option.progress / 10) * 10;
|
||||
|
||||
switch (true) {
|
||||
case option.progress !== null:
|
||||
return (
|
||||
<>
|
||||
<CircularProgress
|
||||
variant={(option.progress === 100) ? "indeterminate" : "determinate"}
|
||||
value={circular_progress}
|
||||
size="3rem"
|
||||
sx={{ color: "var(--primary_300_color)" }}
|
||||
/>
|
||||
<p className={styles.progress_label}>{`${Math.round(option.progress)}%`}</p>
|
||||
</>
|
||||
);
|
||||
case option.is_pending:
|
||||
return <CircularProgress size="3rem" sx={{ color: "var(--dark_600_color)" }}/>;
|
||||
case !option.is_downloaded:
|
||||
return (
|
||||
<button
|
||||
className={styles.download_button}
|
||||
onClick={() => props.downloadStartFunction(option.id)}
|
||||
>
|
||||
<p className={styles.download_button_label}>{t("config_page.common.model_download_button_label")}</p>
|
||||
</button>
|
||||
);
|
||||
case option.update_button:
|
||||
return (
|
||||
<button
|
||||
className={styles.update_button}
|
||||
onClick={() => props.downloadStartFunction(option.id)}
|
||||
>
|
||||
<p className={styles.download_button_label}>Update</p>
|
||||
</button>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return <div className={styles.download_container}>{renderContent()}</div>;
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
.download_container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 8rem;
|
||||
}
|
||||
|
||||
.download_button {
|
||||
pointer-events: auto;
|
||||
background-color: var(--dark_800_color);
|
||||
padding: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
border-radius: 0.2rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_750_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
}
|
||||
.download_button_label {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.progress_label {
|
||||
position: absolute;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.update_button {
|
||||
pointer-events: auto;
|
||||
background-color: var(--primary_400_color);
|
||||
padding: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
border-radius: 0.2rem;
|
||||
&:hover {
|
||||
background-color: var(--primary_450_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--primary_500_color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import clsx from "clsx";
|
||||
import React, { useRef, forwardRef, useImperativeHandle } from "react";
|
||||
import styles from "./_Entry.module.scss";
|
||||
|
||||
const _Entry = forwardRef((props, ref) => {
|
||||
const inputRef = useRef();
|
||||
|
||||
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,
|
||||
});
|
||||
const input_wrapper_class_names = clsx(styles.entry_wrapper, {
|
||||
[styles.is_activated]: props.is_activated,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.entry_container}
|
||||
style={{width: props.width || "100%" }}
|
||||
>
|
||||
<div className={input_wrapper_class_names}>
|
||||
<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)}
|
||||
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>
|
||||
);
|
||||
});
|
||||
|
||||
_Entry.displayName = "_Entry";
|
||||
|
||||
export { _Entry };
|
||||
@@ -0,0 +1,26 @@
|
||||
.entry_container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.entry_wrapper {
|
||||
height: 100%;
|
||||
padding: 0.6rem;
|
||||
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 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 1.4rem;
|
||||
resize: none;
|
||||
font-family: Arial, sans-serif;
|
||||
&.is_disabled {
|
||||
color: var(--dark_500_color);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import styles from "./ActionButton.module.scss";
|
||||
|
||||
export const ActionButton = ({IconComponent, onclickFunction}) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<button className={styles.button_wrapper} onClick={onclickFunction}>
|
||||
<IconComponent className={styles.button_svg}/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
.button_wrapper {
|
||||
padding: 1.6rem;
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_825_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.button_svg {
|
||||
width: 2.4rem;
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import styles from "./ComputeDevice.module.scss";
|
||||
import { DropdownMenu } from "../dropdown_menu/DropdownMenu";
|
||||
import { ActionButton } from "../action_button/ActionButton";
|
||||
import HelpSvg from "@images/help.svg?react";
|
||||
import { useStore_OpenedQuickSetting } from "@store"
|
||||
|
||||
export const ComputeDevice = (props) => {
|
||||
const { updateOpenedQuickSetting } = useStore_OpenedQuickSetting();
|
||||
|
||||
const onClickFunction = () => {
|
||||
updateOpenedQuickSetting("update_software");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<DropdownMenu
|
||||
{...props}
|
||||
is_disabled={true}
|
||||
/>
|
||||
<ActionButton
|
||||
{...props}
|
||||
IconComponent={HelpSvg}
|
||||
onclickFunction={onClickFunction}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import styles from "./DeeplAuthKey.module.scss";
|
||||
import { useI18n } from "@useI18n";
|
||||
import clsx from "clsx";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import ExternalLink from "@images/external_link.svg?react";
|
||||
import { _Entry } from "../_atoms/_entry/_Entry";
|
||||
import { useState, useRef } from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export const DeeplAuthKey = (props) => {
|
||||
const { t } = useI18n();
|
||||
const [is_editable, seIsEditable] = useState(false);
|
||||
const entryRef = useRef(null);
|
||||
|
||||
const revealEditAuthKey = () => {
|
||||
seIsEditable(true);
|
||||
entryRef.current.focus();
|
||||
};
|
||||
|
||||
const onchangeEntryAuthKey = (e) => {
|
||||
props.onChangeFunction(e.target.value);
|
||||
};
|
||||
const saveAuthKey = () => {
|
||||
props.saveFunction();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.variable === "" || props.variable === null) {
|
||||
seIsEditable(true);
|
||||
}
|
||||
}, [props.variable]);
|
||||
|
||||
const is_disabled = props.state === "pending";
|
||||
|
||||
const save_button_class_names = clsx(styles.save_button, {
|
||||
[styles.is_disabled]: is_disabled
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.entry_section_wrapper}>
|
||||
<_Entry ref={entryRef} width="30rem" onChange={onchangeEntryAuthKey} ui_variable={props.variable} is_disabled={is_disabled}/>
|
||||
<button className={save_button_class_names} onClick={saveAuthKey}>
|
||||
{is_disabled
|
||||
? <CircularProgress size="1.4rem" sx={{ color: "var(--dark_basic_text_color)" }}/>
|
||||
: <p className={styles.save_button_label}>{t("config_page.translation.deepl_auth_key.save")}</p>
|
||||
}
|
||||
</button>
|
||||
{is_editable
|
||||
? null
|
||||
:
|
||||
<div className={styles.entry_edit_cover} onClick={revealEditAuthKey}>
|
||||
<button className={styles.edit_button}>{t("config_page.translation.deepl_auth_key.edit")}</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export const OpenWebpage_DeeplAuthKey = () => {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div className={styles.open_webpage_button_wrapper}>
|
||||
<a className={styles.open_webpage_button} href="https://www.deepl.com/ja/your-account/keys" target="_blank" rel="noreferrer" >
|
||||
<p className={styles.open_webpage_text}>{t("config_page.translation.deepl_auth_key.open_auth_key_webpage")}</p>
|
||||
<ExternalLink className={styles.external_link_svg} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.entry_section_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.entry_edit_cover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border-radius: 0.4rem;
|
||||
background-color: var(--dark_1000_color_66);
|
||||
backdrop-filter: blur(4rem);
|
||||
border: solid 0.1rem var(--dark_700_color);
|
||||
&:hover {
|
||||
background-color: var(--dark_1000_color_aa);
|
||||
}
|
||||
&:active {
|
||||
backdrop-filter: blur(1.4rem);
|
||||
}
|
||||
}
|
||||
|
||||
.edit_button {
|
||||
padding: 0.8rem 1.2rem;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: 1.4rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.save_button {
|
||||
padding: 0.8rem 1.2rem;
|
||||
background-color: var(--primary_600_color);
|
||||
border-radius: 0.4rem;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
min-width: 5.4rem;
|
||||
&:hover {
|
||||
background-color: var(--primary_500_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--primary_700_color);
|
||||
}
|
||||
&.is_disabled {
|
||||
pointer-events: none;
|
||||
background-color: var(--primary_800_color);
|
||||
}
|
||||
}
|
||||
|
||||
.save_button_label {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.open_webpage_button_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.open_webpage_button {
|
||||
padding: 0.6rem 2.8rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 0.4rem;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
&:hover {
|
||||
background-color: var(--dark_825_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.open_webpage_text {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.external_link_svg {
|
||||
color: var(--dark_500_color);
|
||||
width: 1.6rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
RadioButton,
|
||||
} from "../index";
|
||||
import { _DownloadButton } from "../_atoms/_download_button/_DownloadButton";
|
||||
|
||||
export const DownloadModels = (props) => {
|
||||
const options = props.options.map(item => ({
|
||||
...item,
|
||||
disabled: !item.is_downloaded
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<RadioButton
|
||||
selectFunction={props.selectFunction}
|
||||
name={props.name}
|
||||
options={options}
|
||||
checked_variable={props.checked_variable}
|
||||
column={true}
|
||||
ChildComponent={_DownloadButton}
|
||||
downloadStartFunction={props.downloadStartFunction}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import styles from "./DropdownMenu.module.scss";
|
||||
import clsx from "clsx";
|
||||
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||
import { useStore_IsOpenedDropdownMenu } from "@store";
|
||||
|
||||
export const DropdownMenu = (props) => {
|
||||
const { updateIsOpenedDropdownMenu, currentIsOpenedDropdownMenu } = useStore_IsOpenedDropdownMenu();
|
||||
|
||||
const toggleDropdownMenu = () => {
|
||||
if (currentIsOpenedDropdownMenu.data === props.dropdown_id) {
|
||||
updateIsOpenedDropdownMenu("");
|
||||
} else {
|
||||
if (props.openListFunction !== undefined) props.openListFunction();
|
||||
updateIsOpenedDropdownMenu(props.dropdown_id);
|
||||
}
|
||||
};
|
||||
|
||||
const selectValue = (key) => {
|
||||
updateIsOpenedDropdownMenu("");
|
||||
props.selectFunction({
|
||||
dropdown_id: props.dropdown_id,
|
||||
selected_id: key,
|
||||
});
|
||||
};
|
||||
|
||||
const dropdown_content_wrapper_class_name = clsx(styles["dropdown_content_wrapper"], {
|
||||
[styles.is_opened]: (currentIsOpenedDropdownMenu.data === props.dropdown_id) ? true : false,
|
||||
[styles.is_disabled]: props.is_disabled,
|
||||
});
|
||||
|
||||
const dropdown_toggle_button_class_name = clsx(styles["dropdown_toggle_button"], {
|
||||
[styles.is_pending]: (props.state === "pending") ? true : false,
|
||||
[styles.is_disabled]: props.is_disabled,
|
||||
});
|
||||
|
||||
const arrow_class_names = clsx(styles["arrow_left_svg"], {
|
||||
[styles.is_opened]: (currentIsOpenedDropdownMenu.data === props.dropdown_id) ? true : false
|
||||
});
|
||||
|
||||
const getSelectedText = () => {
|
||||
if (props.state !== "ok") return;
|
||||
if (props.list[props.selected_id] === undefined) return props.selected_id; // [Fix me]
|
||||
|
||||
return props.list[props.selected_id];
|
||||
};
|
||||
const list = (props.list === undefined) ? {} : props.list;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={dropdown_toggle_button_class_name} onClick={toggleDropdownMenu} style={props.style}>
|
||||
{(props.state === "pending")
|
||||
? <p className={styles.dropdown_selected_text}>Loading...</p>
|
||||
: <p className={styles.dropdown_selected_text}>{getSelectedText()}</p>
|
||||
}
|
||||
{(props.state === "pending")
|
||||
? <span className={styles.loader}></span>
|
||||
: <ArrowLeftSvg className={arrow_class_names} />
|
||||
}
|
||||
</div>
|
||||
<div className={dropdown_content_wrapper_class_name}>
|
||||
<div className={styles.dropdown_content}>
|
||||
{(props.state === "ok")
|
||||
? Object.entries(list).map(([key, value]) => {
|
||||
return (
|
||||
<div key={key} className={styles.value_button} onClick={() => selectValue(key)}>
|
||||
<p className={styles.value_text}>{value}</p>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown_toggle_button {
|
||||
position: relative;
|
||||
background-color: var(--dark_950_color);
|
||||
min-width: 20rem;
|
||||
padding: 0.8rem 1.4rem;
|
||||
cursor: pointer;
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_925_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_975_color);
|
||||
}
|
||||
&.is_pending {
|
||||
pointer-events: none;
|
||||
}
|
||||
&.is_disabled {
|
||||
pointer-events: none;
|
||||
.dropdown_selected_text, .arrow_left_svg {
|
||||
color: var(--dark_550_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown_selected_text {
|
||||
font-size: 1.4rem;
|
||||
padding-right: 2.8rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropdown_content_wrapper {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%; // Position it below the toggle button
|
||||
right: 0;
|
||||
min-width: 20rem;
|
||||
z-index: 1;
|
||||
&.is_opened {
|
||||
display: block;
|
||||
}
|
||||
&.is_disabled {
|
||||
pointer-events: none;
|
||||
.value_text {
|
||||
color: var(--dark_550_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown_content {
|
||||
background-color: var(--dark_900_color);
|
||||
border: 0.1rem solid var(--dark_600_color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
white-space: nowrap;
|
||||
max-height: 20rem;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.value_button {
|
||||
background-color: var(--dark_875_color);
|
||||
padding: 1.2rem;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.value_text {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.loader {
|
||||
@include loader(2rem, 0.2rem, right, 0);
|
||||
}
|
||||
|
||||
.arrow_left_svg {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translate(-50%, -50%) rotate(-90deg);
|
||||
width: 1.4rem;
|
||||
&.is_opened {
|
||||
transform: translate(-50%, -50%) rotate(90deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import styles from "./Entry.module.scss";
|
||||
import { _Entry } from "../_atoms/_entry/_Entry";
|
||||
|
||||
export const Entry = (props) => {
|
||||
return (
|
||||
<div className={styles.entry_container}>
|
||||
<_Entry {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import styles from "./EntryWithSaveButton.module.scss";
|
||||
import { _Entry } from "../_atoms/_entry/_Entry";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import { useI18n } from "@useI18n";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
export const EntryWithSaveButton = (props) => {
|
||||
const { t } = useI18n();
|
||||
const onChangeFunction = (e) => {
|
||||
props.onChangeFunction?.(e.target.value);
|
||||
};
|
||||
const saveFunction = () => {
|
||||
props.saveFunction();
|
||||
};
|
||||
const is_disabled = props.state === "pending";
|
||||
|
||||
const save_button_class_names = clsx(styles.save_button, {
|
||||
[styles.is_disabled]: is_disabled
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<_Entry width={props.width} onChange={onChangeFunction} ui_variable={props.variable} is_disabled={is_disabled}/>
|
||||
<button className={save_button_class_names} onClick={saveFunction}>
|
||||
{is_disabled
|
||||
? <CircularProgress size="1.4rem" sx={{ color: "var(--dark_basic_text_color)" }}/>
|
||||
: <p className={styles.save_button_label}>{t("config_page.translation.deepl_auth_key.save")}</p>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.save_button {
|
||||
padding: 0.8rem 1.2rem;
|
||||
background-color: var(--primary_600_color);
|
||||
border-radius: 0.4rem;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
min-width: 5.4rem;
|
||||
&:hover {
|
||||
background-color: var(--primary_500_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--primary_700_color);
|
||||
}
|
||||
&.is_disabled {
|
||||
pointer-events: none;
|
||||
background-color: var(--primary_800_color);
|
||||
}
|
||||
}
|
||||
|
||||
.save_button_label {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export { ActionButton } from "./action_button/ActionButton";
|
||||
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 { EntryWithSaveButton } from "./entry_with_save_button/EntryWithSaveButton";
|
||||
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";
|
||||
export { Slider } from "./slider/Slider";
|
||||
export { SwitchBox } from "./switch_box/SwitchBox";
|
||||
export { ThresholdComponent } from "./threshold_component/ThresholdComponent";
|
||||
export { WordFilter, WordFilterListToggleComponent } from "./word_filter/WordFilter";
|
||||
export { DownloadModels } from "./download_models/DownloadModels";
|
||||
export { MessageFormat } from "./message_format/MessageFormat";
|
||||
@@ -0,0 +1,13 @@
|
||||
import styles from "./LabelComponent.module.scss";
|
||||
|
||||
export const LabelComponent = (props) => {
|
||||
return (
|
||||
<div className={styles.label_component}>
|
||||
<p className={styles.label}>{props.label}</p>
|
||||
{props.desc
|
||||
? <p className={styles.desc}>{props.desc}</p>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
.label_component {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
// flex-shrink: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 300;
|
||||
color: var(--dark_500_color);
|
||||
max-width: 38rem;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
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,
|
||||
useStore_MessageFormat_ExampleViewFilter,
|
||||
} 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}
|
||||
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, format_id }) => {
|
||||
const { currentUiLanguage } = useAppearance();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
currentMessageFormat_ExampleViewFilter,
|
||||
updateMessageFormat_ExampleViewFilter,
|
||||
} = useStore_MessageFormat_ExampleViewFilter();
|
||||
|
||||
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]: currentMessageFormat_ExampleViewFilter.data[format_id] === "Simplified",
|
||||
[styles.to_up]: currentMessageFormat_ExampleViewFilter.data[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" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const exampleViewFilterToggleFunction = (format_id) => {
|
||||
if (["send", "received"].includes(format_id) === false) return console.error(`format_id should be small case 'send' or 'received'. got format_id: ${format_id}`);
|
||||
|
||||
updateMessageFormat_ExampleViewFilter({
|
||||
...currentMessageFormat_ExampleViewFilter.data,
|
||||
[format_id]: currentMessageFormat_ExampleViewFilter.data[format_id] === "Simplified"
|
||||
? "All"
|
||||
: "Simplified"
|
||||
});
|
||||
};
|
||||
|
||||
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={currentMessageFormat_ExampleViewFilter.data[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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import styles from "./RadioButton.module.scss";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const RadioButton = (props) => {
|
||||
const containerClass = clsx(styles.container, {
|
||||
[styles.column]: props.column === true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={containerClass}>
|
||||
{props.checked_variable.state === "pending" && <span className={styles.loader}></span>}
|
||||
{props.options.map((option) => {
|
||||
const radioWrapperClass = clsx(styles.radio_button_container, {
|
||||
[styles.is_selected]: props.checked_variable.data === option.id,
|
||||
});
|
||||
|
||||
const labelClass = clsx(styles.radio_button_wrapper, {
|
||||
[styles.is_selected]: props.checked_variable.data === option.id,
|
||||
[styles.disabled]: option.disabled === true || props.checked_variable.state === "pending",
|
||||
});
|
||||
|
||||
return (
|
||||
<div key={option.id} className={radioWrapperClass}>
|
||||
<label className={labelClass}>
|
||||
<input
|
||||
className={styles.radio_button_input}
|
||||
type="radio"
|
||||
name={props.name}
|
||||
value={option.id}
|
||||
onChange={() => props.selectFunction(option.id)}
|
||||
checked={props.checked_variable.data === option.id}
|
||||
disabled={option.disabled === true || props.checked_variable.state === "pending"}
|
||||
/>
|
||||
<p className={styles.radio_button_label}>{option.label}</p>
|
||||
</label>
|
||||
{props.ChildComponent && <props.ChildComponent option={option} {...props} />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: wrap;
|
||||
max-width: 70%;
|
||||
&.column {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.radio_button_container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 2rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.radio_button_wrapper {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
gap: 1rem;
|
||||
padding: 0.6rem 0.8rem;
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_925_color);
|
||||
}
|
||||
&.is_selected {
|
||||
pointer-events: none;
|
||||
}
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
color: var(--dark_600_color);
|
||||
}
|
||||
}
|
||||
|
||||
.radio_button_input {
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border: 0.3rem solid var(--dark_600_color);
|
||||
border-radius: 50%;
|
||||
transition: border-color .1s ease, border-width .1s ease;
|
||||
flex-shrink: 0;
|
||||
cursor: inherit;
|
||||
&:checked {
|
||||
border-color: var(--primary_400_color);
|
||||
border-width: 0.6rem;
|
||||
}
|
||||
&:disabled {
|
||||
border-color: var(--dark_825_color);
|
||||
}
|
||||
}
|
||||
|
||||
.radio_button_label {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.loader {
|
||||
@include loader(2rem, 0.2rem, left, -1.6rem);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import styles from "./SectionLabelComponent.module.scss";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const SectionLabelComponent = (props) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<label className={styles.section_label}>{props.label}</label>
|
||||
<div className={styles.section_line}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
.section_label {
|
||||
font-size: 2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.section_line {
|
||||
width: 100%;
|
||||
height: 0.1rem;
|
||||
background: linear-gradient(90deg, var(--dark_400_color) 0%, var(--dark_600_color) 35%, var(--dark_800_color) 100%);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import React from "react";
|
||||
import styles from "./Slider.module.scss";
|
||||
import MUI_Slider from "@mui/material/Slider";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const Slider = (props) => {
|
||||
const location = props.valueLabelDisplayLocation || "top";
|
||||
|
||||
const sliderSx = {
|
||||
color: "var(--dark_700_color)",
|
||||
"& .MuiSlider-thumb": {
|
||||
backgroundColor: "var(--primary_600_color)",
|
||||
"&:hover, &.Mui-focusVisible, &.Mui-active": {
|
||||
boxShadow: `0 0 0 0.8rem var(--primary_600_color_44)`,
|
||||
},
|
||||
"& .MuiSlider-valueLabel": {
|
||||
position: "absolute",
|
||||
backgroundColor: "var(--dark_800_color)",
|
||||
width: "fit-content",
|
||||
minWidth: "4.8rem",
|
||||
padding: "0.4rem 0.8rem",
|
||||
lineHeight: "1.15",
|
||||
"& .MuiSlider-valueLabelLabel": {
|
||||
fontSize: "1.4rem",
|
||||
},
|
||||
...(location === "top" && {
|
||||
top: "-110%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%) scale(0)",
|
||||
transformOrigin: "bottom center",
|
||||
"&.MuiSlider-valueLabelOpen": {
|
||||
transform: "translate(-50%, -50%) scale(1)",
|
||||
},
|
||||
"&::before": {
|
||||
bottom: "0%",
|
||||
left: "50%",
|
||||
},
|
||||
}),
|
||||
...(location === "right" && {
|
||||
top: "50%",
|
||||
left: "150%",
|
||||
transform: "translate(0, -50%) scale(0)",
|
||||
transformOrigin: "left center",
|
||||
"&.MuiSlider-valueLabelOpen": {
|
||||
transform: "translate(0, -50%) scale(1)",
|
||||
},
|
||||
"&::before": {
|
||||
bottom: "50%",
|
||||
left: "0",
|
||||
},
|
||||
}),
|
||||
...(location === "left" && {
|
||||
// top: "50%",
|
||||
// right: "50%",
|
||||
// transform: "translate(-50%, -50%) scale(0)",
|
||||
// transformOrigin: "bottom center",
|
||||
// "&.MuiSlider-valueLabelOpen": {
|
||||
// transform: "translate(-50%, -50%) scale(1)",
|
||||
// },
|
||||
// "&::before": {
|
||||
// bottom: "50%",
|
||||
// left: "100%",
|
||||
// },
|
||||
}),
|
||||
},
|
||||
},
|
||||
"& .MuiSlider-markLabel": {
|
||||
fontSize: "1.4rem",
|
||||
color: "var(--dark_550_color)",
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
"& .MuiSlider-markLabelActive": {
|
||||
color: "var(--primary_300_color)",
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
styles.container,
|
||||
props.className,
|
||||
{ [styles.no_padding]: props.no_padding || props.is_break_point }
|
||||
)}
|
||||
>
|
||||
<MUI_Slider
|
||||
aria-label="Default"
|
||||
// valueLabelDisplay="on"
|
||||
valueLabelDisplay={props.valueLabelDisplay ? props.valueLabelDisplay : "auto"}
|
||||
value={props.variable}
|
||||
step={props.step}
|
||||
min={Number(props.min)}
|
||||
max={Number(props.max)}
|
||||
onChange={(_e, value) => props.onchangeFunction(value)}
|
||||
onChangeCommitted={(_e, value) =>
|
||||
props.onchangeCommittedFunction ? props.onchangeCommittedFunction(value) : null
|
||||
}
|
||||
onMouseEnter={(event) =>
|
||||
props.onMouseEnterFunction ? props.onMouseEnterFunction(event) : null
|
||||
}
|
||||
onMouseLeave={(event) =>
|
||||
props.onMouseLeaveFunction ? props.onMouseLeaveFunction(event) : null
|
||||
}
|
||||
marks={props.marks}
|
||||
track={props.track}
|
||||
orientation={props.orientation}
|
||||
valueLabelFormat={`${props.valueLabelFormat ? props.valueLabelFormat : props.variable}`}
|
||||
sx={sliderSx}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding-left: 4rem;
|
||||
&.no_padding {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import clsx from "clsx";
|
||||
import { useState } from "react";
|
||||
import styles from "./SwitchBox.module.scss";
|
||||
|
||||
export const SwitchBox = (props) => {
|
||||
const [is_hovered, setIsHovered] = useState(false);
|
||||
const [is_mouse_down, setIsMouseDown] = useState(false);
|
||||
|
||||
const is_pending = (props.variable.state === "pending");
|
||||
|
||||
const getClassNames = (base_class) => clsx(base_class, {
|
||||
[styles.is_active]: (props.variable.data === true),
|
||||
[styles.is_pending]: is_pending,
|
||||
[styles.is_hovered]: is_hovered,
|
||||
[styles.is_mouse_down]: is_mouse_down,
|
||||
});
|
||||
|
||||
const onMouseEnter = () => setIsHovered(true);
|
||||
const onMouseLeave = () => setIsHovered(false);
|
||||
const onMouseDown = () => setIsMouseDown(true);
|
||||
const onMouseUp = () => setIsMouseDown(false);
|
||||
|
||||
const toggleFunction = () => {
|
||||
props.toggleFunction();
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.switchbox_container}>
|
||||
<div className={getClassNames(styles.switchbox_wrapper)}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseUp={onMouseUp}
|
||||
onClick={toggleFunction}
|
||||
>
|
||||
<div className={getClassNames(styles.toggle_control)}>
|
||||
<span className={getClassNames(styles.control)}></span>
|
||||
{is_pending && <span className={styles.loader}></span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
.switchbox_container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.switchbox_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 2rem;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
&.is_pending {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle_control {
|
||||
position: relative;
|
||||
@include toggle_control_styles;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loader {
|
||||
@include loader(2rem, 0.2rem, right, -4rem);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import styles from "./ThresholdComponent.module.scss";
|
||||
import { SliderAndMeter } from "./slider_and_meter/SliderAndMeter";
|
||||
import { ThresholdEntry } from "./threshold_entry/ThresholdEntry";
|
||||
import { VolumeCheckButton } from "./volume_check_button/VolumeCheckButton";
|
||||
import { useVolume } from "@logics_common";
|
||||
import MicSvg from "@images/mic.svg?react";
|
||||
import HeadphonesSvg from "@images/headphones.svg?react";
|
||||
import {
|
||||
useDevice,
|
||||
} from "@logics_configs";
|
||||
|
||||
export const ThresholdComponent = (props) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{props.id === "mic_threshold"
|
||||
? <MicComponent {...props} />
|
||||
: <SpeakerComponent {...props} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MicComponent = (props) => {
|
||||
const {
|
||||
currentMicThreshold,
|
||||
setMicThreshold,
|
||||
currentEnableAutomaticMicThreshold,
|
||||
} = useDevice();
|
||||
const [ui_threshold, setUiThreshold] = useState(currentMicThreshold.data);
|
||||
const {
|
||||
volumeCheckStart_Mic,
|
||||
volumeCheckStop_Mic,
|
||||
currentMicThresholdCheckStatus,
|
||||
} = useVolume();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (currentEnableAutomaticMicThreshold.data === true) {
|
||||
setUiThreshold("Auto");
|
||||
} else {
|
||||
setUiThreshold(currentMicThreshold.data);
|
||||
}
|
||||
}, [currentMicThreshold.data, currentEnableAutomaticMicThreshold]);
|
||||
|
||||
const setUiThresholdFunction = (payload_ui_threshold) => {
|
||||
setUiThreshold(payload_ui_threshold);
|
||||
};
|
||||
const setThresholdFunction = (payload_threshold) => {
|
||||
setMicThreshold(payload_threshold);
|
||||
};
|
||||
|
||||
const is_disable = currentEnableAutomaticMicThreshold.data === true ? true : false;
|
||||
|
||||
return (
|
||||
<>
|
||||
<VolumeCheckButton
|
||||
{...props}
|
||||
SvgComponent={MicSvg}
|
||||
startFunction={volumeCheckStart_Mic}
|
||||
stopFunction={volumeCheckStop_Mic}
|
||||
isChecking={currentMicThresholdCheckStatus}
|
||||
/>
|
||||
<SliderAndMeter
|
||||
{...props}
|
||||
ui_threshold={ui_threshold}
|
||||
setUiThresholdFunction={setUiThresholdFunction}
|
||||
setThresholdFunction={setThresholdFunction}
|
||||
/>
|
||||
<ThresholdEntry
|
||||
{...props}
|
||||
ui_threshold={ui_threshold}
|
||||
setUiThresholdFunction={setUiThresholdFunction}
|
||||
setThresholdFunction={setThresholdFunction}
|
||||
is_disable={is_disable}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const SpeakerComponent = (props) => {
|
||||
const {
|
||||
currentSpeakerThreshold,
|
||||
setSpeakerThreshold,
|
||||
currentEnableAutomaticSpeakerThreshold,
|
||||
} = useDevice();
|
||||
const [ui_threshold, setUiThreshold] = useState(currentSpeakerThreshold.data);
|
||||
const {
|
||||
volumeCheckStart_Speaker,
|
||||
volumeCheckStop_Speaker,
|
||||
currentSpeakerThresholdCheckStatus,
|
||||
} = useVolume();
|
||||
|
||||
useEffect(() => {
|
||||
if (currentEnableAutomaticSpeakerThreshold.data === true) {
|
||||
setUiThreshold("Auto");
|
||||
} else {
|
||||
setUiThreshold(currentSpeakerThreshold.data);
|
||||
}
|
||||
}, [currentSpeakerThreshold.data, currentEnableAutomaticSpeakerThreshold]);
|
||||
|
||||
const setUiThresholdFunction = (payload_ui_threshold) => {
|
||||
setUiThreshold(payload_ui_threshold);
|
||||
};
|
||||
const setThresholdFunction = (payload_threshold) => {
|
||||
setSpeakerThreshold(payload_threshold);
|
||||
};
|
||||
|
||||
const is_disable = currentEnableAutomaticSpeakerThreshold.data === true ? true : false;
|
||||
|
||||
return (
|
||||
<>
|
||||
<VolumeCheckButton
|
||||
{...props}
|
||||
SvgComponent={HeadphonesSvg}
|
||||
startFunction={volumeCheckStart_Speaker}
|
||||
stopFunction={volumeCheckStop_Speaker}
|
||||
isChecking={currentSpeakerThresholdCheckStatus}
|
||||
/>
|
||||
<SliderAndMeter
|
||||
{...props}
|
||||
ui_threshold={ui_threshold}
|
||||
setUiThresholdFunction={setUiThresholdFunction}
|
||||
setThresholdFunction={setThresholdFunction}
|
||||
/>
|
||||
<ThresholdEntry
|
||||
{...props}
|
||||
ui_threshold={ui_threshold}
|
||||
setUiThresholdFunction={setUiThresholdFunction}
|
||||
setThresholdFunction={setThresholdFunction}
|
||||
is_disable={is_disable}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
.container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import styles from "./SliderAndMeter.module.scss";
|
||||
import {
|
||||
useStore_MicVolume,
|
||||
useStore_SpeakerVolume,
|
||||
} from "@store";
|
||||
import {
|
||||
useDevice,
|
||||
} from "@logics_configs";
|
||||
|
||||
export const SliderAndMeter = (props) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.meter_container}>
|
||||
{props.id === "mic_threshold"
|
||||
? <ThresholdVolumeMeter_Mic {...props}/>
|
||||
: <ThresholdVolumeMeter_Speaker {...props}/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ThresholdVolumeMeter_Mic = (props) => {
|
||||
const { currentMicVolume } = useStore_MicVolume();
|
||||
|
||||
const { currentEnableAutomaticMicThreshold } = useDevice();
|
||||
|
||||
const currentVolumeVariable = Math.min(currentMicVolume.data, props.max);
|
||||
const volume_width_percentage = (currentVolumeVariable / props.max) * 100;
|
||||
|
||||
return (
|
||||
<>
|
||||
<VolumeMeter volume_width_percentage={volume_width_percentage} volume={currentVolumeVariable} threshold={props.ui_threshold}/>
|
||||
{currentEnableAutomaticMicThreshold.data === false &&
|
||||
<input
|
||||
type="range"
|
||||
min={props.min}
|
||||
max={props.max}
|
||||
value={props.ui_threshold}
|
||||
onChange={(e) => props.setUiThresholdFunction(e.target.value)}
|
||||
onMouseUp={(e) => props.setThresholdFunction(e.target.value)}
|
||||
className={styles.threshold_slider}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ThresholdVolumeMeter_Speaker = (props) => {
|
||||
const { currentSpeakerVolume } = useStore_SpeakerVolume();
|
||||
|
||||
const { currentEnableAutomaticSpeakerThreshold } = useDevice();
|
||||
|
||||
const currentVolumeVariable = Math.min(currentSpeakerVolume.data, props.max);
|
||||
const volume_width_percentage = (currentVolumeVariable / props.max) * 100;
|
||||
|
||||
return (
|
||||
<>
|
||||
<VolumeMeter volume_width_percentage={volume_width_percentage} volume={currentVolumeVariable} threshold={props.ui_threshold} />
|
||||
{currentEnableAutomaticSpeakerThreshold.data === false &&
|
||||
<input
|
||||
type="range"
|
||||
min={props.min}
|
||||
max={props.max}
|
||||
value={props.ui_threshold}
|
||||
onChange={(e) => props.setUiThresholdFunction(e.target.value)}
|
||||
onMouseUp={(e) => props.setThresholdFunction(e.target.value)}
|
||||
className={styles.threshold_slider}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const VolumeMeter = ({ volume_width_percentage, volume, threshold }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.volume_meter}
|
||||
style={{
|
||||
width: `${volume_width_percentage}%`,
|
||||
backgroundColor: (volume < threshold) ? "var(--primary_750_color)" : "var(--primary_400_color)"
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
// width: 100%;
|
||||
position: relative; // for dev
|
||||
}
|
||||
|
||||
.meter_container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0.8rem;
|
||||
background: var(--dark_800_color);
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
|
||||
.volume_meter {
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
transition: width 0.1s ease, background-color 0.1s ease;
|
||||
}
|
||||
|
||||
.threshold_slider {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: all;
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 0.4rem;
|
||||
height: 4rem;
|
||||
background: var(--primary_600_color);
|
||||
border-radius: 0.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover::-webkit-slider-thumb{
|
||||
background: var(--primary_500_color);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import clsx from "clsx";
|
||||
import styles from "./ThresholdEntry.module.scss";
|
||||
|
||||
export const ThresholdEntry = (props) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.entry_wrapper}>
|
||||
{props.id === "mic_threshold"
|
||||
? <ThresholdEntry_Mic {...props}/>
|
||||
: <ThresholdEntry_Speaker {...props}/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ThresholdEntry_Mic = (props) => {
|
||||
const onChangeFunction = (e) => {
|
||||
if (e.currentTarget.value === "") {
|
||||
props.setThresholdFunction("0");
|
||||
} else {
|
||||
props.setThresholdFunction(e.currentTarget.value);
|
||||
}
|
||||
};
|
||||
|
||||
const class_names = clsx(styles.entry_input_area, {
|
||||
[styles.is_disable]: props.is_disable
|
||||
});
|
||||
|
||||
return (
|
||||
<input
|
||||
className={class_names}
|
||||
onChange={onChangeFunction}
|
||||
value={props.ui_threshold}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ThresholdEntry_Speaker = (props) => {
|
||||
const onChangeFunction = (e) => {
|
||||
if (e.currentTarget.value === "") {
|
||||
props.setThresholdFunction("0");
|
||||
} else {
|
||||
props.setThresholdFunction(e.currentTarget.value);
|
||||
}
|
||||
};
|
||||
|
||||
const class_names = clsx(styles.entry_input_area, {
|
||||
[styles.is_disable]: props.is_disable
|
||||
});
|
||||
|
||||
return (
|
||||
<input
|
||||
className={class_names}
|
||||
onChange={onChangeFunction}
|
||||
value={props.ui_threshold}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
.container {
|
||||
|
||||
}
|
||||
|
||||
.entry_wrapper {
|
||||
width: 6rem;
|
||||
height: 100%;
|
||||
padding: 0.6rem;
|
||||
background-color: var(--dark_875_color);
|
||||
border: 0.1rem solid var(--dark_750_color);
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
|
||||
.entry_input_area {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 1.4rem;
|
||||
resize: none;
|
||||
&.is_disable {
|
||||
color: var(--dark_500_color);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import { useI18n } from "@useI18n";
|
||||
import clsx from "clsx";
|
||||
import styles from "./VolumeCheckButton.module.scss";
|
||||
|
||||
export const VolumeCheckButton = React.memo((props) => {
|
||||
const { t } = useI18n();
|
||||
const getClassNames = (baseClass) => clsx(baseClass, {
|
||||
[styles.is_active]: (props.isChecking?.data === true),
|
||||
[styles.is_pending]: (props.isChecking.state === "pending"),
|
||||
});
|
||||
|
||||
const toggleFunction = () => {
|
||||
if (props.isChecking?.data === true) {
|
||||
props.stopFunction();
|
||||
} else if (props.isChecking?.data === false) {
|
||||
props.startFunction();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={getClassNames(styles.button)} onClick={toggleFunction}>
|
||||
<props.SvgComponent className={styles.button_svg} />
|
||||
<p className={styles.button_text}>{t("config_page.device.check_volume")}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
VolumeCheckButton.displayName = "VolumeCheckButton";
|
||||
@@ -0,0 +1,44 @@
|
||||
.button {
|
||||
width: 100%;
|
||||
background-color: var(--dark_800_color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.8rem 1rem;
|
||||
border-radius: 0.4rem;
|
||||
gap: 0.4rem;
|
||||
cursor: pointer;
|
||||
&.is_active {
|
||||
background-color: var(--primary_500_color);
|
||||
&:hover {
|
||||
background-color: var(--primary_450_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--primary_550_color);
|
||||
}
|
||||
}
|
||||
&.is_pending {
|
||||
background-color: var(--dark_850_color);
|
||||
pointer-events: none;
|
||||
.button_svg, .button_text {
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color: var(--dark_775_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
}
|
||||
|
||||
.button_svg {
|
||||
width: 1.4rem;
|
||||
color: var(--dark_350_color);
|
||||
}
|
||||
|
||||
.button_text {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
import styles from "./WordFilter.module.scss";
|
||||
import { _Entry } from "../_atoms/_entry/_Entry";
|
||||
import { useState } from "react";
|
||||
import { useStore_IsOpenedMicWordFilterList } from "@store";
|
||||
import { useTranscription } from "@logics_configs";
|
||||
|
||||
export const WordFilter = () => {
|
||||
const { t } = useI18n();
|
||||
|
||||
const [input_value, setInputValue] = useState("");
|
||||
const { currentMicWordFilterList, updateMicWordFilterList, setMicWordFilterList } = useTranscription();
|
||||
const { currentIsOpenedMicWordFilterList, updateIsOpenedMicWordFilterList } = useStore_IsOpenedMicWordFilterList();
|
||||
|
||||
const onChangeEntry = (e) => {
|
||||
setInputValue(e.target.value);
|
||||
};
|
||||
|
||||
const addWords = () => {
|
||||
if (input_value === undefined) return;
|
||||
updateMicWordFilterList((prev_list) => {
|
||||
const input_value_array = input_value.split(",");
|
||||
let updated_list = [...prev_list.data];
|
||||
for (let each_input_value of input_value_array) {
|
||||
each_input_value = each_input_value.trim();
|
||||
if (each_input_value) {
|
||||
const exists = updated_list.find((item) => item === each_input_value);
|
||||
if (!exists) {
|
||||
updated_list = [...updated_list, each_input_value];
|
||||
}
|
||||
}
|
||||
}
|
||||
setMicWordFilterList(updated_list);
|
||||
return updated_list;
|
||||
});
|
||||
|
||||
updateIsOpenedMicWordFilterList(true);
|
||||
setInputValue("");
|
||||
};
|
||||
|
||||
|
||||
const deleteAction = (target_item_value) => {
|
||||
updateMicWordFilterList((prev_list) => {
|
||||
const updated_list = prev_list.data.filter((item) => item !== target_item_value);
|
||||
setMicWordFilterList(updated_list);
|
||||
return updated_list;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{ currentIsOpenedMicWordFilterList.data &&
|
||||
<div className={styles.list_section_wrapper}>
|
||||
{
|
||||
currentMicWordFilterList.data.map((item, index) => {
|
||||
return <WordFilterItem value={item} key={index} deleteAction={deleteAction}/>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div className={styles.entry_section_wrapper}>
|
||||
<_Entry width="30rem" onChange={onChangeEntry} ui_variable={input_value}/>
|
||||
<button className={styles.add_button} onClick={addWords}>{t("config_page.transcription.mic_word_filter.add_button_label")}</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import DeleteSvg from "@images/cancel.svg?react";
|
||||
import clsx from "clsx";
|
||||
const WordFilterItem = (props) => {
|
||||
const item_wrapper_class_names = clsx(styles["item_wrapper"]);
|
||||
const item_text_class_names = clsx(styles["item_text"]);
|
||||
|
||||
return (
|
||||
<div className={item_wrapper_class_names}>
|
||||
<p className={item_text_class_names}>{props.value}</p>
|
||||
<button className={clsx(styles.action_button, styles.delete)} onClick={() => props.deleteAction(props.value)}>
|
||||
<DeleteSvg className={styles.delete_svg}/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||
export const WordFilterListToggleComponent = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentIsOpenedMicWordFilterList, updateIsOpenedMicWordFilterList } = useStore_IsOpenedMicWordFilterList();
|
||||
const { currentMicWordFilterList } = useTranscription();
|
||||
|
||||
const svg_class_names = clsx(styles["arrow_left_svg"], {
|
||||
[styles.to_down]: !currentIsOpenedMicWordFilterList.data,
|
||||
[styles.to_up]: currentIsOpenedMicWordFilterList.data
|
||||
});
|
||||
|
||||
const OnclickFunction = () => {
|
||||
updateIsOpenedMicWordFilterList(!currentIsOpenedMicWordFilterList.data);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.toggle_button_container}>
|
||||
<p className={styles.words_count_text}>{t("config_page.transcription.mic_word_filter.count_desc", {count: currentMicWordFilterList.data.length} )}</p>
|
||||
<button className={styles.toggle_button_wrapper} onClick={OnclickFunction}>
|
||||
<ArrowLeftSvg className={svg_class_names}/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
|
||||
.list_section_wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
gap: 0.6rem;
|
||||
overflow-y: auto;
|
||||
max-height: 20rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.item_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
background-color: var(--dark_800_color);
|
||||
padding: 0.2rem 0.2rem 0.2rem 1rem;
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
.item_text {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.action_button {
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_750_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
&.delete {
|
||||
padding: 0.2rem;
|
||||
}
|
||||
&.redo {
|
||||
padding: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.delete_svg {
|
||||
width: 2.4rem;
|
||||
color: var(--error_bc_color);
|
||||
}
|
||||
|
||||
.redo_svg {
|
||||
width: 1.6rem;
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.entry_section_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.add_button {
|
||||
padding: 0.8rem 1.2rem;
|
||||
background-color: var(--primary_600_color);
|
||||
font-size: 1.4rem;
|
||||
border-radius: 0.4rem;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
&:hover {
|
||||
background-color: var(--primary_500_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--primary_700_color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.toggle_button_container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.words_count_text {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.toggle_button_wrapper {
|
||||
padding: 1.6rem;
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_825_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.arrow_left_svg {
|
||||
width: 2.4rem;
|
||||
&.to_down {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
&.to_up {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
import styles from "./Templates.module.scss";
|
||||
import { useStore_IsOpenedDropdownMenu, useStore_IsBreakPoint } from "@store";
|
||||
import {
|
||||
LabelComponent,
|
||||
DropdownMenu,
|
||||
Slider,
|
||||
SwitchBox,
|
||||
Entry,
|
||||
EntryWithSaveButton,
|
||||
HotkeysEntry,
|
||||
RadioButton,
|
||||
OpenWebpage_DeeplAuthKey,
|
||||
DeeplAuthKey,
|
||||
ActionButton,
|
||||
ComputeDevice,
|
||||
WordFilter,
|
||||
WordFilterListToggleComponent,
|
||||
DownloadModels,
|
||||
MessageFormat,
|
||||
} from "../_components";
|
||||
import { Checkbox } from "@common_components";
|
||||
|
||||
const LabeledContainer = ({ children, label, desc, custom_class_name }) => (
|
||||
<div className={clsx(styles.container, custom_class_name)}>
|
||||
<LabelComponent label={label} desc={desc} />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const useOnMouseLeaveDropdownMenu = () => {
|
||||
const { updateIsOpenedDropdownMenu } = useStore_IsOpenedDropdownMenu();
|
||||
|
||||
const onMouseLeaveFunction = () => {
|
||||
updateIsOpenedDropdownMenu("");
|
||||
};
|
||||
|
||||
return { onMouseLeaveFunction };
|
||||
};
|
||||
|
||||
export const DropdownMenuContainer = (props) => {
|
||||
const { onMouseLeaveFunction } = useOnMouseLeaveDropdownMenu();
|
||||
return (
|
||||
<div className={styles.container} onMouseLeave={onMouseLeaveFunction}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
<DropdownMenu {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CommonContainer = ({ Component, ...props }) => {
|
||||
const { currentIsBreakPoint } = useStore_IsBreakPoint();
|
||||
|
||||
const container_class = clsx(styles.container, {
|
||||
[styles.is_break_point]: props.add_break_point ?? currentIsBreakPoint.data,
|
||||
});
|
||||
|
||||
return (
|
||||
<LabeledContainer label={props.label} desc={props.desc} custom_class_name={container_class}>
|
||||
<Component {...props} is_break_point={currentIsBreakPoint.data} />
|
||||
</LabeledContainer>
|
||||
);
|
||||
};
|
||||
export const SliderContainer = (props) => (
|
||||
<CommonContainer Component={Slider} {...props} />
|
||||
);
|
||||
|
||||
export const CheckboxContainer = (props) => (
|
||||
<CommonContainer Component={Checkbox} {...props} add_break_point={false} />
|
||||
);
|
||||
|
||||
export const SwitchBoxContainer = (props) => (
|
||||
<CommonContainer Component={SwitchBox} {...props} add_break_point={false}/>
|
||||
);
|
||||
|
||||
export const EntryContainer = (props) => (
|
||||
<CommonContainer Component={Entry} {...props} add_break_point={false} />
|
||||
);
|
||||
export const EntryWithSaveButtonContainer = (props) => (
|
||||
<CommonContainer Component={EntryWithSaveButton} {...props} add_break_point={false} />
|
||||
);
|
||||
|
||||
export const HotkeysEntryContainer = (props) => (
|
||||
<CommonContainer Component={HotkeysEntry} {...props} />
|
||||
);
|
||||
|
||||
export const RadioButtonContainer = (props) => (
|
||||
<CommonContainer Component={RadioButton} {...props} />
|
||||
);
|
||||
|
||||
export const DeeplAuthKeyContainer = (props) => {
|
||||
const { currentIsBreakPoint } = useStore_IsBreakPoint();
|
||||
const container_class = clsx(styles.container, {
|
||||
[styles.is_break_point]: currentIsBreakPoint.data,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={container_class}>
|
||||
<div className={styles.deepl_auth_key_label_section}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
<OpenWebpage_DeeplAuthKey />
|
||||
</div>
|
||||
<DeeplAuthKey {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ActionButtonContainer = (props) => (
|
||||
<CommonContainer Component={ActionButton} {...props} add_break_point={false}/>
|
||||
);
|
||||
|
||||
export const ComputeDeviceContainer = (props) => (
|
||||
<CommonContainer Component={ComputeDevice} {...props} />
|
||||
);
|
||||
|
||||
export const WordFilterContainer = (props) => (
|
||||
<div className={styles.word_filter_container}>
|
||||
<div className={styles.word_filter_switch_section}>
|
||||
<div className={styles.word_filter_label_wrapper}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
</div>
|
||||
<WordFilterListToggleComponent />
|
||||
</div>
|
||||
<div className={styles.word_filter_section}>
|
||||
<WordFilter {...props} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const DownloadModelsContainer = (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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
.container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
gap: 2rem;
|
||||
&.flex_column {
|
||||
flex-direction: column;
|
||||
}
|
||||
border-bottom: solid 0.1rem var(--dark_800_color);
|
||||
|
||||
&.is_break_point {
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
.label_only_section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.deepl_auth_key_label_section {
|
||||
max-width: 34rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1.4rem;
|
||||
}
|
||||
|
||||
.message_format_section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.word_filter_container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.word_filter_switch_section {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.word_filter_label_wrapper {
|
||||
max-width: 34rem;
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import styles from "./AboutVrct.module.scss";
|
||||
import dev_section_title from "@images/about_vrct/dev_section_title.png";
|
||||
import dev_misya from "@images/about_vrct/dev_misya.png";
|
||||
import dev_shiina from "@images/about_vrct/dev_shiina.png";
|
||||
import vrct_logo_for_about_vrct from "@images/about_vrct/vrct_logo_for_about_vrct.png";
|
||||
|
||||
import contributors_section_title from "@images/about_vrct/contributors_section_title.png";
|
||||
import contributor_done from "@images/about_vrct/contributor_done.png";
|
||||
import contributor_iya from "@images/about_vrct/contributor_iya.png";
|
||||
import contributor_rera from "@images/about_vrct/contributor_rera.png";
|
||||
import contributor_poposuke from "@images/about_vrct/contributor_poposuke.png";
|
||||
import contributor_kumaguma from "@images/about_vrct/contributor_kumaguma.png";
|
||||
import contributor_riku from "@images/about_vrct/contributor_riku.png";
|
||||
|
||||
import localization_section_title from "@images/about_vrct/localization_section_title.png";
|
||||
import localization_1 from "@images/about_vrct/localization_1.png";
|
||||
import localization_2 from "@images/about_vrct/localization_2.png";
|
||||
import localization_3 from "@images/about_vrct/localization_3.png";
|
||||
import localization_4 from "@images/about_vrct/localization_4.png";
|
||||
import localization_5 from "@images/about_vrct/localization_5.png";
|
||||
|
||||
import special_thanks_section_title from "@images/about_vrct/special_thanks_section_title.png";
|
||||
import special_thanks_members from "@images/about_vrct/special_thanks_members.png";
|
||||
import special_thanks_message_en from "@images/about_vrct/special_thanks_message_en.png";
|
||||
import special_thanks_message_ja from "@images/about_vrct/special_thanks_message_ja.png";
|
||||
|
||||
import poster_showcase_section_title from "@images/about_vrct/poster_showcase_section_title.png";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useI18n } from "@useI18n";
|
||||
import { useAppearance } from "@logics_configs";
|
||||
import { PosterShowcaseContents } from "./poster_showcase_contents/PosterShowcaseContents";
|
||||
|
||||
export const AboutVrct = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentUiLanguage } = useAppearance();
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.dev_section}>
|
||||
<img src={dev_section_title} className={clsx(styles.section_title, styles.the_developers)} />
|
||||
<div className={styles.dev_section_wrapper}>
|
||||
<div className={styles.dev_card_wrapper}>
|
||||
<img src={dev_misya} className={styles.dev_card_img} />
|
||||
<OpenLinkContainer className={styles.dev_misya_x} href_id="dev_misya_x" />
|
||||
<OpenLinkContainer className={styles.dev_misya_github} href_id="dev_misya_github" />
|
||||
</div>
|
||||
<div className={styles.dev_card_wrapper}>
|
||||
<img src={dev_shiina} className={styles.dev_card_img} />
|
||||
<OpenLinkContainer className={styles.dev_shiina_x} href_id="dev_shiina_x" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.project_links_and_logo_section}>
|
||||
<div className={styles.about_vrct_logo_wrapper}>
|
||||
<img src={vrct_logo_for_about_vrct} className={styles.about_vrct_logo} />
|
||||
</div>
|
||||
<div className={styles.project_links_wrapper}>
|
||||
<OpenLinkContainer className={styles.project_link} href_id="project_link_booth" />
|
||||
<OpenLinkContainer className={styles.project_link} href_id="project_link_documents" />
|
||||
<OpenLinkContainer className={styles.project_link} href_id="project_link_vrct_github" />
|
||||
<OpenLinkContainer className={styles.project_link} href_id="project_link_contact_us" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.contributors_section}>
|
||||
<img src={contributors_section_title} className={clsx(styles.section_title, styles.contributors)} />
|
||||
<div className={styles.contributors_img_wrapper}>
|
||||
<div className={styles.contributor_card_wrapper}>
|
||||
<img src={contributor_done} className={clsx(styles.contributors_img, styles.contributors)} />
|
||||
<OpenLinkContainer className={styles.contributors_done_san_x} href_id="contributors_done_san_x" />
|
||||
</div>
|
||||
<div className={styles.contributor_card_wrapper}>
|
||||
<img src={contributor_iya} className={clsx(styles.contributors_img, styles.contributors)} />
|
||||
<OpenLinkContainer className={styles.contributors_iya_x} href_id="contributors_iya_x" />
|
||||
</div>
|
||||
<div className={styles.contributor_card_wrapper}>
|
||||
<img src={contributor_rera} className={clsx(styles.contributors_img, styles.contributors)} />
|
||||
<OpenLinkContainer className={styles.contributors_rera_x} href_id="contributors_rera_x" />
|
||||
<OpenLinkContainer className={styles.contributors_rera_github} href_id="contributors_rera_github" />
|
||||
</div>
|
||||
<div className={styles.contributor_card_wrapper}>
|
||||
<img src={contributor_poposuke} className={clsx(styles.contributors_img, styles.contributors)} />
|
||||
<OpenLinkContainer className={styles.contributors_poposuke_x} href_id="contributors_poposuke_x" />
|
||||
</div>
|
||||
<div className={styles.contributor_card_wrapper}>
|
||||
<img src={contributor_kumaguma} className={clsx(styles.contributors_img, styles.contributors)} />
|
||||
<OpenLinkContainer className={styles.contributors_kumaguma_x} href_id="contributors_kumaguma_x" />
|
||||
</div>
|
||||
<div className={styles.contributor_card_wrapper}>
|
||||
<img src={contributor_riku} className={clsx(styles.contributors_img, styles.contributors)} />
|
||||
<OpenLinkContainer className={styles.contributors_riku_x} href_id="contributors_riku_x" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.localization_section}>
|
||||
<img src={localization_section_title} className={clsx(styles.section_title, styles.localization)} />
|
||||
<div className={styles.localization_members_wrapper}>
|
||||
<div className={styles.localization_members_row_wrapper}>
|
||||
<img src={localization_1} className={styles.localization_members_img} />
|
||||
<img src={localization_2} className={styles.localization_members_img} />
|
||||
</div>
|
||||
<div className={styles.localization_members_row_wrapper}>
|
||||
<img src={localization_3} className={styles.localization_members_img} />
|
||||
<img src={localization_4} className={styles.localization_members_img} />
|
||||
</div>
|
||||
<div className={styles.localization_members_row_wrapper}>
|
||||
<img src={localization_5} className={styles.localization_members_img} />
|
||||
{/* <img src={localization_6} className={styles.localization_members_img} /> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.special_thanks_section}>
|
||||
<img src={special_thanks_section_title} className={clsx(styles.section_title, styles.special_thanks)} />
|
||||
<img src={special_thanks_members} className={styles.special_thanks_members_img} />
|
||||
{
|
||||
currentUiLanguage.data === "ja"
|
||||
? <img src={special_thanks_message_ja} className={styles.special_thanks_message_img} />
|
||||
: <img src={special_thanks_message_en} className={styles.special_thanks_message_img} />
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
<div className={styles.poster_showcase_section}>
|
||||
<img src={poster_showcase_section_title} className={clsx(styles.section_title, styles.poster_showcase)} />
|
||||
<PosterShowcaseContents />
|
||||
</div>
|
||||
|
||||
<div className={styles.vrchat_disclaimer_section}>
|
||||
<p className={styles.vrchat_disclaimer}>VRCT is not endorsed by VRChat and does not reflect the views or opinions of VRChat or anyone officially involved in producing or managing VRChat properties. VRChat and all associated properties are trademarks or registered trademarks of VRChat Inc. VRChat © VRChat Inc.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import dev_x_icon from "@images/about_vrct/dev_x_icon.png";
|
||||
import dev_github_icon from "@images/about_vrct/dev_github_icon.png";
|
||||
import contributors_x_icon from "@images/about_vrct/contributors_x_icon.png";
|
||||
import contributors_github_icon from "@images/about_vrct/contributors_github_icon.png";
|
||||
|
||||
import project_link_booth from "@images/about_vrct/project_link_booth.png";
|
||||
import project_link_documents from "@images/about_vrct/project_link_documents.png";
|
||||
import project_link_vrct_github from "@images/about_vrct/project_link_vrct_github.png";
|
||||
import project_link_contact_us from "@images/about_vrct/project_link_contact_us.png";
|
||||
|
||||
const about_vrct_links = {
|
||||
dev_misya_x: { img: dev_x_icon, href: "https://twitter.com/misya_ai" },
|
||||
dev_misya_github: { img: dev_github_icon, href: "https://github.com/misyaguziya" },
|
||||
dev_shiina_x: { img: dev_x_icon, href: "https://twitter.com/Shiina_12siy" },
|
||||
|
||||
project_link_booth: { img: project_link_booth, href: "https://misyaguziya.booth.pm/items/5155325" },
|
||||
project_link_documents: { img: project_link_documents, href: "https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246" },
|
||||
project_link_vrct_github: { img: project_link_vrct_github, href: "https://github.com/misyaguziya/VRCT" },
|
||||
project_link_contact_us: { img: project_link_contact_us, href: "https://docs.google.com/forms/d/e/1FAIpQLSei-xoydOY60ivXqhOjaTzNN8PiBQIDcNhzfy6cw2sjYkcg_g/viewform" },
|
||||
|
||||
contributors_done_san_x: { img: contributors_x_icon, href: "https://twitter.com/done_vrc" },
|
||||
contributors_iya_x: { img: contributors_x_icon, href: "https://twitter.com/IYAA_HHHH" },
|
||||
contributors_rera_x: { img: contributors_x_icon, href: "https://twitter.com/rerassi" },
|
||||
contributors_rera_github: { img: contributors_github_icon, href: "https://github.com/soumt-r" },
|
||||
contributors_poposuke_x: { img: contributors_x_icon, href: "https://twitter.com/sig_popo" },
|
||||
contributors_kumaguma_x: { img: contributors_x_icon, href: "https://twitter.com/K_kumaguma_A" },
|
||||
contributors_riku_x: { img: contributors_x_icon, href: "https://twitter.com/Riku7302" },
|
||||
};
|
||||
|
||||
const OpenLinkContainer = ({className, href_id}) => {
|
||||
const href = about_vrct_links[href_id].href;
|
||||
const img = about_vrct_links[href_id].img;
|
||||
return (
|
||||
<a className={className} href={href} target="_blank" rel="noreferrer" >
|
||||
{/* for adjust size to their parent component's width. */}
|
||||
<img style={ {height: "100%", width: "100%", "objectFit": "contain" }} src={img} />
|
||||
</a>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,215 @@
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 2.2rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
max-width: 72rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.section_title {
|
||||
height: 1.2rem;
|
||||
object-fit: contain;
|
||||
object-position: left;
|
||||
&.the_developers {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
&.contributors {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
&.special_thanks {
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
&.poster_showcase {
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dev_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.dev_section_wrapper {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
.dev_card_wrapper {
|
||||
position: relative;
|
||||
// width: 100%;
|
||||
}
|
||||
.dev_card_img {
|
||||
width: 34.6rem;
|
||||
}
|
||||
|
||||
@mixin dev_sns_styles($right) {
|
||||
position: absolute;
|
||||
right: $right;
|
||||
bottom: 0.6rem;
|
||||
width: 2.8rem;
|
||||
padding: 0.4rem;
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color)
|
||||
}
|
||||
}
|
||||
.dev_misya_x {
|
||||
@include dev_sns_styles(6rem);
|
||||
}
|
||||
.dev_misya_github {
|
||||
@include dev_sns_styles(3rem);
|
||||
}
|
||||
.dev_shiina_x {
|
||||
@include dev_sns_styles(2.4rem);
|
||||
}
|
||||
|
||||
|
||||
.project_links_and_logo_section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 0 5.5rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
.about_vrct_logo_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
.about_vrct_logo {
|
||||
width: 20rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
.project_links_wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.2rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.project_link {
|
||||
height: 2.6rem;
|
||||
padding: 0.4rem 1rem;
|
||||
border-radius: 0.4rem;
|
||||
flex-shrink: 0;
|
||||
&:hover {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color)
|
||||
}
|
||||
}
|
||||
|
||||
.contributors_img_wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.contributor_card_wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.contributors_img {
|
||||
width: 22rem;
|
||||
}
|
||||
|
||||
@mixin contributors_sns_styles($bottom, $left) {
|
||||
position: absolute;
|
||||
left: $left;
|
||||
bottom: $bottom;
|
||||
width: 2.4rem;
|
||||
padding: 0.4rem;
|
||||
border-radius: 0.4rem;
|
||||
transform: translate(50%, 50%);
|
||||
&:hover {
|
||||
background-color: var(--dark_825_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_888_color)
|
||||
}
|
||||
}
|
||||
|
||||
$bottom_pos: 16%;
|
||||
$sns_left_pos: 0.8rem;
|
||||
.contributors_done_san_x {
|
||||
@include contributors_sns_styles($bottom_pos, $sns_left_pos);
|
||||
}
|
||||
.contributors_iya_x {
|
||||
@include contributors_sns_styles($bottom_pos, $sns_left_pos);
|
||||
}
|
||||
.contributors_rera_x {
|
||||
@include contributors_sns_styles($bottom_pos, calc($sns_left_pos - 1.4rem));
|
||||
}
|
||||
.contributors_rera_github {
|
||||
@include contributors_sns_styles($bottom_pos, calc($sns_left_pos + 1.4rem));
|
||||
}
|
||||
|
||||
.contributors_poposuke_x {
|
||||
@include contributors_sns_styles($bottom_pos, $sns_left_pos);
|
||||
}
|
||||
.contributors_kumaguma_x {
|
||||
@include contributors_sns_styles($bottom_pos, $sns_left_pos);
|
||||
}
|
||||
.contributors_riku_x {
|
||||
@include contributors_sns_styles($bottom_pos, $sns_left_pos);
|
||||
}
|
||||
|
||||
.localization_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
.localization_members_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: start;
|
||||
column-gap: 6rem;
|
||||
row-gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.localization_members_row_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: start;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
.localization_members_img {
|
||||
height: 2.2rem;
|
||||
}
|
||||
|
||||
.special_thanks_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.special_thanks_members_img {
|
||||
width: 100%;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
.special_thanks_message_img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.poster_showcase_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vrchat_disclaimer {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 8rem;
|
||||
color: var(--dark_600_color);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import styles from "./PosterShowcaseContents.module.scss";
|
||||
import { PostersContents } from "./posters_contents/PostersContents";
|
||||
import { PosterShowcaseWorldsContents } from "./poster_showcase_worlds_contents/PosterShowcaseWorldsContents";
|
||||
|
||||
export const PosterShowcaseContents = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<PosterShowcaseWorldsContents />
|
||||
<PostersContents />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: start;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import clsx from "clsx";
|
||||
import styles from "./PosterShowcaseWorldsContents.module.scss";
|
||||
import { useStore_PosterShowcaseWorldPageIndex } from "@store";
|
||||
const images = import.meta.glob("@images/about_vrct/showcased_worlds/*.{png,jpg,jpeg,svg}", { eager: true });
|
||||
|
||||
const getImageByFileName = (file_name) => {
|
||||
const imagePath = Object.keys(images).find((path) => path.endsWith(file_name + ".png"));
|
||||
return imagePath ? images[imagePath]?.default : null;
|
||||
};
|
||||
|
||||
import poster_showcase_worlds_settings from "./poster_showcase_worlds_settings";
|
||||
import { chunkArray } from "@utils";
|
||||
|
||||
export const PosterShowcaseWorldsContents = () => {
|
||||
const { currentPosterShowcaseWorldPageIndex } = useStore_PosterShowcaseWorldPageIndex();
|
||||
const poster_showcase_world_images = poster_showcase_worlds_settings.map((setting) => ({
|
||||
img: getImageByFileName(setting.image_file_name),
|
||||
x_post_num: setting.x_post_num
|
||||
}));
|
||||
|
||||
const chunked_poster_showcase_world_images = chunkArray(poster_showcase_world_images, 8);
|
||||
const target_poster_showcase_world_images = chunked_poster_showcase_world_images[currentPosterShowcaseWorldPageIndex.data];
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.poster_showcase_world_container}>
|
||||
{target_poster_showcase_world_images.map((poster, index) => {
|
||||
const class_names = clsx(styles.poster_showcase_world_wrapper, {
|
||||
[styles.clickable]: (poster.x_post_num !== null)
|
||||
});
|
||||
|
||||
const content = (
|
||||
<div className={styles.poster_showcase_world_img} >
|
||||
<img style={ {height: "100%", width: "100%", "objectFit": "contain" }} src={poster.img} />
|
||||
</div>
|
||||
);
|
||||
if (poster.x_post_num !== null) {
|
||||
return (
|
||||
<a href={`https://x.com/Shiina_12siy/status/${poster.x_post_num}`} target="_blank" rel="noreferrer" className={class_names} key={index}>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={class_names} key={index}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
<PosterShowcaseWorldsPagination page_length={chunked_poster_showcase_world_images.length}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import chat_white_square from "@images/chato_white_square.png";
|
||||
import { useEffect } from "react";
|
||||
import { randomIntMinMax } from "@utils";
|
||||
const PosterShowcaseWorldsPagination = ({ page_length }) => {
|
||||
const { currentPosterShowcaseWorldPageIndex, updatePosterShowcaseWorldPageIndex } = useStore_PosterShowcaseWorldPageIndex();
|
||||
|
||||
useEffect(() => {
|
||||
updatePosterShowcaseWorldPageIndex(randomIntMinMax(page_length -1));
|
||||
},[page_length]);
|
||||
|
||||
const setPage = (index) => {
|
||||
updatePosterShowcaseWorldPageIndex(index);
|
||||
};
|
||||
|
||||
const getClassNames = (index, baseClass) => clsx(baseClass, {
|
||||
[styles.is_active]: (currentPosterShowcaseWorldPageIndex.data === index),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.pagination_container}>
|
||||
{[...Array(page_length).keys()].map((index) => {
|
||||
return (
|
||||
<div key={index} className={getClassNames(index, styles.pagination_box)} onClick={() => setPage(index)}>
|
||||
<div className={styles.chato_box}>
|
||||
<img src={chat_white_square} className={getClassNames(index, styles.pagination_chato_img)}/>
|
||||
</div>
|
||||
<div className={styles.indicator_box}>
|
||||
<div className={getClassNames(index, styles.indicator)}></div>
|
||||
<p className={getClassNames(index, styles.pagination_num)}>{index + 1}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// flex: 1;
|
||||
gap: 1rem;
|
||||
justify-content: space-between;
|
||||
width: 42rem;
|
||||
}
|
||||
$image_height: 2.8rem;
|
||||
$y_padding: 0.4rem;
|
||||
$image_height_gap: 0.4rem;
|
||||
.poster_showcase_world_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: $image_height_gap;
|
||||
height: calc( (($image_height + ($y_padding*2)) * 8) + ($image_height_gap * (8 - 1)) );
|
||||
}
|
||||
.poster_showcase_world_wrapper {
|
||||
display: flex;
|
||||
padding: $y_padding 0.6rem $y_padding 0.8rem;
|
||||
border-radius: 0.4rem 0 0 0.4rem;
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_925_color);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.poster_showcase_world_img {
|
||||
height: $image_height;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pagination_container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 6%;
|
||||
margin: 0 2.6rem;
|
||||
}
|
||||
|
||||
$animation_duration: .1s;
|
||||
.pagination_box {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
&:active .pagination_chato_img {
|
||||
animation: tremble_animation $animation_duration ease-out;
|
||||
}
|
||||
&:active.is_active .pagination_chato_img {
|
||||
transform: translate(-50%, -50%) rotate(-22deg);
|
||||
}
|
||||
&.is_active .pagination_chato_img {
|
||||
top: 48%;
|
||||
animation: rotate_animation $animation_duration ease-out;
|
||||
}
|
||||
&:hover {
|
||||
& .pagination_chato_img {
|
||||
top: 108%;
|
||||
}
|
||||
&.is_active .pagination_chato_img {
|
||||
animation: tremble_animation $animation_duration ease-out;
|
||||
}
|
||||
& .pagination_num {
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.indicator_box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
width: 100%;
|
||||
height: 0.2rem;
|
||||
background-color: var(--dark_825_color);
|
||||
&.is_active {
|
||||
background-color: var(--primary_400_color);
|
||||
}
|
||||
}
|
||||
|
||||
.pagination_num {
|
||||
font-size: 1.8rem;
|
||||
padding: 1rem;
|
||||
color: var(--dark_600_color);
|
||||
&.is_active {
|
||||
color: var(--primary_300_color);
|
||||
}
|
||||
}
|
||||
|
||||
.chato_box {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 6rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pagination_chato_img {
|
||||
position: absolute;
|
||||
top: 200%;
|
||||
left: 51%;
|
||||
transform: translate(-50%, -50%) rotate(22deg);
|
||||
width: 2.8rem;
|
||||
transition: top $animation_duration ease-out;
|
||||
}
|
||||
@keyframes rotate_animation {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(22deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg + 22deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tremble_animation {
|
||||
0% { left: 51%; }
|
||||
25% { left: 55%; }
|
||||
50% { left: 45%; }
|
||||
75% { left: 48%; }
|
||||
100% { left: 51%; }
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
const poster_showcase_worlds_settings = [
|
||||
// トサカひよ
|
||||
{ image_file_name: "kokekkopiyopiyo", x_post_num: "1779076974369276014" },
|
||||
|
||||
// MiuJepang
|
||||
{ image_file_name: "ippaidou", x_post_num: "1787801976354513319" },
|
||||
{ image_file_name: "nihongokurabu", x_post_num: "1779004631936614893" },
|
||||
{ image_file_name: "language_exchange_tervern", x_post_num: "1779749425923150317" },
|
||||
{ image_file_name: "japanese_culture_osenbeito", x_post_num: "1788522972409721137" },
|
||||
{ image_file_name: "silakan_datang_ke_rumahku", x_post_num: "1788522607631056941" },
|
||||
{ image_file_name: "uj_club", x_post_num: "1780791654196388201" },
|
||||
{ image_file_name: "sushi_stand_guruguru", x_post_num: "1788523302404952218" },
|
||||
{ image_file_name: "sushi_guru_annex", x_post_num: "1825426749770932457" },
|
||||
{ image_file_name: "una_yosh", x_post_num: "1820329216598311065" },
|
||||
{ image_file_name: "cam", x_post_num: "1825427064985686138" },
|
||||
{ image_file_name: "language_exchange_park", x_post_num: "1825806455322324993" },
|
||||
|
||||
// poposuke_sig
|
||||
{ image_file_name: "usanezumi_shrine2", x_post_num: "1781224020383506649" },
|
||||
|
||||
// KUROINU_YOUHEI
|
||||
{ image_file_name: "kuroinu_work_room", x_post_num: "1779750007564112146" },
|
||||
|
||||
// いちや_ICHIYA
|
||||
{ image_file_name: "ehon_no_heikousekai", x_post_num: "1843891361478783425" },
|
||||
{ image_file_name: "ehon_no_heikousekai_1st_anniv", x_post_num: "1842088383746875535" },
|
||||
{ image_file_name: "ehon_no_heikousekai_jimusho", x_post_num: "1780792306976850285" },
|
||||
{ image_file_name: "ikoiba", x_post_num: "1782723006923780580" },
|
||||
{ image_file_name: "kimodameshi", x_post_num: "1781224391692714133" },
|
||||
{ image_file_name: "parallel_collar", x_post_num: "1820693442105934068" },
|
||||
{ image_file_name: "yoru_color", x_post_num: "1842088701599396075" },
|
||||
|
||||
// HayaTikaze
|
||||
{ image_file_name: "study_japanese_world_japanichijou", x_post_num: "1781539871829766550" },
|
||||
|
||||
// aji_3
|
||||
{ image_file_name: "yuttari_eikaiwa", x_post_num: "1779002892999078046" },
|
||||
|
||||
// 八葉そるち
|
||||
{ image_file_name: "re_yatuha_room", x_post_num: "1779830390435590196" },
|
||||
|
||||
// chakamoto
|
||||
{ image_file_name: "chakachaka_multipurpose_room", x_post_num: "1818107831289295065" },
|
||||
|
||||
// MloYolM (よるむ)
|
||||
{ image_file_name: "cafe_cian", x_post_num: "1787802552907739504" },
|
||||
|
||||
// ミラクル・オルカ
|
||||
{ image_file_name: "mamehinata_dogrun", x_post_num: "1782723423179100471" },
|
||||
|
||||
// いんく(Eenkoo)
|
||||
{ image_file_name: "tyuuniti_kouryuukai", x_post_num: "1818109101731422617" },
|
||||
|
||||
// 1ban_meno
|
||||
{ image_file_name: "bar_asagao", x_post_num: "1788523857642758370" },
|
||||
|
||||
// 沈黙静寂
|
||||
{ image_file_name: "monogatari_meetup", x_post_num: "1781538415789674976" },
|
||||
|
||||
// tommie_500
|
||||
{ image_file_name: "stretch_club_starting_from_minus", x_post_num: "1825048889550102597" },
|
||||
|
||||
// MiMi_Sorahana # VRC日韓交流会 (KRJPEX.1355)
|
||||
{ image_file_name: "kr_jp_exchange", x_post_num: "1820328755950473668" },
|
||||
|
||||
// Ein(アイン)
|
||||
{ image_file_name: "smokerz_guild_v2", x_post_num: "1825049450190127187" },
|
||||
|
||||
// KokiM1018
|
||||
{ image_file_name: "poker_room_elysion", x_post_num: "1818880695344980208" },
|
||||
|
||||
// NEET ENGINEER
|
||||
{ image_file_name: "japan_street", x_post_num: "1818881593114861924" },
|
||||
|
||||
// RIKU_VR
|
||||
{ image_file_name: "celestial_blooms", x_post_num: "1820694531568001061" },
|
||||
|
||||
// ღKAEDEಇ
|
||||
{ image_file_name: "omoshiro_kotoba_asobi_game", x_post_num: "1825806909343199700" },
|
||||
{ image_file_name: "chill_sleep_room_03", x_post_num: "1842741645231677506" },
|
||||
{ image_file_name: "chill_sleep_room_04", x_post_num: "1842742135042555906" },
|
||||
|
||||
// アスタルテア
|
||||
{ image_file_name: "oto_no_shitatei", x_post_num: "1831575615520305619" },
|
||||
|
||||
// Sayascape
|
||||
{ image_file_name: "sayasuke_hotel", x_post_num: "1843537673224630740" },
|
||||
|
||||
// ふながし
|
||||
{ image_file_name: "su", x_post_num: "1843537207401058558" },
|
||||
|
||||
// さや-sayasoft
|
||||
{ image_file_name: "saya_town", x_post_num: null },
|
||||
];
|
||||
export default poster_showcase_worlds_settings;
|
||||
@@ -0,0 +1,69 @@
|
||||
import clsx from "clsx";
|
||||
import styles from "./PostersContents.module.scss";
|
||||
import { useAppearance } from "@logics_configs";
|
||||
|
||||
import { useStore_VrctPosterIndex } from "@store";
|
||||
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||
|
||||
import iya_vrct_poster_ja from "@images/about_vrct/vrct_posters/iya_vrct_poster_ja.png";
|
||||
import iya_vrct_poster_en from "@images/about_vrct/vrct_posters/iya_vrct_poster_en.png";
|
||||
import iya_vrct_poster_cn from "@images/about_vrct/vrct_posters/iya_vrct_poster_cn.png";
|
||||
import iya_vrct_poster_ko from "@images/about_vrct/vrct_posters/iya_vrct_poster_ko.png";
|
||||
import iya_vrct_manga_ja from "@images/about_vrct/vrct_posters/iya_vrct_manga_ja.png";
|
||||
import iya_vrct_manga_en from "@images/about_vrct/vrct_posters/iya_vrct_manga_en.png";
|
||||
import iya_vrct_manga_ko from "@images/about_vrct/vrct_posters/iya_vrct_manga_ko.png";
|
||||
|
||||
const poster_images = [
|
||||
{ img: iya_vrct_poster_ja, poster_type: "poster" },
|
||||
{ img: iya_vrct_poster_en, poster_type: "poster" },
|
||||
{ img: iya_vrct_poster_cn, poster_type: "poster" },
|
||||
{ img: iya_vrct_poster_ko, poster_type: "poster" },
|
||||
{ img: iya_vrct_manga_ja, poster_type: "manga" },
|
||||
{ img: iya_vrct_manga_en, poster_type: "manga" },
|
||||
{ img: iya_vrct_manga_ko, poster_type: "manga" },
|
||||
];
|
||||
|
||||
import poster_images_authors_ja from "@images/about_vrct/vrct_posters/authors/poster_images_authors_ja.png";
|
||||
import poster_images_authors_en from "@images/about_vrct/vrct_posters/authors/poster_images_authors_en.png";
|
||||
import poster_images_authors_m_ja from "@images/about_vrct/vrct_posters/authors/poster_images_authors_m_ja.png";
|
||||
import poster_images_authors_m_en from "@images/about_vrct/vrct_posters/authors/poster_images_authors_m_en.png";
|
||||
|
||||
export const PostersContents = () => {
|
||||
const { currentVrctPosterIndex, updateVrctPosterIndex } = useStore_VrctPosterIndex();
|
||||
const { currentUiLanguage } = useAppearance();
|
||||
|
||||
|
||||
const updateIndex = (delta) => {
|
||||
const newIndex = (currentVrctPosterIndex.data + delta + poster_images.length) % poster_images.length;
|
||||
updateVrctPosterIndex(newIndex);
|
||||
};
|
||||
|
||||
const current_poster = poster_images[currentVrctPosterIndex.data];
|
||||
const current_poster_authors_img_ja = (current_poster.poster_type === "poster") ? poster_images_authors_ja : poster_images_authors_m_ja;
|
||||
const current_poster_authors_img_en = (current_poster.poster_type === "poster") ? poster_images_authors_en : poster_images_authors_m_en;
|
||||
|
||||
return (
|
||||
<div className={styles.poster_pagination_container}>
|
||||
<div className={styles.poster_pagination_wrapper}>
|
||||
<button
|
||||
className={clsx(styles.poster_pagination_button, styles.poster_prev)}
|
||||
onClick={() => updateIndex(-1)}
|
||||
>
|
||||
<ArrowLeftSvg className={clsx(styles.poster_pagination_svg, styles.poster_prev_svg)} />
|
||||
</button>
|
||||
<img src={current_poster.img} className={styles.poster_img} />
|
||||
<button
|
||||
className={clsx(styles.poster_pagination_button, styles.poster_next)}
|
||||
onClick={() => updateIndex(1)}
|
||||
>
|
||||
<ArrowLeftSvg className={clsx(styles.poster_pagination_svg, styles.poster_next_svg)} />
|
||||
</button>
|
||||
</div>
|
||||
{
|
||||
currentUiLanguage.data === "ja"
|
||||
? <img src={current_poster_authors_img_ja} className={styles.poster_authors_img} />
|
||||
: <img src={current_poster_authors_img_en} className={styles.poster_authors_img} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
.poster_pagination_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.poster_pagination_wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
$poster_img_width: 18rem;
|
||||
.poster_img {
|
||||
width: $poster_img_width;
|
||||
}
|
||||
|
||||
$poster_pagination_button_width: 4.6rem;
|
||||
.poster_pagination_button {
|
||||
width: $poster_pagination_button_width;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--dark_700_color);
|
||||
&:hover {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_925_color);
|
||||
}
|
||||
&.poster_prev {
|
||||
border-radius: 0.8rem 0 0 0.8rem;
|
||||
}
|
||||
&.poster_next {
|
||||
border-radius: 0 0.8rem 0.8rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.poster_pagination_svg {
|
||||
width: 3.2rem;
|
||||
&.poster_next_svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.poster_authors_img {
|
||||
width: $poster_img_width + $poster_pagination_button_width;
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useI18n } from "@useI18n";
|
||||
import styles from "./AdvancedSettings.module.scss";
|
||||
|
||||
import { useOpenFolder } from "@logics_common";
|
||||
import {
|
||||
useAdvancedSettings,
|
||||
} from "@logics_configs";
|
||||
|
||||
import {
|
||||
CheckboxContainer,
|
||||
ActionButtonContainer,
|
||||
EntryWithSaveButtonContainer,
|
||||
} from "../_templates/Templates";
|
||||
|
||||
import {
|
||||
SectionLabelComponent,
|
||||
} from "../_components";
|
||||
|
||||
import OpenFolderSvg from "@images/open_folder.svg?react";
|
||||
import HelpSvg from "@images/help.svg?react";
|
||||
|
||||
export const AdvancedSettings = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div>
|
||||
<OscIpAddressContainer />
|
||||
<OscPortContainer />
|
||||
<OpenConfigFolderContainer />
|
||||
<OpenSwitchComputeDeviceModalContainer />
|
||||
</div>
|
||||
<WebsocketContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OscIpAddressContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentOscIpAddress, setOscIpAddress } = useAdvancedSettings();
|
||||
const [input_value, setInputValue] = useState(currentOscIpAddress.data);
|
||||
|
||||
const onChangeFunction = (value) => {
|
||||
setInputValue(value);
|
||||
};
|
||||
|
||||
const saveFunction = () => {
|
||||
setOscIpAddress(input_value);
|
||||
};
|
||||
|
||||
useEffect(()=> {
|
||||
if (currentOscIpAddress.state === "pending") return;
|
||||
setInputValue(currentOscIpAddress.data);
|
||||
}, [currentOscIpAddress]);
|
||||
|
||||
return (
|
||||
<EntryWithSaveButtonContainer
|
||||
label={t("config_page.advanced_settings.osc_ip_address.label")}
|
||||
variable={input_value}
|
||||
saveFunction={saveFunction}
|
||||
onChangeFunction={onChangeFunction}
|
||||
state={currentOscIpAddress.state}
|
||||
width="14rem"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const OscPortContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentOscPort, setOscPort } = useAdvancedSettings();
|
||||
const [input_value, setInputValue] = useState(currentOscPort.data);
|
||||
|
||||
const onChangeFunction = (value) => {
|
||||
value = value.replace(/[^0-9]/g, "");
|
||||
setInputValue(value);
|
||||
};
|
||||
|
||||
const saveFunction = () => {
|
||||
setOscPort(input_value);
|
||||
};
|
||||
|
||||
useEffect(()=> {
|
||||
if (currentOscPort.state === "pending") return;
|
||||
setInputValue(currentOscPort.data);
|
||||
}, [currentOscPort]);
|
||||
|
||||
return (
|
||||
<EntryWithSaveButtonContainer
|
||||
label={t("config_page.advanced_settings.osc_port.label")}
|
||||
variable={input_value}
|
||||
saveFunction={saveFunction}
|
||||
onChangeFunction={onChangeFunction}
|
||||
state={currentOscPort.state}
|
||||
width="10rem"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const OpenConfigFolderContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { openFolder_ConfigFile } = useOpenFolder();
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionButtonContainer
|
||||
label={t("config_page.advanced_settings.open_config_filepath.label")}
|
||||
IconComponent={OpenFolderSvg}
|
||||
onclickFunction={openFolder_ConfigFile}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Duplicate
|
||||
import { useStore_OpenedQuickSetting } from "@store";
|
||||
const OpenSwitchComputeDeviceModalContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { updateOpenedQuickSetting } = useStore_OpenedQuickSetting();
|
||||
const onClickFunction = () => {
|
||||
updateOpenedQuickSetting("update_software");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionButtonContainer
|
||||
label={t("config_page.advanced_settings.switch_compute_device.label")}
|
||||
IconComponent={HelpSvg}
|
||||
onclickFunction={onClickFunction}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const WebsocketContainer = () => {
|
||||
return (
|
||||
<div>
|
||||
<SectionLabelComponent label="WebSocket" />
|
||||
<EnableWebsocketContainer />
|
||||
<WebsocketHostContainer />
|
||||
<WebsocketPortContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const EnableWebsocketContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentEnableWebsocket, toggleEnableWebsocket } = useAdvancedSettings();
|
||||
|
||||
return (
|
||||
<CheckboxContainer
|
||||
label={t("config_page.advanced_settings.enable_websocket.label")}
|
||||
variable={currentEnableWebsocket}
|
||||
toggleFunction={toggleEnableWebsocket}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const WebsocketHostContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentWebsocketHost, setWebsocketHost } = useAdvancedSettings();
|
||||
const [input_value, setInputValue] = useState(currentWebsocketHost.data);
|
||||
|
||||
const onChangeFunction = (value) => {
|
||||
setInputValue(value);
|
||||
};
|
||||
|
||||
const saveFunction = () => {
|
||||
setWebsocketHost(input_value);
|
||||
};
|
||||
|
||||
useEffect(()=> {
|
||||
if (currentWebsocketHost.state === "pending") return;
|
||||
setInputValue(currentWebsocketHost.data);
|
||||
}, [currentWebsocketHost]);
|
||||
|
||||
return (
|
||||
<EntryWithSaveButtonContainer
|
||||
label={t("config_page.advanced_settings.websocket_host.label")}
|
||||
variable={input_value}
|
||||
saveFunction={saveFunction}
|
||||
onChangeFunction={onChangeFunction}
|
||||
state={currentWebsocketHost.state}
|
||||
width="14rem"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const WebsocketPortContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentWebsocketPort, setWebsocketPort } = useAdvancedSettings();
|
||||
const [input_value, setInputValue] = useState(currentWebsocketPort.data);
|
||||
|
||||
const onChangeFunction = (value) => {
|
||||
value = value.replace(/[^0-9]/g, "");
|
||||
setInputValue(value);
|
||||
};
|
||||
|
||||
const saveFunction = () => {
|
||||
setWebsocketPort(input_value);
|
||||
};
|
||||
|
||||
useEffect(()=> {
|
||||
if (currentWebsocketPort.state === "pending") return;
|
||||
setInputValue(currentWebsocketPort.data);
|
||||
}, [currentWebsocketPort]);
|
||||
|
||||
return (
|
||||
<EntryWithSaveButtonContainer
|
||||
label={t("config_page.advanced_settings.websocket_port.label")}
|
||||
variable={input_value}
|
||||
saveFunction={saveFunction}
|
||||
onChangeFunction={onChangeFunction}
|
||||
state={currentWebsocketPort.state}
|
||||
width="10rem"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 6.4rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useI18n } from "@useI18n";
|
||||
import styles from "./Appearance.module.scss";
|
||||
import { ui_configs } from "@ui_configs";
|
||||
import { useStore_SelectableFontFamilyList } from "@store";
|
||||
|
||||
import {
|
||||
useWindow,
|
||||
} from "@logics_common";
|
||||
|
||||
import {
|
||||
useAppearance,
|
||||
} from "@logics_configs";
|
||||
|
||||
import {
|
||||
SliderContainer,
|
||||
DropdownMenuContainer,
|
||||
RadioButtonContainer,
|
||||
CheckboxContainer,
|
||||
} from "../_templates/Templates";
|
||||
|
||||
export const Appearance = () => {
|
||||
return (
|
||||
<>
|
||||
<UiLanguageContainer />
|
||||
<UiScalingContainer />
|
||||
<MessageLogUiScalingContainer />
|
||||
<SendMessageButtonTypeContainer />
|
||||
<ShowResendButtonContainer />
|
||||
<FontFamilyContainer />
|
||||
<TransparencyContainer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const UiLanguageContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentUiLanguage, setUiLanguage } = useAppearance();
|
||||
|
||||
const is_not_en_lang = currentUiLanguage.data !== "en" && currentUiLanguage.data !== undefined;
|
||||
return (
|
||||
<RadioButtonContainer
|
||||
label={is_not_en_lang ? "UI Language" : t("config_page.appearance.ui_language.label")}
|
||||
desc={is_not_en_lang ? t("config_page.appearance.ui_language.label") : false}
|
||||
selectFunction={setUiLanguage}
|
||||
name="ui_language"
|
||||
options={ui_configs.selectable_ui_languages}
|
||||
checked_variable={currentUiLanguage}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const UiScalingContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentUiScaling, setUiScaling } = useAppearance();
|
||||
const { asyncUpdateBreakPoint } = useWindow();
|
||||
|
||||
const [ui_ui_scaling, setUiUiScaling] = useState(currentUiScaling.data);
|
||||
|
||||
const onchangeFunction = (value) => {
|
||||
setUiUiScaling(value);
|
||||
};
|
||||
const onchangeCommittedFunction = (value) => {
|
||||
setUiScaling(value);
|
||||
};
|
||||
useEffect(() => {
|
||||
setUiUiScaling(currentUiScaling.data);
|
||||
asyncUpdateBreakPoint();
|
||||
}, [currentUiScaling.data]);
|
||||
|
||||
// [Duplicated]
|
||||
const createMarks = (min, max) => {
|
||||
const marks = [];
|
||||
for (let value = min; value <= max; value += 10) {
|
||||
const label = ([50,70,90,110,130,150,170,190].includes(value)) ? "" : value;
|
||||
marks.push({ value, label: `${label}` });
|
||||
}
|
||||
return marks;
|
||||
};
|
||||
|
||||
const marks = createMarks(40, 200);
|
||||
|
||||
return (
|
||||
<SliderContainer
|
||||
label={t("config_page.appearance.ui_size.label") + " (%)"}
|
||||
min="40"
|
||||
max="200"
|
||||
onchangeCommittedFunction={onchangeCommittedFunction}
|
||||
onchangeFunction={onchangeFunction}
|
||||
variable={ui_ui_scaling}
|
||||
marks={marks}
|
||||
step={null}
|
||||
track={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export const MessageLogUiScalingContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentMessageLogUiScaling, setMessageLogUiScaling } = useAppearance();
|
||||
const [ui_message_log_ui_scaling, setUiMessageLogUiScaling] = useState(currentMessageLogUiScaling.data);
|
||||
|
||||
const onchangeFunction = (value) => {
|
||||
setUiMessageLogUiScaling(value);
|
||||
};
|
||||
const onchangeCommittedFunction = (value) => {
|
||||
setMessageLogUiScaling(value);
|
||||
};
|
||||
useEffect(() => {
|
||||
setUiMessageLogUiScaling(currentMessageLogUiScaling.data);
|
||||
}, [currentMessageLogUiScaling.data]);
|
||||
|
||||
// [Duplicated]
|
||||
const createMarks = (min, max) => {
|
||||
const marks = [];
|
||||
for (let value = min; value <= max; value += 10) {
|
||||
const label = ([50,70,90,110,130,150,170,190].includes(value)) ? "" : value;
|
||||
marks.push({ value, label: `${label}` });
|
||||
}
|
||||
return marks;
|
||||
};
|
||||
|
||||
const marks = createMarks(40, 200);
|
||||
|
||||
return (
|
||||
<SliderContainer
|
||||
label={t("config_page.appearance.textbox_ui_size.label") + " (%)"}
|
||||
min="40"
|
||||
max="200"
|
||||
onchangeCommittedFunction={onchangeCommittedFunction}
|
||||
onchangeFunction={onchangeFunction}
|
||||
variable={ui_message_log_ui_scaling}
|
||||
marks={marks}
|
||||
step={null}
|
||||
track={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const SendMessageButtonTypeContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentSendMessageButtonType, setSendMessageButtonType } = useAppearance();
|
||||
|
||||
return (
|
||||
<RadioButtonContainer
|
||||
label={t("config_page.appearance.send_message_button_type.label")}
|
||||
selectFunction={setSendMessageButtonType}
|
||||
name="send_message_button_type"
|
||||
options={[
|
||||
{ id: "hide", label: t("config_page.appearance.send_message_button_type.hide") },
|
||||
{ id: "show", label: t("config_page.appearance.send_message_button_type.show") },
|
||||
{ id: "show_and_disable_enter_key", label: t("config_page.appearance.send_message_button_type.show_and_disable_enter_key") },
|
||||
]}
|
||||
checked_variable={currentSendMessageButtonType}
|
||||
column={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ShowResendButtonContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentShowResendButton, toggleShowResendButton } = useAppearance();
|
||||
|
||||
return (
|
||||
<CheckboxContainer
|
||||
label={t("config_page.appearance.show_resend_button.label")}
|
||||
desc={t("config_page.appearance.show_resend_button.desc")}
|
||||
variable={currentShowResendButton}
|
||||
toggleFunction={toggleShowResendButton}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const FontFamilyContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentSelectedFontFamily, setSelectedFontFamily } = useAppearance();
|
||||
|
||||
const selectFunction = (selected_data) => {
|
||||
setSelectedFontFamily(selected_data.selected_id);
|
||||
};
|
||||
const { currentSelectableFontFamilyList } = useStore_SelectableFontFamilyList();
|
||||
|
||||
return (
|
||||
<DropdownMenuContainer
|
||||
dropdown_id="font_family"
|
||||
label={t("config_page.appearance.font_family.label")}
|
||||
selected_id={currentSelectedFontFamily.data}
|
||||
list={currentSelectableFontFamilyList.data}
|
||||
selectFunction={selectFunction}
|
||||
state={currentSelectedFontFamily.state}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TransparencyContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentTransparency, setTransparency } = useAppearance();
|
||||
const [ui_message_log_ui_scaling, setUiTransparency] = useState(currentTransparency.data);
|
||||
|
||||
const onchangeFunction = (value) => {
|
||||
setUiTransparency(value);
|
||||
};
|
||||
const onchangeCommittedFunction = (value) => {
|
||||
setTransparency(value);
|
||||
};
|
||||
useEffect(() => {
|
||||
setUiTransparency(currentTransparency.data);
|
||||
}, [currentTransparency.data]);
|
||||
|
||||
// [Duplicated]
|
||||
const createMarks = (min, max) => {
|
||||
const marks = [];
|
||||
for (let value = min; value <= max; value += 10) {
|
||||
marks.push({ value, label: `${value}` });
|
||||
}
|
||||
return marks;
|
||||
};
|
||||
|
||||
const marks = createMarks(40, 100);
|
||||
|
||||
return (
|
||||
<SliderContainer
|
||||
label={t("config_page.appearance.transparency.label") + " (%)"}
|
||||
min="40"
|
||||
max="100"
|
||||
onchangeCommittedFunction={onchangeCommittedFunction}
|
||||
onchangeFunction={onchangeFunction}
|
||||
variable={ui_message_log_ui_scaling}
|
||||
marks={marks}
|
||||
step={null}
|
||||
track={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
@import "@scss_mixins";
|
||||
@@ -0,0 +1,225 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
import styles from "./Device.module.scss";
|
||||
import clsx from "clsx";
|
||||
import { useStore_IsBreakPoint } from "@store";
|
||||
import { ui_configs } from "@ui_configs";
|
||||
import {
|
||||
useDevice,
|
||||
} from "@logics_configs";
|
||||
|
||||
import {
|
||||
useOnMouseLeaveDropdownMenu,
|
||||
} from "../_templates/Templates";
|
||||
|
||||
import {
|
||||
LabelComponent,
|
||||
DropdownMenu,
|
||||
ThresholdComponent,
|
||||
SwitchBox,
|
||||
} from "../_components";
|
||||
|
||||
export const Device = () => {
|
||||
return (
|
||||
<>
|
||||
<Mic_Container />
|
||||
<Speaker_Container />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Mic_Container = () => {
|
||||
const { t } = useI18n();
|
||||
const {
|
||||
currentEnableAutoMicSelect,
|
||||
toggleEnableAutoMicSelect,
|
||||
currentMicDeviceList,
|
||||
currentMicHostList,
|
||||
|
||||
currentSelectedMicHost,
|
||||
setSelectedMicHost,
|
||||
currentSelectedMicDevice,
|
||||
setSelectedMicDevice,
|
||||
|
||||
currentEnableAutomaticMicThreshold,
|
||||
toggleEnableAutomaticMicThreshold,
|
||||
} = useDevice();
|
||||
const { onMouseLeaveFunction } = useOnMouseLeaveDropdownMenu();
|
||||
|
||||
const selectFunction_host = (selected_data) => {
|
||||
setSelectedMicHost(selected_data.selected_id);
|
||||
};
|
||||
|
||||
const selectFunction_device = (selected_data) => {
|
||||
setSelectedMicDevice(selected_data.selected_id);
|
||||
};
|
||||
|
||||
// [Fix me] currentEnableAutoMicSelect.data === "pending"; ? not currentEnableAutoMicSelect.state === "pending"; ??(.state)
|
||||
const is_disabled_selector = currentEnableAutoMicSelect.data === true || currentEnableAutoMicSelect.data === "pending";
|
||||
|
||||
const getLabels = () => {
|
||||
if (currentEnableAutomaticMicThreshold.data === true) {
|
||||
return {
|
||||
label: t("config_page.device.mic_dynamic_energy_threshold.label_for_automatic"),
|
||||
desc: t("config_page.device.mic_dynamic_energy_threshold.desc_for_automatic"),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
label: t("config_page.device.mic_dynamic_energy_threshold.label_for_manual"),
|
||||
desc: t("config_page.device.mic_dynamic_energy_threshold.desc_for_manual"),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const { currentIsBreakPoint } = useStore_IsBreakPoint();
|
||||
const device_container_class = clsx(styles.device_container, {
|
||||
[styles.is_break_point]: currentIsBreakPoint.data,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.mic_container}>
|
||||
<div className={device_container_class} onMouseLeave={onMouseLeaveFunction}>
|
||||
<LabelComponent label={t("config_page.device.mic_host_device.label")} />
|
||||
<div className={styles.device_contents}>
|
||||
|
||||
<div className={styles.device_auto_select_wrapper}>
|
||||
<p className={styles.device_secondary_label}>{t("config_page.device.label_auto_select")}</p>
|
||||
<SwitchBox
|
||||
variable={currentEnableAutoMicSelect}
|
||||
toggleFunction={toggleEnableAutoMicSelect}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.device_dropdown_wrapper}>
|
||||
<div className={styles.device_dropdown}>
|
||||
<p className={styles.device_secondary_label}>{t("config_page.device.label_host")}</p>
|
||||
<DropdownMenu
|
||||
dropdown_id="mic_host"
|
||||
selected_id={currentSelectedMicHost.data}
|
||||
list={currentMicHostList.data}
|
||||
selectFunction={selectFunction_host}
|
||||
state={currentSelectedMicHost.state}
|
||||
style={{ maxWidth: "20rem", minWidth: "10rem" }}
|
||||
is_disabled={is_disabled_selector}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.device_dropdown}>
|
||||
<p className={styles.device_secondary_label}>{t("config_page.device.label_device")}</p>
|
||||
<DropdownMenu
|
||||
dropdown_id="mic_device"
|
||||
selected_id={currentSelectedMicDevice.data}
|
||||
list={currentMicDeviceList.data}
|
||||
selectFunction={selectFunction_device}
|
||||
state={currentSelectedMicDevice.state}
|
||||
is_disabled={is_disabled_selector}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.threshold_container}>
|
||||
<div className={styles.threshold_switch_section}>
|
||||
<LabelComponent {...getLabels()} />
|
||||
<SwitchBox
|
||||
variable={currentEnableAutomaticMicThreshold}
|
||||
toggleFunction={toggleEnableAutomaticMicThreshold}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.threshold_section}>
|
||||
<ThresholdComponent
|
||||
id="mic_threshold"
|
||||
min={ui_configs.mic_threshold_min}
|
||||
max={ui_configs.mic_threshold_max}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Speaker_Container = () => {
|
||||
const { t } = useI18n();
|
||||
const {
|
||||
currentEnableAutoSpeakerSelect,
|
||||
toggleEnableAutoSpeakerSelect,
|
||||
currentSpeakerDeviceList,
|
||||
currentSelectedSpeakerDevice,
|
||||
setSelectedSpeakerDevice,
|
||||
currentEnableAutomaticSpeakerThreshold,
|
||||
toggleEnableAutomaticSpeakerThreshold,
|
||||
} = useDevice();
|
||||
const { onMouseLeaveFunction } = useOnMouseLeaveDropdownMenu();
|
||||
|
||||
const selectFunction = (selected_data) => {
|
||||
setSelectedSpeakerDevice(selected_data.selected_id);
|
||||
};
|
||||
|
||||
const is_disabled_selector = currentEnableAutoSpeakerSelect.data === true || currentEnableAutoSpeakerSelect.data === "pending";
|
||||
|
||||
const getLabels = () => {
|
||||
if (currentEnableAutomaticSpeakerThreshold.data === true) {
|
||||
return {
|
||||
label: t("config_page.device.speaker_dynamic_energy_threshold.label_for_automatic"),
|
||||
desc: t("config_page.device.speaker_dynamic_energy_threshold.desc_for_automatic"),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
label: t("config_page.device.speaker_dynamic_energy_threshold.label_for_manual"),
|
||||
desc: t("config_page.device.speaker_dynamic_energy_threshold.desc_for_manual"),
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const { currentIsBreakPoint } = useStore_IsBreakPoint();
|
||||
const device_container_class = clsx(styles.device_container, {
|
||||
[styles.is_break_point]: currentIsBreakPoint.data,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.speaker_container}>
|
||||
<div className={device_container_class} onMouseLeave={onMouseLeaveFunction}>
|
||||
<LabelComponent label={t("config_page.device.speaker_device.label")} />
|
||||
<div className={styles.device_contents}>
|
||||
|
||||
<div className={styles.device_auto_select_wrapper}>
|
||||
<p className={styles.device_secondary_label}>{t("config_page.device.label_auto_select")}</p>
|
||||
<SwitchBox
|
||||
variable={currentEnableAutoSpeakerSelect}
|
||||
toggleFunction={toggleEnableAutoSpeakerSelect}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.device_dropdown}>
|
||||
<p className={styles.device_secondary_label}>{t("config_page.device.label_device")}</p>
|
||||
<DropdownMenu
|
||||
dropdown_id="speaker_device"
|
||||
label={t("config_page.device.speaker_device.label")}
|
||||
selected_id={currentSelectedSpeakerDevice.data}
|
||||
list={currentSpeakerDeviceList.data}
|
||||
selectFunction={selectFunction}
|
||||
state={currentSelectedSpeakerDevice.state}
|
||||
is_disabled={is_disabled_selector}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.threshold_container}>
|
||||
<div className={styles.threshold_switch_section}>
|
||||
<LabelComponent {...getLabels()}/>
|
||||
<SwitchBox
|
||||
variable={currentEnableAutomaticSpeakerThreshold}
|
||||
toggleFunction={toggleEnableAutomaticSpeakerThreshold}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.threshold_section}>
|
||||
<ThresholdComponent
|
||||
id="speaker_threshold"
|
||||
min={ui_configs.speaker_threshold_min}
|
||||
max={ui_configs.speaker_threshold_max}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
.mic_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: solid 0.1rem var(--dark_800_color);
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.speaker_container {
|
||||
padding-top: 0rem;
|
||||
}
|
||||
|
||||
.device_container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
margin-bottom: 0rem;
|
||||
&.is_break_point {
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
align-items: start;
|
||||
& .device_contents {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
padding-left: 0rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.threshold_container {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.threshold_container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.threshold_switch_section {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.threshold_section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.device_label {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.device_contents {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: end;
|
||||
padding-left: 2rem;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.device_auto_select_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.2rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.device_dropdown_wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2.8rem;
|
||||
}
|
||||
|
||||
.device_dropdown {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
white-space: nowrap;
|
||||
max-width: 24rem;
|
||||
&.is_disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.device_secondary_label {
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.4rem;
|
||||
font-size: 1.4rem;
|
||||
color: var(--dark_500_color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useHotkeys } from "@logics_configs";
|
||||
import styles from "./Hotkeys.module.scss";
|
||||
import { HotkeysEntryContainer } from "../_templates/Templates";
|
||||
import { useI18n } from "@useI18n";
|
||||
export const Hotkeys = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<HotkeysBoxContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const HotkeysBoxContainer = () => {
|
||||
const { t } = useI18n();
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export { Device } from "./device/Device";
|
||||
export { Appearance, MessageLogUiScalingContainer } from "./appearance/Appearance";
|
||||
export { Translation } from "./translation/Translation";
|
||||
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 { Plugins } from "./plugins/Plugins";
|
||||
export { AboutVrct } from "./about_vrct/AboutVrct";
|
||||
export { Supporters } from "./supporters/Supporters";
|
||||
@@ -0,0 +1,240 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
import styles from "./Others.module.scss";
|
||||
|
||||
import { useOpenFolder } from "@logics_common";
|
||||
import {
|
||||
useOthers,
|
||||
} from "@logics_configs";
|
||||
|
||||
import {
|
||||
CheckboxContainer,
|
||||
MessageFormatContainer,
|
||||
} from "../_templates/Templates";
|
||||
|
||||
import {
|
||||
LabelComponent,
|
||||
ActionButton,
|
||||
SectionLabelComponent,
|
||||
} from "../_components";
|
||||
import { Checkbox } from "@common_components";
|
||||
|
||||
import OpenFolderSvg from "@images/open_folder.svg?react";
|
||||
|
||||
export const Others = () => {
|
||||
const { t } = useI18n();
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div>
|
||||
<AutoClearMessageInputBoxContainer />
|
||||
<SendOnlyTranslatedMessagesContainer />
|
||||
<AutoExportMessageLogsContainer />
|
||||
<VrcMicMuteSyncContainer />
|
||||
<SendMessageToVrcContainer />
|
||||
</div>
|
||||
<div>
|
||||
<SectionLabelComponent label={t("config_page.others.section_label_sounds")} />
|
||||
<EnableNotificationVrcSfxContainer />
|
||||
</div>
|
||||
<div>
|
||||
<SectionLabelComponent label="Speaker2Chatbox" />
|
||||
<SendReceivedMessageToVrcContainer />
|
||||
</div>
|
||||
<div>
|
||||
<SectionLabelComponent label={t("config_page.others.section_label_message_formats")} />
|
||||
<SendMessageFormatPartsContainer />
|
||||
<ReceivedMessageFormatPartsContainer />
|
||||
</div>
|
||||
<div>
|
||||
<ConvertMessageToRomajiContainer />
|
||||
<ConvertMessageToHiraganaContainer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AutoClearMessageInputBoxContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentEnableAutoClearMessageInputBox, toggleEnableAutoClearMessageInputBox } = useOthers();
|
||||
|
||||
return (
|
||||
<CheckboxContainer
|
||||
label={t("config_page.others.auto_clear_the_message_box.label")}
|
||||
variable={currentEnableAutoClearMessageInputBox}
|
||||
toggleFunction={toggleEnableAutoClearMessageInputBox}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const SendOnlyTranslatedMessagesContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentEnableSendOnlyTranslatedMessages, toggleEnableSendOnlyTranslatedMessages } = useOthers();
|
||||
|
||||
return (
|
||||
<CheckboxContainer
|
||||
label={t("config_page.others.send_only_translated_messages.label")}
|
||||
variable={currentEnableSendOnlyTranslatedMessages}
|
||||
toggleFunction={toggleEnableSendOnlyTranslatedMessages}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const AutoExportMessageLogsContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentEnableAutoExportMessageLogs, toggleEnableAutoExportMessageLogs } = useOthers();
|
||||
const { openFolder_MessageLogs } = useOpenFolder();
|
||||
|
||||
return (
|
||||
<div className={styles.auto_export_message_logs_container}>
|
||||
<LabelComponent
|
||||
label={t("config_page.others.auto_export_message_logs.label")}
|
||||
desc={t("config_page.others.auto_export_message_logs.desc")}
|
||||
/>
|
||||
<div className={styles.auto_export_message_logs_switch_section_container}>
|
||||
<ActionButton
|
||||
IconComponent={OpenFolderSvg}
|
||||
onclickFunction={openFolder_MessageLogs}
|
||||
/>
|
||||
<Checkbox
|
||||
variable={currentEnableAutoExportMessageLogs}
|
||||
toggleFunction={toggleEnableAutoExportMessageLogs}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export const VrcMicMuteSyncContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentEnableVrcMicMuteSync, toggleEnableVrcMicMuteSync } = useOthers();
|
||||
|
||||
const variable = {
|
||||
state: currentEnableVrcMicMuteSync.state,
|
||||
data: currentEnableVrcMicMuteSync.data.is_enabled,
|
||||
};
|
||||
|
||||
return (
|
||||
<CheckboxContainer
|
||||
label={t("config_page.others.vrc_mic_mute_sync.label")}
|
||||
desc={t("config_page.others.vrc_mic_mute_sync.desc")}
|
||||
variable={variable}
|
||||
is_available={currentEnableVrcMicMuteSync.data.is_available}
|
||||
toggleFunction={toggleEnableVrcMicMuteSync}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const SendMessageToVrcContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentEnableSendMessageToVrc, toggleEnableSendMessageToVrc } = useOthers();
|
||||
|
||||
return (
|
||||
<CheckboxContainer
|
||||
label={t("config_page.others.send_message_to_vrc.label")}
|
||||
desc={t("config_page.others.send_message_to_vrc.desc")}
|
||||
variable={currentEnableSendMessageToVrc}
|
||||
toggleFunction={toggleEnableSendMessageToVrc}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const EnableNotificationVrcSfxContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentEnableNotificationVrcSfx, toggleEnableNotificationVrcSfx } = useOthers();
|
||||
|
||||
return (
|
||||
<CheckboxContainer
|
||||
label={t("config_page.others.notification_vrc_sfx.label")}
|
||||
desc={t("config_page.others.notification_vrc_sfx.desc")}
|
||||
variable={currentEnableNotificationVrcSfx}
|
||||
toggleFunction={toggleEnableNotificationVrcSfx}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const SendReceivedMessageToVrcContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentEnableSendReceivedMessageToVrc, toggleEnableSendReceivedMessageToVrc } = useOthers();
|
||||
|
||||
return (
|
||||
<CheckboxContainer
|
||||
label={t("config_page.others.send_received_message_to_vrc.label")}
|
||||
desc={t("config_page.others.send_received_message_to_vrc.desc")}
|
||||
variable={currentEnableSendReceivedMessageToVrc}
|
||||
toggleFunction={toggleEnableSendReceivedMessageToVrc}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const SendMessageFormatPartsContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const {
|
||||
currentSendMessageFormatParts,
|
||||
setSendMessageFormatParts,
|
||||
} = 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}
|
||||
format_id="send"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ReceivedMessageFormatPartsContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const {
|
||||
currentReceivedMessageFormatParts,
|
||||
setReceivedMessageFormatParts,
|
||||
} = 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}
|
||||
format_id="received"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ConvertMessageToRomajiContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentConvertMessageToRomaji, toggleConvertMessageToRomaji } = useOthers();
|
||||
|
||||
const desc_1 = t("config_page.others.common_convert_message_hiragana_romaji.desc_1");
|
||||
const desc_2 = t("config_page.others.common_convert_message_hiragana_romaji.desc_2");
|
||||
const desc_romaji = t(
|
||||
"config_page.others.convert_message_to_romaji.desc",
|
||||
{ convert_message_to_hiragana: t("config_page.others.convert_message_to_hiragana.label") }
|
||||
);
|
||||
const desc = [desc_1, desc_2, desc_romaji].join("\n");
|
||||
|
||||
return (
|
||||
<CheckboxContainer
|
||||
label={t("config_page.others.convert_message_to_romaji.label")}
|
||||
desc={desc}
|
||||
variable={currentConvertMessageToRomaji}
|
||||
toggleFunction={toggleConvertMessageToRomaji}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ConvertMessageToHiraganaContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentConvertMessageToHiragana, toggleConvertMessageToHiragana } = useOthers();
|
||||
|
||||
const desc_1 = t("config_page.others.common_convert_message_hiragana_romaji.desc_1");
|
||||
const desc_2 = t("config_page.others.common_convert_message_hiragana_romaji.desc_2");
|
||||
const desc = [desc_1, desc_2].join("\n");
|
||||
|
||||
return (
|
||||
<CheckboxContainer
|
||||
label={t("config_page.others.convert_message_to_hiragana.label")}
|
||||
desc={desc}
|
||||
variable={currentConvertMessageToHiragana}
|
||||
toggleFunction={toggleConvertMessageToHiragana}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 6.4rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.auto_export_message_logs_container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
&.flex_column {
|
||||
flex-direction: column;
|
||||
}
|
||||
border-bottom: solid 0.1rem var(--dark_800_color);
|
||||
}
|
||||
|
||||
.auto_export_message_logs_switch_section_container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useI18n } from "@useI18n";
|
||||
import { usePlugins } from "@logics_configs";
|
||||
import styles from "./Plugins.module.scss";
|
||||
import { PluginsControlComponent } from "./plugins_control_component/PluginsControlComponent";
|
||||
import { useNotificationStatus } from "@logics_common";
|
||||
import { HomepageLinkButton } from "@common_components";
|
||||
|
||||
export const Plugins = () => {
|
||||
const {
|
||||
asyncFetchPluginsInfo,
|
||||
} = usePlugins();
|
||||
const hasRunRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasRunRef.current) {
|
||||
asyncFetchPluginsInfo();
|
||||
}
|
||||
return () => hasRunRef.current = true;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<PluginDownloadContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PluginDownloadContainer = () => {
|
||||
const { t, i18n } = useI18n();
|
||||
const {
|
||||
downloadAndExtractPlugin,
|
||||
currentPluginsData,
|
||||
currentSavedPluginsStatus,
|
||||
toggleSavedPluginsStatus,
|
||||
handlePendingPlugin,
|
||||
currentFetchedPluginsInfo,
|
||||
} = usePlugins();
|
||||
const { showNotification_Success, showNotification_Error } = useNotificationStatus();
|
||||
|
||||
// ダウンロード開始時の状態更新処理
|
||||
const downloadStartFunction = async (target_plugin_id) => {
|
||||
handlePendingPlugin(target_plugin_id, true);
|
||||
showNotification_Success(t("plugin_notifications.downloading"));
|
||||
|
||||
const target_plugin_info = currentPluginsData.data.find(
|
||||
(d) => d.plugin_id === target_plugin_id
|
||||
);
|
||||
downloadAndExtractPlugin(target_plugin_info).then(() => {
|
||||
handlePendingPlugin(target_plugin_id, false);
|
||||
showNotification_Success(t("plugin_notifications.downloaded_success"));
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
showNotification_Error(t("plugin_notifications.downloaded_error"));
|
||||
});
|
||||
};
|
||||
|
||||
// プラグインのオンオフ切り替え処理
|
||||
const toggleFunction = (target_plugin_id) => {
|
||||
toggleSavedPluginsStatus(target_plugin_id);
|
||||
};
|
||||
|
||||
const variable_state = currentSavedPluginsStatus.state;
|
||||
|
||||
const filtered_plugins_data = currentPluginsData.data.filter(plugin => !plugin.is_outdated)
|
||||
|
||||
// plugin_id で ABC 順にソート
|
||||
const sorted_plugins_data = filtered_plugins_data.sort((a, b) =>
|
||||
a.plugin_id.localeCompare(b.plugin_id)
|
||||
);
|
||||
|
||||
// Duplicate
|
||||
const is_failed_to_fetch = currentFetchedPluginsInfo.state === "error";
|
||||
const is_fetching = currentFetchedPluginsInfo.state === "pending";
|
||||
|
||||
return (
|
||||
<div className={styles.plugins_list_container}>
|
||||
{is_failed_to_fetch && <p>Failed to fetch plugins data</p>}
|
||||
{is_fetching && <p>Fetching plugins data...</p>}
|
||||
{sorted_plugins_data.map((plugin) => {
|
||||
const target_info = plugin.is_downloaded
|
||||
? plugin.downloaded_plugin_info
|
||||
: plugin.latest_plugin_info;
|
||||
|
||||
const target_locale = target_info.locales && target_info.locales[i18n.language]
|
||||
? target_info.locales[i18n.language]
|
||||
: {
|
||||
title: target_info.title,
|
||||
desc: target_info.desc || null,
|
||||
};
|
||||
const homepage_link = plugin.latest_plugin_info?.homepage_link;
|
||||
|
||||
return (
|
||||
<div key={plugin.plugin_id} className={styles.plugin_wrapper}>
|
||||
<div className={styles.labels_wrapper}>
|
||||
<p className={styles.title}>
|
||||
{target_locale.title}
|
||||
</p>
|
||||
<p className={styles.desc}>
|
||||
{target_locale.desc}
|
||||
</p>
|
||||
{/* <p className={styles.plugin_id}>{plugin.plugin_id}</p> */}
|
||||
{homepage_link && <HomepageLinkButton homepage_link={homepage_link}/>
|
||||
}
|
||||
</div>
|
||||
<div className={styles.plugin_info_wrapper}>
|
||||
{plugin.is_error ? (
|
||||
<div>
|
||||
<p style={{ color: "red" }}>{t(`plugin_notifications.${plugin.error_message_type}`)}</p>
|
||||
<p style={{ color: "red" }}>plugin_version: {plugin.downloaded_plugin_info.plugin_version}</p>
|
||||
<p style={{ color: "red" }}>min_supported_vrct_version: {plugin.downloaded_plugin_info.min_supported_vrct_version}</p>
|
||||
<p style={{ color: "red" }}>max_supported_vrct_version: {plugin.downloaded_plugin_info.max_supported_vrct_version}</p>
|
||||
</div>
|
||||
) : (
|
||||
<PluginsControlComponent
|
||||
variable_state={variable_state}
|
||||
toggleFunction={toggleFunction}
|
||||
downloadStartFunction={downloadStartFunction}
|
||||
plugin_status={plugin}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 6.4rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.plugins_list_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.plugin_wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
gap: 2rem;
|
||||
&:not(:last-child) {
|
||||
border-bottom: 0.1rem solid var(--dark_750_color);
|
||||
}
|
||||
}
|
||||
|
||||
.labels_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
max-width: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.plugin_info_wrapper {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.6rem;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.desc {
|
||||
font-size: 1.4rem;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
// .plugin_id {
|
||||
// font-size: 1rem;
|
||||
// color: var(--dark_600_color);
|
||||
// width: 100%;
|
||||
// overflow: hidden;
|
||||
// white-space: nowrap;
|
||||
// text-overflow: ellipsis;
|
||||
// }
|
||||
@@ -0,0 +1,155 @@
|
||||
import { SwitchBox } from "../../_components";
|
||||
import { _DownloadButton } from "../../_components/_atoms/_download_button/_DownloadButton";
|
||||
import styles from "./PluginsControlComponent.module.scss";
|
||||
import { useI18n } from "@useI18n";
|
||||
|
||||
export const PluginsControlComponent = ({
|
||||
variable_state,
|
||||
plugin_status,
|
||||
toggleFunction,
|
||||
downloadStartFunction,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
|
||||
const option = {
|
||||
id: plugin_status.plugin_id,
|
||||
is_pending: plugin_status.is_pending,
|
||||
is_downloaded: plugin_status.is_downloaded,
|
||||
data: plugin_status.is_enabled,
|
||||
update_button: plugin_status.is_downloaded && plugin_status.is_latest_version_available,
|
||||
state: variable_state,
|
||||
progress: null,
|
||||
};
|
||||
|
||||
const downloaded_version = plugin_status.downloaded_plugin_info?.plugin_version;
|
||||
const latest_version = plugin_status.latest_plugin_info?.plugin_version;
|
||||
|
||||
const downloaded_version_label = t("config_page.plugins.downloaded_version",
|
||||
{ downloaded_version: downloaded_version }
|
||||
);
|
||||
const latest_version_label = t("config_page.plugins.latest_version",
|
||||
{ latest_version: latest_version }
|
||||
);
|
||||
|
||||
if (plugin_status.is_downloaded) {
|
||||
return (
|
||||
<DownloadedPluginControl
|
||||
option={option}
|
||||
plugin_status={plugin_status}
|
||||
toggleFunction={toggleFunction}
|
||||
downloadStartFunction={downloadStartFunction}
|
||||
downloaded_version_label={downloaded_version_label}
|
||||
latest_version_label={latest_version_label}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<NotDownloadedPluginControl
|
||||
option={option}
|
||||
plugin_status={plugin_status}
|
||||
downloadStartFunction={downloadStartFunction}
|
||||
downloaded_version_label={downloaded_version_label}
|
||||
latest_version_label={latest_version_label}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const DownloadedPluginControl = ({
|
||||
option,
|
||||
plugin_status,
|
||||
toggleFunction,
|
||||
downloadStartFunction,
|
||||
downloaded_version_label,
|
||||
latest_version_label,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
|
||||
const togglePlugin = () => {
|
||||
toggleFunction(plugin_status.plugin_id);
|
||||
};
|
||||
|
||||
if (!plugin_status.downloaded_plugin_info.is_plugin_supported) {
|
||||
if (plugin_status.is_latest_version_available) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{downloaded_version_label}</p>
|
||||
<p>{latest_version_label}</p>
|
||||
<p>{t("config_page.plugins.available_after_updating")}</p>
|
||||
<_DownloadButton option={option} downloadStartFunction={downloadStartFunction} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{t("config_page.plugins.unavailable_downloaded")}</p>
|
||||
</div>
|
||||
);
|
||||
} else if (plugin_status.is_outdated) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{t("config_page.plugins.no_latest_info")}</p>
|
||||
<SwitchBox variable={option} toggleFunction={togglePlugin} />
|
||||
</div>
|
||||
);
|
||||
} else if (plugin_status.is_latest_version_already) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{latest_version_label}</p>
|
||||
<p>{t("config_page.plugins.using_latest_version")}</p>
|
||||
<SwitchBox variable={option} toggleFunction={togglePlugin} />
|
||||
</div>
|
||||
);
|
||||
} else if (plugin_status.is_latest_version_available) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{latest_version_label}</p>
|
||||
<p>{t("config_page.plugins.available_latest_version")}</p>
|
||||
<_DownloadButton option={option} downloadStartFunction={downloadStartFunction} />
|
||||
<SwitchBox variable={option} toggleFunction={togglePlugin} />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{t("config_page.plugins.available_latest_version")}</p>
|
||||
<SwitchBox variable={option} toggleFunction={togglePlugin} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const NotDownloadedPluginControl = ({
|
||||
option,
|
||||
plugin_status,
|
||||
downloadStartFunction,
|
||||
downloaded_version_label,
|
||||
latest_version_label,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
|
||||
if (plugin_status.is_latest_version_available) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{latest_version_label}</p>
|
||||
<_DownloadButton option={option} downloadStartFunction={downloadStartFunction} />
|
||||
</div>
|
||||
);
|
||||
} else if (plugin_status.latest_plugin_info?.is_plugin_supported_latest_vrct) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{latest_version_label}</p>
|
||||
<p>{t("config_page.plugins.available_in_latest_vrct_version")}</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{latest_version_label}</p>
|
||||
<p>{t("config_page.plugins.unavailable_not_downloaded")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.unavailable_text {
|
||||
padding: 1rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import styles from "./Supporters.module.scss";
|
||||
import { SupportUsContainer } from "./support_us_container/SupportUsContainer";
|
||||
import { SupportersContainer } from "./supporters_container/SupportersContainer";
|
||||
import { useSupporters } from "@logics_configs";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export const Supporters = () => {
|
||||
const { asyncFetchSupportersData } = useSupporters();
|
||||
|
||||
useEffect(() => {
|
||||
asyncFetchSupportersData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<SupportUsContainer />
|
||||
<div className={styles.supportersWrapper}>
|
||||
<SupportersContainer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 3.2rem;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
.supportersWrapper {
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.8s ease-in-out 1.6s forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import top_img from "@images/supporters/patreon_1600x400px.png";
|
||||
import fanbox_logo from "@images/supporters/fanbox_logo.png";
|
||||
import kofi_logo from "@images/supporters/kofi_logo.png";
|
||||
import patreon_logo from "@images/supporters/patreon_logo.png";
|
||||
import styles from "./SupportUsContainer.module.scss";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const SupportUsContainer = () => {
|
||||
return (
|
||||
<div id="support_us_container" className={styles.support_us_container}>
|
||||
<img className={styles.top_img} src={top_img} />
|
||||
<div className={styles.support_buttons_wrapper}>
|
||||
<div className={styles.support_us_button_wrapper}>
|
||||
<a className={styles.support_button} href="https://vrct-dev.fanbox.cc" target="_blank" rel="noreferrer">
|
||||
<img
|
||||
src={fanbox_logo}
|
||||
className={clsx(styles.support_img, styles.fanbox_logo)}
|
||||
/>
|
||||
<div className={styles.spiral_top}></div>
|
||||
<div className={styles.spiral_bottom}></div>
|
||||
</a>
|
||||
<a className={styles.support_button} href="https://ko-fi.com/vrct_dev" target="_blank" rel="noreferrer">
|
||||
<img
|
||||
src={kofi_logo}
|
||||
className={clsx(styles.support_img, styles.kofi_logo)}
|
||||
/>
|
||||
<div className={styles.spiral_top}></div>
|
||||
<div className={styles.spiral_bottom}></div>
|
||||
</a>
|
||||
<a className={styles.support_button} href="https://www.patreon.com/vrct_dev" target="_blank" rel="noreferrer">
|
||||
<img
|
||||
src={patreon_logo}
|
||||
className={clsx(styles.support_img, styles.patreon_logo)}
|
||||
/>
|
||||
<div className={styles.spiral_top}></div>
|
||||
<div className={styles.spiral_bottom}></div>
|
||||
</a>
|
||||
</div>
|
||||
<div className={styles.lines_container}>
|
||||
<div className={styles.line_basic}></div>
|
||||
<div className={styles.line_fuwa}></div>
|
||||
<div className={styles.line_mochi}></div>
|
||||
<div className={styles.line_mogu}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,220 @@
|
||||
$progress_ease: cubic-bezier(0, 1, 0.75, 1);
|
||||
// Duplicated
|
||||
|
||||
@keyframes revealTopImg {
|
||||
0% {
|
||||
clip-path: inset(0 50% 0 50%);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
clip-path: inset(0 0 0 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounceIn {
|
||||
0% {
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
60% {
|
||||
transform: translateY(-10%);
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
transform: translateY(10%);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes expandWidth {
|
||||
0% {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
100% {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
.support_us_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1.4rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.support_buttons_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1.2rem;
|
||||
}
|
||||
|
||||
.top_img {
|
||||
width: 100%;
|
||||
animation: revealTopImg 0.8s ease forwards;
|
||||
}
|
||||
|
||||
.lines_container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 3.6rem;
|
||||
}
|
||||
|
||||
.line_basic,
|
||||
.line_fuwa,
|
||||
.line_mochi,
|
||||
.line_mogu {
|
||||
width: 8.6rem;
|
||||
height: 0.2rem;
|
||||
transform: scaleX(0);
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
.line_basic {
|
||||
background-color: var(--dark_800_color);
|
||||
animation: expandWidth 1s $progress_ease 0.4s forwards;
|
||||
}
|
||||
|
||||
.line_fuwa {
|
||||
background-color: var(--supporters_color_fuwa);
|
||||
animation: expandWidth 1s $progress_ease 0.6s forwards;
|
||||
}
|
||||
|
||||
.line_mochi {
|
||||
background-color: var(--received_300_color);
|
||||
animation: expandWidth 1s $progress_ease 0.8s forwards;
|
||||
}
|
||||
|
||||
.line_mogu {
|
||||
background-color: var(--dark_basic_text_color);
|
||||
animation: expandWidth 1s $progress_ease 1s forwards;
|
||||
}
|
||||
|
||||
.support_us_button_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
column-gap: 3.6rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.support_button {
|
||||
position: relative;
|
||||
padding: 1.2rem 1.6rem;
|
||||
opacity: 0;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
.support_button:nth-child(1) {
|
||||
animation: bounceIn 0.4s ease-out 1.2s forwards;
|
||||
}
|
||||
.support_button:nth-child(2) {
|
||||
animation: bounceIn 0.4s ease-out 1.4s forwards;
|
||||
}
|
||||
.support_button:nth-child(3) {
|
||||
animation: bounceIn 0.4s ease-out 1.6s forwards;
|
||||
}
|
||||
|
||||
|
||||
.support_img {
|
||||
&.fanbox_logo {
|
||||
height: 1.8rem;
|
||||
}
|
||||
&.kofi_logo, &.patreon_logo {
|
||||
height: 2.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.spiral_top::before,
|
||||
.spiral_top::after,
|
||||
.spiral_bottom::before,
|
||||
.spiral_bottom::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.spiral_top::before {
|
||||
background: var(--dark_800_color);
|
||||
box-shadow: 0 0 0.4rem 0 var(--dark_800_color);
|
||||
}
|
||||
.spiral_top::after {
|
||||
background: var(--supporters_color_fuwa);
|
||||
box-shadow: 0 0 0.4rem 0 var(--supporters_color_fuwa);
|
||||
}
|
||||
.spiral_bottom::before {
|
||||
background: var(--received_300_color);
|
||||
box-shadow: 0 0 0.4rem 0 var(--received_300_color);
|
||||
}
|
||||
.spiral_bottom::after {
|
||||
background: var(--dark_basic_text_color);
|
||||
box-shadow: 0 0 0.4rem 0 var(--dark_basic_text_color);
|
||||
}
|
||||
|
||||
.spiral_top::before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0.1rem;
|
||||
height: 0;
|
||||
}
|
||||
.spiral_top::after {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 0;
|
||||
height: 0.1rem;
|
||||
}
|
||||
.spiral_bottom::before {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 0.1rem;
|
||||
}
|
||||
.spiral_bottom::after {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 0.1rem;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
|
||||
.support_button:hover .spiral_top::before {
|
||||
height: 100%;
|
||||
}
|
||||
.support_button:hover .spiral_top::after {
|
||||
width: 100%;
|
||||
}
|
||||
.support_button:hover .spiral_bottom::before {
|
||||
width: 100%;
|
||||
}
|
||||
.support_button:hover .spiral_bottom::after {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.supporters_container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.vrct_supporters_title {
|
||||
height: 6rem;
|
||||
}
|
||||
|
||||
.vrct_supporters_desc {
|
||||
font-size: 1.4rem;
|
||||
text-align: start;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import styles from "./SupportersContainer.module.scss";
|
||||
import { SupportersWrapper } from "./supporters_wrapper/SupportersWrapper";
|
||||
import { useSupporters } from "@logics_configs";
|
||||
import { supporters_images_url } from "@ui_configs";
|
||||
import vrct_supporters_title from "@images/supporters/vrct_supporters_title.png";
|
||||
|
||||
export const SupportersContainer = () => {
|
||||
const { currentSupportersData } = useSupporters();
|
||||
|
||||
if (currentSupportersData.state === "error")
|
||||
return <div>Failed to retrieve data.</div>;
|
||||
|
||||
if (currentSupportersData.state === "pending" || currentSupportersData.data === null)
|
||||
return <div>Loading...</div>;
|
||||
|
||||
const supporters_settings = currentSupportersData.data.supporters_settings;
|
||||
const last_updated_local_date = new Date(supporters_settings.last_updated_utc_date).toLocaleString();
|
||||
|
||||
return (
|
||||
<div className={styles.supporters_container}>
|
||||
<div className={styles.vrct_supporters_title_wrapper}>
|
||||
<img className={styles.vrct_supporters_title} src={vrct_supporters_title}/>
|
||||
<img className={styles.calc_period} src={`${supporters_images_url}/calc_period_label.png`}/>
|
||||
<p className={styles.last_updated_local_date}>{`Last updated date: ${last_updated_local_date}`}</p>
|
||||
</div>
|
||||
<SupportersWrapper />
|
||||
<p className={styles.vrct_supporters_desc_end}>{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(in開発室) しいなは喜び庭駆け回っています!!!ふわもちもぐもぐです!ありがとうございます。これからもまだまだ進化するVRCTをどうかよろしくお願いします!\nThanks to everyone, Misha has been granted the privilege of sleeping in a proper bed (in the development room), and Shiina is so happy, running around the yard! Fuwa-mochi-mogu-mogu! Thank you so much! We hope you'll continue to support the ever-evolving VRCT!`}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
.supporters_container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.vrct_supporters_title_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
width: 100%;
|
||||
}
|
||||
.vrct_supporters_title {
|
||||
height: 4.2rem;
|
||||
}
|
||||
.calc_period {
|
||||
height: 1.6rem;
|
||||
}
|
||||
.last_updated_local_date {
|
||||
font-size: 1.2rem;
|
||||
color: var(--dark_600_color);
|
||||
width: 100%;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.vrct_supporters_desc_end {
|
||||
font-size: 1.4rem;
|
||||
margin-top: 2rem;
|
||||
color: var(--dark_300_color);
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
import React, { useState, useCallback, useEffect } from "react";
|
||||
import clsx from "clsx";
|
||||
import Tooltip, { tooltipClasses } from '@mui/material/Tooltip';
|
||||
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||
import styles from "./SupportersWrapper.module.scss";
|
||||
import { shuffleArray, randomIntMinMax, randomMinMax } from "@utils";
|
||||
|
||||
import {
|
||||
useSettingBoxScrollPosition,
|
||||
useSupporters,
|
||||
} from "@logics_configs";
|
||||
|
||||
import { supporters_images_url } from "@ui_configs";
|
||||
|
||||
const SHUFFLE_INTERVAL_TIME = 20000;
|
||||
|
||||
const and_you_data = {
|
||||
supporter_id: "and_you",
|
||||
};
|
||||
|
||||
|
||||
const image_sets = {
|
||||
supporter_cards: `${supporters_images_url}/supporter_cards/`,
|
||||
chato_expressions: `${supporters_images_url}/chato_expressions/`,
|
||||
supporters_labels: `${supporters_images_url}/supporters_labels/`,
|
||||
supporters_icons: `${supporters_images_url}/supporters_icons/`,
|
||||
};
|
||||
|
||||
const getSupporterCard = (plan_name) => {
|
||||
const card_map = {
|
||||
"mogu_2000": "mogu_card",
|
||||
"mochi_1000": "mochi_card",
|
||||
"fuwa_500": "fuwa_card",
|
||||
"basic_300": "basic_card",
|
||||
};
|
||||
if (!card_map[plan_name]) return `${image_sets.supporter_cards}basic_card.png`;
|
||||
|
||||
return `${image_sets.supporter_cards}${card_map[plan_name]}.png`;
|
||||
};
|
||||
|
||||
const getChatoExpressionsPath = (file_name) => `${image_sets.chato_expressions}${file_name}.png`;
|
||||
const getSupportersLabelsPath = (file_name) => `${image_sets.supporters_labels}${file_name}.png`;
|
||||
const getSupportersIconsPath = (file_name) => `${image_sets.supporters_icons}${file_name}.png`;
|
||||
|
||||
|
||||
export const SupportersWrapper = () => {
|
||||
const { saveScrollPosition, restoreScrollPosition } = useSettingBoxScrollPosition();
|
||||
const { currentSupportersData } = useSupporters();
|
||||
|
||||
const [json_data, setJsonData] = useState();
|
||||
const [supportersData, setSupportersData] = useState([]);
|
||||
const [chatoExpressions, setChatoExpressions] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setJsonData(currentSupportersData.data);
|
||||
}, [currentSupportersData.data]);
|
||||
|
||||
const supporters_settings = currentSupportersData.data.supporters_settings;
|
||||
const calc_support_period = supporters_settings.calc_support_period;
|
||||
const chato_ex_count = supporters_settings.chato_ex_count;
|
||||
|
||||
const recalcAndUpdateSupporters = useCallback(() => {
|
||||
if (!json_data) return;
|
||||
|
||||
const grouped_data = {
|
||||
mogu_2000: [],
|
||||
mochi_1000: [],
|
||||
fuwa_500: [],
|
||||
basic_300: [],
|
||||
former_supporter: [],
|
||||
and_you: [],
|
||||
};
|
||||
|
||||
json_data.supporters_data.forEach((supporter) => {
|
||||
const value = supporter.highest_plan_during_the_period || "former_supporter";
|
||||
if (grouped_data[value]) {
|
||||
grouped_data[value].push(supporter);
|
||||
} else {
|
||||
grouped_data["former_supporter"].push(supporter);
|
||||
}
|
||||
});
|
||||
|
||||
const newSupportersData = [
|
||||
...shuffleArray(grouped_data["mogu_2000"]),
|
||||
...shuffleArray(grouped_data["mochi_1000"]),
|
||||
...shuffleArray(grouped_data["fuwa_500"]),
|
||||
...shuffleArray(grouped_data["basic_300"]),
|
||||
...shuffleArray(grouped_data["former_supporter"]),
|
||||
and_you_data,
|
||||
];
|
||||
|
||||
setSupportersData(newSupportersData);
|
||||
|
||||
setChatoExpressions(
|
||||
newSupportersData.map(() =>
|
||||
getChatoExpressionsPath(
|
||||
`chato_expression_${randomIntMinMax(1, chato_ex_count)}`
|
||||
)
|
||||
)
|
||||
);
|
||||
}, [json_data]);
|
||||
|
||||
useEffect(() => {
|
||||
recalcAndUpdateSupporters();
|
||||
}, [json_data, recalcAndUpdateSupporters]);
|
||||
|
||||
const shuffleSupporters = useCallback(() => {
|
||||
if (!json_data) return;
|
||||
saveScrollPosition();
|
||||
recalcAndUpdateSupporters();
|
||||
setTimeout(() => restoreScrollPosition(), 0);
|
||||
}, [json_data, recalcAndUpdateSupporters, saveScrollPosition, restoreScrollPosition]);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
shuffleSupporters();
|
||||
}, SHUFFLE_INTERVAL_TIME);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [shuffleSupporters]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<ProgressBar />
|
||||
<div className={styles.supporters_wrapper}>
|
||||
<SupporterCardsComponent
|
||||
supportersData={supportersData}
|
||||
chatoExpressions={chatoExpressions}
|
||||
calc_support_period={calc_support_period}
|
||||
/>
|
||||
</div>
|
||||
<ProgressBar />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AndYouIcon = () => {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.and_you_container}>
|
||||
<div className={styles.and_you_1}></div>
|
||||
<div className={styles.and_you_2}></div>
|
||||
</div>
|
||||
<p className={styles.and_you_fanbox_link_text}>
|
||||
FANBOX Ko-fi Patreon
|
||||
</p>
|
||||
<ArrowLeftSvg className={styles.arrow_left_svg} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const SupporterCardsComponent = ({ supportersData, chatoExpressions, calc_support_period }) => {
|
||||
return supportersData.map((item, index) => {
|
||||
const target_plan = item.highest_plan_during_the_period;
|
||||
|
||||
const img_src = getSupporterCard(target_plan);
|
||||
|
||||
const is_and_you = item.supporter_id === "and_you";
|
||||
|
||||
const random_delay = `${randomMinMax(0.1, 6).toFixed(1)}s`;
|
||||
|
||||
const supporter_image_wrapper_classname = clsx(
|
||||
styles.supporter_image_wrapper,
|
||||
{
|
||||
[styles.mogu_image]: target_plan === "mogu_2000",
|
||||
}
|
||||
);
|
||||
|
||||
return is_and_you ? (
|
||||
<a href="#support_us_container" key={item.supporter_id}>
|
||||
<div className={styles.supporter_image_container}>
|
||||
<div
|
||||
className={supporter_image_wrapper_classname}
|
||||
style={{ "--delay": random_delay }}
|
||||
>
|
||||
<img
|
||||
className={styles.supporter_image}
|
||||
src={img_src}
|
||||
alt="supporter"
|
||||
/>
|
||||
<SupporterLabelComponent
|
||||
target_plan={target_plan}
|
||||
item={item}
|
||||
chatoExpressions={chatoExpressions}
|
||||
/>
|
||||
<AndYouIcon />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
) : img_src ? (
|
||||
<div key={item.supporter_id} className={styles.supporter_image_container}>
|
||||
<div
|
||||
className={supporter_image_wrapper_classname}
|
||||
style={{ "--delay": random_delay }}
|
||||
>
|
||||
<img
|
||||
className={styles.supporter_image}
|
||||
src={img_src}
|
||||
alt="supporter"
|
||||
/>
|
||||
<SupporterLabelComponent
|
||||
target_plan={target_plan}
|
||||
item={item}
|
||||
chato_src={chatoExpressions[index]}
|
||||
index={index}
|
||||
/>
|
||||
</div>
|
||||
<SupporterPeriodContainer settings={item} calc_support_period={calc_support_period}/>
|
||||
</div>
|
||||
) : null;
|
||||
});
|
||||
};
|
||||
|
||||
const SupporterLabelComponent = ({ item, target_plan, chato_src }) => {
|
||||
const is_icon_plan = ["mogu_2000", "mochi_1000"].includes(
|
||||
target_plan
|
||||
);
|
||||
|
||||
const supporter_label_component_classname = clsx(
|
||||
styles.supporter_label_component,
|
||||
{
|
||||
[styles.is_icon_plan]: is_icon_plan,
|
||||
}
|
||||
);
|
||||
|
||||
const is_and_you = item.supporter_id === "and_you";
|
||||
const is_default_icon = item.supporter_icon_id === "";
|
||||
|
||||
const file_name = is_and_you ? "and_you" : `supporter_${item.supporter_id}`;
|
||||
const label_img_src = getSupportersLabelsPath(file_name);
|
||||
const icon_img_src = getSupportersIconsPath(
|
||||
`supporter_icon_${item.supporter_icon_id}`
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={supporter_label_component_classname}>
|
||||
{is_icon_plan && (
|
||||
<div className={styles.supporter_icon_wrapper}>
|
||||
{is_default_icon ? (
|
||||
<img
|
||||
className={styles.default_chato_expression_image}
|
||||
src={chato_src}
|
||||
alt="chato expression"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
className={styles.supporter_icon}
|
||||
src={icon_img_src}
|
||||
alt="supporter icon"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<img
|
||||
className={styles.supporter_label_image}
|
||||
src={label_img_src}
|
||||
alt="supporter label"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SupporterPeriodContainer = ({ settings, calc_support_period }) => {
|
||||
const period_data = extractKeys(settings, calc_support_period);
|
||||
const offset = {
|
||||
popper: {
|
||||
sx: {
|
||||
[`&.${tooltipClasses.popper}[data-popper-placement*="top"] .${tooltipClasses.tooltip}`]: { marginBottom: "0.2em" },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.supporter_period_container}>
|
||||
<div className={styles.supporter_period_wrapper}>
|
||||
{Object.entries(period_data).map(([key, item], index) => {
|
||||
if (item === "") return null;
|
||||
const period_box_class_name = clsx(styles.period_box, {
|
||||
[styles.mogu_bar]: item === "mogu_2000",
|
||||
[styles.mochi_bar]: item === "mochi_1000",
|
||||
[styles.fuwa_bar]: item === "fuwa_500",
|
||||
[styles.basic_bar]: item === "basic_300",
|
||||
});
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={index}
|
||||
title={
|
||||
<p className={styles.tooltip_period_label}>{key}</p>
|
||||
}
|
||||
placement="top"
|
||||
slotProps={offset}
|
||||
>
|
||||
<div className={styles.period_box_wrapper}>
|
||||
<div className={period_box_class_name}></div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const extractKeys = (data, keys_to_extract) => {
|
||||
const result = {};
|
||||
for (const key of keys_to_extract) {
|
||||
if (key in data) {
|
||||
result[key] = data[key];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
const ProgressBar = () => {
|
||||
const [is_active, setIsActive] = useState(false);
|
||||
useEffect(() => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
setIsActive(true);
|
||||
});
|
||||
});
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setIsActive(false);
|
||||
setTimeout(() => setIsActive(true), 50);
|
||||
}, SHUFFLE_INTERVAL_TIME);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(styles.progress_bar, {
|
||||
[styles.progress_bar_active]: is_active,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,259 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.supporters_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-content: start;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 1.8rem;
|
||||
row-gap: 2rem;
|
||||
}
|
||||
|
||||
.supporter_image_container {
|
||||
position: relative;
|
||||
width: 18rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.supporter_image_wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&:hover .supporter_icon_wrapper{
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.supporter_image {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.supporter_label_component {
|
||||
position: absolute;
|
||||
left: 0.6rem;
|
||||
&.is_icon_plan {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
top: 0.4rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.supporter_label_image {
|
||||
height: 2rem;
|
||||
// margin-top: 0.2rem;
|
||||
&.small {
|
||||
height: 1.4rem;
|
||||
}
|
||||
}
|
||||
$progress_ease: cubic-bezier(0, 1, 0.75, 1);
|
||||
// Duplicated
|
||||
.supporter_icon_wrapper {
|
||||
height: 4rem;
|
||||
aspect-ratio: 1 /1;
|
||||
border-radius: 50%;
|
||||
background-color: var(--dark_basic_text_color);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: transform 0.6s $progress_ease;
|
||||
}
|
||||
|
||||
.supporter_icon {
|
||||
aspect-ratio: 1 / 1;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.default_chato_expression_image {
|
||||
position: absolute;
|
||||
top: 52%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) rotate(10deg);
|
||||
width: 2.8rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.mogu_image {
|
||||
position: relative;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -200%;
|
||||
left: -200%;
|
||||
width: 300%;
|
||||
height: 300%;
|
||||
background: linear-gradient(45deg, rgba(255, 255, 255, 0) 30%, rgba(255, 255, 255, 0.7) 50%, rgba(255, 255, 255, 0) 70%);
|
||||
transform: rotate(90deg);
|
||||
animation: shine 2.5s infinite;
|
||||
filter: blur(0.4rem);
|
||||
animation-delay: var(--delay, 0s);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@keyframes shine {
|
||||
0% {
|
||||
top: -200%;
|
||||
left: -200%;
|
||||
}
|
||||
50% {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
opacity: 0.7;
|
||||
}
|
||||
100% {
|
||||
top: 200%;
|
||||
left: 200%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.and_you_container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.and_you_1, .and_you_2 {
|
||||
width: 2.2rem;
|
||||
height: 0.2rem;
|
||||
border-radius: 50%;
|
||||
background-color: var(--dark_400_color);
|
||||
}
|
||||
|
||||
.and_you_2 {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) rotate(90deg);
|
||||
}
|
||||
|
||||
.supporter_image_container {
|
||||
&:hover .and_you_container {
|
||||
top: 36%;
|
||||
transform: translate(-50%, -50%) rotate(180deg);
|
||||
animation: disappear 0.3s forwards;
|
||||
}
|
||||
&:hover .and_you_fanbox_link_text {
|
||||
top: 74%;
|
||||
opacity: 1;
|
||||
}
|
||||
&:hover .arrow_left_svg {
|
||||
opacity: 0;
|
||||
animation: arrow_up_down 0.3s forwards;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
.supporter_image_container.and_you_image {
|
||||
cursor: pointer;
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.and_you_fanbox_link_text {
|
||||
font-size: 1.2rem;
|
||||
color: var(--dark_400_color);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: all 0.3s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.arrow_left_svg {
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) rotate(90deg);
|
||||
color: var(--dark_400_color);
|
||||
width: 2rem;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes arrow_up_down {
|
||||
0% {
|
||||
top: 60%;
|
||||
}
|
||||
100% {
|
||||
top: 36%;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes disappear {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.supporter_period_container {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
}
|
||||
.supporter_period_wrapper {
|
||||
display: flex;
|
||||
gap: 0.4rem 0.2rem;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.3rem 0.4rem 0.4rem 0.4rem;
|
||||
}
|
||||
|
||||
.period_box_wrapper {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.period_box {
|
||||
width: 1.7rem;
|
||||
height: 0.3rem;
|
||||
border-radius: 0.3rem;
|
||||
&.mogu_bar {
|
||||
background-color: var(--dark_basic_text_color);
|
||||
}
|
||||
&.mochi_bar {
|
||||
background-color: var(--received_300_color);
|
||||
}
|
||||
&.fuwa_bar {
|
||||
background-color: var(--supporters_color_fuwa);
|
||||
}
|
||||
&.basic_bar {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip_period_label {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
|
||||
.progress_bar {
|
||||
height: 0.2rem;
|
||||
width: 0%;
|
||||
&.progress_bar_active {
|
||||
transition: width 20000ms linear;
|
||||
background-color: var(--primary_400_color);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,642 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useI18n } from "@useI18n";
|
||||
import styles from "./Transcription.module.scss";
|
||||
import { updateLabelsById, genNumObjArray, arrayToObject } from "@utils";
|
||||
import { useStore_IsBreakPoint } from "@store";
|
||||
|
||||
import {
|
||||
useTranscription,
|
||||
} from "@logics_configs";
|
||||
|
||||
import {
|
||||
WordFilterContainer,
|
||||
DownloadModelsContainer,
|
||||
RadioButtonContainer,
|
||||
DropdownMenuContainer,
|
||||
SliderContainer,
|
||||
|
||||
useOnMouseLeaveDropdownMenu,
|
||||
} from "../_templates/Templates";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
LabelComponent,
|
||||
SectionLabelComponent,
|
||||
} from "../_components";
|
||||
|
||||
export const Transcription = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Mic_Container />
|
||||
<Speaker_Container />
|
||||
<TranscriptionEngine_Container />
|
||||
<Advanced_Container />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const Mic_Container = () => {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div>
|
||||
<SectionLabelComponent label={t("config_page.transcription.section_label_mic")} />
|
||||
<MicRecordTimeout_Box />
|
||||
<MicPhraseTimeout_Box />
|
||||
<MicMaxWords_Box />
|
||||
<MicWordFilter_Box />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MicRecordTimeout_Box = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentMicRecordTimeout, setMicRecordTimeout } = useTranscription();
|
||||
|
||||
const selectFunction = (selected_data) => {
|
||||
setMicRecordTimeout(selected_data.selected_id);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuContainer
|
||||
dropdown_id="mic_record_timeout"
|
||||
label={t("config_page.transcription.mic_record_timeout.label")}
|
||||
desc={t("config_page.transcription.mic_record_timeout.desc")}
|
||||
selected_id={currentMicRecordTimeout.data}
|
||||
list={genNumObjArray(31)}
|
||||
selectFunction={selectFunction}
|
||||
state={currentMicRecordTimeout.state}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const MicPhraseTimeout_Box = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentMicPhraseTimeout, setMicPhraseTimeout } = useTranscription();
|
||||
|
||||
const selectFunction = (selected_data) => {
|
||||
setMicPhraseTimeout(selected_data.selected_id);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuContainer
|
||||
dropdown_id="mic_phrase_timeout"
|
||||
label={t("config_page.transcription.mic_phrase_timeout.label")}
|
||||
desc={t("config_page.transcription.mic_phrase_timeout.desc")}
|
||||
selected_id={currentMicPhraseTimeout.data}
|
||||
list={genNumObjArray(31)}
|
||||
selectFunction={selectFunction}
|
||||
state={currentMicPhraseTimeout.state}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const MicMaxWords_Box = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentMicMaxWords, setMicMaxWords } = useTranscription();
|
||||
|
||||
const selectFunction = (selected_data) => {
|
||||
setMicMaxWords(selected_data.selected_id);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuContainer
|
||||
dropdown_id="mic_max_phrase"
|
||||
label={t("config_page.transcription.mic_max_phrase.label")}
|
||||
desc={t("config_page.transcription.mic_max_phrase.desc")}
|
||||
selected_id={currentMicMaxWords.data}
|
||||
list={genNumObjArray(31)}
|
||||
selectFunction={selectFunction}
|
||||
state={currentMicMaxWords.state}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const MicWordFilter_Box = () => {
|
||||
const { t } = useI18n();
|
||||
|
||||
return (
|
||||
<WordFilterContainer
|
||||
label={t("config_page.transcription.mic_word_filter.label")}
|
||||
desc={t("config_page.transcription.mic_word_filter.desc")}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const Speaker_Container = () => {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div>
|
||||
<SectionLabelComponent label={t("config_page.transcription.section_label_speaker")} />
|
||||
<SpeakerRecordTimeout_Box />
|
||||
<SpeakerPhraseTimeout_Box />
|
||||
<SpeakerMaxWords_Box />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SpeakerRecordTimeout_Box = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentSpeakerRecordTimeout, setSpeakerRecordTimeout } = useTranscription();
|
||||
|
||||
const selectFunction = (selected_data) => {
|
||||
setSpeakerRecordTimeout(selected_data.selected_id);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuContainer
|
||||
dropdown_id="speaker_record_timeout"
|
||||
desc={t("config_page.transcription.speaker_record_timeout.desc")}
|
||||
label={t("config_page.transcription.speaker_record_timeout.label")}
|
||||
selected_id={currentSpeakerRecordTimeout.data}
|
||||
list={genNumObjArray(31)}
|
||||
selectFunction={selectFunction}
|
||||
state={currentSpeakerRecordTimeout.state}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const SpeakerPhraseTimeout_Box = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentSpeakerPhraseTimeout, setSpeakerPhraseTimeout } = useTranscription();
|
||||
|
||||
const selectFunction = (selected_data) => {
|
||||
setSpeakerPhraseTimeout(selected_data.selected_id);
|
||||
};
|
||||
return (
|
||||
<DropdownMenuContainer
|
||||
dropdown_id="speaker_phrase_timeout"
|
||||
label={t("config_page.transcription.speaker_phrase_timeout.label")}
|
||||
desc={t("config_page.transcription.speaker_phrase_timeout.desc")}
|
||||
selected_id={currentSpeakerPhraseTimeout.data}
|
||||
list={genNumObjArray(31)}
|
||||
selectFunction={selectFunction}
|
||||
state={currentSpeakerPhraseTimeout.state}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const SpeakerMaxWords_Box = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentSpeakerMaxWords, setSpeakerMaxWords } = useTranscription();
|
||||
|
||||
const selectFunction = (selected_data) => {
|
||||
setSpeakerMaxWords(selected_data.selected_id);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuContainer
|
||||
dropdown_id="speaker_max_phrase"
|
||||
label={t("config_page.transcription.speaker_max_phrase.label")}
|
||||
desc={t("config_page.transcription.speaker_max_phrase.desc")}
|
||||
selected_id={currentSpeakerMaxWords.data}
|
||||
list={genNumObjArray(61)}
|
||||
selectFunction={selectFunction}
|
||||
state={currentSpeakerMaxWords.state}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const TranscriptionEngine_Container = () => {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div>
|
||||
<SectionLabelComponent label={t("config_page.transcription.section_label_transcription_engines")} />
|
||||
<TranscriptionEngine_Box />
|
||||
<WhisperWeightType_Box />
|
||||
<TranscriptionComputeDevice_Box />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TranscriptionEngine_Box = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentSelectedTranscriptionEngine, setSelectedTranscriptionEngine } = useTranscription();
|
||||
|
||||
return (
|
||||
<RadioButtonContainer
|
||||
label={t("config_page.transcription.select_transcription_engine.label")}
|
||||
selectFunction={setSelectedTranscriptionEngine}
|
||||
name="select_transcription_engine"
|
||||
options={[
|
||||
{ id: "Google", label: "Google" },
|
||||
{ id: "Whisper", label: "Whisper" },
|
||||
]}
|
||||
checked_variable={currentSelectedTranscriptionEngine}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const WhisperWeightType_Box = () => {
|
||||
const { t } = useI18n();
|
||||
const {
|
||||
currentWhisperWeightTypeStatus,
|
||||
pendingWhisperWeightTypeStatus,
|
||||
downloadWhisperWeightTypeStatus,
|
||||
} = useTranscription();
|
||||
const { currentSelectedWhisperWeightType, setSelectedWhisperWeightType } = useTranscription();
|
||||
|
||||
const selectFunction = (id) => {
|
||||
setSelectedWhisperWeightType(id);
|
||||
};
|
||||
|
||||
const downloadStartFunction = (id) => {
|
||||
pendingWhisperWeightTypeStatus(id);
|
||||
downloadWhisperWeightTypeStatus(id);
|
||||
};
|
||||
|
||||
const whisper_weight_types = currentWhisperWeightTypeStatus.data.map(item => {
|
||||
return {
|
||||
...item,
|
||||
label: `${item.id} (${item.capacity})`,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<DownloadModelsContainer
|
||||
label={t("config_page.transcription.whisper_weight_type.label")}
|
||||
desc={t(
|
||||
"config_page.transcription.whisper_weight_type.desc",
|
||||
{translator: t("main_page.translator")}
|
||||
)}
|
||||
name="whisper_weight_type"
|
||||
options={whisper_weight_types}
|
||||
checked_variable={currentSelectedWhisperWeightType}
|
||||
selectFunction={selectFunction}
|
||||
downloadStartFunction={downloadStartFunction}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Duplicate
|
||||
const TranscriptionComputeDevice_Box = () => {
|
||||
const { t } = useI18n();
|
||||
const {
|
||||
currentSelectableTranscriptionComputeDeviceList,
|
||||
currentSelectedTranscriptionComputeDevice,
|
||||
setSelectedTranscriptionComputeDevice,
|
||||
currentSelectedTranscriptionComputeType,
|
||||
setSelectedTranscriptionComputeType,
|
||||
} = useTranscription();
|
||||
const { onMouseLeaveFunction } = useOnMouseLeaveDropdownMenu();
|
||||
const { currentIsBreakPoint } = useStore_IsBreakPoint();
|
||||
|
||||
const list_for_ui = transformDeviceArray(currentSelectableTranscriptionComputeDeviceList.data);
|
||||
|
||||
const target_index = findKeyByDeviceValue(currentSelectableTranscriptionComputeDeviceList.data, currentSelectedTranscriptionComputeDevice.data);
|
||||
|
||||
const DEFAULT_ORDER = [
|
||||
"auto",
|
||||
"int8",
|
||||
"int8_bfloat16",
|
||||
"int8_float16",
|
||||
"int8_float32",
|
||||
"bfloat16",
|
||||
"float16",
|
||||
"int16",
|
||||
"float32"
|
||||
];
|
||||
|
||||
const sortComputeTypesArray = (compute_types_array = [], order) => {
|
||||
const src_set = new Set(compute_types_array);
|
||||
|
||||
const from_order = order.filter((id) => src_set.has(id));
|
||||
|
||||
const invalid_ids = compute_types_array.filter((id) => !order.includes(id));
|
||||
if (invalid_ids.length > 0) {
|
||||
console.error("[sortComputeTypesArray] Unsupported compute types ignored:", invalid_ids);
|
||||
}
|
||||
|
||||
return from_order;
|
||||
};
|
||||
|
||||
|
||||
const buildSimpleLabels = (ordered_array = []) => {
|
||||
const n = ordered_array.length;
|
||||
if (n === 0) return {};
|
||||
|
||||
const labels = {};
|
||||
|
||||
ordered_array.forEach((id, idx) => {
|
||||
if (idx === 0 && id === "auto") {
|
||||
labels[id] = t("config_page.common.compute_device.type_template_auto");
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx === 1) {
|
||||
labels[id] = t(
|
||||
"config_page.common.compute_device.type_template_low",
|
||||
{ type_name: id }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx === n - 1) {
|
||||
labels[id] = t(
|
||||
"config_page.common.compute_device.type_template_high",
|
||||
{ type_name: id }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
labels[id] = id;
|
||||
});
|
||||
|
||||
return labels;
|
||||
};
|
||||
|
||||
|
||||
const computeTypesArray = currentSelectableTranscriptionComputeDeviceList.data[target_index].compute_types;
|
||||
|
||||
const ordered_array = sortComputeTypesArray(computeTypesArray, DEFAULT_ORDER);
|
||||
|
||||
const new_compute_types_labels = buildSimpleLabels(ordered_array);
|
||||
|
||||
const selectFunction_ComputeDevice = (selected_data) => {
|
||||
const target_obj = currentSelectableTranscriptionComputeDeviceList.data[selected_data.selected_id];
|
||||
setSelectedTranscriptionComputeDevice(target_obj);
|
||||
};
|
||||
|
||||
const selectFunction_ComputeType = (selected_data) => {
|
||||
setSelectedTranscriptionComputeType(selected_data.selected_id);
|
||||
};
|
||||
|
||||
const device_container_class = clsx(styles.device_container, {
|
||||
[styles.is_break_point]: currentIsBreakPoint.data,
|
||||
});
|
||||
|
||||
const is_disabled_selector = currentSelectedTranscriptionComputeDevice.state === "pending" || currentSelectedTranscriptionComputeType.state === "pending";
|
||||
|
||||
return (
|
||||
<div className={styles.mic_container}>
|
||||
<div className={device_container_class} onMouseLeave={onMouseLeaveFunction}>
|
||||
<LabelComponent
|
||||
label={t("config_page.transcription.transcription_compute_device.label")}
|
||||
desc={t("config_page.common.compute_device.desc")}
|
||||
/>
|
||||
<div className={styles.device_contents}>
|
||||
|
||||
<div className={styles.device_dropdown_wrapper}>
|
||||
<div className={styles.device_dropdown}>
|
||||
<p className={styles.device_secondary_label}>{t("config_page.common.compute_device.label_device")}</p>
|
||||
<DropdownMenu
|
||||
dropdown_id="transcription_compute_device"
|
||||
selected_id={target_index}
|
||||
list={list_for_ui}
|
||||
selectFunction={selectFunction_ComputeDevice}
|
||||
state={currentSelectedTranscriptionComputeDevice.state}
|
||||
style={{ maxWidth: "20rem", minWidth: "10rem" }}
|
||||
is_disabled={is_disabled_selector}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.device_dropdown}>
|
||||
<p className={styles.device_secondary_label}>{t("config_page.common.compute_device.label_type")}</p>
|
||||
<DropdownMenu
|
||||
dropdown_id="transcription_compute_type"
|
||||
selected_id={currentSelectedTranscriptionComputeType.data}
|
||||
list={new_compute_types_labels}
|
||||
selectFunction={selectFunction_ComputeType}
|
||||
state={currentSelectedTranscriptionComputeType.state}
|
||||
is_disabled={is_disabled_selector}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Duplicate
|
||||
const transformDeviceArray = (devices) => {
|
||||
const name_counts = Object.values(devices).reduce((counts, device) => {
|
||||
const name = device.device_name;
|
||||
counts[name] = (counts[name] || 0) + 1;
|
||||
return counts;
|
||||
}, {});
|
||||
|
||||
const name_indices = {};
|
||||
const result = {};
|
||||
|
||||
Object.entries(devices).forEach(([key, device]) => {
|
||||
const name = device.device_name;
|
||||
|
||||
if (name_counts[name] > 1) {
|
||||
name_indices[name] = (name_indices[name] || 0);
|
||||
const value = `${name}:${name_indices[name]}`;
|
||||
name_indices[name]++;
|
||||
result[key] = value;
|
||||
} else {
|
||||
result[key] = name;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const findKeyByDeviceValue = (devices, target_value) => {
|
||||
for (const [key, value] of Object.entries(devices)) {
|
||||
if (
|
||||
value.device === target_value.device &&
|
||||
value.device_index === target_value.device_index &&
|
||||
value.device_name === target_value.device_name
|
||||
) {
|
||||
return parseInt(key);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const Advanced_Container = () => {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div>
|
||||
<SectionLabelComponent label="Advanced Settings (Whisper Model)" />
|
||||
{/* <SectionLabelComponent label={t("config_page.transcription.section_label_transcription_engines")} /> */}
|
||||
<MicAvgLogprobContainer />
|
||||
<MicNoSpeechProbContainer />
|
||||
<SpeakerAvgLogprobContainer />
|
||||
<SpeakerNoSpeechProbContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const MicAvgLogprobContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentMicAvgLogprob, setMicAvgLogprob } = useTranscription();
|
||||
const [ui_mic_avg_logprob, setUiMicAvgLogprob] = useState(currentMicAvgLogprob.data);
|
||||
|
||||
const onchangeFunction = (value) => {
|
||||
setUiMicAvgLogprob(value);
|
||||
};
|
||||
const onchangeCommittedFunction = (value) => {
|
||||
setMicAvgLogprob(value);
|
||||
};
|
||||
useEffect(() => {
|
||||
setUiMicAvgLogprob(currentMicAvgLogprob.data);
|
||||
}, [currentMicAvgLogprob.data]);
|
||||
|
||||
// [Duplicated]
|
||||
const createMarks = (min, max) => {
|
||||
const marks = [];
|
||||
for (let value = min; value <= max; value += 0.2) {
|
||||
value = parseFloat(value.toFixed(1));
|
||||
marks.push({ value, label: `${value}` });
|
||||
}
|
||||
return marks;
|
||||
};
|
||||
|
||||
const marks = createMarks(-2, 0);
|
||||
|
||||
return (
|
||||
<SliderContainer
|
||||
label="Mic Avg Logprob"
|
||||
desc="Default: -0.8"
|
||||
min="-2"
|
||||
max="0"
|
||||
onchangeCommittedFunction={onchangeCommittedFunction}
|
||||
onchangeFunction={onchangeFunction}
|
||||
variable={ui_mic_avg_logprob}
|
||||
marks={marks}
|
||||
step={0.1}
|
||||
track={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const MicNoSpeechProbContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentMicNoSpeechProb, setMicNoSpeechProb } = useTranscription();
|
||||
const [ui_mic_no_speech_prob, setUiMicNoSpeechProb] = useState(currentMicNoSpeechProb.data);
|
||||
|
||||
const onchangeFunction = (value) => {
|
||||
setUiMicNoSpeechProb(value);
|
||||
};
|
||||
const onchangeCommittedFunction = (value) => {
|
||||
setMicNoSpeechProb(value);
|
||||
};
|
||||
useEffect(() => {
|
||||
setUiMicNoSpeechProb(currentMicNoSpeechProb.data);
|
||||
}, [currentMicNoSpeechProb.data]);
|
||||
|
||||
// [Duplicated]
|
||||
const createMarks = (min, max) => {
|
||||
const marks = [];
|
||||
for (let value = min; value <= max; value += 0.1) {
|
||||
value = parseFloat(value.toFixed(1));
|
||||
marks.push({ value, label: `${value}` });
|
||||
}
|
||||
return marks;
|
||||
};
|
||||
|
||||
const marks = createMarks(0, 1);
|
||||
|
||||
return (
|
||||
<SliderContainer
|
||||
label="Mic No Speech Prob"
|
||||
desc="Default: 0.6"
|
||||
min="0"
|
||||
max="1"
|
||||
onchangeCommittedFunction={onchangeCommittedFunction}
|
||||
onchangeFunction={onchangeFunction}
|
||||
variable={ui_mic_no_speech_prob}
|
||||
marks={marks}
|
||||
step={0.1}
|
||||
track={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SpeakerAvgLogprobContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentSpeakerAvgLogprob, setSpeakerAvgLogprob } = useTranscription();
|
||||
const [ui_speaker_avg_logprob, setUiSpeakerAvgLogprob] = useState(currentSpeakerAvgLogprob.data);
|
||||
|
||||
const onchangeFunction = (value) => {
|
||||
setUiSpeakerAvgLogprob(value);
|
||||
};
|
||||
const onchangeCommittedFunction = (value) => {
|
||||
setSpeakerAvgLogprob(value);
|
||||
};
|
||||
useEffect(() => {
|
||||
setUiSpeakerAvgLogprob(currentSpeakerAvgLogprob.data);
|
||||
}, [currentSpeakerAvgLogprob.data]);
|
||||
|
||||
// [Duplicated]
|
||||
const createMarks = (min, max) => {
|
||||
const marks = [];
|
||||
for (let value = min; value <= max; value += 0.2) {
|
||||
value = parseFloat(value.toFixed(1));
|
||||
marks.push({ value, label: `${value}` });
|
||||
}
|
||||
return marks;
|
||||
};
|
||||
|
||||
const marks = createMarks(-2, 0);
|
||||
|
||||
return (
|
||||
<SliderContainer
|
||||
label="Speaker Avg Logprob"
|
||||
desc="Default: -0.8"
|
||||
min="-2"
|
||||
max="0"
|
||||
onchangeCommittedFunction={onchangeCommittedFunction}
|
||||
onchangeFunction={onchangeFunction}
|
||||
variable={ui_speaker_avg_logprob}
|
||||
marks={marks}
|
||||
step={0.1}
|
||||
track={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SpeakerNoSpeechProbContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentSpeakerNoSpeechProb, setSpeakerNoSpeechProb } = useTranscription();
|
||||
const [ui_speaker_no_speech_prob, setUiSpeakerNoSpeechProb] = useState(currentSpeakerNoSpeechProb.data);
|
||||
|
||||
const onchangeFunction = (value) => {
|
||||
setUiSpeakerNoSpeechProb(value);
|
||||
};
|
||||
const onchangeCommittedFunction = (value) => {
|
||||
setSpeakerNoSpeechProb(value);
|
||||
};
|
||||
useEffect(() => {
|
||||
setUiSpeakerNoSpeechProb(currentSpeakerNoSpeechProb.data);
|
||||
}, [currentSpeakerNoSpeechProb.data]);
|
||||
|
||||
// [Duplicated]
|
||||
const createMarks = (min, max) => {
|
||||
const marks = [];
|
||||
for (let value = min; value <= max; value += 0.1) {
|
||||
value = parseFloat(value.toFixed(1));
|
||||
marks.push({ value, label: `${value}` });
|
||||
}
|
||||
return marks;
|
||||
};
|
||||
|
||||
const marks = createMarks(0, 1);
|
||||
|
||||
return (
|
||||
<SliderContainer
|
||||
label="Speaker No Speech Prob"
|
||||
desc="Default: 0.6"
|
||||
min="0"
|
||||
max="1"
|
||||
onchangeCommittedFunction={onchangeCommittedFunction}
|
||||
onchangeFunction={onchangeFunction}
|
||||
variable={ui_speaker_no_speech_prob}
|
||||
marks={marks}
|
||||
step={0.1}
|
||||
track={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,121 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6.4rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// [Fix me] Need refactor.
|
||||
.mic_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: solid 0.1rem var(--dark_800_color);
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.speaker_container {
|
||||
padding-top: 0rem;
|
||||
}
|
||||
|
||||
.device_container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
margin-bottom: 0rem;
|
||||
&.is_break_point {
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
align-items: start;
|
||||
& .device_contents {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
padding-left: 0rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.threshold_container {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.threshold_container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.threshold_switch_section {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.threshold_section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.device_label {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.device_contents {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: end;
|
||||
padding-left: 2rem;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.device_auto_select_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.2rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.device_dropdown_wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2.8rem;
|
||||
}
|
||||
|
||||
.device_dropdown {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
white-space: nowrap;
|
||||
max-width: 24rem;
|
||||
&.is_disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.device_secondary_label {
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.4rem;
|
||||
font-size: 1.4rem;
|
||||
color: var(--dark_500_color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useI18n } from "@useI18n";
|
||||
import styles from "./Translation.module.scss";
|
||||
import { updateLabelsById, arrayToObject } from "@utils";
|
||||
import { useStore_IsBreakPoint } from "@store";
|
||||
|
||||
import {
|
||||
useTranslation,
|
||||
} from "@logics_configs";
|
||||
|
||||
import {
|
||||
DownloadModelsContainer,
|
||||
DeeplAuthKeyContainer,
|
||||
|
||||
useOnMouseLeaveDropdownMenu,
|
||||
} from "../_templates/Templates";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
LabelComponent,
|
||||
} from "../_components";
|
||||
|
||||
export const Translation = () => {
|
||||
return (
|
||||
<>
|
||||
<CTranslate2WeightType_Box />
|
||||
<TranslationComputeDevice_Box />
|
||||
<DeeplAuthKey_Box />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const CTranslate2WeightType_Box = () => {
|
||||
const { t } = useI18n();
|
||||
const {
|
||||
currentCTranslate2WeightTypeStatus,
|
||||
pendingCTranslate2WeightTypeStatus,
|
||||
downloadCTranslate2WeightTypeStatus,
|
||||
|
||||
currentSelectedCTranslate2WeightType,
|
||||
setSelectedCTranslate2WeightType,
|
||||
} = useTranslation();
|
||||
|
||||
const selectFunction = (id) => {
|
||||
setSelectedCTranslate2WeightType(id);
|
||||
};
|
||||
|
||||
const downloadStartFunction = (id) => {
|
||||
pendingCTranslate2WeightTypeStatus(id);
|
||||
downloadCTranslate2WeightTypeStatus(id);
|
||||
};
|
||||
|
||||
|
||||
const c_translate2_weight_types_object = currentCTranslate2WeightTypeStatus.data.map(item => {
|
||||
return {
|
||||
...item,
|
||||
label: `${item.id} (${item.capacity})`,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<DownloadModelsContainer
|
||||
label={t(
|
||||
"config_page.translation.ctranslate2_weight_type.label",
|
||||
{ctranslate2: "CTranslate2"}
|
||||
)}
|
||||
desc={t(
|
||||
"config_page.translation.ctranslate2_weight_type.desc",
|
||||
{ctranslate2: "CTranslate2"}
|
||||
)}
|
||||
name="ctranslate2_weight_type"
|
||||
options={c_translate2_weight_types_object}
|
||||
checked_variable={currentSelectedCTranslate2WeightType}
|
||||
selectFunction={selectFunction}
|
||||
downloadStartFunction={downloadStartFunction}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
// Duplicate
|
||||
const TranslationComputeDevice_Box = () => {
|
||||
const { t } = useI18n();
|
||||
const {
|
||||
currentSelectableTranslationComputeDeviceList,
|
||||
currentSelectedTranslationComputeDevice,
|
||||
setSelectedTranslationComputeDevice,
|
||||
currentSelectedTranslationComputeType,
|
||||
setSelectedTranslationComputeType,
|
||||
} = useTranslation();
|
||||
const { onMouseLeaveFunction } = useOnMouseLeaveDropdownMenu();
|
||||
const { currentIsBreakPoint } = useStore_IsBreakPoint();
|
||||
|
||||
const list_for_ui = transformDeviceArray(currentSelectableTranslationComputeDeviceList.data);
|
||||
|
||||
const target_index = findKeyByDeviceValue(currentSelectableTranslationComputeDeviceList.data, currentSelectedTranslationComputeDevice.data);
|
||||
|
||||
const DEFAULT_ORDER = [
|
||||
"auto",
|
||||
"int8",
|
||||
"int8_bfloat16",
|
||||
"int8_float16",
|
||||
"int8_float32",
|
||||
"bfloat16",
|
||||
"float16",
|
||||
"int16",
|
||||
"float32"
|
||||
];
|
||||
|
||||
const sortComputeTypesArray = (compute_types_array = [], order) => {
|
||||
const src_set = new Set(compute_types_array);
|
||||
|
||||
const from_order = order.filter((id) => src_set.has(id));
|
||||
|
||||
const invalid_ids = compute_types_array.filter((id) => !order.includes(id));
|
||||
if (invalid_ids.length > 0) {
|
||||
console.error("[sortComputeTypesArray] Unsupported compute types ignored:", invalid_ids);
|
||||
}
|
||||
|
||||
return from_order;
|
||||
};
|
||||
|
||||
|
||||
const buildSimpleLabels = (ordered_array = []) => {
|
||||
const n = ordered_array.length;
|
||||
if (n === 0) return {};
|
||||
|
||||
const labels = {};
|
||||
|
||||
ordered_array.forEach((id, idx) => {
|
||||
if (idx === 0 && id === "auto") {
|
||||
labels[id] = t("config_page.common.compute_device.type_template_auto");
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx === 1) {
|
||||
labels[id] = t(
|
||||
"config_page.common.compute_device.type_template_low",
|
||||
{ type_name: id }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx === n - 1) {
|
||||
labels[id] = t(
|
||||
"config_page.common.compute_device.type_template_high",
|
||||
{ type_name: id }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
labels[id] = id;
|
||||
});
|
||||
|
||||
return labels;
|
||||
};
|
||||
|
||||
|
||||
const computeTypesArray = currentSelectableTranslationComputeDeviceList.data[target_index].compute_types;
|
||||
|
||||
const ordered_array = sortComputeTypesArray(computeTypesArray, DEFAULT_ORDER);
|
||||
|
||||
const new_compute_types_labels = buildSimpleLabels(ordered_array);
|
||||
|
||||
const selectFunction_ComputeDevice = (selected_data) => {
|
||||
const target_obj = currentSelectableTranslationComputeDeviceList.data[selected_data.selected_id];
|
||||
setSelectedTranslationComputeDevice(target_obj);
|
||||
};
|
||||
|
||||
const selectFunction_ComputeType = (selected_data) => {
|
||||
setSelectedTranslationComputeType(selected_data.selected_id);
|
||||
};
|
||||
|
||||
const device_container_class = clsx(styles.device_container, {
|
||||
[styles.is_break_point]: currentIsBreakPoint.data,
|
||||
});
|
||||
|
||||
const is_disabled_selector = currentSelectedTranslationComputeDevice.state === "pending" || currentSelectedTranslationComputeType.state === "pending";
|
||||
|
||||
return (
|
||||
<div className={styles.mic_container}>
|
||||
<div className={device_container_class} onMouseLeave={onMouseLeaveFunction}>
|
||||
<LabelComponent
|
||||
label={t("config_page.translation.translation_compute_device.label")}
|
||||
desc={t("config_page.common.compute_device.desc")}
|
||||
/>
|
||||
<div className={styles.device_contents}>
|
||||
|
||||
<div className={styles.device_dropdown_wrapper}>
|
||||
<div className={styles.device_dropdown}>
|
||||
<p className={styles.device_secondary_label}>{t("config_page.common.compute_device.label_device")}</p>
|
||||
<DropdownMenu
|
||||
dropdown_id="translation_compute_device"
|
||||
selected_id={target_index}
|
||||
list={list_for_ui}
|
||||
selectFunction={selectFunction_ComputeDevice}
|
||||
state={currentSelectedTranslationComputeDevice.state}
|
||||
style={{ maxWidth: "20rem", minWidth: "10rem" }}
|
||||
is_disabled={is_disabled_selector}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.device_dropdown}>
|
||||
<p className={styles.device_secondary_label}>{t("config_page.common.compute_device.label_type")}</p>
|
||||
<DropdownMenu
|
||||
dropdown_id="translation_compute_type"
|
||||
selected_id={currentSelectedTranslationComputeType.data}
|
||||
list={new_compute_types_labels}
|
||||
selectFunction={selectFunction_ComputeType}
|
||||
state={currentSelectedTranslationComputeType.state}
|
||||
is_disabled={is_disabled_selector}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DeeplAuthKey_Box = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentDeepLAuthKey, setDeepLAuthKey, deleteDeepLAuthKey } = useTranslation();
|
||||
const [input_value, seInputValue] = useState(currentDeepLAuthKey.data);
|
||||
|
||||
const onChangeFunction = (value) => {
|
||||
seInputValue(value);
|
||||
};
|
||||
|
||||
const saveFunction = () => {
|
||||
if (input_value === "") return deleteDeepLAuthKey();
|
||||
setDeepLAuthKey(input_value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (currentDeepLAuthKey.state === "pending") return;
|
||||
seInputValue(currentDeepLAuthKey.data);
|
||||
}, [currentDeepLAuthKey]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeeplAuthKeyContainer
|
||||
label={t("config_page.translation.deepl_auth_key.label")}
|
||||
desc={t(
|
||||
"config_page.translation.deepl_auth_key.desc",
|
||||
{translator: t("main_page.translator")}
|
||||
)}
|
||||
variable={input_value}
|
||||
state={currentDeepLAuthKey.state}
|
||||
onChangeFunction={onChangeFunction}
|
||||
saveFunction={saveFunction}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Duplicate
|
||||
const transformDeviceArray = (devices) => {
|
||||
const name_counts = Object.values(devices).reduce((counts, device) => {
|
||||
const name = device.device_name;
|
||||
counts[name] = (counts[name] || 0) + 1;
|
||||
return counts;
|
||||
}, {});
|
||||
|
||||
const name_indices = {};
|
||||
const result = {};
|
||||
|
||||
Object.entries(devices).forEach(([key, device]) => {
|
||||
const name = device.device_name;
|
||||
|
||||
if (name_counts[name] > 1) {
|
||||
name_indices[name] = (name_indices[name] || 0);
|
||||
const value = `${name}:${name_indices[name]}`;
|
||||
name_indices[name]++;
|
||||
result[key] = value;
|
||||
} else {
|
||||
result[key] = name;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const findKeyByDeviceValue = (devices, target_value) => {
|
||||
for (const [key, value] of Object.entries(devices)) {
|
||||
if (
|
||||
value.device === target_value.device &&
|
||||
value.device_index === target_value.device_index &&
|
||||
value.device_name === target_value.device_name
|
||||
) {
|
||||
return parseInt(key);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,106 @@
|
||||
// [Fix me] Need refactor.
|
||||
.mic_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: solid 0.1rem var(--dark_800_color);
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.speaker_container {
|
||||
padding-top: 0rem;
|
||||
}
|
||||
|
||||
.device_container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
margin-bottom: 0rem;
|
||||
&.is_break_point {
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
align-items: start;
|
||||
& .device_contents {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
padding-left: 0rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.threshold_container {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.threshold_container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.threshold_switch_section {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.threshold_section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.device_label {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.device_contents {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: end;
|
||||
padding-left: 2rem;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.device_auto_select_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.2rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.device_dropdown_wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2.8rem;
|
||||
}
|
||||
|
||||
.device_dropdown {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
white-space: nowrap;
|
||||
max-width: 24rem;
|
||||
&.is_disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.device_secondary_label {
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.4rem;
|
||||
font-size: 1.4rem;
|
||||
color: var(--dark_500_color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,634 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useI18n } from "@useI18n";
|
||||
import clsx from "clsx";
|
||||
import styles from "./Vr.module.scss";
|
||||
import { ui_configs } from "@ui_configs";
|
||||
import { Slider } from "../_components";
|
||||
import {
|
||||
RadioButtonContainer,
|
||||
SwitchBoxContainer,
|
||||
CheckboxContainer,
|
||||
} from "../_templates/Templates";
|
||||
|
||||
import {
|
||||
SectionLabelComponent,
|
||||
} from "../_components";
|
||||
|
||||
import { ResetButton } from "@common_components";
|
||||
|
||||
import {
|
||||
useVr,
|
||||
} from "@logics_configs";
|
||||
|
||||
import SquareSvg from "@images/square.svg?react";
|
||||
import TriangleSvg from "@images/triangle.svg?react";
|
||||
import { randomIntMinMax } from "@utils";
|
||||
|
||||
export const Vr = () => {
|
||||
const { t } = useI18n();
|
||||
const [is_opened_small_settings, setIsOpenedSmallSettings] = useState(true);
|
||||
const toggleIsOpenedSmallSettings = () => {
|
||||
setIsOpenedSmallSettings(!is_opened_small_settings);
|
||||
};
|
||||
|
||||
const {
|
||||
currentIsEnabledOverlayLargeLog,
|
||||
toggleIsEnabledOverlayLargeLog,
|
||||
currentIsEnabledOverlaySmallLog,
|
||||
toggleIsEnabledOverlaySmallLog,
|
||||
currentOverlayLargeLogSettings,
|
||||
setOverlayLargeLogSettings,
|
||||
currentOverlaySmallLogSettings,
|
||||
setOverlaySmallLogSettings,
|
||||
} = useVr();
|
||||
|
||||
|
||||
const restoreDefaultSettings = () => {
|
||||
setOverlaySmallLogSettings(ui_configs.overlay_small_log_default_settings);
|
||||
setOverlayLargeLogSettings(ui_configs.overlay_large_log_default_settings);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.wrapper}>
|
||||
<PageSwitcherContainer
|
||||
toggleFunction={toggleIsOpenedSmallSettings}
|
||||
is_selected={is_opened_small_settings}
|
||||
label_1={t("config_page.vr.single_line")}
|
||||
label_2={t("config_page.vr.multi_lines")}
|
||||
/>
|
||||
{is_opened_small_settings ? (
|
||||
<OverlaySettingsContainer
|
||||
id="overlay_settings_small"
|
||||
ui_configs={ui_configs.overlay_small_log}
|
||||
default_ui_configs={ui_configs.overlay_small_log_default_settings}
|
||||
current_overlay_settings={currentOverlaySmallLogSettings.data}
|
||||
set_overlay_settings={setOverlaySmallLogSettings}
|
||||
current_is_enabled_overlay={currentIsEnabledOverlaySmallLog}
|
||||
toggle_is_enabled_overlay={toggleIsEnabledOverlaySmallLog}
|
||||
/>
|
||||
) : (
|
||||
<OverlaySettingsContainer
|
||||
id="overlay_settings_large"
|
||||
ui_configs={ui_configs.overlay_large_log}
|
||||
default_ui_configs={ui_configs.overlay_large_log_default_settings}
|
||||
current_overlay_settings={currentOverlayLargeLogSettings.data}
|
||||
set_overlay_settings={setOverlayLargeLogSettings}
|
||||
current_is_enabled_overlay={currentIsEnabledOverlayLargeLog}
|
||||
toggle_is_enabled_overlay={toggleIsEnabledOverlayLargeLog}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<CommonSettingsContainer />
|
||||
<button
|
||||
className={styles.restore_default_settings_button}
|
||||
onClick={restoreDefaultSettings}
|
||||
>
|
||||
{t("config_page.vr.restore_default_settings")}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OverlaySettingsContainer = ({
|
||||
current_overlay_settings,
|
||||
set_overlay_settings,
|
||||
current_is_enabled_overlay,
|
||||
toggle_is_enabled_overlay,
|
||||
ui_configs,
|
||||
default_ui_configs,
|
||||
id
|
||||
}) => {
|
||||
|
||||
const { t } = useI18n();
|
||||
useEffect(() => {
|
||||
setSettings(current_overlay_settings);
|
||||
}, [current_overlay_settings]);
|
||||
|
||||
const [settings, setSettings] = useState(current_overlay_settings);
|
||||
const [timeout_id, setTimeoutId] = useState(null);
|
||||
|
||||
const [is_opened_position_controller, setIsOpenedPositionController] = useState(true);
|
||||
const togglePositionRotationController = () => {
|
||||
setIsOpenedPositionController(!is_opened_position_controller);
|
||||
};
|
||||
|
||||
const onchangeFunction = (key, value) => {
|
||||
setSettings((prev) => ({ ...prev, [key]: value }));
|
||||
|
||||
if (timeout_id) clearTimeout(timeout_id);
|
||||
|
||||
const newTimeoutId = setTimeout(() => {
|
||||
const new_data = { ...settings, [key]: value };
|
||||
set_overlay_settings(new_data);
|
||||
}, 50);
|
||||
|
||||
setTimeoutId(newTimeoutId);
|
||||
};
|
||||
|
||||
const selectFunction = (key, value) => {
|
||||
const new_data = { ...settings, [key]: value };
|
||||
set_overlay_settings(new_data);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<SwitchBoxContainer
|
||||
label={t("config_page.vr.overlay_enable")}
|
||||
variable={current_is_enabled_overlay}
|
||||
toggleFunction={toggle_is_enabled_overlay}
|
||||
/>
|
||||
<PageSwitcherContainer
|
||||
toggleFunction={togglePositionRotationController}
|
||||
is_selected={is_opened_position_controller}
|
||||
label_1={t("config_page.vr.position")}
|
||||
label_2={t("config_page.vr.rotation")}
|
||||
/>
|
||||
|
||||
<div className={styles.position_rotation_controls_box}>
|
||||
{is_opened_position_controller ? (
|
||||
<PositionControls settings={settings} onchangeFunction={onchangeFunction} ui_configs={ui_configs} default_ui_configs={default_ui_configs} selectFunction={selectFunction}/>
|
||||
) : (
|
||||
<RotationControls settings={settings} onchangeFunction={onchangeFunction} ui_configs={ui_configs} default_ui_configs={default_ui_configs} selectFunction={selectFunction}/>
|
||||
)}
|
||||
<SendSampleTextToggleButton />
|
||||
</div>
|
||||
<OtherControls settings={settings} onchangeFunction={onchangeFunction} ui_configs={ui_configs} />
|
||||
<RadioButtonContainer
|
||||
label={t("config_page.vr.tracker")}
|
||||
selectFunction={(value) => selectFunction("tracker", value)}
|
||||
name={id}
|
||||
options={[
|
||||
{ id: "HMD", label: t("config_page.vr.hmd") },
|
||||
{ id: "LeftHand", label: t("config_page.vr.left_hand") },
|
||||
{ id: "RightHand", label: t("config_page.vr.right_hand") },
|
||||
]}
|
||||
checked_variable={{data: settings.tracker}}
|
||||
column={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const PageSwitcherContainer = (props) => {
|
||||
const toggle_button_class_names__position = clsx(styles.controller_type_switcher, {
|
||||
[styles.is_selected]: props.is_selected,
|
||||
});
|
||||
const toggle_button_class_names__rotation = clsx(styles.controller_type_switcher, {
|
||||
[styles.is_selected]: !props.is_selected,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.controller_type_switch} onClick={() => props.toggleFunction()}>
|
||||
<div className={toggle_button_class_names__position}>
|
||||
<p className={styles.controller_switcher_label}>{props.label_1}</p>
|
||||
</div>
|
||||
<div className={toggle_button_class_names__rotation}>
|
||||
<p className={styles.controller_switcher_label}>{props.label_2}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export const PositionControls = ({ settings, onchangeFunction, selectFunction, ui_configs, default_ui_configs }) => {
|
||||
const { t } = useI18n();
|
||||
|
||||
const {
|
||||
variable_display: x_variable_display,
|
||||
is_max: is_max_position_x,
|
||||
is_min: is_min_position_x,
|
||||
countUp: countUpPositionX,
|
||||
countDown: countDownPositionX,
|
||||
} = useVariableControl("x_pos", settings, onchangeFunction, ui_configs);
|
||||
|
||||
const {
|
||||
variable_display: y_variable_display,
|
||||
is_max: is_max_position_y,
|
||||
is_min: is_min_position_y,
|
||||
countUp: countUpPositionY,
|
||||
countDown: countDownPositionY,
|
||||
} = useVariableControl("y_pos", settings, onchangeFunction, ui_configs);
|
||||
|
||||
const {
|
||||
variable_display: z_variable_display,
|
||||
is_max: is_max_position_z,
|
||||
is_min: is_min_position_z,
|
||||
countUp: countUpPositionZ,
|
||||
countDown: countDownPositionZ,
|
||||
} = useVariableControl("z_pos", settings, onchangeFunction, ui_configs);
|
||||
|
||||
return (
|
||||
<div className={styles.position_controls}>
|
||||
<div className={styles.position_wrapper}>
|
||||
<p className={clsx(styles.slider_label, styles.x_position_label)}>
|
||||
{t("config_page.vr.x_position")}
|
||||
<ResetButton onClickFunction={() => selectFunction("x_pos", default_ui_configs.x_pos)} />
|
||||
</p>
|
||||
<Slider
|
||||
className={styles.x_position_slider}
|
||||
no_padding={true}
|
||||
variable={settings.x_pos}
|
||||
step={ui_configs.x_pos.step}
|
||||
min={ui_configs.x_pos.min}
|
||||
max={ui_configs.x_pos.max}
|
||||
onchangeFunction={(value) => onchangeFunction("x_pos", value)}
|
||||
valueLabelDisplay={x_variable_display}
|
||||
valueLabelDisplayLocation="top"
|
||||
/>
|
||||
<AdjustButtonContainer
|
||||
wrapper_class_name={styles.x_position_button_wrapper}
|
||||
is_max={is_max_position_x}
|
||||
is_min={is_min_position_x}
|
||||
countUp={countUpPositionX}
|
||||
countDown={countDownPositionX}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.position_wrapper}>
|
||||
<p className={clsx(styles.slider_label, styles.y_position_label)}>
|
||||
{t("config_page.vr.y_position")}
|
||||
<ResetButton onClickFunction={() => selectFunction("y_pos", default_ui_configs.y_pos)} />
|
||||
</p>
|
||||
<Slider
|
||||
className={styles.y_position_slider}
|
||||
no_padding={true}
|
||||
variable={settings.y_pos}
|
||||
step={ui_configs.y_pos.step}
|
||||
min={ui_configs.y_pos.min}
|
||||
max={ui_configs.y_pos.max}
|
||||
onchangeFunction={(value) => onchangeFunction("y_pos", value)}
|
||||
orientation="vertical"
|
||||
valueLabelDisplay={y_variable_display}
|
||||
valueLabelDisplayLocation="right"
|
||||
/>
|
||||
<AdjustButtonContainer
|
||||
wrapper_class_name={styles.y_position_button_wrapper}
|
||||
is_max={is_max_position_y}
|
||||
is_min={is_min_position_y}
|
||||
countUp={countUpPositionY}
|
||||
countDown={countDownPositionY}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.position_wrapper}>
|
||||
<p className={clsx(styles.slider_label, styles.z_position_label)}>
|
||||
{t("config_page.vr.z_position")}
|
||||
<ResetButton onClickFunction={() => selectFunction("z_pos", default_ui_configs.z_pos)} />
|
||||
</p>
|
||||
<Slider
|
||||
className={styles.z_position_slider}
|
||||
no_padding={true}
|
||||
variable={settings.z_pos}
|
||||
step={ui_configs.z_pos.step}
|
||||
min={ui_configs.z_pos.min}
|
||||
max={ui_configs.z_pos.max}
|
||||
onchangeFunction={(value) => onchangeFunction("z_pos", value)}
|
||||
orientation="vertical"
|
||||
valueLabelDisplay={z_variable_display}
|
||||
valueLabelDisplayLocation="left"
|
||||
/>
|
||||
<AdjustButtonContainer
|
||||
wrapper_class_name={styles.z_position_button_wrapper}
|
||||
is_max={is_max_position_z}
|
||||
is_min={is_min_position_z}
|
||||
countUp={countUpPositionZ}
|
||||
countDown={countDownPositionZ}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const RotationControls = ({ settings, onchangeFunction, selectFunction, ui_configs, default_ui_configs }) => {
|
||||
const { t } = useI18n();
|
||||
|
||||
const {
|
||||
variable_display: x_variable_display,
|
||||
is_max: is_max_rotation_x,
|
||||
is_min: is_min_rotation_x,
|
||||
countUp: countUpRotationX,
|
||||
countDown: countDownRotationX,
|
||||
} = useVariableControl("x_rotation", settings, onchangeFunction, ui_configs);
|
||||
|
||||
const {
|
||||
variable_display: y_variable_display,
|
||||
is_max: is_max_rotation_y,
|
||||
is_min: is_min_rotation_y,
|
||||
countUp: countUpRotationY,
|
||||
countDown: countDownRotationY,
|
||||
} = useVariableControl("y_rotation", settings, onchangeFunction, ui_configs);
|
||||
|
||||
const {
|
||||
variable_display: z_variable_display,
|
||||
is_max: is_max_rotation_z,
|
||||
is_min: is_min_rotation_z,
|
||||
countUp: countUpRotationZ,
|
||||
countDown: countDownRotationZ,
|
||||
} = useVariableControl("z_rotation", settings, onchangeFunction, ui_configs);
|
||||
|
||||
return (
|
||||
<div className={styles.rotation_controls}>
|
||||
<div className={styles.rotation_wrapper}>
|
||||
<p className={clsx(styles.slider_label, styles.x_rotation_label)}>
|
||||
{t("config_page.vr.x_rotation")}
|
||||
<ResetButton onClickFunction={() => selectFunction("x_rotation", default_ui_configs.x_rotation)} />
|
||||
</p>
|
||||
<Slider
|
||||
className={styles.x_rotation_slider}
|
||||
no_padding={true}
|
||||
variable={-settings.x_rotation}
|
||||
valueLabelFormat={settings.x_rotation}
|
||||
step={ui_configs.x_rotation.step}
|
||||
min={ui_configs.x_rotation.min}
|
||||
max={ui_configs.x_rotation.max}
|
||||
onchangeFunction={(value) => onchangeFunction("x_rotation", -value)}
|
||||
orientation="vertical"
|
||||
valueLabelDisplay={x_variable_display}
|
||||
valueLabelDisplayLocation="right"
|
||||
/>
|
||||
<AdjustButtonContainer
|
||||
wrapper_class_name={styles.x_rotation_button_wrapper}
|
||||
is_max={is_min_rotation_x}
|
||||
is_min={is_max_rotation_x}
|
||||
countUp={countDownRotationX}
|
||||
countDown={countUpRotationX}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.rotation_wrapper}>
|
||||
<p className={clsx(styles.slider_label, styles.y_rotation_label)}>
|
||||
{t("config_page.vr.y_rotation")}
|
||||
<ResetButton onClickFunction={() => selectFunction("y_rotation", default_ui_configs.y_rotation)} />
|
||||
</p>
|
||||
<Slider
|
||||
className={styles.y_rotation_slider}
|
||||
no_padding={true}
|
||||
variable={settings.y_rotation}
|
||||
step={ui_configs.y_rotation.step}
|
||||
min={ui_configs.y_rotation.min}
|
||||
max={ui_configs.y_rotation.max}
|
||||
onchangeFunction={(value) => onchangeFunction("y_rotation", value)}
|
||||
valueLabelDisplay={y_variable_display}
|
||||
valueLabelDisplayLocation="top"
|
||||
/>
|
||||
<AdjustButtonContainer
|
||||
wrapper_class_name={styles.y_rotation_button_wrapper}
|
||||
is_max={is_max_rotation_y}
|
||||
is_min={is_min_rotation_y}
|
||||
countUp={countUpRotationY}
|
||||
countDown={countDownRotationY}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.rotation_wrapper}>
|
||||
<p className={clsx(styles.slider_label, styles.z_rotation_label)}>
|
||||
{t("config_page.vr.z_rotation")}
|
||||
<ResetButton onClickFunction={() => selectFunction("z_rotation", default_ui_configs.z_rotation)} />
|
||||
</p>
|
||||
<Slider
|
||||
className={styles.z_rotation_slider}
|
||||
no_padding={true}
|
||||
variable={settings.z_rotation}
|
||||
step={ui_configs.z_rotation.step}
|
||||
min={ui_configs.z_rotation.min}
|
||||
max={ui_configs.z_rotation.max}
|
||||
onchangeFunction={(value) => onchangeFunction("z_rotation", value)}
|
||||
orientation="vertical"
|
||||
valueLabelDisplay={z_variable_display}
|
||||
valueLabelDisplayLocation="left"
|
||||
/>
|
||||
<AdjustButtonContainer
|
||||
wrapper_class_name={styles.z_rotation_button_wrapper}
|
||||
is_max={is_max_rotation_z}
|
||||
is_min={is_min_rotation_z}
|
||||
countUp={countUpRotationZ}
|
||||
countDown={countDownRotationZ}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AdjustButtonContainer = ({ wrapper_class_name, is_max, is_min, countUp, countDown }) => {
|
||||
return (
|
||||
<div className={wrapper_class_name}>
|
||||
<div
|
||||
className={clsx(
|
||||
styles.button_wrapper,
|
||||
{
|
||||
[styles.is_disabled]: is_max,
|
||||
[styles.up]: true,
|
||||
}
|
||||
)}
|
||||
onClick={countUp}
|
||||
>
|
||||
<TriangleSvg className={styles.adjust_button_triangle_svg} />
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
styles.button_wrapper,
|
||||
{
|
||||
[styles.is_disabled]: is_min,
|
||||
}
|
||||
)}
|
||||
onClick={countDown}
|
||||
>
|
||||
<TriangleSvg className={styles.adjust_button_triangle_svg} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const OtherControls = ({settings, onchangeFunction, ui_configs}) => {
|
||||
const { t } = useI18n();
|
||||
|
||||
const ui_variable_opacity = (settings.opacity * 100).toFixed(0);
|
||||
const ui_variable_ui_scaling = (settings.ui_scaling * 100).toFixed(0);
|
||||
|
||||
return(
|
||||
<div className={styles.other_controls}>
|
||||
<div className={styles.other_controls_wrapper}>
|
||||
<p className={clsx(styles.other_controls_slider_label, styles.opacity_label)}>
|
||||
{t("config_page.vr.opacity")}
|
||||
</p>
|
||||
<Slider
|
||||
className={clsx(styles.other_controls_slider, styles.opacity_slider)}
|
||||
no_padding={true}
|
||||
variable={settings.opacity * 100}
|
||||
valueLabelFormat={`${ui_variable_opacity}%`}
|
||||
step={5}
|
||||
min={10}
|
||||
max={100}
|
||||
onchangeFunction={(value) => onchangeFunction("opacity", value / 100)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.other_controls_wrapper}>
|
||||
<p className={clsx(styles.other_controls_slider_label, styles.ui_scaling_label)}>
|
||||
{t("config_page.vr.ui_scaling")}
|
||||
</p>
|
||||
<Slider
|
||||
className={clsx(styles.other_controls_slider, styles.ui_scaling_slider)}
|
||||
no_padding={true}
|
||||
variable={settings.ui_scaling * 100}
|
||||
valueLabelFormat={`${ui_variable_ui_scaling}%`}
|
||||
step={ui_configs.ui_scaling.step}
|
||||
min={ui_configs.ui_scaling.min}
|
||||
max={ui_configs.ui_scaling.max}
|
||||
onchangeFunction={(value) => onchangeFunction("ui_scaling", value / 100)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.other_controls_wrapper}>
|
||||
<p className={clsx(styles.other_controls_slider_label, styles.display_duration_label)}>{t("config_page.vr.display_duration")}</p>
|
||||
<Slider
|
||||
className={clsx(styles.other_controls_slider, styles.display_duration_slider)}
|
||||
no_padding={true}
|
||||
variable={settings.display_duration}
|
||||
valueLabelFormat={`${settings.display_duration} second(s)`}
|
||||
step={1}
|
||||
min={1}
|
||||
max={60}
|
||||
onchangeFunction={(value) => onchangeFunction("display_duration", value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.other_controls_wrapper}>
|
||||
<p className={clsx(styles.other_controls_slider_label, styles.fadeout_duration_label)}>{t("config_page.vr.fadeout_duration")}</p>
|
||||
<Slider
|
||||
className={clsx(styles.other_controls_slider, styles.fadeout_duration_slider)}
|
||||
no_padding={true}
|
||||
variable={settings.fadeout_duration}
|
||||
valueLabelFormat={`${settings.fadeout_duration} second(s)`}
|
||||
step={1}
|
||||
min={0}
|
||||
max={5}
|
||||
onchangeFunction={(value) => onchangeFunction("fadeout_duration", value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const CommonSettingsContainer = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentOverlayShowOnlyTranslatedMessages, toggleOverlayShowOnlyTranslatedMessages } = useVr();
|
||||
|
||||
return (
|
||||
<div className={styles.common_container}>
|
||||
<SectionLabelComponent label={t("config_page.vr.common_settings")} />
|
||||
<CheckboxContainer
|
||||
label={t("config_page.vr.overlay_show_only_translated_messages.label")}
|
||||
variable={currentOverlayShowOnlyTranslatedMessages}
|
||||
toggleFunction={toggleOverlayShowOnlyTranslatedMessages}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const SendSampleTextToggleButton = () => {
|
||||
const { t } = useI18n();
|
||||
const { sendTextToOverlay } = useVr();
|
||||
const [is_started, setIsStarted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let interval_id;
|
||||
|
||||
if (is_started) {
|
||||
interval_id = setInterval(() => {
|
||||
const text_data = Array.from(
|
||||
{ length: randomIntMinMax(1, 5) },
|
||||
() => t("config_page.vr.sample_text_button.sample_text")
|
||||
).join(" ");
|
||||
sendTextToOverlay(text_data);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
return () => {
|
||||
if (interval_id) {
|
||||
clearInterval(interval_id);
|
||||
}
|
||||
};
|
||||
}, [is_started]);
|
||||
|
||||
const toggleFunction = () => {
|
||||
setIsStarted(!is_started);
|
||||
};
|
||||
|
||||
const label = is_started
|
||||
? t("config_page.vr.sample_text_button.stop")
|
||||
: t("config_page.vr.sample_text_button.start");
|
||||
|
||||
return (
|
||||
<div className={styles.sample_text_button_wrapper}>
|
||||
<button
|
||||
className={clsx(styles.sample_text_button, { [styles.is_started]: is_started })}
|
||||
onClick={toggleFunction}
|
||||
>
|
||||
{is_started ? (
|
||||
<SquareSvg className={styles.sample_text_button_square_svg} />
|
||||
) : (
|
||||
<TriangleSvg className={styles.sample_text_button_triangle_svg} />
|
||||
)}
|
||||
</button>
|
||||
<p className={styles.sample_text_button_label}>{label}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const useVariableControl = (key, settings, onchangeFunction, ui_configs) => {
|
||||
const [variable_display, setVariableDisplay] = useState("auto");
|
||||
|
||||
const [is_max, setIsMax] = useState(settings[key] >= ui_configs[key].max);
|
||||
const [is_min, setIsMin] = useState(settings[key] <= ui_configs[key].min);
|
||||
|
||||
const timerRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTimeout(timerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const triggerDisplay = () => {
|
||||
setVariableDisplay("on");
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = setTimeout(() => {
|
||||
setVariableDisplay("auto");
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsMax(settings[key] >= ui_configs[key].max);
|
||||
setIsMin(settings[key] <= ui_configs[key].min);
|
||||
}, [settings[key]]);
|
||||
|
||||
const countUp = () => {
|
||||
if (is_max) return;
|
||||
const step = ui_configs[key].step;
|
||||
const new_value = parseFloat((settings[key] + step).toFixed(2));
|
||||
onchangeFunction(key, new_value);
|
||||
triggerDisplay();
|
||||
};
|
||||
|
||||
const countDown = () => {
|
||||
if (is_min) return;
|
||||
const step = ui_configs[key].step;
|
||||
const new_value = parseFloat((settings[key] - step).toFixed(2));
|
||||
onchangeFunction(key, new_value);
|
||||
triggerDisplay();
|
||||
};
|
||||
|
||||
return {
|
||||
variable_display,
|
||||
is_max,
|
||||
is_min,
|
||||
countUp,
|
||||
countDown,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,352 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
gap: 4rem;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 56rem;
|
||||
gap: 4rem;
|
||||
}
|
||||
|
||||
.controller_type_switch {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
border: 0.1rem solid var(--dark_600_color);
|
||||
border-radius: 0.4rem;
|
||||
width: 80%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: var(--dark_600_color);
|
||||
&:hover {
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
}
|
||||
.controller_type_switcher {
|
||||
width: 100%;
|
||||
&.is_selected {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
&.is_selected .controller_switcher_label {
|
||||
color: var(--dark_200_color);
|
||||
}
|
||||
}
|
||||
.controller_switcher_label {
|
||||
padding: 1rem;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.position_rotation_controls_box {
|
||||
margin-top: 8rem;
|
||||
position: relative;
|
||||
aspect-ratio: 1 / 1;
|
||||
width: 36%;
|
||||
max-width: 36rem;
|
||||
transform: translate(-10%);
|
||||
}
|
||||
|
||||
.sample_text_button_wrapper {
|
||||
position: absolute;
|
||||
bottom: -12%;
|
||||
left: -80%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
// transform: translate(-50%, -50%);
|
||||
}
|
||||
.sample_text_button {
|
||||
background-color: var(--dark_850_color);
|
||||
padding: 1.8rem;
|
||||
border-radius: 50%;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_925_color);
|
||||
}
|
||||
&.is_started {
|
||||
background-color: var(--primary_600_color);
|
||||
&:hover {
|
||||
background-color: var(--primary_500_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--primary_700_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.sample_text_button_triangle_svg, .sample_text_button_square_svg {
|
||||
width: 2.4rem;
|
||||
}
|
||||
.sample_text_button_triangle_svg {
|
||||
transform: translateX(10%) rotate(90deg);
|
||||
}
|
||||
.sample_text_button_label {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 110%;
|
||||
// bottom: -2rem;
|
||||
transform: translateX(-50%);
|
||||
white-space: pre-wrap;
|
||||
font-size: 1.2rem;
|
||||
width: max-content;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// .position_controls {
|
||||
// background-color: gray;
|
||||
// }
|
||||
|
||||
// .position_wrapper {
|
||||
// background-color: gray;
|
||||
// }
|
||||
|
||||
.slider_label {
|
||||
font-size: 1.4rem;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.x_position_label {
|
||||
position: absolute;
|
||||
bottom: -5rem;
|
||||
right: -46%;
|
||||
justify-content: end;
|
||||
}
|
||||
.y_position_label {
|
||||
position: absolute;
|
||||
bottom: 110%;
|
||||
right: 119%;
|
||||
justify-content: end;
|
||||
}
|
||||
.z_position_label {
|
||||
position: absolute;
|
||||
top: 14%;
|
||||
left: 110%;
|
||||
}
|
||||
|
||||
.x_position_slider {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 27%;
|
||||
width: 100%;
|
||||
height: 0%;
|
||||
}
|
||||
|
||||
.y_position_slider {
|
||||
position: absolute;
|
||||
bottom: 27%;
|
||||
left: 0;
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.z_position_slider {
|
||||
position: absolute;
|
||||
bottom: 80%;
|
||||
left: 88%;
|
||||
transform: translate(50%,50%) rotate(45deg);
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
%variable-button {
|
||||
width: 3.8rem;
|
||||
border-radius: 0.4rem;
|
||||
aspect-ratio: 1.2 / 1;
|
||||
background-color: var(--dark_850_color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1.6rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--primary_500_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--primary_600_color);
|
||||
}
|
||||
&.is_disabled {
|
||||
pointer-events: none;
|
||||
background-color: var(--dark_875_color);
|
||||
& .adjust_button_triangle_svg {
|
||||
color: var(--dark_800_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin variable-button-wrapper($vertical-pos, $vertical-value, $horizontal-pos, $horizontal-value, $rotate: 0deg) {
|
||||
position: absolute;
|
||||
#{$vertical-pos}: $vertical-value;
|
||||
#{$horizontal-pos}: $horizontal-value;
|
||||
display: flex;
|
||||
gap: 1.6rem;
|
||||
flex-direction: column;
|
||||
transform: translate(-50%) rotate($rotate);
|
||||
}
|
||||
|
||||
.button_wrapper {
|
||||
@extend %variable-button;
|
||||
|
||||
&.up .adjust_button_triangle_svg {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
&:not(.up) .adjust_button_triangle_svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
&.is_disabled {
|
||||
pointer-events: none;
|
||||
color: var(--dark_875_color);
|
||||
}
|
||||
}
|
||||
|
||||
.adjust_button_triangle_svg {
|
||||
width: 1.8rem;
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.y_position_button_wrapper {
|
||||
@include variable-button-wrapper(top, 30%, left, -26%);
|
||||
}
|
||||
|
||||
.x_position_button_wrapper {
|
||||
@include variable-button-wrapper(bottom, -38%, left, 46%, 90deg);
|
||||
}
|
||||
|
||||
.z_position_button_wrapper {
|
||||
@include variable-button-wrapper(bottom, 26%, right, -4%, 45deg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// .rotation_controls {
|
||||
// background-color: gray;
|
||||
// }
|
||||
|
||||
// .rotation_wrapper {
|
||||
// background-color: gray;
|
||||
// }
|
||||
|
||||
.x_rotation_label {
|
||||
position: absolute;
|
||||
bottom: 110%;
|
||||
right: 119%;
|
||||
justify-content: end;
|
||||
}
|
||||
.y_rotation_label {
|
||||
position: absolute;
|
||||
bottom: -5rem;
|
||||
right: -46%;
|
||||
justify-content: end;
|
||||
}
|
||||
.z_rotation_label {
|
||||
position: absolute;
|
||||
top: -20%;
|
||||
right: -100%;
|
||||
}
|
||||
|
||||
.x_rotation_slider {
|
||||
position: absolute;
|
||||
bottom: 27%;
|
||||
left: 0;
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.y_rotation_slider {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 27%;
|
||||
width: 100%;
|
||||
height: 0%;
|
||||
}
|
||||
|
||||
.z_rotation_slider {
|
||||
position: absolute;
|
||||
bottom: 80%;
|
||||
left: 100%;
|
||||
transform: translate(50%,50%) rotate(-45deg);
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.x_rotation_button_wrapper {
|
||||
@include variable-button-wrapper(top, 30%, left, -26%);
|
||||
}
|
||||
|
||||
.y_rotation_button_wrapper {
|
||||
@include variable-button-wrapper(bottom, -38%, left, 46%, 90deg);
|
||||
}
|
||||
|
||||
.z_rotation_button_wrapper {
|
||||
@include variable-button-wrapper(bottom, 50%, right, -60%, -45deg);
|
||||
}
|
||||
|
||||
.other_controls {
|
||||
margin-top: 6rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.other_controls_wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.other_controls_slider {
|
||||
// margin-left: 18rem;
|
||||
// width: 60%;
|
||||
}
|
||||
|
||||
.other_controls_slider_label {
|
||||
// position: absolute;
|
||||
font-size: 1.6rem;
|
||||
flex-shrink: 0;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.common_container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.common_label {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.restore_default_settings_button {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 6rem;
|
||||
padding: 0.8rem;
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_775_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import styles from "./SidebarSection.module.scss";
|
||||
|
||||
export const SidebarSection = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.scroll_container}>
|
||||
<div className={styles.tabs_wrapper}>
|
||||
<Tab tab_id="device" />
|
||||
<Tab tab_id="appearance" />
|
||||
<Tab tab_id="translation" />
|
||||
<Tab tab_id="transcription" />
|
||||
<Tab tab_id="vr" />
|
||||
<Tab tab_id="others" />
|
||||
<Tab tab_id="hotkeys" />
|
||||
<Tab tab_id="plugins" />
|
||||
<Tab tab_id="advanced_settings" />
|
||||
</div>
|
||||
<div className={styles.separated_tabs_wrapper}>
|
||||
<Tab tab_id="supporters" />
|
||||
<Tab tab_id="about_vrct" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useI18n } from "@useI18n";
|
||||
import { useStore_SelectedConfigTabId } from "@store";
|
||||
|
||||
const Tab = (props) => {
|
||||
const { t } = useI18n();
|
||||
const { updateSelectedConfigTabId, currentSelectedConfigTabId } = useStore_SelectedConfigTabId();
|
||||
const onclickFunction = () => {
|
||||
updateSelectedConfigTabId(props.tab_id);
|
||||
};
|
||||
|
||||
const tab_container_class_names = clsx(styles["tab_container"], {
|
||||
[styles["is_selected"]]: (currentSelectedConfigTabId.data === props.tab_id) ? true : false
|
||||
});
|
||||
const switch_indicator_class_names = clsx(styles["switch_indicator"], {
|
||||
[styles["is_selected"]]: (currentSelectedConfigTabId.data === props.tab_id) ? true : false
|
||||
});
|
||||
|
||||
const getLabel = () => {
|
||||
if (props.tab_id === "vr") return "VR";
|
||||
if (props.tab_id === "supporters") return (
|
||||
<>Supporters<span className={styles.crown_emoji}>👑</span></>
|
||||
);
|
||||
if (props.tab_id === "about_vrct") return "About VRCT";
|
||||
return t(`config_page.side_menu_labels.${props.tab_id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={tab_container_class_names} onClick={onclickFunction}>
|
||||
<p className={styles.tab_text}>{getLabel()}</p>
|
||||
<div className={switch_indicator_class_names}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
.container {
|
||||
width: var(--config_page_sidebar_width);
|
||||
flex-shrink: 0;
|
||||
padding: 0rem 0rem 5.8rem 1.2rem;
|
||||
}
|
||||
|
||||
.scroll_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow-y: auto;
|
||||
// overflow-x: hidden;
|
||||
height: 100%;
|
||||
max-height: 60rem;
|
||||
}
|
||||
|
||||
.tabs_wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tab_container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
padding: 0.8rem 0 0.8rem 1rem;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
&.is_selected {
|
||||
background-color: inherit;
|
||||
color: var(--primary_200_color);
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.switch_indicator {
|
||||
display: none;
|
||||
&.is_selected {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0rem;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 0.2rem;
|
||||
height: 2.6rem;
|
||||
border-radius: 0.1rem;
|
||||
background-color: var(--primary_300_color);
|
||||
}
|
||||
}
|
||||
|
||||
.tab_text {
|
||||
// overflow: hidden;
|
||||
font-size: 1.6rem;
|
||||
// text-overflow: ellipsis;
|
||||
position: relative;
|
||||
}
|
||||
.crown_emoji {
|
||||
font-size: 1.6rem;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 100%;
|
||||
transform: translateY(-50%);
|
||||
padding-left: 0.4rem;
|
||||
}
|
||||
|
||||
.separated_tabs_wrapper {
|
||||
// padding-bottom: 1.2rem;
|
||||
}
|
||||
38
src-ui/views/app/config_page/topbar/Topbar.jsx
Normal file
38
src-ui/views/app/config_page/topbar/Topbar.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
import clsx from "clsx";
|
||||
|
||||
import styles from "./Topbar.module.scss";
|
||||
import { useIsOpenedConfigPage } from "@logics_common";
|
||||
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||
|
||||
import { TitleBox } from "./title_box/TitleBox";
|
||||
import { SectionTitleBox } from "./section_title_box/SectionTitleBox";
|
||||
import { CompactSwitchBox } from "./compact_switch_box/CompactSwitchBox";
|
||||
|
||||
export const Topbar = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentIsOpenedConfigPage, setIsOpenedConfigPage } = useIsOpenedConfigPage();
|
||||
const closeConfigPage = () => {
|
||||
setIsOpenedConfigPage(false);
|
||||
};
|
||||
return (
|
||||
<div className={clsx(styles.container, {
|
||||
[styles.show_config]: currentIsOpenedConfigPage.data,
|
||||
[styles.show_main]: !currentIsOpenedConfigPage.data
|
||||
})}>
|
||||
<div className={styles.wrapper} onClick={() => closeConfigPage()}>
|
||||
<div className={styles.go_back_button}>
|
||||
<ArrowLeftSvg className={styles.arrow_left_svg} />
|
||||
</div>
|
||||
<div className={styles.go_back_text_wrapper}>
|
||||
<p className={styles.go_back_text}>{t("common.go_back_button_label")}</p>
|
||||
</div>
|
||||
|
||||
|
||||
{/* <TitleBox />
|
||||
<SectionTitleBox />
|
||||
<CompactSwitchBox /> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
82
src-ui/views/app/config_page/topbar/Topbar.module.scss
Normal file
82
src-ui/views/app/config_page/topbar/Topbar.module.scss
Normal file
@@ -0,0 +1,82 @@
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 0%;
|
||||
transition: top 0.5s ease;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.show_config.container {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.show_main.container {
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
|
||||
.wrapper {
|
||||
background-color: var(--dark_850_color);
|
||||
cursor: pointer;
|
||||
height: 1rem;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
height: 2rem;
|
||||
.go_back_button {
|
||||
top: -12rem;
|
||||
transform: rotate(35deg);
|
||||
}
|
||||
.arrow_left_svg {
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
.go_back_text {
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
.go_back_text_wrapper {
|
||||
padding-top: 8.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.go_back_button {
|
||||
height: 16rem;
|
||||
width: 16rem;
|
||||
border-radius: 1.2rem;
|
||||
background-color: var(--dark_850_color);
|
||||
position: absolute;
|
||||
top: -13.2rem;
|
||||
right: 10rem;
|
||||
transform: rotate(30deg);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.arrow_left_svg {
|
||||
height: 2.8rem;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
bottom: 0.4rem;
|
||||
color: var(--dark_600_color);
|
||||
transform: rotate(-100deg);
|
||||
}
|
||||
|
||||
.go_back_text_wrapper {
|
||||
position: absolute;
|
||||
top: -4.6rem;
|
||||
right: 0rem;
|
||||
padding-top: 7.2rem;
|
||||
padding-bottom: 1rem;
|
||||
width: 15.2rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.go_back_text {
|
||||
color: var(--dark_650_color);
|
||||
font-size: 1.6rem;
|
||||
padding-left: 4rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
|
||||
import styles from "./CompactSwitchBox.module.scss";
|
||||
|
||||
export const CompactSwitchBox = () => {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{t("config_page.compact_mode")}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
.container {
|
||||
// flex: 0;
|
||||
// width: 100%;
|
||||
width: 14rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
import styles from "./SectionTitleBox.module.scss";
|
||||
import { useStore_SelectedConfigTabId } from "@store";
|
||||
|
||||
export const SectionTitleBox = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentSelectedConfigTabId } = useStore_SelectedConfigTabId();
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p className={styles.title}>{t(`config_page.side_menu_labels.${currentSelectedConfigTabId.data}`)}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
.container {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
14
src-ui/views/app/config_page/topbar/title_box/TitleBox.jsx
Normal file
14
src-ui/views/app/config_page/topbar/title_box/TitleBox.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
|
||||
import styles from "./TitleBox.module.scss";
|
||||
import chato_img from "@images/chato_white.png";
|
||||
|
||||
export const TitleBox = () => {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<img src={chato_img} className={styles.logo_chato} alt="VRCT logo chato" />
|
||||
<p className={styles.title}>{t("config_page.config_title")}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
.container {
|
||||
// flex: 0;
|
||||
width: var(--config_page_sidebar_width);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
padding-left: 2.6rem;
|
||||
gap: 1.4rem;
|
||||
}
|
||||
|
||||
.logo_chato {
|
||||
width: 3.2rem;
|
||||
padding-top: 0.6rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
48
src-ui/views/app/config_page/version_label/VersionLabel.jsx
Normal file
48
src-ui/views/app/config_page/version_label/VersionLabel.jsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
import { useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import styles from "./VersionLabel.module.scss";
|
||||
|
||||
import { useSoftwareVersion, useComputeMode } from "@logics_common";
|
||||
import CopySvg from "@images/copy.svg?react";
|
||||
import CheckMarkSvg from "@images/check_mark.svg?react";
|
||||
|
||||
export const VersionLabel = () => {
|
||||
const [is_copied, setIsCopied] = useState(false);
|
||||
|
||||
const { t } = useI18n();
|
||||
const { currentSoftwareVersion } = useSoftwareVersion();
|
||||
const { currentComputeMode } = useComputeMode();
|
||||
|
||||
const version_label = currentComputeMode.data === "cpu"
|
||||
? t("config_page.common.version", { version: currentSoftwareVersion.data })
|
||||
: currentComputeMode.data === "cuda"
|
||||
? t("config_page.common.version", { version: currentSoftwareVersion.data }) + " CUDA"
|
||||
: t("config_page.common.version", { version: currentSoftwareVersion.data });
|
||||
|
||||
const is_cpu = currentComputeMode.data === "cpu";
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
if (is_copied) return;
|
||||
const copy_text = is_cpu ? `${currentSoftwareVersion.data}` : `${currentSoftwareVersion.data} CUDA`;
|
||||
await navigator.clipboard.writeText(copy_text);
|
||||
setIsCopied(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={clsx(styles.wrapper, {[styles.is_copied]: is_copied})} onClick={copyToClipboard}>
|
||||
<p className={styles.version_label}>{version_label}</p>
|
||||
{is_copied
|
||||
? <CheckMarkSvg className={styles.check_mark_svg}/>
|
||||
: <CopySvg className={styles.copy_svg}/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
.container {
|
||||
position: absolute;
|
||||
bottom: 1.2rem;
|
||||
left: 1.4rem;
|
||||
font-size: 1.2rem;
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
cursor: pointer;
|
||||
&.is_copied {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.version_label {
|
||||
font-size: 1.2rem;
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
|
||||
.copy_svg {
|
||||
width: 1.4rem;
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
|
||||
.check_mark_svg {
|
||||
width: 1.4rem;
|
||||
color: var(--primary_300_color);
|
||||
}
|
||||
Reference in New Issue
Block a user