[Refactor] Move to src-ui/views and src-ui/logics structure.

This commit is contained in:
Sakamoto Shiina
2025-11-05 11:49:48 +09:00
parent 62f7c6d534
commit db820375f1
339 changed files with 19 additions and 19 deletions

View File

@@ -0,0 +1,33 @@
import clsx from "clsx";
import styles from "./SidebarSection.module.scss";
import { useStore_IsOpenedLanguageSelector } from "@store";
import { useIsMainPageCompactMode } from "@logics_main";
import { Logo } from "./logo/Logo";
import { MainFunctionSwitch } from "./main_function_switch/MainFunctionSwitch";
import { LanguageSettings } from "./language_settings/LanguageSettings";
import { OpenSettings } from "./open_settings/OpenSettings";
export const SidebarSection = () => {
const { currentIsMainPageCompactMode } = useIsMainPageCompactMode();
const container_class_name = clsx(styles.container, {
[styles.is_compact_mode]: currentIsMainPageCompactMode.data
});
const { currentIsOpenedLanguageSelector } = useStore_IsOpenedLanguageSelector();
const scroll_container_class_names = clsx(styles.scroll_container, {
[styles.is_opened]: (currentIsOpenedLanguageSelector.data.your_language === true || currentIsOpenedLanguageSelector.data.target_language === true)
});
return (
<div className={container_class_name}>
<Logo />
<div className={scroll_container_class_names}>
<MainFunctionSwitch />
{!currentIsMainPageCompactMode.data && <LanguageSettings />}
</div>
<OpenSettings />
</div>
);
};

View File

@@ -0,0 +1,51 @@
.container {
position: relative;
min-width: 23rem;
height: 100%;
display: flex;
flex-direction: column;
background-color: var(--dark_850_color);
&.is_compact_mode {
min-width: auto;
.scroll_container {
// overflow-y: hidden;
// width: auto;
}
}
}
.scroll_container {
width: calc(100% + 0.8rem);
overflow-y: scroll;
overflow-x: hidden;
margin-bottom: calc(2rem + 1.6rem + 2rem); // config button's sizes (svg + padding + margin).
pointer-events: auto;
z-index: 1;
&::-webkit-scrollbar {
width: 0.8rem;
}
&::-webkit-scrollbar-track {
background-color: var(--dark_888_color);
border-radius: 0.4rem;
}
&::-webkit-scrollbar-thumb {
background-color: var(--dark_888_color);
border-radius: 0.4rem;
}
&.is_opened {
&::-webkit-scrollbar-track {
background-color: var(--dark_875_color);
}
&::-webkit-scrollbar-thumb {
background-color: var(--dark_875_color);
}
}
&:hover {
&::-webkit-scrollbar-thumb {
background-color: var(--dark_800_color);
}
}
}

View File

@@ -0,0 +1,55 @@
import { useI18n } from "@useI18n";
import styles from "./LanguageSettings.module.scss";
import { PresetTabSelector } from "./preset_tab_selector/PresetTabSelector";
import { LanguageSelectorOpenButton } from "./language_selector_open_button/LanguageSelectorOpenButton";
import { LanguageSwapButton } from "./language_swap_button/LanguageSwapButton";
import { TranslatorSelectorOpenButton } from "./translator_selector_open_button/TranslatorSelectorOpenButton";
import { AddRemoveTargetLanguageButtons } from "./add_remove_target_language_buttons/AddRemoveTargetLanguageButtons";
import { useStore_IsOpenedTranslatorSelector } from "@store";
export const LanguageSettings = () => {
const { t } = useI18n();
const { updateIsOpenedTranslatorSelector } = useStore_IsOpenedTranslatorSelector();
const closeTranslatorSelector = () => updateIsOpenedTranslatorSelector(false);
return (
<div className={styles.container} onMouseLeave={closeTranslatorSelector}>
<p className={styles.title}>{t("main_page.language_settings")}</p>
<PresetTabSelector />
<PresetContainer />
</div>
);
};
import MicSvg from "@images/mic.svg?react";
import HeadphonesSvg from "@images/headphones.svg?react";
import { useMainFunction } from "@logics_main";
const PresetContainer = () => {
const { t } = useI18n();
const { currentTranscriptionSendStatus, currentTranscriptionReceiveStatus } = useMainFunction();
const yourLanguageSettings = {
TurnedOnSvgComponent: MicSvg,
is_turned_on: currentTranscriptionSendStatus.data,
};
const targetLanguageSettings = {
TurnedOnSvgComponent: HeadphonesSvg,
is_turned_on: currentTranscriptionReceiveStatus.data,
};
return (
<div className={styles.preset_container}>
<LanguageSelectorOpenButton {...yourLanguageSettings} selector_key="your_language" target_key="1"/>
<LanguageSwapButton />
<div className={styles.target_language_containers}>
<LanguageSelectorOpenButton {...targetLanguageSettings} selector_key="target_language" target_key="1" />
<LanguageSelectorOpenButton {...targetLanguageSettings} selector_key="target_language" target_key="2" />
<LanguageSelectorOpenButton {...targetLanguageSettings} selector_key="target_language" target_key="3" />
</div>
<AddRemoveTargetLanguageButtons />
<TranslatorSelectorOpenButton />
</div>
);
};

