[Update] Combine in one window. Rename Main/Config Window to Main/Config Page accordingly.
For now, I put Config Page to below the main page temporary. Open Config Button does not work.
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { useSelectedConfigTabId } from "@store";
|
||||
|
||||
import { Appearance } from "./appearance/Appearance";
|
||||
import { AboutVrct } from "./about_vrct/AboutVrct";
|
||||
|
||||
export const SettingBox = () => {
|
||||
const { currentSelectedConfigTabId } = useSelectedConfigTabId();
|
||||
switch (currentSelectedConfigTabId) {
|
||||
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 { useUiLanguageStatus } from "@store";
|
||||
import { PosterShowcaseContents } from "./poster_showcase_contents/PosterShowcaseContents";
|
||||
|
||||
export const AboutVrct = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentUiLanguageStatus } = useUiLanguageStatus();
|
||||
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} />
|
||||
{
|
||||
currentUiLanguageStatus === "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,126 @@
|
||||
.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: .1s;
|
||||
.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;
|
||||
}
|
||||
& .pagination_num {
|
||||
color: var(--dark_400_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.indicator_box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
width: 100%;
|
||||
height: 0.2rem;
|
||||
background-color: var(--dark_825_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 { useUiLanguageStatus } 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 { currentUiLanguageStatus } = useUiLanguageStatus();
|
||||
|
||||
|
||||
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>
|
||||
{
|
||||
currentUiLanguageStatus === "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,68 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import FolderOpenSvg from "@images/folder_open.svg?react";
|
||||
|
||||
import { useSettingBox } from "../components/useSettingBox";
|
||||
import { useSelectedMicDeviceStatus, useMicDeviceListStatus } from "@store";
|
||||
export const Appearance = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentSelectedMicDeviceStatus, updateSelectedMicDeviceStatus } = useSelectedMicDeviceStatus();
|
||||
const { currentMicDeviceListStatus } = useMicDeviceListStatus();
|
||||
const {
|
||||
DropdownMenuContainer,
|
||||
SliderContainer,
|
||||
CheckboxContainer,
|
||||
SwitchboxContainer,
|
||||
EntryContainer,
|
||||
ThresholdContainer,
|
||||
RadioButtonContainer,
|
||||
DeeplAuthKeyContainer,
|
||||
MessageFormatContainer,
|
||||
WordFilterContainer,
|
||||
ActionButtonContainer,
|
||||
} = useSettingBox();
|
||||
|
||||
const selectFunction = (selected_data) => {
|
||||
const asyncFunction = () => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(selected_data.selected_id);
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
updateSelectedMicDeviceStatus(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={currentSelectedMicDeviceStatus.data} list={currentMicDeviceListStatus} selectFunction={selectFunction} state={currentSelectedMicDeviceStatus.state} />
|
||||
|
||||
<SliderContainer label="Transparent" desc="description" min="0" max="3000"/>
|
||||
<CheckboxContainer label="Transparent" desc="description" checkbox_id="checkbox_id_1"/>
|
||||
<SwitchboxContainer label="Transparent" desc="description" switchbox_id="switchbox_id_1"/>
|
||||
|
||||
<RadioButtonContainer label="Transparent" desc="description" switchbox_id="radiobutton_id_1"/>
|
||||
|
||||
<EntryContainer width="20rem" label="Transparent" desc="description" switchbox_id="entry_id_1"/>
|
||||
|
||||
<ThresholdContainer label="Transparent" desc="description" id="mic_threshold" min="0" max="3000"/>
|
||||
|
||||
<DeeplAuthKeyContainer label={t(`config_page.deepl_auth_key.label`)} desc={t(`config_page.deepl_auth_key.desc`)}/>
|
||||
|
||||
|
||||
<MessageFormatContainer label={t(`config_page.send_message_format.label`)} desc={t(`config_page.send_message_format.desc`)} id="send"/>
|
||||
|
||||
<MessageFormatContainer label={t(`config_page.send_message_format_with_t.label`)} desc={t(`config_page.send_message_format_with_t.desc`)} id="send_with_t"/>
|
||||
|
||||
|
||||
<MessageFormatContainer label={t(`config_page.send_message_format.label`)} desc={t(`config_page.send_message_format.desc`)} id="received"/>
|
||||
|
||||
<MessageFormatContainer label={t(`config_page.send_message_format_with_t.label`)} desc={t(`config_page.send_message_format_with_t.desc`)} id="received_with_t"/>
|
||||
|
||||
<WordFilterContainer label={t(`config_page.mic_word_filter.label`)} desc={t(`config_page.mic_word_filter.desc`)}/>
|
||||
|
||||
<ActionButtonContainer label={t(`config_page.open_config_filepath.label`)} IconComponent={FolderOpenSvg} OnclickFunction={()=>{}}/>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import React, { useState, useRef, forwardRef, useImperativeHandle } from "react";
|
||||
import styles from "./_Entry.module.scss";
|
||||
|
||||
const _Entry = forwardRef(({ width, onChange, initialValue = "" }, ref) => {
|
||||
const [input_value, setInputValue] = useState(initialValue);
|
||||
const inputRef = useRef();
|
||||
|
||||
const onChangeFunction = (e) => {
|
||||
setInputValue(e.currentTarget.value);
|
||||
if (onChange) {
|
||||
onChange(e);
|
||||
}
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
focus: () => {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className={styles.entry_container}>
|
||||
<div
|
||||
className={styles.entry_wrapper}
|
||||
style={{ width }}
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={styles.entry_input_area}
|
||||
value={input_value}
|
||||
onChange={onChangeFunction}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
_Entry.displayName = "_Entry";
|
||||
|
||||
export { _Entry };
|
||||
@@ -0,0 +1,19 @@
|
||||
.entry_container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.entry_wrapper {
|
||||
width: 10rem;
|
||||
height: 100%;
|
||||
padding: 0.6rem;
|
||||
background-color: var(--dark_875_color);
|
||||
border: 0.1rem solid var(--dark_750_color);
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
|
||||
.entry_input_area {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 1.4rem;
|
||||
resize: none;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import styles from "./ActionButton.module.scss";
|
||||
|
||||
export const ActionButton = ({IconComponent, OnclickFunction}) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<button className={styles.button_wrapper} onClick={OnclickFunction}>
|
||||
<IconComponent className={styles.button_svg}/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
.button_wrapper {
|
||||
padding: 1.6rem;
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_825_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.button_svg {
|
||||
width: 2.4rem;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import React, { useState } from 'react';
|
||||
import styles from "./Checkbox.module.scss";
|
||||
|
||||
export const Checkbox = (props) => {
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
|
||||
const handleCheckboxChange = () => {
|
||||
setIsChecked(!isChecked);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.checkbox_container}>
|
||||
<label className={styles.checkbox_wrapper} htmlFor={props.checkbox_id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={props.checkbox_id}
|
||||
checked={isChecked}
|
||||
onChange={handleCheckboxChange}
|
||||
/>
|
||||
<span className={styles.cbx}>
|
||||
<svg viewBox="0 0 12 12">
|
||||
<polyline points="1 6.29411765 4.5 10 11 1"></polyline>
|
||||
</svg>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
.checkbox_container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.checkbox_wrapper {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: 2rem;
|
||||
&:hover {
|
||||
& .cbx {
|
||||
border: var(--primary_600_color) solid 0.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox_wrapper .cbx {
|
||||
display: block;
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
border-radius: 0.4rem;
|
||||
border: var(--dark_700_color) solid 0.2rem;
|
||||
transition: all 0.15s ease;
|
||||
padding: 0.4rem;
|
||||
}
|
||||
|
||||
.checkbox_wrapper .cbx svg {
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke: var(--dark_basic_text_color);
|
||||
stroke-width: 0.2rem;
|
||||
stroke-dasharray: 1.7rem;
|
||||
stroke-dashoffset: 1.7rem;
|
||||
}
|
||||
|
||||
.checkbox_wrapper input[type="checkbox"] {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.checkbox_wrapper input[type="checkbox"]:checked + .cbx {
|
||||
background-color: var(--primary_600_color);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.checkbox_wrapper input[type="checkbox"]:checked + .cbx svg {
|
||||
stroke-dashoffset: 0;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import styles from "./DeeplAuthKey.module.scss";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import clsx from "clsx";
|
||||
import ExternalLink from "@images/external_link.svg?react";
|
||||
import { _Entry } from "../_atoms/_entry/_Entry";
|
||||
import { useState, useRef } from "react";
|
||||
|
||||
export const DeeplAuthKey = () => {
|
||||
const { t } = useTranslation();
|
||||
const [is_editable, seIsEditable] = useState(false);
|
||||
const [input_value, seInputValue] = useState("");
|
||||
const entryRef = useRef(null);
|
||||
|
||||
const revealEditAuthKey = () => {
|
||||
seIsEditable(true);
|
||||
entryRef.current.focus();
|
||||
};
|
||||
|
||||
const onchangeEntryAuthKey = (e) => {
|
||||
seInputValue(e.target.value);
|
||||
};
|
||||
const saveAuthKey = () => {
|
||||
console.log(input_value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.entry_section_wrapper}>
|
||||
<_Entry ref={entryRef} width="30rem" onChange={onchangeEntryAuthKey}/>
|
||||
<button className={styles.save_button} onClick={saveAuthKey}>Save</button>
|
||||
{is_editable
|
||||
? null
|
||||
:
|
||||
<div className={styles.entry_edit_cover} onClick={revealEditAuthKey}>
|
||||
<button className={styles.edit_button}>Edit</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export const OpenWebpage_DeeplAuthKey = () => {
|
||||
return (
|
||||
<div className={styles.open_webpage_button_wrapper}>
|
||||
<a className={styles.open_webpage_button} href="https://www.deepl.com/ja/your-account/keys" target="_blank" rel="noreferrer" >
|
||||
<p className={styles.open_webpage_text}>Open DeepL Account Webpage</p>
|
||||
<ExternalLink className={styles.external_link_svg} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.entry_section_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.entry_edit_cover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border-radius: 0.4rem;
|
||||
background-color: (#00000044);
|
||||
backdrop-filter: blur(4rem);
|
||||
border: solid 0.1rem var(--dark_700_color);
|
||||
&:hover {
|
||||
background-color: (#00000088);
|
||||
}
|
||||
&:active {
|
||||
backdrop-filter: blur(1.4rem);
|
||||
}
|
||||
}
|
||||
|
||||
.edit_button {
|
||||
padding: 0.8rem 1.2rem;
|
||||
color: var(--dark_basic_text_color);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: 1.4rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.save_button {
|
||||
padding: 0.8rem 1.2rem;
|
||||
background-color: var(--primary_600_color);
|
||||
color: var(--dark_basic_text_color);
|
||||
font-size: 1.4rem;
|
||||
border-radius: 0.4rem;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
&:hover {
|
||||
background-color: var(--primary_500_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--primary_700_color);
|
||||
}
|
||||
}
|
||||
|
||||
.open_webpage_button_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.open_webpage_button {
|
||||
padding: 0.6rem 3.2rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 0.4rem;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_825_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.open_webpage_text {
|
||||
font-size: 1.2rem;
|
||||
color: var(--dark_basic_text_color);
|
||||
}
|
||||
|
||||
.external_link_svg {
|
||||
color: var(--dark_500_color);
|
||||
width: 1.6rem;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import styles from "./DropdownMenu.module.scss";
|
||||
|
||||
import clsx from "clsx";
|
||||
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||
import { useIsOpenedDropdownMenu } from "@store";
|
||||
|
||||
export const DropdownMenu = (props) => {
|
||||
|
||||
const { updateIsOpenedDropdownMenu, currentIsOpenedDropdownMenu } = useIsOpenedDropdownMenu();
|
||||
const toggleDropdownMenu = () => {
|
||||
if (currentIsOpenedDropdownMenu === props.dropdown_id) {
|
||||
updateIsOpenedDropdownMenu("");
|
||||
} else {
|
||||
updateIsOpenedDropdownMenu(props.dropdown_id);
|
||||
}
|
||||
};
|
||||
|
||||
const selectValue = (key) => {
|
||||
updateIsOpenedDropdownMenu("");
|
||||
props.selectFunction({
|
||||
dropdown_id: props.dropdown_id,
|
||||
selected_id: key,
|
||||
});
|
||||
};
|
||||
|
||||
const dropdown_content_wrapper_class_name = clsx(styles["dropdown_content_wrapper"], {
|
||||
[styles["is_opened"]]: (currentIsOpenedDropdownMenu === props.dropdown_id) ? true : false
|
||||
});
|
||||
|
||||
const dropdown_toggle_button_class_name = clsx(styles["dropdown_toggle_button"], {
|
||||
[styles["is_loading"]]: (props.state === "loading") ? true : false
|
||||
});
|
||||
|
||||
const arrow_class_names = clsx(styles["arrow_left_svg"], {
|
||||
[styles["is_opened"]]: (currentIsOpenedDropdownMenu === props.dropdown_id) ? true : false
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={dropdown_toggle_button_class_name} onClick={toggleDropdownMenu}>
|
||||
{(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>
|
||||
: <ArrowLeftSvg className={arrow_class_names} />
|
||||
}
|
||||
</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,78 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown_toggle_button {
|
||||
position: relative;
|
||||
background-color: var(--dark_925_color);
|
||||
min-width: 20rem;
|
||||
padding: 0.6rem 1rem;
|
||||
cursor: pointer;
|
||||
border-radius: 0.4rem;
|
||||
&: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;
|
||||
cursor: pointer;
|
||||
&: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);
|
||||
}
|
||||
|
||||
.arrow_left_svg {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translate(-50%, -50%) rotate(-90deg);
|
||||
width: 1.4rem;
|
||||
&.is_opened {
|
||||
transform: translate(-50%, -50%) rotate(90deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import styles from "./Entry.module.scss";
|
||||
import { _Entry } from "../_atoms/_entry/_Entry";
|
||||
|
||||
export const Entry = ({width}) => {
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
console.log(e.currentTarget.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.entry_container}>
|
||||
<_Entry width={width} onChange={handleInputChange} initialValue="" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import styles from "./LabelComponent.module.scss";
|
||||
|
||||
export const LabelComponent = (props) => {
|
||||
return (
|
||||
<div className={styles.label_component}>
|
||||
<p className={styles.label}>{props.label}</p>
|
||||
<p className={styles.desc}>{props.desc}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
.label_component {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 400;
|
||||
color: var(--dark_basic_text_color);
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 300;
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import styles from "./MessageFormat.module.scss";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
useUiLanguageStatus,
|
||||
useSendMessageFormat,
|
||||
useSendMessageFormatWithT,
|
||||
useReceivedMessageFormat,
|
||||
useReceivedMessageFormatWithT,
|
||||
} from "@store";
|
||||
import { _Entry } from "../_atoms/_entry/_Entry";
|
||||
import SwapImg from "@images/swap_icon.png";
|
||||
|
||||
export const MessageFormat = (props) => {
|
||||
const { currentSendMessageFormat, updateSendMessageFormat } = useSendMessageFormat();
|
||||
const { currentSendMessageFormatWithT, updateSendMessageFormatWithT } = useSendMessageFormatWithT();
|
||||
const { currentReceivedMessageFormat, updateReceivedMessageFormat } = useReceivedMessageFormat();
|
||||
const { currentReceivedMessageFormatWithT, updateReceivedMessageFormatWithT } = useReceivedMessageFormatWithT();
|
||||
|
||||
let atoms = [];
|
||||
switch (props.id) {
|
||||
case "send":
|
||||
atoms = [currentSendMessageFormat, updateSendMessageFormat];
|
||||
break;
|
||||
|
||||
case "send_with_t":
|
||||
atoms = [currentSendMessageFormatWithT, updateSendMessageFormatWithT];
|
||||
break;
|
||||
|
||||
case "received":
|
||||
atoms = [currentReceivedMessageFormat, updateReceivedMessageFormat];
|
||||
break;
|
||||
|
||||
case "received_with_t":
|
||||
atoms = [currentReceivedMessageFormatWithT, updateReceivedMessageFormatWithT];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<ExampleComponent {...props} current_format={atoms[0]} />
|
||||
<InputComponent {...props} atoms={atoms} />
|
||||
{["send_with_t", "received_with_t"].includes(props.id) && <SwapButton atoms={atoms} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ExampleComponent = ({ id, current_format }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentUiLanguageStatus } = useUiLanguageStatus();
|
||||
|
||||
const createExampleMessage = () => {
|
||||
const originalLangMessage = t("config_page.send_message_format.example_text");
|
||||
let format = current_format;
|
||||
|
||||
if (["send_with_t", "received_with_t"].includes(id)) {
|
||||
const translationLocale = currentUiLanguageStatus === "en" ? "ja" : "en";
|
||||
const translatedLangMessage = t("config_page.send_message_format.example_text", { lng: translationLocale });
|
||||
|
||||
return format.is_message_first
|
||||
? `${format.before}${originalLangMessage}${format.between}${translatedLangMessage}${format.after}`
|
||||
: `${format.before}${translatedLangMessage}${format.between}${originalLangMessage}${format.after}`;
|
||||
} else {
|
||||
return `${format.before}${originalLangMessage}${format.after}`;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.example_container}>
|
||||
<p className={styles.example_text}>{createExampleMessage()}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const InputComponent = ({ id, atoms }) => {
|
||||
const [current, updater] = atoms;
|
||||
|
||||
const handleChange = (key) => (e) => {
|
||||
updater({ ...current, [key]: e.target.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.input_wrapper}>
|
||||
<_Entry width="100%" onChange={handleChange("before")} />
|
||||
{["send_with_t", "received_with_t"].includes(id) ? (
|
||||
<>
|
||||
<p className={styles.preset_text}>{current.is_message_first ? "[message]" : "[translation]"}</p>
|
||||
<_Entry width="100%" onChange={handleChange("between")} />
|
||||
<p className={styles.preset_text}>{current.is_message_first ? "[translation]" : "[message]"}</p>
|
||||
</>
|
||||
) : (
|
||||
<p className={styles.preset_text}>[message]</p>
|
||||
)}
|
||||
<_Entry width="100%" onChange={handleChange("after")} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SwapButton = ({ atoms }) => {
|
||||
const [current, updater] = atoms;
|
||||
|
||||
const swapMessageAndTranslate = () => {
|
||||
updater({ ...current, is_message_first: !current.is_message_first });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.swap_button_container}>
|
||||
<div className={styles.swap_button_wrapper} onClick={swapMessageAndTranslate}>
|
||||
<p className={styles.swap_text}>{current.is_message_first ? "[message]" : "[translation]"}</p>
|
||||
<img className={styles.swap_img} src={SwapImg} alt="Swap Icon" />
|
||||
<p className={styles.swap_text}>{current.is_message_first ? "[translation]" : "[message]"}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// const extractMessageFormat = (text) => {
|
||||
// const split_result = text.split("[message]");
|
||||
// let result_data = {
|
||||
// before: split_result[0],
|
||||
// after: split_result[1]
|
||||
// };
|
||||
// return result_data;
|
||||
// };
|
||||
|
||||
|
||||
|
||||
|
||||
// const extractMessageFormatWithT = (text) => {
|
||||
// const message_index = text.indexOf("[message]");
|
||||
// const translation_index = text.indexOf("[translation]");
|
||||
|
||||
// let result_data = {
|
||||
// is_message_first: true,
|
||||
// before: "",
|
||||
// between: "",
|
||||
// after: ""
|
||||
// };
|
||||
|
||||
// if (message_index < translation_index) {
|
||||
// const text_before_message = text.slice(0, message_index);
|
||||
// result_data.before = text_before_message;
|
||||
|
||||
// const match = text.match(/\[message\](.*?)\[translation\]/);
|
||||
// if (match) {
|
||||
// const extracted_text = match[1];
|
||||
// result_data.between = extracted_text;
|
||||
// } else {
|
||||
// throw new Error("Invalid Message Format");
|
||||
// }
|
||||
|
||||
// const text_after_translation = text.slice(translation_index + "[translation]".length);
|
||||
// result_data.after = text_after_translation;
|
||||
|
||||
// } else if (translation_index < message_index) {
|
||||
// result_data.is_message_first = false;
|
||||
// const text_before_translation = text.slice(0, translation_index);
|
||||
// result_data.before = text_before_translation;
|
||||
|
||||
// const match = text.match(/\[translation\](.*?)\[message\]/);
|
||||
// if (match) {
|
||||
// const extracted_text = match[1];
|
||||
// result_data.between = extracted_text;
|
||||
// } else {
|
||||
// throw new Error("Invalid Message Format");
|
||||
// }
|
||||
|
||||
// const text_after_message = text.slice(message_index + "[message]".length);
|
||||
// result_data.after = text_after_message;
|
||||
|
||||
// } else {
|
||||
// throw new Error("Invalid Message Format");
|
||||
// }
|
||||
|
||||
// return result_data;
|
||||
// };
|
||||
@@ -0,0 +1,65 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 1.6rem;
|
||||
}
|
||||
|
||||
.example_container {
|
||||
padding: 1rem;
|
||||
background-color: #3A4554;
|
||||
border-radius: 1.4rem;
|
||||
max-width: 30rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.example_text {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.input_wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.preset_text {
|
||||
font-size: 1.6rem;
|
||||
width: 40rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.swap_button_container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.swap_button_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: 0.4rem;
|
||||
background-color: var(--dark_850_color);
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.swap_text {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.swap_img {
|
||||
width: 2rem;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import clsx from "clsx";
|
||||
import { useState } from "react";
|
||||
import styles from "./RadioButton.module.scss";
|
||||
|
||||
export const RadioButton = (props) => {
|
||||
const options = [
|
||||
{ radio_button_id: "1", label: "AAAAAAAA" },
|
||||
{ radio_button_id: "2", label: "BBBBBB" }
|
||||
];
|
||||
|
||||
const changeValue = (e) => {
|
||||
console.log(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{options.map((option) => (
|
||||
<label key={option.radio_button_id} className={styles.radio_button_wrapper}>
|
||||
<input
|
||||
type="radio"
|
||||
name="radio"
|
||||
value={option.radio_button_id}
|
||||
onChange={changeValue}
|
||||
/>
|
||||
<p className={styles.radio_button_label}>{option.label}</p>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.radio_button_wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
gap: 1rem;
|
||||
padding: 0.6rem 0.8rem;
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
border: 0.3rem solid var(--dark_600_color);
|
||||
border-radius: 50%;
|
||||
transition: border-color .1s ease, border-width .1s ease;
|
||||
cursor: inherit;
|
||||
&:checked {
|
||||
border-color: var(--primary_400_color);
|
||||
border-width: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.radio_button_label {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import styles from "./Slider.module.scss";
|
||||
import MUI_Slider from "@mui/material/Slider";
|
||||
|
||||
export const Slider = ({ min, max }) => {
|
||||
const [baseColor, setBaseColor] = useState("");
|
||||
const [activeColor, setActiveColor] = useState("");
|
||||
const [toolTipColor, setToolTipColor] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const baseColor = getComputedStyle(document.documentElement).getPropertyValue("--dark_700_color");
|
||||
const activeColor = getComputedStyle(document.documentElement).getPropertyValue("--primary_600_color");
|
||||
const toolTipColor = getComputedStyle(document.documentElement).getPropertyValue("--dark_800_color");
|
||||
setBaseColor(baseColor.trim());
|
||||
setActiveColor(activeColor.trim());
|
||||
setToolTipColor(toolTipColor.trim());
|
||||
}, []);
|
||||
|
||||
const showSliderValue = (_e, value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<MUI_Slider className={styles.range_slider} defaultValue={50} aria-label="Default" valueLabelDisplay="auto"
|
||||
step={1}
|
||||
min={Number(min)}
|
||||
max={Number(max)}
|
||||
onChange={showSliderValue}
|
||||
sx={{
|
||||
color: baseColor,
|
||||
"& .MuiSlider-thumb": {
|
||||
backgroundColor: activeColor,
|
||||
"&:hover, &.Mui-focusVisible, &.Mui-active": {
|
||||
boxShadow: "0 0 0 0.8rem" + activeColor + "44",
|
||||
},
|
||||
"& .MuiSlider-valueLabel": {
|
||||
fontSize: "1.4rem",
|
||||
backgroundColor: toolTipColor,
|
||||
padding: "0.6rem 1rem",
|
||||
lineHeight: "1.15",
|
||||
top: "-1.4rem",
|
||||
"&::before": {
|
||||
left: "30%",
|
||||
width: "1rem",
|
||||
height: "1rem",
|
||||
clipPath: "polygon(50% 0, 100% 100%, 0 100%)",
|
||||
}
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: end;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.range_slider {
|
||||
max-width: 40rem;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import clsx from "clsx";
|
||||
import { useState } from "react";
|
||||
import styles from "./Switchbox.module.scss";
|
||||
|
||||
export const Switchbox = (props) => {
|
||||
|
||||
const [is_turned_on, setIsTurnedOn] = useState(false);
|
||||
const [is_hovered, setIsHovered] = useState(false);
|
||||
const [is_mouse_down, setIsMouseDown] = useState(false);
|
||||
|
||||
const getClassNames = (baseClass) => clsx(baseClass, {
|
||||
[styles.is_active]: (is_turned_on === 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);
|
||||
|
||||
const toggleFunction = () => {
|
||||
setIsTurnedOn(!is_turned_on);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.switchbox_container}>
|
||||
<div className={styles.switchbox_wrapper}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseUp={onMouseUp}
|
||||
onClick={toggleFunction}
|
||||
>
|
||||
<div className={getClassNames(styles.toggle_control)}>
|
||||
<span className={getClassNames(styles.control)}></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
.switchbox_container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.switchbox_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 2rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.toggle_control {
|
||||
@include toggle_control_styles;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import styles from "./ThresholdComponent.module.scss";
|
||||
import { SliderAndMeter } from "./slider_and_meter/SliderAndMeter";
|
||||
import { ThresholdEntry } from "./threshold_entry/ThresholdEntry";
|
||||
import { VolumeCheckButton } from "./volume_check_button/VolumeCheckButton";
|
||||
|
||||
export const ThresholdComponent = (props) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<VolumeCheckButton {...props}/>
|
||||
<SliderAndMeter {...props}/>
|
||||
<ThresholdEntry/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
.container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 3rem;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import styles from "./SliderAndMeter.module.scss";
|
||||
|
||||
export const SliderAndMeter = (props) => {
|
||||
const [volume, setVolume] = useState(0);
|
||||
const [threshold, setThreshold] = useState(props.max / 2);
|
||||
|
||||
const updateVolume = () => {
|
||||
setVolume(Math.random());
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// const intervalId = setInterval(updateVolume, 200);
|
||||
// return () => clearInterval(intervalId);
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.meter_container}>
|
||||
<div
|
||||
className={styles.volume_meter}
|
||||
style={{
|
||||
width: `${(volume * 100)}%`,
|
||||
backgroundColor: volume < (threshold / props.max) ? "var(--primary_750_color)" : "var(--primary_400_color)"
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
type="range"
|
||||
min={props.min}
|
||||
max={props.max}
|
||||
value={threshold}
|
||||
onChange={(e) => setThreshold(e.target.value)}
|
||||
className={styles.threshold_slider}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.dev_info_box}>
|
||||
<p>dev</p>
|
||||
<button onClick={updateVolume}>Update Volume</button>
|
||||
<div className={styles.volume_info}>
|
||||
<span>Current Volume: {(volume * props.max).toFixed(2)}</span>
|
||||
</div>
|
||||
<div className={styles.threshold_info}>
|
||||
<span>Threshold: {threshold}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
// width: 100%;
|
||||
position: relative; // for dev
|
||||
}
|
||||
|
||||
.meter_container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0.8rem;
|
||||
background: var(--dark_800_color);
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
|
||||
.volume_meter {
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
transition: width 0.1s ease, background-color 0.1s ease;
|
||||
}
|
||||
|
||||
.threshold_slider {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: all;
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 0.4rem;
|
||||
height: 4rem;
|
||||
background: var(--primary_600_color);
|
||||
border-radius: 0.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover::-webkit-slider-thumb{
|
||||
background: var(--primary_500_color);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.volume_info, .threshold_info {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// for dev
|
||||
.dev_info_box {
|
||||
position: absolute;
|
||||
top: -4rem;
|
||||
display: flex;
|
||||
gap: 2rem
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import styles from "./ThresholdEntry.module.scss";
|
||||
|
||||
export const ThresholdEntry = () => {
|
||||
const [input_value, setInputValue] = useState();
|
||||
|
||||
const onChangeFunction = (e) => {
|
||||
setInputValue(e.currentTarget.value);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.entry_wrapper}>
|
||||
<input
|
||||
className={styles.entry_input_area}
|
||||
onChange={onChangeFunction}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
.container {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.entry_wrapper {
|
||||
width: 10rem;
|
||||
height: 100%;
|
||||
padding: 0.6rem;
|
||||
background-color: var(--dark_875_color);
|
||||
border: 0.1rem solid var(--dark_750_color);
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
|
||||
.entry_input_area {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 1.4rem;
|
||||
resize: none;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import styles from "./VolumeCheckButton.module.scss";
|
||||
import MicSvg from "@images/mic.svg?react";
|
||||
import HeadphonesSvg from "@images/headphones.svg?react";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const VolumeCheckButton = (props) => {
|
||||
const SvgComponent = props.id === "mic_threshold" ? MicSvg : HeadphonesSvg;
|
||||
|
||||
const getClassNames = (baseClass) => clsx(baseClass, {
|
||||
// [styles.is_active]: (currentState.data === true),
|
||||
// [styles.is_loading]: (currentState.state === "loading"),
|
||||
// [styles.is_hovered]: is_hovered,
|
||||
// [styles.is_mouse_down]: is_mouse_down,
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={getClassNames(styles.button_button)}>
|
||||
<SvgComponent className={getClassNames(styles.button_svg)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
.button_button {
|
||||
width: 100%;
|
||||
background-color: var(--dark_800_color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.button_svg {
|
||||
width: 2.6rem;
|
||||
color: var(--dark_350_color);
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import styles from "./useSettingBox.module.scss";
|
||||
import { useIsOpenedDropdownMenu } from "@store";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { LabelComponent } from "./label_component/LabelComponent";
|
||||
import { DropdownMenu } from "./dropdown_menu/DropdownMenu";
|
||||
import { Slider } from "./slider/Slider";
|
||||
import { Checkbox } from "./checkbox/Checkbox";
|
||||
import { Switchbox } from "./switchbox/Switchbox";
|
||||
import { Entry } from "./entry/Entry";
|
||||
import { ThresholdComponent } from "./threshold_component/ThresholdComponent";
|
||||
import { RadioButton } from "./radio_button/RadioButton";
|
||||
import { OpenWebpage_DeeplAuthKey, DeeplAuthKey } from "./deepl_auth_key/DeeplAuthKey";
|
||||
import { MessageFormat } from "./message_format/MessageFormat";
|
||||
import { ActionButton } from "./action_button/ActionButton";
|
||||
import { WordFilter, WordFilterListToggleComponent } from "./word_filter/WordFilter";
|
||||
|
||||
export const useSettingBox = () => {
|
||||
const { updateIsOpenedDropdownMenu } = useIsOpenedDropdownMenu();
|
||||
|
||||
const DropdownMenuContainer = (props) => {
|
||||
const onMouseLeaveFunction = () => {
|
||||
updateIsOpenedDropdownMenu("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container} onMouseLeave={onMouseLeaveFunction}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
<DropdownMenu {...props}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SliderContainer = (props) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
<Slider {...props}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CheckboxContainer = (props) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
<Checkbox {...props}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SwitchboxContainer = (props) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
<Switchbox {...props}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const EntryContainer = (props) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
<Entry {...props}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RadioButtonContainer = (props) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
<RadioButton {...props}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ThresholdContainer = (props) => {
|
||||
return (
|
||||
<div className={styles.threshold_container}>
|
||||
<div className={styles.threshold_switch_section}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
<Switchbox {...props}/>
|
||||
</div>
|
||||
<div className={styles.threshold_section}>
|
||||
<ThresholdComponent {...props}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DeeplAuthKeyContainer = (props) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.deepl_auth_key_label_section}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
<OpenWebpage_DeeplAuthKey />
|
||||
</div>
|
||||
<DeeplAuthKey {...props}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const MessageFormatContainer = (props) => {
|
||||
return (
|
||||
<div className={clsx(styles.container, styles.flex_column)}>
|
||||
<div className={styles.label_only_section}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
</div>
|
||||
<div className={styles.message_format_section}>
|
||||
<MessageFormat {...props}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const ActionButtonContainer = (props) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<LabelComponent label={props.label} desc={props.desc} />
|
||||
<ActionButton {...props}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const WordFilterContainer = (props) => {
|
||||
return (
|
||||
<div className={styles.word_filter_container}>
|
||||
<div className={styles.word_filter_switch_section}>
|
||||
<div className={styles.word_filter_label_wrapper}>
|
||||
<LabelComponent label={props.label} desc={props.desc}/>
|
||||
</div>
|
||||
<WordFilterListToggleComponent/>
|
||||
</div>
|
||||
<div className={styles.word_filter_section}>
|
||||
<WordFilter {...props}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
DropdownMenuContainer,
|
||||
SliderContainer,
|
||||
CheckboxContainer,
|
||||
SwitchboxContainer,
|
||||
EntryContainer,
|
||||
ThresholdContainer,
|
||||
RadioButtonContainer,
|
||||
DeeplAuthKeyContainer,
|
||||
MessageFormatContainer,
|
||||
WordFilterContainer,
|
||||
ActionButtonContainer,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
.container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: var(--dark_888_color);
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
&.flex_column {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.label_only_section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.threshold_container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
background-color: var(--dark_888_color);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.threshold_switch_section {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.threshold_section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.deepl_auth_key_label_section {
|
||||
max-width: 34rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1.4rem;
|
||||
}
|
||||
|
||||
.message_format_section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.word_filter_container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
background-color: var(--dark_888_color);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.word_filter_switch_section {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.word_filter_label_wrapper {
|
||||
max-width: 34rem;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import styles from "./WordFilter.module.scss";
|
||||
import { _Entry } from "../_atoms/_entry/_Entry";
|
||||
import { useState } from "react";
|
||||
import { useWordFilterList, useIsOpenedWordFilterList } from "@store";
|
||||
export const WordFilter = () => {
|
||||
const [input_value, setInputValue] = useState();
|
||||
const { currentWordFilterList, updateWordFilterList } = useWordFilterList();
|
||||
const { currentIsOpenedWordFilterList, updateIsOpenedWordFilterList } = useIsOpenedWordFilterList();
|
||||
|
||||
const onChangeEntry = (e) => {
|
||||
setInputValue(e.target.value);
|
||||
};
|
||||
|
||||
const addWords = () => {
|
||||
if (input_value === undefined) return;
|
||||
const input_value_array = input_value.split(",");
|
||||
|
||||
let updated_list = [...currentWordFilterList];
|
||||
|
||||
for (let each_input_value of input_value_array) {
|
||||
each_input_value = each_input_value.trim();
|
||||
if (each_input_value) {
|
||||
const target_item = updated_list.find((item) => item.value === each_input_value);
|
||||
if (target_item === undefined) {
|
||||
// Add
|
||||
updated_list = [...updated_list, { value: each_input_value, is_redoable: false }];
|
||||
} else {
|
||||
// Update
|
||||
updated_list = updated_list.map(item =>
|
||||
item.value === each_input_value ? { ...item, is_redoable: false } : item
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateWordFilterList(updated_list);
|
||||
updateIsOpenedWordFilterList(true);
|
||||
};
|
||||
|
||||
|
||||
const updateRedoable = (target_item_value, is_redoable) => {
|
||||
updateWordFilterList((prev_list) =>
|
||||
prev_list.map(item =>
|
||||
item.value === target_item_value ? { ...item, is_redoable: is_redoable } : item
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const deleteAction = (target_item_value) => {
|
||||
updateRedoable(target_item_value, true);
|
||||
};
|
||||
|
||||
const redoAction = (target_item_value) => {
|
||||
updateRedoable(target_item_value, false);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{ currentIsOpenedWordFilterList &&
|
||||
<div className={styles.list_section_wrapper}>
|
||||
{
|
||||
currentWordFilterList.map((item, index) => {
|
||||
return <WordFilterItem value={item.value} key={index} is_redoable={item.is_redoable} deleteAction={deleteAction} redoAction={redoAction}/>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div className={styles.entry_section_wrapper}>
|
||||
<_Entry width="30rem" onChange={onChangeEntry}/>
|
||||
<button className={styles.add_button} onClick={addWords}>Add</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import DeleteSvg from "@images/cancel.svg?react";
|
||||
import RedoSvg from "@images/redo.svg?react";
|
||||
import clsx from "clsx";
|
||||
const WordFilterItem = (props) => {
|
||||
|
||||
|
||||
const item_wrapper_class_names = clsx(styles["item_wrapper"], {
|
||||
[styles["is_redoable"]]: props.is_redoable
|
||||
});
|
||||
|
||||
const item_text_class_names = clsx(styles["item_text"], {
|
||||
[styles["is_redoable"]]: props.is_redoable
|
||||
});
|
||||
|
||||
const target_item_value = props.value;
|
||||
|
||||
return (
|
||||
<div className={item_wrapper_class_names}>
|
||||
<p className={item_text_class_names}>{target_item_value}</p>
|
||||
{props.is_redoable
|
||||
?
|
||||
<button className={clsx(styles.action_button, styles.redo)} onClick={() => props.redoAction(target_item_value)}>
|
||||
<RedoSvg className={styles.redo_svg}/>
|
||||
</button>
|
||||
:
|
||||
<button className={clsx(styles.action_button, styles.delete)} onClick={() => props.deleteAction(target_item_value)}>
|
||||
<DeleteSvg className={styles.delete_svg}/>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||
export const WordFilterListToggleComponent = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentIsOpenedWordFilterList, updateIsOpenedWordFilterList } = useIsOpenedWordFilterList();
|
||||
const { currentWordFilterList } = useWordFilterList();
|
||||
|
||||
|
||||
const svg_class_names = clsx(styles["arrow_left_svg"], {
|
||||
[styles.to_down]: !currentIsOpenedWordFilterList,
|
||||
[styles.to_up]: currentIsOpenedWordFilterList
|
||||
});
|
||||
|
||||
const OnclickFunction = () => {
|
||||
updateIsOpenedWordFilterList(!currentIsOpenedWordFilterList);
|
||||
};
|
||||
|
||||
const word_filter_list_length = currentWordFilterList.filter(item => item.is_redoable === false).length;
|
||||
|
||||
|
||||
return (
|
||||
<div className={styles.toggle_button_container}>
|
||||
<p className={styles.words_count_text}>{t("config_page.mic_word_filter.count_desc", {count: word_filter_list_length} )}</p>
|
||||
<button className={styles.toggle_button_wrapper} onClick={OnclickFunction}>
|
||||
<ArrowLeftSvg className={svg_class_names}/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
|
||||
.list_section_wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
gap: 0.6rem;
|
||||
overflow-y: auto;
|
||||
max-height: 20rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.item_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
background-color: var(--dark_800_color);
|
||||
padding: 0.2rem 0.2rem 0.2rem 1rem;
|
||||
border-radius: 0.4rem;
|
||||
&.is_redoable {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
}
|
||||
.item_text {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 300;
|
||||
color: var(--dark_basic_text_color);
|
||||
&.is_redoable {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
.action_button {
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_750_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
&.delete {
|
||||
padding: 0.2rem;
|
||||
}
|
||||
&.redo {
|
||||
padding: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.delete_svg {
|
||||
width: 2.4rem;
|
||||
color: #bb4448;
|
||||
}
|
||||
|
||||
.redo_svg {
|
||||
width: 1.6rem;
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.entry_section_wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.add_button {
|
||||
padding: 0.8rem 1.2rem;
|
||||
background-color: var(--primary_600_color);
|
||||
color: var(--dark_basic_text_color);
|
||||
font-size: 1.4rem;
|
||||
border-radius: 0.4rem;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
&:hover {
|
||||
background-color: var(--primary_500_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--primary_700_color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.toggle_button_container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.words_count_text {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.toggle_button_wrapper {
|
||||
padding: 1.6rem;
|
||||
border-radius: 0.4rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_825_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color);
|
||||
}
|
||||
}
|
||||
|
||||
.arrow_left_svg {
|
||||
width: 2.4rem;
|
||||
&.to_down {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
&.to_up {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user