[Refactor] Move to src-ui/views and src-ui/logics structure.
This commit is contained in:
21
src-ui/views/app/main_page/MainPage.jsx
Normal file
21
src-ui/views/app/main_page/MainPage.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import clsx from "clsx";
|
||||
import styles from "./MainPage.module.scss";
|
||||
import { SidebarSection } from "./sidebar_section/SidebarSection";
|
||||
import { MainSection } from "./main_section/MainSection";
|
||||
import { useIsOpenedConfigPage } from "@logics_common";
|
||||
|
||||
export const MainPage = () => {
|
||||
const { currentIsOpenedConfigPage } = useIsOpenedConfigPage();
|
||||
|
||||
return (
|
||||
<div className={clsx(styles.page, styles.main_page, {
|
||||
[styles.show_config]: currentIsOpenedConfigPage.data,
|
||||
[styles.show_main]: !currentIsOpenedConfigPage.data
|
||||
})}>
|
||||
<div className={styles.container}>
|
||||
<SidebarSection />
|
||||
<MainSection />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
28
src-ui/views/app/main_page/MainPage.module.scss
Normal file
28
src-ui/views/app/main_page/MainPage.module.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
.page {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
|
||||
.show_config.main_page {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.show_main.main_page {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
background-color: var(--dark_888_color);
|
||||
position: relative;
|
||||
}
|
||||
102
src-ui/views/app/main_page/main_section/MainSection.jsx
Normal file
102
src-ui/views/app/main_page/main_section/MainSection.jsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
import styles from "./MainSection.module.scss";
|
||||
|
||||
import { TopBar } from "./top_bar/TopBar";
|
||||
import { MessageContainer } from "./message_container/MessageContainer";
|
||||
import { LanguageSelector } from "./language_selector/LanguageSelector";
|
||||
|
||||
import { useStore_IsOpenedLanguageSelector } from "@store";
|
||||
import { useLanguageSettings } from "@logics_main";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { PluginHost } from "./PluginHost";
|
||||
|
||||
import { usePlugins } from "@logics_configs";
|
||||
|
||||
export const MainSection = () => {
|
||||
const { currentPluginsData } = usePlugins();
|
||||
|
||||
const render_plugins = currentPluginsData.data.filter((plugin) => (
|
||||
plugin.is_downloaded &&
|
||||
plugin.is_enabled &&
|
||||
plugin.downloaded_plugin_info.is_plugin_supported &&
|
||||
plugin.downloaded_plugin_info.location === "main_section"
|
||||
));
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<TopBar />
|
||||
{render_plugins.length
|
||||
? <PluginHost render_components={render_plugins}/>
|
||||
: <MessageContainer />
|
||||
}
|
||||
<HandleLanguageSelector />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const HandleLanguageSelector = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentIsOpenedLanguageSelector, updateIsOpenedLanguageSelector } = useStore_IsOpenedLanguageSelector();
|
||||
const {
|
||||
currentSelectedPresetTabNumber,
|
||||
currentSelectedYourLanguages,
|
||||
setSelectedYourLanguages,
|
||||
currentSelectedTargetLanguages,
|
||||
setSelectedTargetLanguages,
|
||||
} = useLanguageSettings();
|
||||
|
||||
useEffect(() => {
|
||||
updateIsOpenedLanguageSelector({
|
||||
your_language: false,
|
||||
target_language: false,
|
||||
target_key: "1"
|
||||
});
|
||||
|
||||
}, [currentSelectedPresetTabNumber.data, currentSelectedYourLanguages.data, currentSelectedTargetLanguages.data]);
|
||||
|
||||
const getTitle = (target_selector_key) => {
|
||||
if (target_selector_key === "your_language") return t("main_page.language_selector.title_your_language");
|
||||
if (target_selector_key === "target_language") {
|
||||
if (currentSelectedTargetLanguages.data[currentSelectedPresetTabNumber.data]["2"].enable === false) return t("main_page.language_selector.title_target_language");
|
||||
return `${t("main_page.language_selector.title_target_language")} (${currentIsOpenedLanguageSelector.data.target_key})`;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (currentIsOpenedLanguageSelector.data.your_language === true) {
|
||||
const onclickFunction_YourLanguage = (payload) => {
|
||||
updateIsOpenedLanguageSelector({ your_language: false, target_language: false, target_key: currentIsOpenedLanguageSelector.data.target_key });
|
||||
setSelectedYourLanguages({
|
||||
...payload,
|
||||
target_key: currentIsOpenedLanguageSelector.data.target_key,
|
||||
});
|
||||
};
|
||||
const title = getTitle("your_language");
|
||||
return (
|
||||
<LanguageSelector
|
||||
title={title}
|
||||
onClickFunction={onclickFunction_YourLanguage}
|
||||
/>
|
||||
);
|
||||
} else if (currentIsOpenedLanguageSelector.data.target_language === true) {
|
||||
const onclickFunction_TargetLanguage = (payload) => {
|
||||
updateIsOpenedLanguageSelector({ your_language: false, target_language: false, target_key: currentIsOpenedLanguageSelector.data.target_key });
|
||||
setSelectedTargetLanguages({
|
||||
...payload,
|
||||
target_key: currentIsOpenedLanguageSelector.data.target_key,
|
||||
});
|
||||
};
|
||||
const title = getTitle("target_language");
|
||||
return (
|
||||
<LanguageSelector
|
||||
title={title}
|
||||
onClickFunction={onclickFunction_TargetLanguage}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// justify-content: space-between;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.language_selector_container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
28
src-ui/views/app/main_page/main_section/PluginHost.jsx
Normal file
28
src-ui/views/app/main_page/main_section/PluginHost.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { usePlugins } from "@logics_configs";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
|
||||
export const PluginHost = ({ render_components }) => {
|
||||
const { setErrorPlugin } = usePlugins();
|
||||
|
||||
return (
|
||||
<>
|
||||
{render_components.map((plugin, index) => {
|
||||
const PluginComponent = plugin.component;
|
||||
const plugin_id = plugin.plugin_id;
|
||||
|
||||
return PluginComponent ? (
|
||||
<ErrorBoundary
|
||||
key={plugin_id || index}
|
||||
fallbackRender={() => null}
|
||||
onError={(_error, _info) => {
|
||||
// Disable the plugin on error
|
||||
setErrorPlugin(plugin_id, "disabled_due_to_an_error");
|
||||
}}
|
||||
>
|
||||
<PluginComponent />
|
||||
</ErrorBoundary>
|
||||
) : null;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
|
||||
import { useLanguageSettings } from "@logics_main";
|
||||
import styles from "./LanguageSelector.module.scss";
|
||||
|
||||
import { LanguageSelectorTopBar } from "./language_selector_top_bar/LanguageSelectorTopBar";
|
||||
export const LanguageSelector = ({ title, onClickFunction }) => {
|
||||
const { t } = useI18n();
|
||||
const { currentSelectableLanguageList } = useLanguageSettings();
|
||||
|
||||
const groupLanguagesByFirstLetter = (languages) => {
|
||||
return languages.reduce((acc, { language, country }) => {
|
||||
const firstLetter = language[0].toUpperCase();
|
||||
if (!acc[firstLetter]) {
|
||||
acc[firstLetter] = [];
|
||||
}
|
||||
acc[firstLetter].push({ language, country });
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const groupedLanguages = groupLanguagesByFirstLetter(currentSelectableLanguageList.data);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<LanguageSelectorTopBar title={title}/>
|
||||
<div className={styles.language_list_scroll_wrapper}>
|
||||
<div className={styles.language_list}>
|
||||
{Object.entries(groupedLanguages).map(([letter, languages]) => (
|
||||
<LanguageGroup key={letter} onClickFunction={onClickFunction} letter={letter} languages={languages} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LanguageGroup = ({ onClickFunction, letter, languages }) => {
|
||||
return (
|
||||
<div className={styles.language_each_letter_box}>
|
||||
<p className={styles.language_latter}>{letter}</p>
|
||||
{languages.map((language_data, index) => (
|
||||
<LanguageButton key={index} onClickFunction={onClickFunction} language_data={language_data} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LanguageButton = ({ onClickFunction, language_data }) => {
|
||||
|
||||
const adjustedOnClickFunction = () => {
|
||||
onClickFunction({
|
||||
language: language_data.language,
|
||||
country: language_data.country,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.language_button} onClick={adjustedOnClickFunction}>
|
||||
<p className={styles.language_label}>{language_data.language} ({language_data.country})</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
.container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: var(--dark_875_color);
|
||||
flex: 1;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.language_list_scroll_wrapper {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 1rem 1rem 8rem 1.6rem;
|
||||
}
|
||||
|
||||
.language_list {
|
||||
column-count: auto;
|
||||
column-width: 16rem;
|
||||
}
|
||||
|
||||
.language_each_letter_box {
|
||||
break-inside: avoid-column;
|
||||
margin-bottom: 1.4rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
.language_latter {
|
||||
font-size: 1.4rem;
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
|
||||
.language_button {
|
||||
padding: 0.8rem 0.6rem;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
background-color: var(--dark_825_color);
|
||||
}
|
||||
&:active{
|
||||
background-color: var(--dark_888_color);
|
||||
}
|
||||
}
|
||||
|
||||
.language_label {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
import styles from "./LanguageSelectorTopBar.module.scss";
|
||||
import { useStore_IsOpenedLanguageSelector } from "@store";
|
||||
|
||||
export const LanguageSelectorTopBar = (props) => {
|
||||
const { t } = useI18n();
|
||||
const { updateIsOpenedLanguageSelector } = useStore_IsOpenedLanguageSelector();
|
||||
const closeLanguageSelector = () => {
|
||||
updateIsOpenedLanguageSelector({
|
||||
your_language: false,
|
||||
target_language: false,
|
||||
target_key: "1"
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.go_back_button_wrapper} onClick={closeLanguageSelector}>
|
||||
<p className={styles.go_back_button_label}>{t("common.go_back_button_label")}</p>
|
||||
</div>
|
||||
<p className={styles.title}>{props.title}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
.container {
|
||||
height: var(--main_page_topbar_height);
|
||||
background-color: var(--dark_850_color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
|
||||
.go_back_button_wrapper {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
background-color: var(--dark_800_color);
|
||||
padding: 0 2rem 0 1.6rem;
|
||||
height: 100%;
|
||||
min-width: 8rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
background-color: var(--dark_750_color);
|
||||
}
|
||||
&:active{
|
||||
background-color: var(--dark_875_color);
|
||||
}
|
||||
}
|
||||
|
||||
.go_back_button_label {
|
||||
font-size: 1.4rem;
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { useResizable } from "react-resizable-layout";
|
||||
import { useRef, useEffect, useState, forwardRef } from "react";
|
||||
import styles from "./MessageContainer.module.scss";
|
||||
import { LogBox } from "./log_box/LogBox";
|
||||
import { MessageLogSettingsContainer } from "./message_log_settings_container/MessageLogSettingsContainer";
|
||||
import { MessageInputBox } from "./message_input_box/MessageInputBox";
|
||||
import { useMessageInputBoxRatio } from "@logics_main";
|
||||
|
||||
export const MessageContainer = () => {
|
||||
const { currentMessageInputBoxRatio, asyncSetMessageInputBoxRatio } = useMessageInputBoxRatio();
|
||||
const [ui_message_box_ratio, setUiMessageBoxRatio] = useState(false);
|
||||
const [is_hovered, setIsHovered] = useState(false);
|
||||
|
||||
const container_ref = useRef(null);
|
||||
const separator_ref = useRef(null);
|
||||
const log_box_ref = useRef(null);
|
||||
const message_input_box_wrapper_ref = useRef(null);
|
||||
|
||||
const calculateMessageInputBoxRatio = (position) => {
|
||||
if (log_box_ref.current && message_input_box_wrapper_ref.current && separator_ref.current && container_ref.current) {
|
||||
const container_padding_bottom = parseFloat(
|
||||
window.getComputedStyle(container_ref.current).paddingBottom
|
||||
);
|
||||
const total_height =
|
||||
log_box_ref.current.offsetHeight +
|
||||
separator_ref.current.offsetHeight * 2 +
|
||||
message_input_box_wrapper_ref.current.offsetHeight;
|
||||
const adjusted_position = position - container_padding_bottom;
|
||||
const message_box_ratio = (adjusted_position / total_height) * 100;
|
||||
return message_box_ratio;
|
||||
}
|
||||
console.warn("References not ready for calculation");
|
||||
return 10; // Default initial height percentage
|
||||
};
|
||||
|
||||
const asyncSaveRatio = (position) => {
|
||||
if (position > 0) {
|
||||
asyncSetMessageInputBoxRatio(calculateMessageInputBoxRatio(position));
|
||||
}
|
||||
};
|
||||
|
||||
const { position, separatorProps } = useResizable({
|
||||
axis: "y",
|
||||
reverse: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (position > 0) {
|
||||
setUiMessageBoxRatio(calculateMessageInputBoxRatio(position));
|
||||
const timeout = setTimeout(() => {
|
||||
asyncSaveRatio(position);
|
||||
}, 200);
|
||||
return () => clearTimeout(timeout);
|
||||
}
|
||||
}, [position]);
|
||||
|
||||
useEffect(() => {
|
||||
setUiMessageBoxRatio(currentMessageInputBoxRatio.data);
|
||||
}, [currentMessageInputBoxRatio]);
|
||||
|
||||
return (
|
||||
<div className={styles.container} ref={container_ref}>
|
||||
<div
|
||||
className={styles.log_box_resize_wrapper}
|
||||
ref={log_box_ref}
|
||||
onMouseOver={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<LogBox />
|
||||
<MessageLogSettingsContainer to_visible_toggle_bar={is_hovered} />
|
||||
</div>
|
||||
<Separator {...separatorProps} ref={separator_ref} />
|
||||
<div
|
||||
className={styles.message_box_resize_wrapper}
|
||||
ref={message_input_box_wrapper_ref}
|
||||
style={{ height: `${ui_message_box_ratio}%` }}
|
||||
>
|
||||
<MessageInputBox />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Separator = forwardRef((props, ref) => (
|
||||
<div tabIndex={0} className={styles.separator} ref={ref} {...props}>
|
||||
<span className={styles.separator_line}></span>
|
||||
</div>
|
||||
));
|
||||
@@ -0,0 +1,42 @@
|
||||
.container {
|
||||
height: 0%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 0 1.6rem 1rem 1.6rem;
|
||||
}
|
||||
|
||||
.log_box_resize_wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.separator {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0.8rem;
|
||||
cursor: row-resize;
|
||||
flex-shrink: 0;
|
||||
&:hover {
|
||||
& .separator_line {
|
||||
background-color: var(--primary_300_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
.separator_line {
|
||||
position: absolute;
|
||||
bottom: 0%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
height: 50%;
|
||||
width: 99%;
|
||||
transition: background-color .15s ease-out;
|
||||
}
|
||||
|
||||
.message_box_resize_wrapper {
|
||||
height: 10%;
|
||||
min-height: 3.8rem;
|
||||
max-height: 90%;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import React, { useRef, useLayoutEffect, useEffect } from "react";
|
||||
import styles from "./LogBox.module.scss";
|
||||
import { MessageContainer } from "./message_container/MessageContainer";
|
||||
import { useMessage } from "@logics_common";
|
||||
import { useMessageLogScroll } from "@logics_main";
|
||||
import { store } from "@store";
|
||||
|
||||
export const LogBox = () => {
|
||||
const { currentMessageLogs } = useMessage();
|
||||
const { scrollToBottom, isScrolling } = useMessageLogScroll();
|
||||
const logContainerRef = useRef(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
store.log_box_ref = logContainerRef;
|
||||
if (!isScrolling) {
|
||||
scrollToBottom();
|
||||
}
|
||||
}, [currentMessageLogs.data, isScrolling]);
|
||||
|
||||
return (
|
||||
<div id="log_container" className={styles.container} ref={logContainerRef}>
|
||||
<MessageLogUiSizeController />
|
||||
{currentMessageLogs.data.map((message_data) => (
|
||||
<MessageContainer key={message_data.id} {...message_data} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import { useAppearance } from "@logics_configs";
|
||||
const MessageLogUiSizeController = () => {
|
||||
const { currentMessageLogUiScaling } = useAppearance();
|
||||
const font_size = currentMessageLogUiScaling.data / 100;
|
||||
|
||||
useEffect(() => {
|
||||
const log_container_el = document.getElementById("log_container");
|
||||
log_container_el.style.setProperty("font-size", `${font_size}rem`);
|
||||
}, [currentMessageLogUiScaling.data]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
.container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
background-color: var(--dark_900_color);
|
||||
overflow: auto;
|
||||
border-radius: 0.8rem;
|
||||
padding: 1rem;
|
||||
font-size: 1rem; // This is the standard font size for message logs, and is controlled by JavaScript. All child elements this are generally expressed in "em" and are affected by the font size setting here.
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import { useState } from "react";
|
||||
import { useI18n } from "@useI18n";
|
||||
import clsx from "clsx";
|
||||
import styles from "./MessageContainer.module.scss";
|
||||
import { MessageSubMenuContainer } from "./message_sub_menu_container/MessageSubMenuContainer";
|
||||
import { useMessage } from "@logics_common";
|
||||
import { useAppearance } from "@logics_configs";
|
||||
|
||||
export const MessageContainer = ({ messages, status, category, created_at }) => {
|
||||
const { t } = useI18n();
|
||||
const {
|
||||
sendMessage,
|
||||
updateMessageInputValue,
|
||||
} = useMessage();
|
||||
const { currentShowResendButton } = useAppearance();
|
||||
const [is_hovered, setIsHovered] = useState(false);
|
||||
const [is_locked, setIsLocked] = useState(false);
|
||||
|
||||
const resendFunction = () => {
|
||||
sendMessage(messages.original.message);
|
||||
};
|
||||
const editFunction = () => {
|
||||
updateMessageInputValue(messages.original.message);
|
||||
};
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (!is_locked) {
|
||||
setIsHovered(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setIsHovered(false);
|
||||
setIsLocked(false);
|
||||
};
|
||||
|
||||
const lockHoverState = () => {
|
||||
setIsHovered(false);
|
||||
setIsLocked(true);
|
||||
};
|
||||
|
||||
const is_translation_exist = messages.translations?.length > 0;
|
||||
const is_pending = status === "pending";
|
||||
const is_sent_message = category === "sent";
|
||||
const is_system_message = category === "system";
|
||||
const category_text = is_sent_message
|
||||
? t("main_page.message_log.sent")
|
||||
: is_system_message
|
||||
? t("main_page.message_log.system")
|
||||
: t("main_page.message_log.received");
|
||||
|
||||
const message_type_class_name = clsx({
|
||||
[styles.sent_message]: is_sent_message,
|
||||
[styles.received_message]: !is_sent_message && !is_system_message,
|
||||
[styles.system_message]: is_system_message,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(styles.container, message_type_class_name)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<div className={clsx(styles.message_wrapper, message_type_class_name)}>
|
||||
<div className={clsx(styles.info_box, message_type_class_name)}>
|
||||
<p className={styles.time}>{created_at}</p>
|
||||
<p className={clsx(styles.category, message_type_class_name)}>{category_text}</p>
|
||||
{is_sent_message && is_pending && <span className={styles.loader}></span>}
|
||||
</div>
|
||||
<div className={clsx(styles.message_box, message_type_class_name)}>
|
||||
{is_system_message ? (
|
||||
<p className={styles.message_main_system}>{messages.original.message}</p>
|
||||
) : is_translation_exist ? (
|
||||
<WithTranslatedMessages messages={messages} />
|
||||
) : (
|
||||
<OriginalMessage messages={messages} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{currentShowResendButton.data && is_sent_message && is_hovered ? (
|
||||
<MessageSubMenuContainer
|
||||
setIsHovered={lockHoverState}
|
||||
resendFunction={resendFunction}
|
||||
editFunction={editFunction}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MessageWithTransliteration = ({ item }) => {
|
||||
const renderTokenNode = (token, key) => {
|
||||
const orig = token.orig ?? "";
|
||||
const hira = token.hira ?? "";
|
||||
const hepburn = token.hepburn ?? "";
|
||||
|
||||
// Only hovered romaji if it exists. (No ruby cuz 'orig' and 'hira' are same.)
|
||||
if (hira && hira === orig && hepburn) {
|
||||
return (
|
||||
<span key={key} title={hepburn} className={styles.with_hepburn}>
|
||||
{orig}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Ruby hiragana and hovered romaji.
|
||||
if (hira && hepburn) {
|
||||
return (
|
||||
<ruby key={key} title={hepburn} className={styles.with_hepburn}>
|
||||
{orig}
|
||||
<rt>{hira}</rt>
|
||||
</ruby>
|
||||
);
|
||||
}
|
||||
|
||||
// Ruby romaji or hiragana.
|
||||
if (hepburn || hira) {
|
||||
const ruby = hepburn ? hepburn : hira;
|
||||
if (ruby !== orig) {
|
||||
return (
|
||||
<ruby key={key} className={styles.ruby}>
|
||||
{orig}
|
||||
<rt>{ruby}</rt>
|
||||
</ruby>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// Nothing. Original only.
|
||||
return (
|
||||
<span key={key} className={styles.original_only}>
|
||||
{orig}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
if (!item.transliteration.length) {
|
||||
return <p className={styles.message_main}>{item.message}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<p className={styles.message_main}>
|
||||
{item.transliteration.map((token, idx) => renderTokenNode(token, idx))}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
const OriginalMessage = ({ messages }) => {
|
||||
return (
|
||||
<>
|
||||
<MessageWithTransliteration item={messages.original} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const WithTranslatedMessages = ({ messages }) => {
|
||||
return (
|
||||
<>
|
||||
<p className={styles.message_second}>{messages.original.message}</p>
|
||||
{messages.translations.map((item, idx) => (
|
||||
<div key={idx}>
|
||||
<MessageWithTransliteration item={item} />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,122 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
// ******************* *******************
|
||||
// ******************* Express in "em" not "rem" *******************
|
||||
// ******************* *******************
|
||||
.container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
user-select: text;
|
||||
gap: 0.6rem;
|
||||
&.sent_message.is_shown_resend_button:hover {
|
||||
background-color: var(--dark_950_color);
|
||||
}
|
||||
}
|
||||
|
||||
.message_wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: end;
|
||||
user-select: text;
|
||||
padding: 0.6em 0;
|
||||
&.sent_message {
|
||||
align-items: end;
|
||||
}
|
||||
&.received_message {
|
||||
align-items: start;
|
||||
}
|
||||
&.system_message {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.info_box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 0.8em;
|
||||
justify-content: center;
|
||||
&.sent_message {
|
||||
align-items: end;
|
||||
}
|
||||
&.received_message {
|
||||
flex-flow: row-reverse;
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
@include loader(0.8em, 0.1em, left, -1em);
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 1em;
|
||||
color: var(--dark_600_color);
|
||||
}
|
||||
|
||||
.category {
|
||||
font-size: 1em;
|
||||
&.sent_message {
|
||||
color: var(--sent_400_color);
|
||||
}
|
||||
&.received_message {
|
||||
align-items: start;
|
||||
color: var(--received_300_color);
|
||||
}
|
||||
}
|
||||
|
||||
.message_box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&.sent_message {
|
||||
width: 100%;
|
||||
align-items: end;
|
||||
text-align: end;
|
||||
}
|
||||
&.received_message {
|
||||
width: 100%;
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
.message_main {
|
||||
user-select: text;
|
||||
font-size: 1.4em;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.message_second {
|
||||
color: var(--dark_450_color);
|
||||
user-select: text;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
|
||||
.system_message {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
.category {
|
||||
color: var(--primary_300_color);
|
||||
}
|
||||
|
||||
.message_box {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.message_main_system {
|
||||
font-size: 1.2rem;
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
}
|
||||
|
||||
// For ruby
|
||||
.with_hepburn {
|
||||
&:hover {
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import React, { useState, useRef } from "react";
|
||||
import { useI18n } from "@useI18n";
|
||||
import Tooltip, { tooltipClasses } from '@mui/material/Tooltip';
|
||||
import styles from "./MessageSubMenuContainer.module.scss";
|
||||
import SendMessageSvg from "@images/send_message.svg?react";
|
||||
import RefreshSvg from "@images/refresh_2.svg?react";
|
||||
|
||||
export const MessageSubMenuContainer = (props) => {
|
||||
const [is_holding, setIsHolding] = useState(false);
|
||||
const progressRef = useRef(null);
|
||||
const holdTimeout = useRef(null);
|
||||
|
||||
const startHold = () => {
|
||||
setIsHolding(true);
|
||||
if (progressRef.current) {
|
||||
progressRef.current.style.transition = "width 500ms linear";
|
||||
progressRef.current.style.width = "100%";
|
||||
}
|
||||
holdTimeout.current = setTimeout(() => {
|
||||
props.resendFunction();
|
||||
props.setIsHovered(false);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const cancelHold = () => {
|
||||
setIsHolding(false);
|
||||
if (progressRef.current) {
|
||||
progressRef.current.style.transition = "none";
|
||||
progressRef.current.style.width = "0%";
|
||||
}
|
||||
clearTimeout(holdTimeout.current);
|
||||
};
|
||||
|
||||
const onClickFunction = () => {
|
||||
props.editFunction();
|
||||
|
||||
};
|
||||
|
||||
const offset = {
|
||||
popper: {
|
||||
sx: {
|
||||
[`&.${tooltipClasses.popper}[data-popper-placement*="top"] .${tooltipClasses.tooltip}`]: { marginBottom: "0.2em" },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Tooltip
|
||||
title={<Title_p />}
|
||||
placement="top"
|
||||
slotProps={offset}
|
||||
>
|
||||
<button
|
||||
className={styles.resend_button}
|
||||
onMouseDown={startHold}
|
||||
onMouseUp={cancelHold}
|
||||
onMouseLeave={cancelHold}
|
||||
onClick={onClickFunction}
|
||||
>
|
||||
<SendMessageSvg className={styles.send_message_svg} />
|
||||
<RefreshSvg className={styles.refresh_svg} />
|
||||
<div ref={progressRef} className={styles.hold_progress_bar}></div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Title_p = () => {
|
||||
const { t } = useI18n();
|
||||
return <p className={styles.tooltip_title}>{t("main_page.message_log.resend_button_on_hover_desc")}</p>;
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
// ******************* *******************
|
||||
// ******************* Express in "em" not "rem" *******************
|
||||
// ******************* *******************
|
||||
.container {
|
||||
}
|
||||
.resend_button {
|
||||
background-color: var(--dark_825_color);
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 3.8em;
|
||||
}
|
||||
|
||||
.send_message_svg {
|
||||
position: absolute;
|
||||
top: 58%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 2.2em;
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
.refresh_svg {
|
||||
position: absolute;
|
||||
top: 36%;
|
||||
left: 42%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 1.8em;
|
||||
color: var(--sent_400_color);
|
||||
filter: drop-shadow(0.2em 0.2em 0 var(--dark_825_color));
|
||||
}
|
||||
|
||||
.tooltip_title {
|
||||
font-size: 1.2rem;
|
||||
color: var(--dark_200_color);
|
||||
}
|
||||
|
||||
.hold_progress_bar {
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 0%;
|
||||
height: 0.4em;
|
||||
background-color: var(--sent_400_color);
|
||||
transition: none;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { useState, useEffect, useLayoutEffect, useRef } from "react";
|
||||
import styles from "./MessageInputBox.module.scss";
|
||||
import SendMessageSvg from "@images/send_message.svg?react";
|
||||
import { useMessage } from "@logics_common";
|
||||
import { useAppearance, useOthers } from "@logics_configs";
|
||||
import { useMessageLogScroll } from "@logics_main";
|
||||
import { store } from "@store";
|
||||
|
||||
export const MessageInputBox = () => {
|
||||
const [message_history, setMessageHistory] = useState([]);
|
||||
const [history_index, setHistoryIndex] = useState(-1);
|
||||
const {
|
||||
sendMessage,
|
||||
currentMessageLogs,
|
||||
currentMessageInputValue,
|
||||
updateMessageInputValue,
|
||||
startTyping,
|
||||
stopTyping,
|
||||
} = useMessage();
|
||||
|
||||
const { currentEnableAutoClearMessageInputBox } = useOthers();
|
||||
const { currentSendMessageButtonType } = useAppearance();
|
||||
|
||||
const { scrollToBottom } = useMessageLogScroll();
|
||||
|
||||
const log_box_ref = useRef(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
store.text_area_ref = log_box_ref;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentMessageLogs.data) {
|
||||
const sentMessages = currentMessageLogs.data
|
||||
.filter(log => log.category === "sent")
|
||||
.map(log => log.messages.original.message);
|
||||
setMessageHistory(sentMessages);
|
||||
}
|
||||
}, [currentMessageLogs.data]);
|
||||
|
||||
const onSubmitFunction = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!currentMessageInputValue.data.trim()) return updateMessageInputValue("");
|
||||
|
||||
sendMessage(currentMessageInputValue.data);
|
||||
|
||||
if (currentEnableAutoClearMessageInputBox.data) updateMessageInputValue("");
|
||||
|
||||
setTimeout(() => {
|
||||
scrollToBottom();
|
||||
}, 10);
|
||||
|
||||
setHistoryIndex(-1);
|
||||
};
|
||||
|
||||
const onChangeFunction = (e) => {
|
||||
const value = e.currentTarget.value;
|
||||
updateMessageInputValue(value);
|
||||
value.trim() ? startTyping() : stopTyping();
|
||||
};
|
||||
|
||||
const onKeyDownFunction = (e) => {
|
||||
if (e.key === "ArrowUp" && e.shiftKey) {
|
||||
e.preventDefault();
|
||||
|
||||
if (history_index + 1 < message_history.length) {
|
||||
const new_index = history_index + 1;
|
||||
setHistoryIndex(new_index);
|
||||
updateMessageInputValue(message_history[message_history.length - 1 - new_index]);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.key === "ArrowDown" && e.shiftKey) {
|
||||
e.preventDefault();
|
||||
|
||||
if (history_index > -1) {
|
||||
const new_index = history_index - 1;
|
||||
setHistoryIndex(new_index);
|
||||
updateMessageInputValue(
|
||||
new_index >= 0
|
||||
? message_history[message_history.length - 1 - new_index]
|
||||
: ""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentSendMessageButtonType.data === "show_and_disable_enter_key") return;
|
||||
|
||||
if (e.keyCode === 13 && !e.shiftKey) {
|
||||
onSubmitFunction(e);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.message_box_wrapper}>
|
||||
<textarea
|
||||
ref={log_box_ref}
|
||||
className={styles.message_box_input_area}
|
||||
onChange={onChangeFunction}
|
||||
onBlur={stopTyping}
|
||||
// placeholder="Input Textfield"
|
||||
value={currentMessageInputValue.data}
|
||||
onKeyDown={onKeyDownFunction}
|
||||
/>
|
||||
</div>
|
||||
{currentSendMessageButtonType.data !== "hide" && (
|
||||
<SendMessageButton onSubmitFunction={onSubmitFunction} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SendMessageButton = ({ onSubmitFunction }) => {
|
||||
return (
|
||||
<button
|
||||
className={styles.message_send_button}
|
||||
type="button"
|
||||
onClick={onSubmitFunction}
|
||||
>
|
||||
<SendMessageSvg className={styles.message_send_icon} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.message_box_wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0.8rem;
|
||||
background-color: var(--dark_875_color);
|
||||
border: 0.1rem solid var(--dark_750_color);
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
|
||||
.message_box_input_area {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 1.6rem;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.message_send_button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-width: 10rem;
|
||||
height: 100%;
|
||||
font-size: 1.2rem;
|
||||
background-color: var(--dark_850_color);
|
||||
border-radius: 0.4rem;
|
||||
aspect-ratio: 1 / 1;
|
||||
&:hover {
|
||||
background-color: var(--dark_825_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.message_send_icon {
|
||||
width: 2rem;
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useState } from "react";
|
||||
import styles from "./MessageLogSettingsContainer.module.scss";
|
||||
import clsx from "clsx";
|
||||
import { useI18n } from "@useI18n";
|
||||
|
||||
import { MessageLogUiScalingContainer } from "@setting_box";
|
||||
import ConfigSvg from "@images/configuration.svg?react";
|
||||
|
||||
export const MessageLogSettingsContainer = (props) => {
|
||||
const { t } = useI18n();
|
||||
const [is_opened, setIsOpened] = useState(false);
|
||||
const [is_hovered, setIsHovered] = useState(false);
|
||||
|
||||
const container_class_name = clsx(styles.container, {
|
||||
[styles.to_visible_toggle_bar]: props.to_visible_toggle_bar,
|
||||
[styles.is_hovered]: is_hovered,
|
||||
[styles.is_opened]: is_opened
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={container_class_name}
|
||||
onMouseOver={() => setIsHovered(true)}
|
||||
onMouseLeave={() => {setIsHovered(false); setIsOpened(false);}}
|
||||
onClick={() => setIsOpened(true)}
|
||||
>
|
||||
<div className={styles.container_relative_wrapper}>
|
||||
<div className={styles.config_svg_wrapper}>
|
||||
<ConfigSvg className={styles.config_svg}/>
|
||||
</div>
|
||||
</div>
|
||||
<MessageLogUiScalingContainer />
|
||||
<div className={styles.others_wrapper}>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
$container_height: 18rem;
|
||||
$toggle_config_size: 3rem;
|
||||
.container {
|
||||
position: absolute;
|
||||
top: calc(-#{$container_height} + -#{$toggle_config_size});
|
||||
left: 0;
|
||||
height: $container_height;
|
||||
width: 100%;
|
||||
background-color: var(--dark_825_color);
|
||||
transition: top 0.3s ease;
|
||||
padding: 0.6rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
&.to_visible_toggle_bar {
|
||||
top: calc(-#{$container_height} + 1rem);
|
||||
& .config_svg_wrapper {
|
||||
transform: translate(-50%, -50%) rotate(180deg);
|
||||
}
|
||||
|
||||
}
|
||||
&.is_hovered {
|
||||
top: calc(-#{$container_height} + 2rem);
|
||||
}
|
||||
&.is_opened {
|
||||
top: 0;
|
||||
background-color: var(--dark_825_color_cc);
|
||||
backdrop-filter: blur(0.6rem);
|
||||
& .config_svg_wrapper {
|
||||
width: 0%;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
&:not(.is_opened) {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.container_relative_wrapper {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.config_svg_wrapper {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -#{$toggle_config_size};
|
||||
transform: translate(-50%, -50%) rotate(0deg);
|
||||
background-color: var(--dark_825_color);
|
||||
width: $toggle_config_size;
|
||||
border-radius: 50%;
|
||||
aspect-ratio: 1 / 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.3s ease, transform 0.6s ease;
|
||||
}
|
||||
|
||||
.config_svg {
|
||||
width: calc($toggle_config_size / 1.6);
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
|
||||
.others_wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.resend_checkbox_toggle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.6rem 1.2rem;
|
||||
gap: 1rem;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
}
|
||||
|
||||
.resend_checkbox_label {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
13
src-ui/views/app/main_page/main_section/top_bar/TopBar.jsx
Normal file
13
src-ui/views/app/main_page/main_section/top_bar/TopBar.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import styles from "./TopBar.module.scss";
|
||||
|
||||
import { SidebarCompactModeButton } from "./sidebar_compact_mode_button/SidebarCompactModeButton";
|
||||
import { RightSideComponents } from "./right_side_components/RightSideComponents";
|
||||
|
||||
export const TopBar = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<SidebarCompactModeButton />
|
||||
<RightSideComponents />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
.container {
|
||||
height: var(--main_page_topbar_height);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-right: 1.6rem;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
import styles from "./RightSideComponents.module.scss";
|
||||
import RefreshSvg from "@images/refresh.svg?react";
|
||||
import HelpSvg from "@images/help.svg?react";
|
||||
|
||||
import { useStore_OpenedQuickSetting } from "@store";
|
||||
import { useSoftwareVersion } from "@logics_common";
|
||||
import { useVr, useOthers } from "@logics_configs";
|
||||
import { OpenQuickSettingButton } from "./_buttons/OpenQuickSettingButton";
|
||||
|
||||
export const RightSideComponents = () => {
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
|
||||
<PluginsQuickSetting />
|
||||
<OpenVrcMicMuteSyncQuickSetting />
|
||||
<OpenOverlayQuickSetting />
|
||||
<SoftwareUpdateAvailableButton />
|
||||
<a
|
||||
className={styles.help_and_info_button}
|
||||
href="https://docs.google.com/spreadsheets/d/1_L5i-1U6PB1dnaPPTE_5uKMfqOpkLziPyRkiMLi4mqU/edit?usp=sharing"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<HelpSvg className={styles.help_svg} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OpenOverlayQuickSetting = () => {
|
||||
// const { t } = useI18n();
|
||||
const { updateOpenedQuickSetting } = useStore_OpenedQuickSetting();
|
||||
const {
|
||||
currentIsEnabledOverlaySmallLog,
|
||||
currentIsEnabledOverlayLargeLog,
|
||||
} = useVr();
|
||||
|
||||
const onClickFunction = () => {
|
||||
updateOpenedQuickSetting("overlay");
|
||||
};
|
||||
|
||||
const is_enable = currentIsEnabledOverlaySmallLog.data === true || currentIsEnabledOverlayLargeLog.data === true;
|
||||
|
||||
return (
|
||||
<OpenQuickSettingButton
|
||||
label="Overlay(VR)"
|
||||
variable={is_enable}
|
||||
onClickFunction={onClickFunction}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const PluginsQuickSetting = () => {
|
||||
const { t } = useI18n();
|
||||
const { updateOpenedQuickSetting } = useStore_OpenedQuickSetting();
|
||||
|
||||
const onClickFunction = () => {
|
||||
updateOpenedQuickSetting("plugins");
|
||||
};
|
||||
|
||||
return (
|
||||
<OpenQuickSettingButton
|
||||
label={t("config_page.side_menu_labels.plugins")}
|
||||
onClickFunction={onClickFunction}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const OpenVrcMicMuteSyncQuickSetting = () => {
|
||||
const { t } = useI18n();
|
||||
const { updateOpenedQuickSetting } = useStore_OpenedQuickSetting();
|
||||
const { currentEnableVrcMicMuteSync } = useOthers();
|
||||
|
||||
const onClickFunction = () => {
|
||||
updateOpenedQuickSetting("vrc_mic_mute_sync");
|
||||
};
|
||||
|
||||
return (
|
||||
<OpenQuickSettingButton
|
||||
label={t("config_page.others.vrc_mic_mute_sync.label")}
|
||||
variable={currentEnableVrcMicMuteSync.data.is_enabled}
|
||||
onClickFunction={onClickFunction}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const SoftwareUpdateAvailableButton = () => {
|
||||
const { currentLatestSoftwareVersionInfo } = useSoftwareVersion();
|
||||
const { t } = useI18n();
|
||||
if (currentLatestSoftwareVersionInfo.data.is_update_available === false) return null;
|
||||
|
||||
const { updateOpenedQuickSetting } = useStore_OpenedQuickSetting();
|
||||
|
||||
return (
|
||||
<button className={styles.software_update_button} onClick={()=>updateOpenedQuickSetting("update_software")}>
|
||||
<RefreshSvg className={styles.refresh_svg}/>
|
||||
<p className={styles.software_update_label}>{t("main_page.update_available")}</p>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.help_and_info_button {
|
||||
padding: 0.6rem;
|
||||
border-radius: 0.6rem;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.help_svg {
|
||||
width: 2.4rem;
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
|
||||
.software_update_button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
color: var(--primary_300_color);
|
||||
padding: 1rem 0.4rem;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.refresh_svg {
|
||||
width: 1.8rem;
|
||||
transform: rotate(-30deg);
|
||||
}
|
||||
|
||||
.software_update_label {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useI18n } from "@useI18n";
|
||||
import clsx from "clsx";
|
||||
import styles from "./OpenQuickSettingButton.module.scss";
|
||||
|
||||
export const OpenQuickSettingButton = (props) => {
|
||||
const { t } = useI18n();
|
||||
const variable = (typeof props.variable === "boolean") ? props.variable : null;
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.button_wrapper} onClick={props.onClickFunction}>
|
||||
<p className={styles.button_label}>{props.label}</p>
|
||||
{variable !== null && (
|
||||
props.variable === true ? (
|
||||
<p className={clsx(styles.button_indicator_label, styles.enabled)}>
|
||||
{t("main_page.state_text_enabled")}
|
||||
</p>
|
||||
) : (
|
||||
<p className={clsx(styles.button_indicator_label, styles.disabled)}>
|
||||
{t("main_page.state_text_disabled")}
|
||||
</p>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
.container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.button_wrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
padding: 0 0.8rem;
|
||||
border-radius: 0.2rem;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.button_label {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.button_indicator_label {
|
||||
font-size: 1rem;
|
||||
&.disabled {
|
||||
color: var(--dark_600_color);
|
||||
}
|
||||
&.enabled {
|
||||
color: var(--primary_300_color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import clsx from "clsx";
|
||||
import styles from "./SidebarCompactModeButton.module.scss";
|
||||
|
||||
import { useIsMainPageCompactMode } from "@logics_main";
|
||||
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||
|
||||
export const SidebarCompactModeButton = () => {
|
||||
const { toggleIsMainPageCompactMode, currentIsMainPageCompactMode } = useIsMainPageCompactMode();
|
||||
|
||||
const class_names = clsx(styles["arrow_left_svg"], {
|
||||
[styles["reverse"]]: currentIsMainPageCompactMode.data
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.container} onClick={toggleIsMainPageCompactMode}>
|
||||
<ArrowLeftSvg className={class_names} preserveAspectRatio="none" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
.container {
|
||||
height: 100%;
|
||||
width: 2.2rem;
|
||||
background-color: var(--dark_850_color);
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.arrow_left_svg {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 1.1rem 0rem;
|
||||
color: var(--dark_400_color);
|
||||
&.reverse {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
23
src-ui/views/app/main_page/sidebar_section/logo/Logo.jsx
Normal file
23
src-ui/views/app/main_page/sidebar_section/logo/Logo.jsx
Normal 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" />;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user