View File

@@ -0,0 +1,29 @@
.container {
display: flex;
flex-direction: column;
align-items: center;
}
.title {
font-size: 1.4rem;
padding-top: 1rem;
padding-bottom: 0.8rem;
color: var(--dark_400_color);
}
.preset_container {
width: 100%;
padding-top: 0.8rem;
background-color: var(--dark_800_color);
display: flex;
flex-direction: column;
align-items: center;
}
.target_language_containers {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.2rem;
}

View File

@@ -0,0 +1,34 @@
import clsx from "clsx";
import styles from "./AddRemoveTargetLanguageButtons.module.scss";
import RemoveSvg from "@images/remove.svg?react";
import AddSvg from "@images/add.svg?react";
import { useLanguageSettings } from "@logics_main";
export const AddRemoveTargetLanguageButtons = () => {
const {
currentSelectedPresetTabNumber,
// currentSelectedYourLanguages,
currentSelectedTargetLanguages,
removeTargetLanguage,
addTargetLanguage,
} = useLanguageSettings();
const remove_button_class = clsx(styles.remove_target_language_button, {
[styles.is_disabled]: !currentSelectedTargetLanguages.data[currentSelectedPresetTabNumber.data]["2"].enable,
});
const add_button_class = clsx(styles.add_target_language_button, {
[styles.is_disabled]: currentSelectedTargetLanguages.data[currentSelectedPresetTabNumber.data]["3"].enable,
});
return (
<div className={styles.add_remove_target_language_container}>
<div className={remove_button_class} onClick={removeTargetLanguage}>
<RemoveSvg className={styles.remove_svg} />
</div>
<div className={add_button_class} onClick={addTargetLanguage}>
<AddSvg className={styles.add_svg} />
</div>
</div>
);
};

View File

@@ -0,0 +1,40 @@
.add_remove_target_language_container {
width: 100%;
display: flex;
justify-content: end;
align-items: center;
}
.remove_target_language_button, .add_target_language_button {
display: flex;
justify-content: center;
align-items: center;
padding: 0.6rem 1rem;
cursor: pointer;
background-color: var(--dark_825_color);
&:hover {
background-color: var(--dark_875_color);
}
&:active {
background-color: var(--dark_900_color);
}
&.is_disabled {
pointer-events: none;
.remove_svg, .add_svg {
color: var(--dark_700_color);
}
}
}
.remove_target_language_button {
border-radius: 0 0 0 0.4rem;
}
.add_target_language_button {
border-radius: 0 0 0.4rem 0;
}
.remove_svg, .add_svg {
width: 0.8rem;
color: var(--dark_200_color);
}

View File

