[Refactor] Move to src-ui/views and src-ui/logics structure.
This commit is contained in:
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user