[Update] Migrate to tauri-app(Web UI)
This commit is contained in:
30
src-ui/windows/main_window/main_section/MainSection.jsx
Normal file
30
src-ui/windows/main_window/main_section/MainSection.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
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 { useIsOpenedLanguageSelector } from "@store";
|
||||
|
||||
export const MainSection = () => {
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<TopBar />
|
||||
<MessageContainer />
|
||||
<HandleLanguageSelector />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const HandleLanguageSelector = () => {
|
||||
const { currentIsOpenedLanguageSelector } = useIsOpenedLanguageSelector();
|
||||
|
||||
if (currentIsOpenedLanguageSelector.your_language === true) {
|
||||
return <LanguageSelector id="your_language"/>;
|
||||
} else if (currentIsOpenedLanguageSelector.target_language === true) {
|
||||
return <LanguageSelector id="target_language"/>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.language_selector_container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { language_list } from "@data";
|
||||
import styles from "./LanguageSelector.module.scss";
|
||||
|
||||
import { LanguageSelectorTopBar } from "./language_selector_top_bar/LanguageSelectorTopBar";
|
||||
export const LanguageSelector = ({ id }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const languageTitles = {
|
||||
"your_language": t("selectable_language_window.title_your_language"),
|
||||
"target_language": t("selectable_language_window.title_target_language")
|
||||
};
|
||||
|
||||
const language_selector_title = languageTitles[id] || "";
|
||||
|
||||
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(language_list);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<LanguageSelectorTopBar title={language_selector_title}/>
|
||||
<div className={styles.language_list_scroll_wrapper}>
|
||||
<div className={styles.language_list}>
|
||||
{Object.entries(groupedLanguages).map(([letter, languages]) => (
|
||||
<LanguageGroup key={letter} letter={letter} languages={languages} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LanguageGroup = ({ letter, languages }) => {
|
||||
return (
|
||||
<div className={styles.language_each_letter_box}>
|
||||
<p className={styles.language_latter}>{letter}</p>
|
||||
{languages.map((languageData, index) => (
|
||||
<LanguageButton key={index} languageData={languageData} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LanguageButton = ({ languageData }) => {
|
||||
return (
|
||||
<div className={styles.language_button}>
|
||||
<p className={styles.language_label}>{languageData.language} ({languageData.country})</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
.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: 1.4rem 1rem 8rem 1rem;
|
||||
}
|
||||
|
||||
.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;
|
||||
color: var(--dark_basic_text_color);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import styles from "./LanguageSelectorTopBar.module.scss";
|
||||
|
||||
import { useIsOpenedLanguageSelector } from "@store";
|
||||
|
||||
export const LanguageSelectorTopBar = (props) => {
|
||||
const { updateIsOpenedLanguageSelector } = useIsOpenedLanguageSelector();
|
||||
|
||||
const closeLanguageSelector = () => {
|
||||
updateIsOpenedLanguageSelector({
|
||||
your_language: false,
|
||||
target_language: false,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.go_back_button_wrapper} onClick={closeLanguageSelector}>
|
||||
<p className={styles.go_back_button_label}>Go Back</p>
|
||||
</div>
|
||||
<p className={styles.title}>{props.title}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
.container {
|
||||
height: var(--main_window_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: 1.2rem;
|
||||
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,34 @@
|
||||
import { useResizable } from "react-resizable-layout";
|
||||
|
||||
import styles from "./MessageContainer.module.scss";
|
||||
|
||||
import { LogBox } from "./log_box/LogBox";
|
||||
import { MessageInputBox } from "./message_input_box/MessageInputBox";
|
||||
|
||||
export const MessageContainer = () => {
|
||||
const { position, separatorProps } = useResizable({
|
||||
axis: "y",
|
||||
reverse: true
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<LogBox />
|
||||
<Separator
|
||||
dir={"horizontal"}
|
||||
{...separatorProps}
|
||||
/>
|
||||
<div className={styles.message_box_resize_wrapper} style={ { height: `${(position / 10) - 1.5 }rem` } }>
|
||||
<MessageInputBox />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Separator = ({ ...props }) => {
|
||||
return (
|
||||
<div tabIndex={0} className={styles.separator} {...props}>
|
||||
<span className={styles.separator_line}></span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
.container {
|
||||
height: 0%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 0 1.6rem 1rem 1.6rem;
|
||||
}
|
||||
|
||||
.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: 10rem;
|
||||
min-height: 3.8rem;
|
||||
max-height: 80%;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import styles from "./LogBox.module.scss";
|
||||
import { useMessageLogs, store } from "@store";
|
||||
import { MessageContainer } from "./message_container/MessageContainer";
|
||||
import { scrollToBottom } from "@logics/scrollToBottom";
|
||||
|
||||
export const LogBox = () => {
|
||||
const { currentMessageLogs } = useMessageLogs();
|
||||
const log_container_ref = useRef(null);
|
||||
const [is_scrolling, setIsScrolling] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
store.log_box_ref = log_container_ref;
|
||||
if (!is_scrolling) {
|
||||
scrollToBottom(store.log_box_ref, true);
|
||||
}
|
||||
}, [currentMessageLogs]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const element = log_container_ref.current;
|
||||
const currentScrollTop = element.scrollTop;
|
||||
const at_bottom = element.scrollHeight - currentScrollTop === element.clientHeight;
|
||||
|
||||
if (at_bottom) {
|
||||
setIsScrolling(false);
|
||||
} else {
|
||||
setIsScrolling(true);
|
||||
}
|
||||
};
|
||||
|
||||
const element = log_container_ref.current;
|
||||
element.addEventListener("scroll", handleScroll);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div id="log_container" className={styles.container} ref={log_container_ref}>
|
||||
{currentMessageLogs.map(message_data => (
|
||||
<MessageContainer key={message_data.id} {...message_data} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
.container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
background-color: var(--dark_900_color);
|
||||
overflow: auto;
|
||||
border-radius: 0.8rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.text {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import clsx from "clsx";
|
||||
import styles from "./MessageContainer.module.scss";
|
||||
|
||||
export const MessageContainer = ({ messages, status, category, created_at }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const is_translated_exist = messages.translated.length >= 1;
|
||||
const is_pending = status === "pending";
|
||||
const is_sent_message = category === "sent";
|
||||
const category_text = is_sent_message ? t("main_window.textbox_tab_sent") : t("main_window.textbox_tab_received");
|
||||
|
||||
const message_type_class_name = clsx({
|
||||
[styles.sent_message]: is_sent_message,
|
||||
[styles.received_message]: !is_sent_message,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={clsx(styles.container, 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_translated_exist
|
||||
? <WithTranslatedMessages messages={messages} />
|
||||
: <p className={styles.message_main}>{messages.original}</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const WithTranslatedMessages = ({ messages }) => {
|
||||
return (
|
||||
<>
|
||||
<p className={styles.message_second}>{messages.original}</p>
|
||||
{messages.translated.map((message, index) => (
|
||||
<p key={index} className={styles.message_main}>{message}</p>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
.container {
|
||||
margin-bottom: 1rem;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
&.sent_message {
|
||||
align-items: end;
|
||||
}
|
||||
&.received_message {
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
.info_box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
justify-content: center;
|
||||
&.sent_message {
|
||||
align-items: end;
|
||||
}
|
||||
&.received_message {
|
||||
flex-flow: row-reverse;
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
@include loader(0.8rem, 0.1rem, left, -1rem);
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 1rem;
|
||||
color: var(--dark_600_color);
|
||||
}
|
||||
|
||||
.category {
|
||||
font-size: 1rem;
|
||||
&.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 {
|
||||
align-items: end;
|
||||
}
|
||||
&.received_message {
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
.message_main {
|
||||
color: var(--dark_basic_text_color);
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.message_second {
|
||||
color: var(--dark_450_color);
|
||||
font-size: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useState } from "react";
|
||||
import styles from "./MessageInputBox.module.scss";
|
||||
import SendMessageSvg from "@images/send_message.svg?react";
|
||||
import { useMessage } from "@logics/useMessage";
|
||||
import { store } from "@store";
|
||||
import { scrollToBottom } from "@logics/scrollToBottom";
|
||||
|
||||
|
||||
export const MessageInputBox = () => {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const { sendMessage } = useMessage();
|
||||
|
||||
const onSubmitFunction = (e) => {
|
||||
e.preventDefault();
|
||||
sendMessage(inputValue);
|
||||
|
||||
setTimeout(() => {
|
||||
scrollToBottom(store.log_box_ref);
|
||||
}, 10);
|
||||
|
||||
};
|
||||
|
||||
const onChangeFunction = (e) => {
|
||||
setInputValue(e.currentTarget.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.message_box_wrapper}>
|
||||
<textarea
|
||||
className={styles.message_box_input_area}
|
||||
onChange={onChangeFunction}
|
||||
placeholder="Input Textfield"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className={styles.message_send_button}
|
||||
type="button"
|
||||
onClick={onSubmitFunction}
|
||||
>
|
||||
<SendMessageSvg className={styles.message_send_icon} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.message_box_wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-right: 1rem;
|
||||
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);
|
||||
}
|
||||
13
src-ui/windows/main_window/main_section/top_bar/TopBar.jsx
Normal file
13
src-ui/windows/main_window/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_window_topbar_height);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-right: 1.6rem;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import styles from "./RightSideComponents.module.scss";
|
||||
|
||||
import HelpSvg from "@images/help.svg?react";
|
||||
|
||||
export const RightSideComponents = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>VRC mic mute sync</p>
|
||||
<p>Overlay(VR)</p>
|
||||
<a
|
||||
className={styles.help_and_info_button}
|
||||
href="https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<HelpSvg className={styles.help_svg} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import clsx from "clsx";
|
||||
import styles from "./SidebarCompactModeButton.module.scss";
|
||||
|
||||
import { useIsCompactMode } from "@store";
|
||||
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||
|
||||
export const SidebarCompactModeButton = () => {
|
||||
const { updateIsCompactMode, currentIsCompactMode } = useIsCompactMode();
|
||||
|
||||
const toggleCompactMode = () => {
|
||||
updateIsCompactMode(!currentIsCompactMode);
|
||||
};
|
||||
|
||||
const class_names = clsx(styles["arrow_left_svg"], {
|
||||
[styles["reverse"]]: currentIsCompactMode
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.container} onClick={toggleCompactMode}>
|
||||
<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