@@ -0,0 +1,73 @@
import { useI18n } from "@useI18n";
import clsx from "clsx";
import styles from "./LanguageSelectorOpenButton.module.scss";
import ArrowLeftSvg from "@images/arrow_left.svg?react";
import { useStore_IsOpenedLanguageSelector } from "@store";
import {
useLanguageSettings,
} from "@logics_main";
export const LanguageSelectorOpenButton = ({ TurnedOnSvgComponent, is_turned_on, selector_key, target_key }) => {
const { t } = useI18n();
const { updateIsOpenedLanguageSelector, currentIsOpenedLanguageSelector } = useStore_IsOpenedLanguageSelector();
const {
currentSelectedPresetTabNumber,
currentSelectedYourLanguages,
currentSelectedTargetLanguages,
} = useLanguageSettings();
const toggleSelector = () => {
if (currentIsOpenedLanguageSelector.data[selector_key] === true && currentIsOpenedLanguageSelector.data.target_key === target_key) { // Close Language Selector
updateIsOpenedLanguageSelector({ your_language: false, target_language: false, target_key: "1" });
} else { // Open Language Selector
updateIsOpenedLanguageSelector({
your_language: selector_key === "your_language",
target_language: selector_key === "target_language",
target_key: target_key,
});
}
};
const arrow_class_names = clsx(styles.arrow_left_svg, {
[styles.reverse]: (currentIsOpenedLanguageSelector.data[selector_key] === true && currentIsOpenedLanguageSelector.data.target_key === target_key),
});
const category_class_names = clsx(styles.category_svg, {
[styles.is_turned_on]: is_turned_on,
});
const getVariable = (target_selector_key) => {
if (target_selector_key === "your_language") return currentSelectedYourLanguages.data[currentSelectedPresetTabNumber.data];
if (target_selector_key === "target_language") return currentSelectedTargetLanguages.data[currentSelectedPresetTabNumber.data];
};
const getTitle = (target_selector_key) => {
if (target_selector_key === "your_language") return t("main_page.your_language");
if (target_selector_key === "target_language") {
if (currentSelectedTargetLanguages.data[currentSelectedPresetTabNumber.data]["2"].enable === false) return t("main_page.target_language");
return `${t("main_page.target_language")} ${target_key}`;
}
};
const title = getTitle(selector_key);
if (getVariable(selector_key)[target_key].enable === false) return null;
const language_text = getVariable(selector_key)[target_key].language ?? "Loading...";
const country_text = getVariable(selector_key)[target_key].country ?? "Loading...";
return (
<div className={styles.container}>
<div className={styles.title_container}>
<TurnedOnSvgComponent className={category_class_names} />
<p className={styles.title}>{title}</p>
</div>
<div className={styles.dropdown_menu_container} onClick={toggleSelector}>
<p className={styles.selected_language}>{language_text}</p>
<p className={styles.selected_language}>({country_text})</p>
<ArrowLeftSvg className={arrow_class_names} />
</div>
</div>
);
};

View File

@@ -0,0 +1,71 @@
.container {
width: 100%;
background-color: var(--dark_825_color);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0.8rem;
gap: 0.8rem;
}
.title_container {
position: relative;
width: 100%;
display: flex;
justify-content: center;
text-align: center;
}
.category_svg {
position: absolute;
top: 50%;
left: 1.2rem;
transform: translate(-50%, -50%);
width: 1.4rem;
color: var(--dark_400_color);
display: none;
&.is_turned_on {
display: block;
}
}
.title {
font-size: 1.6rem;
color: var(--dark_400_color);
}
.dropdown_menu_container {
position: relative;
background-color: var(--dark_888_color);
width: 100%;
padding: 0.4rem 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 0.4rem;
gap: 0.2rem;
cursor: pointer;
&:hover {
background-color: var(--dark_875_color);
}
&:active {
background-color: var(--dark_900_color);
}
}
.selected_language {
font-size: 1.2rem;
}
.arrow_left_svg {
position: absolute;
right: 0;
margin: 0 0.2rem;
transform: rotate(180deg);
width: 1.6rem;
&.reverse {
transform: rotate(0deg);
}
}

View File

@@ -0,0 +1,40 @@
import { useState } from "react";
import clsx from "clsx";
import { useI18n } from "@useI18n";
import styles from "./LanguageSwapButton.module.scss";
import NarrowArrowDownSvg from "@images/narrow_arrow_down.svg?react";
import { useLanguageSettings } from "@logics_main";
export const LanguageSwapButton = () => {
const [isHovered, setIsHovered] = useState(false);
const { t } = useI18n();
const { swapSelectedLanguages } = useLanguageSettings();
const label = isHovered
? t("main_page.swap_button_label")
: t("main_page.translate_each_other_label");
const labelClassName = clsx(styles["label"], {
[styles["is_hovered"]]: isHovered
});
const handleMouseEnter = () => setIsHovered(true);
const handleMouseLeave = () => setIsHovered(false);
return (
<div className={styles.container}>
<div
className={styles.swap_button_wrapper}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={swapSelectedLanguages}
>
<NarrowArrowDownSvg className={clsx(styles.narrow_arrow_down_svg, styles.reverse)} />
<p className={labelClassName}>{label}</p>
<NarrowArrowDownSvg className={styles.narrow_arrow_down_svg} />
</div>
</div>
);
};

