[Update] Migrate to tauri-app(Web UI)
This commit is contained in:
20
src-ui/windows/config_window/ConfigWindow.jsx
Normal file
20
src-ui/windows/config_window/ConfigWindow.jsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import "../../../locales/config.js";
|
||||
import "@utils/root.css";
|
||||
|
||||
import styles from "./ConfigWindow.module.scss";
|
||||
|
||||
import { Topbar } from "./topbar/Topbar";
|
||||
import { SidebarSection } from "./sidebar_section/SidebarSection";
|
||||
import { SettingSection } from "./setting_section/SettingSection.jsx";
|
||||
|
||||
export const ConfigWindow = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Topbar />
|
||||
<div className={styles.main_container}>
|
||||
<SidebarSection />
|
||||
<SettingSection />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
16
src-ui/windows/config_window/ConfigWindow.module.scss
Normal file
16
src-ui/windows/config_window/ConfigWindow.module.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
background-color: var(--dark_950_color);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main_container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
padding-top: var(--config_window_topbar_height);
|
||||
}
|
||||
14
src-ui/windows/config_window/index.html
Normal file
14
src-ui/windows/config_window/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Config Window</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
15
src-ui/windows/config_window/index.jsx
Normal file
15
src-ui/windows/config_window/index.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
// import "../locales/config.js";
|
||||
import { ConfigWindow } from "./ConfigWindow";
|
||||
// import "./reset.css";
|
||||
// import "./root.css";
|
||||
// import { useWindow } from "@utils/useWindow";
|
||||
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
<React.StrictMode>
|
||||
<ConfigWindow />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import styles from "./SettingSection.module.scss";
|
||||
import { SettingBox } from "./setting_box/SettingBox";
|
||||
|
||||
export const SettingSection = () => {
|
||||
return (
|
||||
<div className={styles.scroll_container}>
|
||||
<div className={styles.container}>
|
||||
<SettingBox />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
.scroll_container {
|
||||
width: 100%;
|
||||
margin-left: 4rem;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 4rem;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { useSettingBox } from "../useSettingBox";
|
||||
import { useSelectedMicDevice, useMicDeviceList } from "@store";
|
||||
export const Appearance = () => {
|
||||
const { currentSelectedMicDevice, updateSelectedMicDevice } = useSelectedMicDevice();
|
||||
const { currentMicDeviceList } = useMicDeviceList();
|
||||
const { DropdownMenuContainer } = useSettingBox();
|
||||
|
||||
const selectFunction = (selected_data) => {
|
||||
const asyncFunction = () => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(selected_data.selected_id);
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
updateSelectedMicDevice(asyncFunction);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuContainer dropdown_id="mic_host" label="Mic Host/Driver" desc="description" selected_id="b" list={{a: "A", b: "B", c: "C"}} />
|
||||
<DropdownMenuContainer dropdown_id="mic_device" label="Mic Device" desc="description" selected_id={currentSelectedMicDevice.data} list={currentMicDeviceList} selectFunction={selectFunction} state={currentSelectedMicDevice.state} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import styles from "./SettingBox.module.scss";
|
||||
import { useSelectedConfigTab } from "@store";
|
||||
|
||||
import { Appearance } from "./appearance/Appearance";
|
||||
import { AboutVrct } from "./about_vrct/AboutVrct";
|
||||
|
||||
export const SettingBox = () => {
|
||||
const { currentSelectedConfigTab } = useSelectedConfigTab();
|
||||
switch (currentSelectedConfigTab) {
|
||||
case "appearance":
|
||||
return <Appearance />;
|
||||
case "about_vrct":
|
||||
return <AboutVrct />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
import styles from "./AboutVrct.module.scss";
|
||||
import dev_section_title from "@images/about_vrct/dev_section_title.png";
|
||||
import dev_misya from "@images/about_vrct/dev_misya.png";
|
||||
import dev_shiina from "@images/about_vrct/dev_shiina.png";
|
||||
import vrct_logo_for_about_vrct from "@images/about_vrct/vrct_logo_for_about_vrct.png";
|
||||
import contributors_section_title from "@images/about_vrct/contributors_section_title.png";
|
||||
import contributors_members from "@images/about_vrct/contributors_members.png";
|
||||
import localization_section_title from "@images/about_vrct/localization_section_title.png";
|
||||
import localization_members from "@images/about_vrct/localization_members.png";
|
||||
|
||||
import special_thanks_section_title from "@images/about_vrct/special_thanks_section_title.png";
|
||||
import special_thanks_members from "@images/about_vrct/special_thanks_members.png";
|
||||
import special_thanks_message_en from "@images/about_vrct/special_thanks_message_en.png";
|
||||
import special_thanks_message_ja from "@images/about_vrct/special_thanks_message_ja.png";
|
||||
|
||||
import poster_showcase_section_title from "@images/about_vrct/poster_showcase_section_title.png";
|
||||
|
||||
import vrchat_disclaimer from "@images/about_vrct/vrchat_disclaimer.png";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useUiLanguage } from "@store";
|
||||
import { PosterShowcaseContents } from "./poster_showcase_contents/PosterShowcaseContents";
|
||||
|
||||
export const AboutVrct = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentUiLanguage } = useUiLanguage();
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.dev_section}>
|
||||
<img src={dev_section_title} className={clsx(styles.section_title, styles.the_developers)} />
|
||||
<div className={styles.dev_section_wrapper}>
|
||||
<div className={styles.dev_card_wrapper}>
|
||||
<img src={dev_misya} className={styles.dev_card_img} />
|
||||
<OpenLinkContainer className={styles.dev_misya_x} href_id="dev_misya_x" />
|
||||
<OpenLinkContainer className={styles.dev_misya_github} href_id="dev_misya_github" />
|
||||
</div>
|
||||
<div className={styles.dev_card_wrapper}>
|
||||
<img src={dev_shiina} className={styles.dev_card_img} />
|
||||
<OpenLinkContainer className={styles.dev_shiina_x} href_id="dev_shiina_x" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.project_links_and_logo_section}>
|
||||
<img src={vrct_logo_for_about_vrct} className={styles.about_vrct_logo} />
|
||||
<div className={styles.project_links_wrapper}>
|
||||
<OpenLinkContainer className={styles.project_link} href_id="project_link_booth" />
|
||||
<OpenLinkContainer className={styles.project_link} href_id="project_link_documents" />
|
||||
<OpenLinkContainer className={styles.project_link} href_id="project_link_vrct_github" />
|
||||
<OpenLinkContainer className={styles.project_link} href_id="project_link_contact_us" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.contributors_section}>
|
||||
<img src={contributors_section_title} className={clsx(styles.section_title, styles.contributors)} />
|
||||
<div className={styles.contributors_img_wrapper}>
|
||||
<img src={contributors_members} className={clsx(styles.contributors_img, styles.contributors)} />
|
||||
<OpenLinkContainer className={styles.contributors_done_san_x} href_id="contributors_done_san_x" />
|
||||
<OpenLinkContainer className={styles.contributors_iya_x} href_id="contributors_iya_x" />
|
||||
<OpenLinkContainer className={styles.contributors_rera_x} href_id="contributors_rera_x" />
|
||||
<OpenLinkContainer className={styles.contributors_rera_github} href_id="contributors_rera_github" />
|
||||
<OpenLinkContainer className={styles.contributors_poposuke_x} href_id="contributors_poposuke_x" />
|
||||
<OpenLinkContainer className={styles.contributors_kumaguma_x} href_id="contributors_kumaguma_x" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.localization_section}>
|
||||
<img src={localization_section_title} className={clsx(styles.section_title, styles.localization)} />
|
||||
<img src={localization_members} className={clsx(styles.localization_members_img, styles.localization)} />
|
||||
</div>
|
||||
|
||||
<div className={styles.special_thanks_section}>
|
||||
<img src={special_thanks_section_title} className={clsx(styles.section_title, styles.special_thanks)} />
|
||||
<img src={special_thanks_members} className={styles.special_thanks_members_img} />
|
||||
{
|
||||
currentUiLanguage === "ja"
|
||||
? <img src={special_thanks_message_ja} className={styles.special_thanks_message_img} />
|
||||
: <img src={special_thanks_message_en} className={styles.special_thanks_message_img} />
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
<div className={styles.poster_showcase_section}>
|
||||
<img src={poster_showcase_section_title} className={clsx(styles.section_title, styles.poster_showcase)} />
|
||||
<PosterShowcaseContents />
|
||||
</div>
|
||||
|
||||
<div className={styles.vrchat_disclaimer_section}>
|
||||
<img src={vrchat_disclaimer} className={styles.vrchat_disclaimer} />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import dev_x_icon from "@images/about_vrct/dev_x_icon.png";
|
||||
import dev_github_icon from "@images/about_vrct/dev_github_icon.png";
|
||||
import contributors_x_icon from "@images/about_vrct/contributors_x_icon.png";
|
||||
import contributors_github_icon from "@images/about_vrct/contributors_github_icon.png";
|
||||
|
||||
import project_link_booth from "@images/about_vrct/project_link_booth.png";
|
||||
import project_link_documents from "@images/about_vrct/project_link_documents.png";
|
||||
import project_link_vrct_github from "@images/about_vrct/project_link_vrct_github.png";
|
||||
import project_link_contact_us from "@images/about_vrct/project_link_contact_us.png";
|
||||
|
||||
const about_vrct_links = {
|
||||
dev_misya_x: { img: dev_x_icon, href: "https://twitter.com/misya_ai" },
|
||||
dev_misya_github: { img: dev_github_icon, href: "https://github.com/misyaguziya" },
|
||||
dev_shiina_x: { img: dev_x_icon, href: "https://twitter.com/Shiina_12siy" },
|
||||
|
||||
project_link_booth: { img: project_link_booth, href: "https://misyaguziya.booth.pm/items/5155325" },
|
||||
project_link_documents: { img: project_link_documents, href: "https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246" },
|
||||
project_link_vrct_github: { img: project_link_vrct_github, href: "https://github.com/misyaguziya/VRCT" },
|
||||
project_link_contact_us: { img: project_link_contact_us, href: "https://docs.google.com/forms/d/e/1FAIpQLSei-xoydOY60ivXqhOjaTzNN8PiBQIDcNhzfy6cw2sjYkcg_g/viewform" },
|
||||
|
||||
contributors_done_san_x: { img: contributors_x_icon, href: "https://twitter.com/done_vrc" },
|
||||
contributors_iya_x: { img: contributors_x_icon, href: "https://twitter.com/IYAA_HHHH" },
|
||||
contributors_rera_x: { img: contributors_x_icon, href: "https://twitter.com/rerassi" },
|
||||
contributors_rera_github: { img: contributors_github_icon, href: "https://github.com/soumt-r" },
|
||||
contributors_poposuke_x: { img: contributors_x_icon, href: "https://twitter.com/sig_popo" },
|
||||
contributors_kumaguma_x: { img: contributors_x_icon, href: "https://twitter.com/K_kumaguma_A" },
|
||||
};
|
||||
|
||||
const OpenLinkContainer = ({className, href_id}) => {
|
||||
const href = about_vrct_links[href_id].href;
|
||||
const img = about_vrct_links[href_id].img;
|
||||
return (
|
||||
<a className={className} href={href} target="_blank" rel="noreferrer" >
|
||||
{/* for adjust size to their parent component's width. */}
|
||||
<img style={ {height: "100%", width: "100%", "objectFit": "contain" }} src={img} />
|
||||
</a>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,171 @@
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 2.2rem;
|
||||
flex-direction: column;
|
||||
width: 72rem;
|
||||
// background-color: gray;
|
||||
}
|
||||
|
||||
.section_title {
|
||||
height: 1.2rem;
|
||||
object-fit: contain;
|
||||
object-position: left;
|
||||
&.the_developers {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
&.contributors {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
&.special_thanks {
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
&.poster_showcase {
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dev_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.dev_section_wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.dev_card_wrapper {
|
||||
width: 34.6rem;
|
||||
position: relative;
|
||||
}
|
||||
.dev_card_img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@mixin dev_sns_styles($right) {
|
||||
position: absolute;
|
||||
right: $right;
|
||||
bottom: 1.2rem;
|
||||
width: 2.8rem;
|
||||
padding: 0.4rem;
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color)
|
||||
}
|
||||
}
|
||||
.dev_misya_x {
|
||||
@include dev_sns_styles(6rem);
|
||||
}
|
||||
.dev_misya_github {
|
||||
@include dev_sns_styles(3rem);
|
||||
}
|
||||
.dev_shiina_x {
|
||||
@include dev_sns_styles(3rem);
|
||||
}
|
||||
|
||||
|
||||
.project_links_and_logo_section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
text-align: center;
|
||||
padding: 0 5.5rem;
|
||||
}
|
||||
.about_vrct_logo {
|
||||
width: 20rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
.project_links_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.project_link {
|
||||
height: 2.6rem;
|
||||
padding: 0.4rem 1rem;
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color)
|
||||
}
|
||||
}
|
||||
|
||||
.contributors_img_wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.contributors_img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@mixin contributors_sns_styles($top, $left) {
|
||||
position: absolute;
|
||||
left: $left;
|
||||
top: $top;
|
||||
width: 2.4rem;
|
||||
padding: 0.4rem;
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_825_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_888_color)
|
||||
}
|
||||
}
|
||||
|
||||
$first_line_top: 6.2rem;
|
||||
.contributors_done_san_x {
|
||||
@include contributors_sns_styles($first_line_top, 2rem);
|
||||
}
|
||||
.contributors_iya_x {
|
||||
@include contributors_sns_styles($first_line_top, 27.6rem);
|
||||
}
|
||||
.contributors_rera_x {
|
||||
@include contributors_sns_styles($first_line_top, 52.2rem);
|
||||
}
|
||||
.contributors_rera_github {
|
||||
@include contributors_sns_styles($first_line_top, 55rem);
|
||||
}
|
||||
|
||||
$second_line_top: 16.6rem;
|
||||
.contributors_poposuke_x {
|
||||
@include contributors_sns_styles($second_line_top, 14.8rem);
|
||||
}
|
||||
.contributors_kumaguma_x {
|
||||
@include contributors_sns_styles($second_line_top, 40.8rem);
|
||||
}
|
||||
|
||||
.localization_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.localization_members_img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.special_thanks_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.special_thanks_members_img {
|
||||
width: 100%;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
.special_thanks_message_img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.poster_showcase_section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.vrchat_disclaimer {
|
||||
width: 100%;
|
||||
margin-top: 8rem;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import styles from "./PosterShowcaseContents.module.scss";
|
||||
import { PostersContents } from "./posters_contents/PostersContents";
|
||||
import { PosterShowcaseWorldsContents } from "./poster_showcase_worlds_contents/PosterShowcaseWorldsContents";
|
||||
|
||||
export const PosterShowcaseContents = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<PosterShowcaseWorldsContents />
|
||||
<PostersContents />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
gap: 2rem;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import clsx from "clsx";
|
||||
import styles from "./PosterShowcaseWorldsContents.module.scss";
|
||||
import { usePosterShowcaseWorldPageIndex } from "@store";
|
||||
const images = import.meta.glob("@images/about_vrct/showcased_worlds/*.{png,jpg,jpeg,svg}", { eager: true });
|
||||
|
||||
const getImageByFileName = (file_name) => {
|
||||
const imagePath = Object.keys(images).find((path) => path.includes(file_name));
|
||||
return imagePath ? images[imagePath]?.default : null;
|
||||
};
|
||||
|
||||
import poster_showcase_worlds_settings from "./poster_showcase_worlds_settings";
|
||||
import { chunkArray } from "@utils/chunkArray";
|
||||
|
||||
export const PosterShowcaseWorldsContents = () => {
|
||||
const { currentPosterShowcaseWorldPageIndex } = usePosterShowcaseWorldPageIndex();
|
||||
const poster_showcase_world_images = poster_showcase_worlds_settings.map((setting) => ({
|
||||
img: getImageByFileName(setting.image_file_name),
|
||||
x_post_num: setting.x_post_num
|
||||
}));
|
||||
|
||||
const chunked_poster_showcase_world_images = chunkArray(poster_showcase_world_images, 8);
|
||||
const target_poster_showcase_world_images = chunked_poster_showcase_world_images[currentPosterShowcaseWorldPageIndex];
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.poster_showcase_world_container}>
|
||||
{target_poster_showcase_world_images.map((poster, index) => {
|
||||
let content = (
|
||||
<div className={styles.poster_showcase_world_img} >
|
||||
<img style={ {height: "100%", width: "100%", "objectFit": "contain" }} src={poster.img} />
|
||||
</div>
|
||||
);
|
||||
if (poster.x_post_num !== null) {
|
||||
content = (
|
||||
<a href={`https://twitter.com/Shiina_12siy/status/${poster.x_post_num}`} target="_blank" rel="noreferrer" >
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
const class_names = clsx(styles.poster_showcase_world_wrapper, {
|
||||
[styles.clickable]: (poster.x_post_num !== null)
|
||||
});
|
||||
return (
|
||||
<div className={class_names} key={index}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<PosterShowcaseWorldsPagination page_length={chunked_poster_showcase_world_images.length}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import chat_white_square from "@images/chato_white_square.png";
|
||||
import { useEffect } from "react";
|
||||
import { randomIntMinMax } from "@utils/randomIntMinMax";
|
||||
const PosterShowcaseWorldsPagination = ({ page_length }) => {
|
||||
const { currentPosterShowcaseWorldPageIndex, updatePosterShowcaseWorldPageIndex } = usePosterShowcaseWorldPageIndex();
|
||||
|
||||
useEffect(() => {
|
||||
updatePosterShowcaseWorldPageIndex(randomIntMinMax(page_length -1));
|
||||
},[page_length]);
|
||||
|
||||
const setPage = (index) => {
|
||||
updatePosterShowcaseWorldPageIndex(index);
|
||||
};
|
||||
|
||||
const getClassNames = (index, baseClass) => clsx(baseClass, {
|
||||
[styles.is_active]: (currentPosterShowcaseWorldPageIndex === index),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.pagination_container}>
|
||||
{[...Array(page_length).keys()].map((index) => {
|
||||
return (
|
||||
<div key={index} className={getClassNames(index, styles.pagination_box)} onClick={() => setPage(index)}>
|
||||
<div className={styles.chato_box}>
|
||||
<img src={chat_white_square} className={getClassNames(index, styles.pagination_chato_img)}/>
|
||||
</div>
|
||||
<div className={styles.indicator_box}>
|
||||
<div className={getClassNames(index, styles.indicator)}></div>
|
||||
<p className={getClassNames(index, styles.pagination_num)}>{index + 1}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
gap: 1rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
$image_height: 2.8rem;
|
||||
$y_padding: 0.4rem;
|
||||
$image_height_gap: 0.4rem;
|
||||
.poster_showcase_world_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: $image_height_gap;
|
||||
height: calc( (($image_height + ($y_padding*2)) * 8) + ($image_height_gap * (8 - 1)) );
|
||||
}
|
||||
.poster_showcase_world_wrapper {
|
||||
display: flex;
|
||||
padding: $y_padding 0.6rem $y_padding 0.8rem;
|
||||
border-radius: 0.4rem 0 0 0.4rem;
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_925_color);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.poster_showcase_world_img {
|
||||
height: $image_height;
|
||||
}
|
||||
|
||||
.pagination_container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 6%;
|
||||
margin: 0 2.6rem;
|
||||
}
|
||||
|
||||
$animation_duration: .15s;
|
||||
.pagination_box {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
&:active .pagination_chato_img {
|
||||
animation: tremble_animation $animation_duration ease-out;
|
||||
}
|
||||
&:active.is_active .pagination_chato_img {
|
||||
transform: translate(-50%, -50%) rotate(-22deg);
|
||||
}
|
||||
&.is_active .pagination_chato_img {
|
||||
top: 48%;
|
||||
animation: rotate_animation $animation_duration ease-out;
|
||||
}
|
||||
&:hover {
|
||||
& .pagination_chato_img {
|
||||
top: 108%;
|
||||
}
|
||||
&.is_active .pagination_chato_img {
|
||||
animation: tremble_animation $animation_duration ease-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.indicator_box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
width: 100%;
|
||||
height: 0.2rem;
|
||||
background-color: var(--dark_800_color);
|
||||
&.is_active {
|
||||
background-color: var(--primary_400_color);
|
||||
}
|
||||
}
|
||||
|
||||
.pagination_num {
|
||||
font-size: 1.8rem;
|
||||
padding: 1rem;
|
||||
color: var(--dark_600_color);
|
||||
&.is_active {
|
||||
color: var(--primary_300_color);
|
||||
}
|
||||
}
|
||||
|
||||
.chato_box {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 6rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pagination_chato_img {
|
||||
position: absolute;
|
||||
top: 200%;
|
||||
left: 51%;
|
||||
transform: translate(-50%, -50%) rotate(22deg);
|
||||
width: 2.8rem;
|
||||
transition: top $animation_duration ease-out;
|
||||
}
|
||||
@keyframes rotate_animation {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(22deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg + 22deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tremble_animation {
|
||||
0% { left: 51%; }
|
||||
25% { left: 55%; }
|
||||
50% { left: 45%; }
|
||||
75% { left: 48%; }
|
||||
100% { left: 51%; }
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
const poster_showcase_worlds_settings = [
|
||||
// トサカひよ
|
||||
{ image_file_name: "kokekkopiyopiyo", x_post_num: "1779076974369276014" },
|
||||
|
||||
// MiuJepang
|
||||
{ image_file_name: "ippaidou", x_post_num: "1787801976354513319" },
|
||||
{ image_file_name: "nihongokurabu", x_post_num: "1779004631936614893" },
|
||||
{ image_file_name: "language_exchange_tervern", x_post_num: "1779749425923150317" },
|
||||
{ image_file_name: "japanese_culture_osenbeito", x_post_num: "1788522972409721137" },
|
||||
{ image_file_name: "silakan_datang_ke_rumahku", x_post_num: "1788522607631056941" },
|
||||
{ image_file_name: "uj_club", x_post_num: "1780791654196388201" },
|
||||
{ image_file_name: "sushi_stand_guruguru", x_post_num: "1788523302404952218" },
|
||||
{ image_file_name: "sushi_guru_annex", x_post_num: null },
|
||||
|
||||
// poposuke_sig
|
||||
{ image_file_name: "usanezumi_shrine2", x_post_num: "1781224020383506649" },
|
||||
|
||||
// KUROINU_YOUHEI
|
||||
{ image_file_name: "kuroinu_work_room", x_post_num: "1779750007564112146" },
|
||||
|
||||
// いちや_ICHIYA
|
||||
{ image_file_name: "ehon_no_heikousekai_jimusho", x_post_num: "1780792306976850285" },
|
||||
{ image_file_name: "ikoiba", x_post_num: "1782723006923780580" },
|
||||
{ image_file_name: "kimodameshi", x_post_num: "1781224391692714133" },
|
||||
{ image_file_name: "parallel_collar", x_post_num: null },
|
||||
|
||||
// HayaTikaze
|
||||
{ image_file_name: "study_japanese_world_japanichijou", x_post_num: "1781539871829766550" },
|
||||
|
||||
// aji_3
|
||||
{ image_file_name: "yuttari_eikaiwa", x_post_num: "1779002892999078046" },
|
||||
|
||||
// 八葉そるち
|
||||
{ image_file_name: "re_yatuha_room", x_post_num: "1779830390435590196" },
|
||||
|
||||
// chakamoto
|
||||
{ image_file_name: "chakachaka_multipurpose_room", x_post_num: null },
|
||||
|
||||
// MloYolM (よるむ)
|
||||
{ image_file_name: "cafe_cian", x_post_num: "1787802552907739504" },
|
||||
|
||||
// ミラクル・オルカ
|
||||
{ image_file_name: "mamehinata_dogrun", x_post_num: "1782723423179100471" },
|
||||
|
||||
// いんく(Eenkoo)
|
||||
{ image_file_name: "tyuuniti_kouryuukai", x_post_num: null },
|
||||
|
||||
// 1ban_meno
|
||||
{ image_file_name: "bar_asagao", x_post_num: "1788523857642758370" },
|
||||
|
||||
// 沈黙静寂
|
||||
{ image_file_name: "monogatari_meetup", x_post_num: "1781538415789674976" },
|
||||
|
||||
// tommie_500
|
||||
{ image_file_name: "stretch_club_starting_from_minus", x_post_num: null },
|
||||
|
||||
// MiMi_Sorahana # VRC日韓交流会 (KRJPEX.1355)
|
||||
{ image_file_name: "kr_jp_exchange", x_post_num: null },
|
||||
|
||||
// Ein(アイン)
|
||||
{ image_file_name: "smokerz_guild_v2", x_post_num: null },
|
||||
];
|
||||
export default poster_showcase_worlds_settings;
|
||||
@@ -0,0 +1,69 @@
|
||||
import clsx from "clsx";
|
||||
import styles from "./PostersContents.module.scss";
|
||||
import { useUiLanguage } from "@store";
|
||||
|
||||
import { useVrctPosterIndex } from "@store";
|
||||
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||
|
||||
import iya_vrct_poster_ja from "@images/about_vrct/vrct_posters/iya_vrct_poster_ja.png";
|
||||
import iya_vrct_poster_en from "@images/about_vrct/vrct_posters/iya_vrct_poster_en.png";
|
||||
import iya_vrct_poster_cn from "@images/about_vrct/vrct_posters/iya_vrct_poster_cn.png";
|
||||
import iya_vrct_poster_ko from "@images/about_vrct/vrct_posters/iya_vrct_poster_ko.png";
|
||||
import iya_vrct_manga_ja from "@images/about_vrct/vrct_posters/iya_vrct_manga_ja.png";
|
||||
import iya_vrct_manga_en from "@images/about_vrct/vrct_posters/iya_vrct_manga_en.png";
|
||||
import iya_vrct_manga_ko from "@images/about_vrct/vrct_posters/iya_vrct_manga_ko.png";
|
||||
|
||||
const poster_images = [
|
||||
{ img: iya_vrct_poster_ja, poster_type: "poster" },
|
||||
{ img: iya_vrct_poster_en, poster_type: "poster" },
|
||||
{ img: iya_vrct_poster_cn, poster_type: "poster" },
|
||||
{ img: iya_vrct_poster_ko, poster_type: "poster" },
|
||||
{ img: iya_vrct_manga_ja, poster_type: "manga" },
|
||||
{ img: iya_vrct_manga_en, poster_type: "manga" },
|
||||
{ img: iya_vrct_manga_ko, poster_type: "manga" },
|
||||
];
|
||||
|
||||
import poster_images_authors_ja from "@images/about_vrct/vrct_posters/authors/poster_images_authors_ja.png";
|
||||
import poster_images_authors_en from "@images/about_vrct/vrct_posters/authors/poster_images_authors_en.png";
|
||||
import poster_images_authors_m_ja from "@images/about_vrct/vrct_posters/authors/poster_images_authors_m_ja.png";
|
||||
import poster_images_authors_m_en from "@images/about_vrct/vrct_posters/authors/poster_images_authors_m_en.png";
|
||||
|
||||
export const PostersContents = () => {
|
||||
const { currentVrctPosterIndex, updateVrctPosterIndex } = useVrctPosterIndex();
|
||||
const { currentUiLanguage } = useUiLanguage();
|
||||
|
||||
|
||||
const updateIndex = (delta) => {
|
||||
const newIndex = (currentVrctPosterIndex + delta + poster_images.length) % poster_images.length;
|
||||
updateVrctPosterIndex(newIndex);
|
||||
};
|
||||
|
||||
const current_poster = poster_images[currentVrctPosterIndex];
|
||||
const current_poster_authors_img_ja = (current_poster.poster_type === "poster") ? poster_images_authors_ja : poster_images_authors_m_ja;
|
||||
const current_poster_authors_img_en = (current_poster.poster_type === "poster") ? poster_images_authors_en : poster_images_authors_m_en;
|
||||
|
||||
return (
|
||||
<div className={styles.poster_pagination_container}>
|
||||
<div className={styles.poster_pagination_wrapper}>
|
||||
<button
|
||||
className={clsx(styles.poster_pagination_button, styles.poster_prev)}
|
||||
onClick={() => updateIndex(-1)}
|
||||
>
|
||||
<ArrowLeftSvg className={clsx(styles.poster_pagination_svg, styles.poster_prev_svg)} />
|
||||
</button>
|
||||
<img src={current_poster.img} className={styles.poster_img} />
|
||||
<button
|
||||
className={clsx(styles.poster_pagination_button, styles.poster_next)}
|
||||
onClick={() => updateIndex(1)}
|
||||
>
|
||||
<ArrowLeftSvg className={clsx(styles.poster_pagination_svg, styles.poster_next_svg)} />
|
||||
</button>
|
||||
</div>
|
||||
{
|
||||
currentUiLanguage === "ja"
|
||||
? <img src={current_poster_authors_img_ja} className={styles.poster_authors_img} />
|
||||
: <img src={current_poster_authors_img_en} className={styles.poster_authors_img} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
.poster_pagination_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.poster_pagination_wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
$poster_img_width: 18rem;
|
||||
.poster_img {
|
||||
width: $poster_img_width;
|
||||
}
|
||||
|
||||
$poster_pagination_button_width: 4.6rem;
|
||||
.poster_pagination_button {
|
||||
width: $poster_pagination_button_width;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--dark_700_color);
|
||||
&:hover {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_925_color);
|
||||
}
|
||||
&.poster_prev {
|
||||
border-radius: 0.8rem 0 0 0.8rem;
|
||||
}
|
||||
&.poster_next {
|
||||
border-radius: 0 0.8rem 0.8rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.poster_pagination_svg {
|
||||
width: 3.2rem;
|
||||
&.poster_next_svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.poster_authors_img {
|
||||
width: $poster_img_width + $poster_pagination_button_width;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import styles from "./DropdownMenu.module.scss";
|
||||
|
||||
import clsx from "clsx";
|
||||
|
||||
import { useOpenedDropdownMenu } from "@store";
|
||||
|
||||
export const DropdownMenu = (props) => {
|
||||
|
||||
const { updateOpenedDropdownMenu, currentOpenedDropdownMenu } = useOpenedDropdownMenu();
|
||||
const openDropdownMenu = () => {
|
||||
updateOpenedDropdownMenu(props.dropdown_id);
|
||||
};
|
||||
|
||||
const selectValue = (key) => {
|
||||
updateOpenedDropdownMenu("");
|
||||
props.selectFunction({
|
||||
dropdown_id: props.dropdown_id,
|
||||
selected_id: key,
|
||||
});
|
||||
};
|
||||
|
||||
const dropdown_content_wrapper_class_name = clsx(styles["dropdown_content_wrapper"], {
|
||||
[styles["is_opened"]]: (currentOpenedDropdownMenu === props.dropdown_id) ? true : false
|
||||
});
|
||||
|
||||
const dropdown_toggle_button_class_name = clsx(styles["dropdown_toggle_button"], {
|
||||
[styles["is_loading"]]: (props.state === "loading") ? true : false
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={dropdown_toggle_button_class_name} onClick={openDropdownMenu}>
|
||||
{(props.state === "loading")
|
||||
? <p className={styles.dropdown_selected_text}>Loading...</p>
|
||||
: <p className={styles.dropdown_selected_text}>{props.list[props.selected_id]}</p>
|
||||
}
|
||||
{(props.state === "loading")
|
||||
? <span className={styles.loader}></span>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
<div className={dropdown_content_wrapper_class_name}>
|
||||
<div className={styles.dropdown_content}>
|
||||
{
|
||||
Object.entries(props.list).map(([key, value]) => {
|
||||
return (
|
||||
<div key={key} className={styles.value_button} onClick={() => selectValue(key)}>
|
||||
<p className={styles.value_text} >{value}</p>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown_toggle_button {
|
||||
position: relative;
|
||||
background-color: var(--dark_925_color);
|
||||
min-width: 20rem;
|
||||
padding: 0.6rem 0.8rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_950_color);
|
||||
}
|
||||
&.is_loading {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown_selected_text {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.dropdown_content_wrapper {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%; // Position it below the toggle button
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
&.is_opened {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown_content {
|
||||
background-color: var(--dark_900_color);
|
||||
border: 0.1rem solid var(--dark_600_color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
.value_button {
|
||||
background-color: var(--dark_875_color);
|
||||
padding: 0.8rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.value_text {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.loader {
|
||||
@include loader(2rem, 0.2rem, right, 0);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import styles from "./LabelComponent.module.scss";
|
||||
|
||||
export const LabelComponent = (props) => {
|
||||
return (
|
||||
<div className={styles.label_component}>
|
||||
<p>{props.label}</p>
|
||||
<p>{props.desc}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
.label_component {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import styles from "./useSettingBox.module.scss";
|
||||
import { LabelComponent } from "./components/label_component/LabelComponent";
|
||||
import { DropdownMenu } from "./components/dropdown_menu/DropdownMenu";
|
||||
import { useOpenedDropdownMenu } from "@store";
|
||||
|
||||
export const useSettingBox = () => {
|
||||
const { updateOpenedDropdownMenu } = useOpenedDropdownMenu();
|
||||
|
||||
const DropdownMenuContainer = (props) => {
|
||||
const onMouseLeaveFunction = () => {
|
||||
updateOpenedDropdownMenu("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container} onMouseLeave={onMouseLeaveFunction}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
<DropdownMenu {...props}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return { DropdownMenuContainer };
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
.container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: var(--dark_888_color);
|
||||
padding: 2rem;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import styles from "./SidebarSection.module.scss";
|
||||
|
||||
export const SidebarSection = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.tabs_wrapper}>
|
||||
<Tab tab_id="appearance" />
|
||||
<Tab tab_id="translation" />
|
||||
<Tab tab_id="transcription" />
|
||||
<Tab tab_id="vr" />
|
||||
<Tab tab_id="others" />
|
||||
<Tab tab_id="advanced_settings" />
|
||||
</div>
|
||||
<div className={styles.separated_tabs_wrapper}>
|
||||
<Tab tab_id="about_vrct" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelectedConfigTab } from "@store";
|
||||
|
||||
const Tab = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const { updateSelectedConfigTab, currentSelectedConfigTab } = useSelectedConfigTab();
|
||||
const onclickFunction = () => {
|
||||
updateSelectedConfigTab(props.tab_id);
|
||||
};
|
||||
|
||||
const tab_container_class_names = clsx(styles["tab_container"], {
|
||||
[styles["is_selected"]]: (currentSelectedConfigTab === props.tab_id) ? true : false
|
||||
});
|
||||
const switch_indicator_class_names = clsx(styles["switch_indicator"], {
|
||||
[styles["is_selected"]]: (currentSelectedConfigTab === props.tab_id) ? true : false
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={tab_container_class_names} onClick={onclickFunction}>
|
||||
<p className={styles.tab_text}>{t(`config_window.side_menu_labels.${props.tab_id}`)}</p>
|
||||
<div className={switch_indicator_class_names}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
.container {
|
||||
width: var(--config_window_sidebar_width);
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 5.8rem 0rem 5.8rem 2.8rem;
|
||||
max-height: 60rem;
|
||||
}
|
||||
|
||||
.tabs_wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tab_container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
color: var(--dark_basic_text_color);
|
||||
padding: 0.8rem 0 0.8rem 1.8rem;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
&.is_selected {
|
||||
background-color: inherit;
|
||||
color: var(--primary_200_color);
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.switch_indicator {
|
||||
display: none;
|
||||
&.is_selected {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0rem;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 0.2rem;
|
||||
height: 2.6rem;
|
||||
border-radius: 0.1rem;
|
||||
background-color: var(--primary_300_color);
|
||||
}
|
||||
}
|
||||
|
||||
.tab_text {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.separated_tabs_wrapper {
|
||||
// padding-bottom: 1.2rem;
|
||||
}
|
||||
17
src-ui/windows/config_window/topbar/Topbar.jsx
Normal file
17
src-ui/windows/config_window/topbar/Topbar.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import styles from "./Topbar.module.scss";
|
||||
|
||||
import { TitleBox } from "./title_box/TitleBox";
|
||||
import { SectionTitleBox } from "./section_title_box/SectionTitleBox";
|
||||
import { CompactSwitchBox } from "./compact_switch_box/CompactSwitchBox";
|
||||
|
||||
export const Topbar = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.wrapper}>
|
||||
<TitleBox />
|
||||
<SectionTitleBox />
|
||||
<CompactSwitchBox />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
12
src-ui/windows/config_window/topbar/Topbar.module.scss
Normal file
12
src-ui/windows/config_window/topbar/Topbar.module.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 0%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
height: var(--config_window_topbar_height);
|
||||
background-color: var(--dark_850_color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import styles from "./CompactSwitchBox.module.scss";
|
||||
|
||||
export const CompactSwitchBox = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{t("config_window.compact_mode")}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
.container {
|
||||
// flex: 0;
|
||||
// width: 100%;
|
||||
width: 14rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styles from "./SectionTitleBox.module.scss";
|
||||
import { useSelectedConfigTab } from "@store";
|
||||
|
||||
export const SectionTitleBox = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentSelectedConfigTab } = useSelectedConfigTab();
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p className={styles.title}>{t(`config_window.side_menu_labels.${currentSelectedConfigTab}`)}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
.container {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
14
src-ui/windows/config_window/topbar/title_box/TitleBox.jsx
Normal file
14
src-ui/windows/config_window/topbar/title_box/TitleBox.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import styles from "./TitleBox.module.scss";
|
||||
import chato_img from "@images/chato_white.png";
|
||||
|
||||
export const TitleBox = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<img src={chato_img} className={styles.logo_chato} alt="VRCT logo chato" />
|
||||
<p className={styles.title}>{t("config_window.config_title")}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
.container {
|
||||
// flex: 0;
|
||||
width: var(--config_window_sidebar_width);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
padding-left: 2.6rem;
|
||||
gap: 1.4rem;
|
||||
}
|
||||
|
||||
.logo_chato {
|
||||
width: 3.2rem;
|
||||
padding-top: 0.6rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
52
src-ui/windows/main_window/MainWindow.jsx
Normal file
52
src-ui/windows/main_window/MainWindow.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import styles from "./MainWindow.module.scss";
|
||||
import { SidebarSection } from "./sidebar_section/SidebarSection";
|
||||
import { MainSection } from "./main_section/MainSection";
|
||||
import { useStartPython } from "@logics/useStartPython";
|
||||
|
||||
export const MainWindow = () => {
|
||||
const { asyncStartPython } = useStartPython();
|
||||
const hasRunRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasRunRef.current) {
|
||||
asyncStartPython();
|
||||
}
|
||||
return () => hasRunRef.current = true;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<SidebarSection />
|
||||
<MainSection />
|
||||
<MainWindowCover />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useIsOpenedConfigWindow } from "@store";
|
||||
import { useWindow } from "@utils/useWindow";
|
||||
|
||||
export const MainWindowCover = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentIsOpenedConfigWindow } = useIsOpenedConfigWindow();
|
||||
const { closeConfigWindow } = useWindow();
|
||||
// console.log(currentIsOpenedConfigWindow);
|
||||
if ( currentIsOpenedConfigWindow === false) return null;
|
||||
|
||||
const closeSettingsWindow = () => closeConfigWindow();
|
||||
|
||||
return (
|
||||
<div className={styles.main_window_cover}>
|
||||
<p className={styles.cover_message}>{t("main_window.cover_message")}</p>
|
||||
<button
|
||||
className={styles.close_settings_window_button}
|
||||
onClick={closeSettingsWindow}
|
||||
>
|
||||
{t("main_window.close_settings_window")}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
45
src-ui/windows/main_window/MainWindow.module.scss
Normal file
45
src-ui/windows/main_window/MainWindow.module.scss
Normal file
@@ -0,0 +1,45 @@
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
background-color: var(--dark_888_color);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main_window_cover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: (#000000cc);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
backdrop-filter: blur(0.8rem);
|
||||
color: var(--dark_basic_text_color);
|
||||
}
|
||||
|
||||
.cover_message {
|
||||
font-size: 2rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.close_settings_window_button {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 300;
|
||||
padding: 1rem 1.4rem;
|
||||
border-radius: 0.4rem;
|
||||
background-color: var(--dark_888_color);
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_825_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_925_color);
|
||||
}
|
||||
}
|
||||
11
src-ui/windows/main_window/index.jsx
Normal file
11
src-ui/windows/main_window/index.jsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "../../../locales/config.js";
|
||||
import "@utils/root.css";
|
||||
import { MainWindow } from "./MainWindow";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
<React.StrictMode>
|
||||
<MainWindow />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
import styles from "./SidebarSection.module.scss";
|
||||
import { useIsCompactMode } from "@store";
|
||||
|
||||
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 { currentIsCompactMode } = useIsCompactMode();
|
||||
const container_class_name = clsx(styles["container"], {
|
||||
[styles["is_compact_mode"]]: currentIsCompactMode
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={container_class_name}>
|
||||
<Logo />
|
||||
<MainFunctionSwitch />
|
||||
{!currentIsCompactMode && <LanguageSettings />}
|
||||
<OpenSettings />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import styles from "./LanguageSettings.module.scss";
|
||||
|
||||
import { PresetSelectTabs } from "./preset_select_tabs/PresetSelectTabs";
|
||||
import { LanguageSelectorOpenButton } from "./language_selector_open_button/LanguageSelectorOpenButton";
|
||||
import { LanguageSwapButton } from "./language_swap_button/LanguageSwapButton";
|
||||
import { TranslatorSelectorOpenButton } from "./translator_selector_open_button/TranslatorSelectorOpenButton";
|
||||
import { useOpenedTranslatorSelector } from "@store";
|
||||
|
||||
export const LanguageSettings = () => {
|
||||
const { updateOpenedTranslatorSelector} = useOpenedTranslatorSelector();
|
||||
const closeTranslatorSelector = () => updateOpenedTranslatorSelector(false);
|
||||
|
||||
return (
|
||||
<div className={styles.container} onMouseLeave={closeTranslatorSelector} >
|
||||
<p className={styles.title}>Language Settings</p>
|
||||
<PresetSelectTabs />
|
||||
<PresetContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
import MicSvg from "@images/mic.svg?react";
|
||||
import HeadphonesSvg from "@images/headphones.svg?react";
|
||||
import { useIsOpenedLanguageSelector } from "@store";
|
||||
import { useMainFunction } from "@logics/useMainFunction";
|
||||
|
||||
const PresetContainer = () => {
|
||||
const { t } = useTranslation();
|
||||
const { updateIsOpenedLanguageSelector, currentIsOpenedLanguageSelector } = useIsOpenedLanguageSelector();
|
||||
|
||||
const {
|
||||
currentState_TranscriptionSend,
|
||||
currentState_TranscriptionReceive,
|
||||
} = useMainFunction();
|
||||
|
||||
|
||||
const closeLanguageSelector = () => {
|
||||
updateIsOpenedLanguageSelector({
|
||||
your_language: false,
|
||||
target_language: false,
|
||||
});
|
||||
};
|
||||
|
||||
const toggleYourLanguageSelector = () => {
|
||||
if (currentIsOpenedLanguageSelector.your_language === true) {
|
||||
closeLanguageSelector();
|
||||
} else {
|
||||
updateIsOpenedLanguageSelector({
|
||||
your_language: true,
|
||||
target_language: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const toggleTargetLanguageSelector = () => {
|
||||
if (currentIsOpenedLanguageSelector.target_language === true) {
|
||||
closeLanguageSelector();
|
||||
} else {
|
||||
updateIsOpenedLanguageSelector({
|
||||
your_language: false,
|
||||
target_language: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleLanguageSelectorClick = (selector) => {
|
||||
if (selector === "your_language") {
|
||||
toggleYourLanguageSelector();
|
||||
} else if (selector === "target_language") {
|
||||
toggleTargetLanguageSelector();
|
||||
}
|
||||
};
|
||||
|
||||
const your_language_settings = {
|
||||
title: t("main_window.your_language"),
|
||||
is_opened: currentIsOpenedLanguageSelector.your_language,
|
||||
onClickFunction: () => handleLanguageSelectorClick("your_language"),
|
||||
TurnedOnSvgComponent: <MicSvg />,
|
||||
is_turned_on: currentState_TranscriptionSend.data,
|
||||
};
|
||||
|
||||
const target_language_settings = {
|
||||
title: t("main_window.target_language"),
|
||||
is_opened: currentIsOpenedLanguageSelector.target_language,
|
||||
onClickFunction: () => handleLanguageSelectorClick("target_language"),
|
||||
TurnedOnSvgComponent: <HeadphonesSvg />,
|
||||
is_turned_on: currentState_TranscriptionReceive.data,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.preset_container}>
|
||||
<LanguageSelectorOpenButton {...your_language_settings} />
|
||||
<LanguageSwapButton />
|
||||
<LanguageSelectorOpenButton {...target_language_settings} />
|
||||
<TranslatorSelectorOpenButton />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import clsx from "clsx";
|
||||
import styles from "./LanguageSelectorOpenButton.module.scss";
|
||||
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||
import { useSvg } from "@utils/useSvg";
|
||||
export const LanguageSelectorOpenButton = (props) => {
|
||||
|
||||
const toggleLanguageSelector = () => {
|
||||
props.onClickFunction();
|
||||
};
|
||||
|
||||
const class_names = clsx(styles["arrow_left_svg"], {
|
||||
[styles["reverse"]]: props.is_opened
|
||||
});
|
||||
|
||||
const SvgComponent = useSvg(props.TurnedOnSvgComponent,
|
||||
{className: clsx(styles["category_svg"], {
|
||||
[styles["is_turned_on"]]: props.is_turned_on
|
||||
})}
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.title_container}>
|
||||
{SvgComponent}
|
||||
<p className={styles.title}>{props.title}</p>
|
||||
</div>
|
||||
<div className={styles.dropdown_menu_container} onClick={toggleLanguageSelector}>
|
||||
<p className={styles.selected_language}>Japanese</p>
|
||||
<p className={styles.selected_language}>(Japan)</p>
|
||||
<ArrowLeftSvg className={class_names} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
.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.2rem 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;
|
||||
color: var(--dark_basic_text_color);
|
||||
}
|
||||
|
||||
.arrow_left_svg {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin: 0 0.2rem;
|
||||
transform: rotate(180deg);
|
||||
width: 1.6rem;
|
||||
color: var(--dark_basic_text_color);
|
||||
&.reverse {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useState } from "react";
|
||||
import clsx from "clsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import styles from "./LanguageSwapButton.module.scss";
|
||||
|
||||
import NarrowArrowDownSvg from "@images/narrow_arrow_down.svg?react";
|
||||
|
||||
export const LanguageSwapButton = () => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const label = isHovered
|
||||
? t("main_window.swap_button_label")
|
||||
: t("main_window.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}
|
||||
>
|
||||
<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_basic_text_color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import styles from "./PresetSelectTabs.module.scss";
|
||||
|
||||
export const PresetSelectTabs = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Tab preset_number={1} />
|
||||
<Tab preset_number={2} />
|
||||
<Tab preset_number={3} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import clsx from "clsx";
|
||||
|
||||
import { useSelectedTab } from "@store";
|
||||
|
||||
const Tab = (props) => {
|
||||
const { updateSelectedTab, currentSelectedTab } = useSelectedTab();
|
||||
const onclickFunction = () => {
|
||||
updateSelectedTab(props.preset_number);
|
||||
};
|
||||
|
||||
const class_names = clsx(styles["tab_container"], {
|
||||
[styles["is_selected"]]: (currentSelectedTab === 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_basic_text_color);
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tab_number {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import styles from "./TranslatorSelectorOpenButton.module.scss";
|
||||
import { TranslatorSelector } from "./translator_selector/TranslatorSelector";
|
||||
import { useTranslatorList, useSelectedTranslator, useOpenedTranslatorSelector } from "@store";
|
||||
|
||||
export const TranslatorSelectorOpenButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentSelectedTranslator } = useSelectedTranslator();
|
||||
const { currentTranslatorList } = useTranslatorList();
|
||||
const currentTranslator = currentTranslatorList.find(
|
||||
translator_data => translator_data.translator_key === currentSelectedTranslator
|
||||
);
|
||||
|
||||
const { currentOpenedTranslatorSelector, updateOpenedTranslatorSelector} = useOpenedTranslatorSelector();
|
||||
|
||||
const openTranslatorSelector = () => updateOpenedTranslatorSelector(!currentOpenedTranslatorSelector);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.translator_selector_button} onClick={openTranslatorSelector}>
|
||||
<p className={styles.label}>{t("main_window.translator")}</p>
|
||||
<p className={styles.label}>{currentTranslator?.translator_name}</p>
|
||||
</div>
|
||||
{currentOpenedTranslatorSelector && <TranslatorSelector />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.translator_selector_button {
|
||||
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;
|
||||
color: var(--dark_basic_text_color);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import styles from "./TranslatorSelector.module.scss";
|
||||
import { chunkArray } from "@utils/chunkArray";
|
||||
|
||||
import { useTranslatorList, useSelectedTranslator, useOpenedTranslatorSelector } from "@store";
|
||||
export const TranslatorSelector = () => {
|
||||
const { currentTranslatorList } = useTranslatorList();
|
||||
const columns = chunkArray(currentTranslatorList, 2);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.wrapper}>
|
||||
{columns.map((column, column_index) => (
|
||||
<div className={styles.column_wrapper} key={`column_${column_index}`}>
|
||||
{column.map(({ translator_key, translator_name, is_available }) => (
|
||||
<TranslatorBox
|
||||
key={translator_key}
|
||||
translator_id={translator_key}
|
||||
translator_name={translator_name}
|
||||
is_available={is_available}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import clsx from "clsx";
|
||||
const TranslatorBox = (props) => {
|
||||
const { currentSelectedTranslator, updateSelectedTranslator} = useSelectedTranslator();
|
||||
const { updateOpenedTranslatorSelector} = useOpenedTranslatorSelector();
|
||||
|
||||
const box_class_name = clsx(
|
||||
styles.box,
|
||||
{ [styles["is_selected"]]: (currentSelectedTranslator === props.translator_id) ? true : false },
|
||||
{ [styles["is_available"]]: (props.is_available === true) ? true : false }
|
||||
);
|
||||
|
||||
const selectTranslator = () => {
|
||||
updateSelectedTranslator(props.translator_id);
|
||||
updateOpenedTranslatorSelector(false);
|
||||
};
|
||||
return (
|
||||
<div className={box_class_name} onClick={selectTranslator}>
|
||||
<p className={styles.translator_name}>{props.translator_name}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
.container {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
width: 100%;
|
||||
height: 26rem;
|
||||
padding: 1rem;
|
||||
background-color: (#000000dd);
|
||||
// background-color: (var(--dark_875_color) + 80);
|
||||
backdrop-filter: blur(0.1rem);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.column_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
$box_size: 6.8rem;
|
||||
.box {
|
||||
width: $box_size;
|
||||
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.6rem;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_825_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
border: 0.1rem solid var(--primary_300_color);
|
||||
}
|
||||
&.is_selected {
|
||||
border: 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;
|
||||
}
|
||||
23
src-ui/windows/main_window/sidebar_section/logo/Logo.jsx
Normal file
23
src-ui/windows/main_window/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 { useIsCompactMode } from "@store";
|
||||
|
||||
export const LogoBox = () => {
|
||||
const { currentIsCompactMode } = useIsCompactMode();
|
||||
if (currentIsCompactMode === 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,18 @@
|
||||
.container {
|
||||
height: var(--main_window_topbar_height);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 12rem;
|
||||
height: auto;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.logo_chato {
|
||||
width: 2rem;
|
||||
height: auto;
|
||||
margin: auto;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 { useIsCompactMode } from "@store";
|
||||
|
||||
import { useMainFunction } from "@logics/useMainFunction";
|
||||
|
||||
export const MainFunctionSwitch = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
toggleTranslation, currentState_Translation,
|
||||
toggleTranscriptionSend, currentState_TranscriptionSend,
|
||||
toggleTranscriptionReceive, currentState_TranscriptionReceive,
|
||||
toggleForeground, currentState_Foreground,
|
||||
} = useMainFunction();
|
||||
|
||||
|
||||
const switch_items = [
|
||||
{
|
||||
switch_id: "translation",
|
||||
label: t("main_window.translation"),
|
||||
SvgComponent: TranslationSvg,
|
||||
currentState: currentState_Translation,
|
||||
toggleFunction: toggleTranslation,
|
||||
},
|
||||
{
|
||||
switch_id: "transcription_send",
|
||||
label: t("main_window.transcription_send"),
|
||||
SvgComponent: MicSvg,
|
||||
currentState: currentState_TranscriptionSend,
|
||||
toggleFunction: toggleTranscriptionSend,
|
||||
},
|
||||
{
|
||||
switch_id: "transcription_receive",
|
||||
label: t("main_window.transcription_receive"),
|
||||
SvgComponent: HeadphonesSvg,
|
||||
currentState: currentState_TranscriptionReceive,
|
||||
toggleFunction: toggleTranscriptionReceive,
|
||||
},
|
||||
{
|
||||
switch_id: "foreground",
|
||||
label: t("main_window.foreground"),
|
||||
SvgComponent: ForegroundSvg,
|
||||
currentState: currentState_Foreground,
|
||||
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 { currentIsCompactMode } = useIsCompactMode();
|
||||
|
||||
const getClassNames = (baseClass) => clsx(baseClass, {
|
||||
[styles.is_compact_mode]: currentIsCompactMode,
|
||||
[styles.is_active]: (currentState.data === true),
|
||||
[styles.is_loading]: (currentState.state === "loading"),
|
||||
[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 === "loading")
|
||||
? <span className={styles.loader}></span>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
@import "@scss_mixins";
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
.switch_container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
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;
|
||||
}
|
||||
&.is_loading {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.label_wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
$basic_label_color: var(--dark_basic_text_color);
|
||||
$loading_label_color: var(--dark_500_color);
|
||||
.switch_label {
|
||||
font-size: 1.4rem;
|
||||
color: $basic_label_color;
|
||||
&.is_compact_mode {
|
||||
display: none;
|
||||
}
|
||||
&.is_loading {
|
||||
color: $loading_label_color;
|
||||
}
|
||||
}
|
||||
|
||||
.switch_svg {
|
||||
width: 1.8rem;
|
||||
color: $basic_label_color;
|
||||
&.is_loading {
|
||||
color: $loading_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;
|
||||
&.is_compact_mode {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import styles from "./OpenSettings.module.scss";
|
||||
import ConfigurationSvg from "@images/configuration.svg?react";
|
||||
|
||||
import { useWindow } from "@utils/useWindow";
|
||||
|
||||
export const OpenSettings = () => {
|
||||
const { createConfigWindow } = useWindow();
|
||||
|
||||
const openConfigWindow = () => {
|
||||
createConfigWindow();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.open_config_window_button} onClick={openConfigWindow}>
|
||||
<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_window_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