View File

@@ -0,0 +1,36 @@
.container {
width: 100%;
}
.swap_button_wrapper {
width: auto;
display: flex;
justify-content: space-between;
align-items: center;
margin: 0.8rem 2rem;
padding: 0.4rem 0.8rem;
border-radius: 0.6rem;
cursor: pointer;
&:hover {
background-color: var(--dark_750_color);
}
&:active {
background-color: var(--dark_850_color);
}
}
.narrow_arrow_down_svg {
width: 1.6rem;
color: var(--dark_500_color);
&.reverse {
transform: rotate(180deg);
}
}
.label {
font-size: 1.2rem;
color: var(--dark_500_color);
&.is_hovered {
color: var(--dark_200_color);
}
}

View File

@@ -0,0 +1,32 @@
import styles from "./PresetTabSelector.module.scss";
export const PresetTabSelector = () => {
return (
<div className={styles.container}>
<Tab preset_number={"1"} />
<Tab preset_number={"2"} />
<Tab preset_number={"3"} />
</div>
);
};
import clsx from "clsx";
import { useLanguageSettings } from "@logics_main";
const Tab = (props) => {
const { currentSelectedPresetTabNumber, setSelectedPresetTabNumber } = useLanguageSettings();
const onclickFunction = () => {
setSelectedPresetTabNumber(props.preset_number);
};
const class_names = clsx(styles.tab_container, {
[styles.is_selected]: (currentSelectedPresetTabNumber.data === props.preset_number) ? true : false
});
return (
<div className={class_names} onClick={onclickFunction}>
<p className={styles.tab_number}>{props.preset_number}</p>
</div>
);
};

View File

@@ -0,0 +1,32 @@
.container {
width: 100%;
display: flex;
justify-content: space-between;
}
.tab_container {
height: 3rem;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 0.6rem 0.6rem 0 0;
color: var(--dark_600_color);
cursor: pointer;
&:hover {
background-color: var(--dark_825_color);
}
&:active {
background-color: var(--dark_875_color);
}
&.is_selected {
background-color: var(--dark_800_color);
color: var(--dark_200_color);
cursor: default;
pointer-events: none;
}
}
.tab_number {
font-size: 1.6rem;
}

View File

@@ -0,0 +1,94 @@
import { useI18n } from "@useI18n";
import { updateLabelsById } from "@utils";
import styles from "./TranslatorSelectorOpenButton.module.scss";
import { TranslatorSelector } from "./translator_selector/TranslatorSelector";
import { useStore_IsOpenedTranslatorSelector } from "@store";
import { useLanguageSettings } from "@logics_main";
import WarningSvg from "@images/warning.svg?react";
export const TranslatorSelectorOpenButton = () => {
const { t } = useI18n();
const {
currentSelectedYourLanguages,
currentSelectedTargetLanguages,
currentSelectedPresetTabNumber,
currentTranslationEngines,
currentSelectedTranslationEngines,
} = useLanguageSettings();
// const new_labels = [
// {id: "CTranslate2", label: "AI\nCTranslate2"}
// ];
const translation_engines = currentTranslationEngines.data;
// const translation_engines = updateLabelsById(currentTranslationEngines.data, new_labels);
const selected_engine_id = currentSelectedTranslationEngines.data[currentSelectedPresetTabNumber.data];
const checkIsSelectedSameLanguage = () => {
const your_language_data = currentSelectedYourLanguages.data[currentSelectedPresetTabNumber.data];
const target_language_data = currentSelectedTargetLanguages.data[currentSelectedPresetTabNumber.data];
const yourLanguage = your_language_data["1"];
const yourLanguageName = yourLanguage.language;
const yourCountry = yourLanguage.country;
let is_selected_same_language = false;
for (const key in target_language_data) {
const targetLanguage = target_language_data[key];
if (targetLanguage.enable) {
const targetLanguageName = targetLanguage.language;
const targetCountry = targetLanguage.country;
if (yourLanguageName === targetLanguageName && yourCountry === targetCountry) {
is_selected_same_language = true;
break;
}
}
}
return is_selected_same_language;
};
const is_selected_same_language = checkIsSelectedSameLanguage();
const getSelectedLabel = () => {
const selected_engine = translation_engines.find(
d => d.id === selected_engine_id
);
return selected_engine?.label;
};
const is_loading = currentTranslationEngines.state === "pending";
const selected_label = is_loading ? "Loading..." : getSelectedLabel();
const { currentIsOpenedTranslatorSelector, updateIsOpenedTranslatorSelector} = useStore_IsOpenedTranslatorSelector();
const openTranslatorSelector = () => {
updateIsOpenedTranslatorSelector(!currentIsOpenedTranslatorSelector.data);
};
return (
<div className={styles.container}>
<div className={styles.translator_selector_button} onClick={openTranslatorSelector}>
<p className={styles.label}>{t("main_page.translator")}:</p>
<p className={styles.label}>{selected_label}</p>
{is_selected_same_language
? <WarningSvg className={styles.warning_svg}/>
: null
}
</div>
{currentIsOpenedTranslatorSelector.data &&
<TranslatorSelector
selected_id={selected_engine_id}
translation_engines={translation_engines}
is_selected_same_language={is_selected_same_language}
/>
}
</div>
);
};

View File

@@ -0,0 +1,35 @@
.container {
position: relative;
width: 100%;
}
.translator_selector_button {
position: relative;
width: auto;
display: flex;
justify-content: center;
align-items: center;
gap: 0.2rem;
margin: 0.4rem;
padding: 0.6rem 0;
border-radius: 0.6rem;
cursor: pointer;
&:hover {
background-color: var(--dark_875_color);
}
&:active {
background-color: var(--dark_900_color);
}
}
.label {
font-size: 1.2rem;
white-space: nowrap;
}
.warning_svg {
margin-left: 0.2rem;
padding-bottom: 0.2rem;
width: 1.8rem;
color: var(--warning_color);
}

View File

@@ -0,0 +1,77 @@
import clsx from "clsx";
import styles from "./TranslatorSelector.module.scss";
import { useI18n } from "@useI18n";
import { chunkArray } from "@utils";
import { useStore_IsOpenedTranslatorSelector } from "@store";
import { useLanguageSettings } from "@logics_main";
export const TranslatorSelector = ({selected_id, translation_engines, is_selected_same_language}) => {
const { t } = useI18n();
const columns = chunkArray(translation_engines, 2);
return (
<div className={styles.container}>
<div className={styles.relative_container}>
<div className={styles.wrapper}>
{columns.map((column, column_index) => (
<div className={styles.column_wrapper} key={`column_${column_index}`}>
{column.map(({ id, label, is_available, is_default }) => (
<TranslatorBox
key={id}
id={id}
label={label}
is_available={is_available}
is_default={is_default}
is_selected={(id === selected_id)}
/>
))}
</div>
))}
</div>
{is_selected_same_language ?
<div className={styles.is_selected_same_language_wrapper}>
<p className={styles.is_selected_same_language_text}>
{t("main_page.translator_selector.is_selected_same_language", {
your_language: t("main_page.your_language"),
target_language: t("main_page.target_language"),
ctranslate2: "CTranslate2",
})}
</p>
</div>
: null
}
</div>
</div>
);
};
const TranslatorBox = (props) => {
const { t } = useI18n();
const { setSelectedTranslationEngines} = useLanguageSettings();
const { updateIsOpenedTranslatorSelector} = useStore_IsOpenedTranslatorSelector();
const box_class_name = clsx(
styles.box,
{ [styles.is_selected]: props.is_selected },
{ [styles.is_available]: props.is_available }
);
const label_default_class_name = clsx(
styles.label_default,
{ [styles.is_selected]: props.is_selected },
);
const selectTranslator = () => {
if (props.is_selected === false) {
setSelectedTranslationEngines(props.id);
}
updateIsOpenedTranslatorSelector(false);
};
return (
<div className={box_class_name} onClick={selectTranslator}>
<p className={styles.translator_name}>{props.label}</p>
{props.is_default && <p className={label_default_class_name}>{t("main_page.translator_label_default")}</p>}
</div>
);
};

View File

@@ -0,0 +1,105 @@
.container {
position: absolute;
bottom: 100%;
width: 100%;
height: 26rem;
background-color: var(--dark_1000_color_dd);
backdrop-filter: blur(0.1rem);
display: flex;
justify-content: center;
align-items: center;
}
.relative_container {
position: relative;
width: 100%;
height: 100%;
}
.wrapper {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 1.4rem;
}
.column_wrapper {
display: flex;
justify-content: center;
align-items: center;
gap: 1.2rem;
}
$box_size: 6.2rem;
.box {
position: relative;
width: 9.4rem;
height: $box_size;
background-color: var(--dark_875_color);
display: flex;
justify-content: center;
align-items: center;
white-space: pre-wrap;
text-align: center;
border-radius: 0.2rem;
cursor: pointer;
&:hover {
background-color: var(--dark_825_color);
}
&:active {
background-color: var(--dark_900_color);
outline: 0.1rem solid var(--primary_300_color);
}
&.is_selected {
outline: 0.2rem solid var(--primary_300_color);
}
&:not(.is_available) {
pointer-events: none;
background-color: var(--dark_950_color);
& .translator_name {
color: var(--dark_600_color);
}
}
}
.translator_name {
font-size: 1.4rem;
}
.label_default {
background-color: var(--dark_875_color);
outline: 0.1rem solid var(--dark_1000_color);
padding: 0.2rem 0.4rem;
border-radius: 0.2rem;
font-size: 1.2rem;
position: absolute;
top: -0.8rem;
right: -0.8rem;
pointer-events: none;
&.is_selected {
outline: 0.1rem solid var(--primary_300_color);
}
}
.is_selected_same_language_wrapper {
position: absolute;
bottom: 0;
width: 100%;
height: 100%;
background-color: var(--dark_1000_color_66);
display: flex;
justify-content: center;
align-items: start;
pointer-events: none;
padding: 4rem 1rem;
}
.is_selected_same_language_text {
font-size: 1.4rem;
text-align: center;
text-wrap: balance;
}

View File

@@ -0,0 +1,23 @@
import styles from "./Logo.module.scss";
export const Logo = () => {
return (
<div className={styles.container}>
<LogoBox />
</div>
);
};
import vrct_logo from "@images/vrct_logo_for_dark_mode.png";
import chato_img from "@images/chato_white.png";
import { useIsMainPageCompactMode } from "@logics_main";
export const LogoBox = () => {
const { currentIsMainPageCompactMode } = useIsMainPageCompactMode();
if (currentIsMainPageCompactMode.data === true) {
return <img src={chato_img} className={styles.logo_chato} alt="VRCT logo chato" />;
} else {
return <img src={vrct_logo} className={styles.logo} alt="VRCT logo" />;
}
};

View File

@@ -0,0 +1,19 @@
.container {
height: var(--main_page_topbar_height);
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
}
.logo {
width: 12rem;
height: auto;
margin: auto;
}
.logo_chato {
width: 2rem;
height: auto;
margin: auto;
}

View File

@@ -0,0 +1,118 @@
import { useI18n } from "@useI18n";
import clsx from "clsx";
import styles from "./MainFunctionSwitch.module.scss";
import TranslationSvg from "@images/translation.svg?react";
import MicSvg from "@images/mic.svg?react";
import HeadphonesSvg from "@images/headphones.svg?react";
import ForegroundSvg from "@images/foreground.svg?react";
import {
useIsMainPageCompactMode,
useMainFunction,
} from "@logics_main";
export const MainFunctionSwitch = () => {
const { t } = useI18n();
const {
toggleTranslation, currentTranslationStatus,
toggleTranscriptionSend, currentTranscriptionSendStatus,
toggleTranscriptionReceive, currentTranscriptionReceiveStatus,
toggleForeground, currentForegroundStatus,
} = useMainFunction();
const switch_items = [
{
switch_id: "translation",
label: t("main_page.translation"),
SvgComponent: TranslationSvg,
currentState: currentTranslationStatus,
toggleFunction: toggleTranslation,
},
{
switch_id: "transcription_send",
label: t("main_page.transcription_send"),
SvgComponent: MicSvg,
currentState: currentTranscriptionSendStatus,
toggleFunction: toggleTranscriptionSend,
},
{
switch_id: "transcription_receive",
label: t("main_page.transcription_receive"),
SvgComponent: HeadphonesSvg,
currentState: currentTranscriptionReceiveStatus,
toggleFunction: toggleTranscriptionReceive,
},
{
switch_id: "foreground",
label: t("main_page.foreground"),
SvgComponent: ForegroundSvg,
currentState: currentForegroundStatus,
toggleFunction: toggleForeground,
},
];
return (
<div className={styles.container}>
{switch_items.map(item => (
<SwitchContainer
key={item.switch_id}
switch_id={item.switch_id}
switchLabel={item.label}
currentState={item.currentState}
toggleFunction={item.toggleFunction}
SvgComponent={item.SvgComponent}
>
</SwitchContainer>
))}
</div>
);
};
import { useState } from "react";
export const SwitchContainer = ({ switchLabel, switch_id, children, currentState, toggleFunction, SvgComponent }) => {
const [is_hovered, setIsHovered] = useState(false);
const [is_mouse_down, setIsMouseDown] = useState(false);
const { currentIsMainPageCompactMode } = useIsMainPageCompactMode();
const getClassNames = (baseClass) => clsx(baseClass, {
[styles.is_compact_mode]: currentIsMainPageCompactMode.data,
[styles.is_active]: (currentState.data === true),
[styles.is_pending]: (currentState.state === "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);
return (
<div className={getClassNames(styles.switch_container)}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onClick={toggleFunction}
>
<div className={styles.label_wrapper}>
<SvgComponent className={getClassNames(styles.switch_svg)} />
<p className={getClassNames(styles.switch_label)}>{switchLabel}</p>
{children}
</div>
<div className={getClassNames(styles.toggle_control)}>
<span className={getClassNames(styles.control)}></span>
</div>
<div className={getClassNames(styles.switch_indicator)}></div>
{(currentState.state === "pending")
? <span className={styles.loader}></span>
: null
}
</div>
);
};

View File

@@ -0,0 +1,90 @@
@import "@scss_mixins";
.container {
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.1rem;
}
.switch_container {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.6rem 1.4rem;
background-color: var(--dark_825_color);
cursor: pointer;
&:hover {
background-color: var(--dark_800_color);
}
&:active {
background-color: var(--dark_875_color);
}
&.is_compact_mode {
padding: 1.5rem;
justify-content: center;
}
&.is_pending {
pointer-events: none;
}
}
.label_wrapper {
display: flex;
justify-content: left;
align-items: center;
gap: 0.8rem;
}
$pending_label_color: var(--dark_500_color);
.switch_label {
font-size: 1.4rem;
&.is_compact_mode {
display: none;
}
&.is_pending {
color: $pending_label_color;
}
}
.switch_svg {
width: 1.8rem;
&.is_pending {
color: $pending_label_color;
}
&:not(.is_compact_mode) {
width: 1.6rem;
color: var(--dark_350_color);
}
}
.switch_indicator {
position: absolute;
top: 50%;
right: 0.4rem;
transform: translate(-50%, -50%);
width: 0.2rem;
height: 2.6rem;
border-radius: 0.1rem;
background-color: var(--primary_300_color);
display: none;
&.is_compact_mode.is_active {
display: block;
}
}
.loader {
@include loader(2rem, 0.2rem, left, 50%);
}
.toggle_control {
// @include toggle_control_styles;
@include toggle_control_styles($toggle_width: 3.6rem, $toggle_height: 1.4rem);
display: flex;
justify-content: end;
align-items: center;
&.is_compact_mode {
display: none;
}
}

View File

@@ -0,0 +1,19 @@
import styles from "./OpenSettings.module.scss";
import { useIsOpenedConfigPage } from "@logics_common";
import ConfigurationSvg from "@images/configuration.svg?react";
export const OpenSettings = () => {
const { setIsOpenedConfigPage } = useIsOpenedConfigPage();
const openConfigPage = () => {
setIsOpenedConfigPage(true);
};
return (
<div className={styles.container}>
<div className={styles.open_config_page_button} onClick={openConfigPage}>
<ConfigurationSvg className={styles.configuration_svg} />
</div>
</div>
);
};

View File

@@ -0,0 +1,28 @@
.container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: var(--dark_850_color);
}
.open_config_page_button {
width: auto;
margin: 1rem;
padding: 0.8rem 0;
display: flex;
justify-content: center;
border-radius: 0.6rem;
cursor: pointer;
&:hover {
background-color: var(--dark_800_color);
}
&:active {
background-color: var(--dark_875_color);
}
}
.configuration_svg {
width: 2rem;
color: var(--dark_500_color);
}