uM`zX}Fpa#X12SXaTBrDk>{w$qM*r&nSP$(&=xy0y#F5h0x_>
zkJAiZEI|!9tA`R8!zN#}mGTV#mXc(6DW7FjbC{mPP@AwsIS!MGMji?g<*pm|#>y##
zG?wpgZQI8$r^voi8hUmKA#C0z0GxQwT7F?xE{aPdE{k7|*`w)ZKrc2U^lv|%_PRJw-sx6vwQ6_?>^h=j*
zA-_6FjW*R}Aeh=eR&tw5L2w=EEqlW>ss2!g>v_>*@zqK1p(R1+>i=;?*$+HK{F@nd`=clUdbVZH0bmA(8we02mDvQ-Ez=*
z$h#66{ntwP?x_lHJ@4!F(u4s}(p=k|_xXP;JX!1M=J5zc(;@UP}30U1Cf*Xtn5y)OhgXTFvh*TQBPVf^okF2xs5z7BoKqqqE3@_p*J`42U^n2SYW~O2|7T*WOv}oqA5x^4nJ)GZa~vqpH*jGz6C)_(OmyGJWVte
zMmRhRRjuyS`aJWfE2sztLH#FF4^P)OjS3pk7
zG-oRaK_(iw7=ww_7e(ljGrDg{&AIb-=1=!>b4(?QmYEH)S<5H#p`XpyY@*?@Ep#k=KyLe&~9p|Jy@q5B6tCDR2R$C@YzRM3+~-Es@VWV=;itEmtMbpvu@AS2TpwB&(M|FIlNj%{pYpT5Ji
zP{C1G%ru$M&}h=!xd|4OZMbh49c55+w5q!q#!>4A-F=8`#BenZQ!9yQr7K
z)S9Y(y59fB&yKYNFKPBre1B7IWnNmB9hdl;`?TfJsYs
zQ6sA2aV?RBeuVlo<##&fSJwLZ+o)caZk^lED4CEqZab@Rc>M)
z?tw2LE4Cu($NE;f-+wB@L^Y8ZVcJpa02Nrvzys;$1hmv+1_BTlsTi`ZLgVlq!4?
z;+0ZcuBe`?Lv@?%unCOa=}^gLhfdNVH1@@6I+nb=JeNb%EfW0F2QiuMq0>MABGY)d
zOio&4sJ$)Hsmj{CAjba;Jcee5mybB|Ly%&&y7e|5tuWE9^+ZQ~qoV#mu@h`h9&VdV
zBg_E}>Ww4)E`W_v7+BYc@Q;m|Q{}$i=L%@7$
zw?9Nh2FE}?-jGl?^fu=acAYG10VQaOw$|D*{U>&6il)X!BuvJ4B0yVhC7;>TgA`lx
zIVlnCf>sR5XFor=c;h_qMS$egVB?A^XR_4BWK^T=TyghyySt=|vfAg|_iq#QelBF9
zb_~HtbW{=))z8gw6@`|OL5JR;sjmINhI`k=q_LhoQT-NDD9xOmE??Di
zL2D%+%DcsSiENhO8K<2`gmNk9j*F-BYdj5F?B;@?gAykEOOe3047B6p<5ffZwT(Fo
z?l+ubdN;-2Lf5HziPL9E-;F`kk+n$o>0jTtdtR&C{O?USF5Ijr?QGYE+~8B
z|GofRExJdYcnZg(dIF18H+tcAHl?}AXSp)L_suPagPT={&U)4RnZWfo%kkl*ObJvl
z@^aZp7UUO+9trTJ583YJW$tNqVL(8AN=$^C9#@J%RaI#N4I&6;*={gD4mHQb#YOb=
z4E#h=c&9JonLX>iy*fKP(*>M;P__EgPI+FHcWT7E;B(e$m8FYcYYtHXgzMMdY$?F`)7I{G9oN>-_vY(S-Qlgl!AG-EX#v
zh;Xc~Adm|O*07yzY)uTQf-xJW`N}>^9<;OSL3H3$M4lTR%pOU6s3IC=H!BKPxNbMca{$~adhQ$;m3FC_jYXT|zFw`vwbzb4w|iwYJvIIX#cGR>IHiuANem35OI4
ziLy>eOq@)T{+ki1=-mtf-L19T?KFs4v@w%fs3u$PQZT}}+t$9mqKu4;LukKq;SV3?
z3_^2e=H{rzGli(?*vs52-_6n2onl#m{~OvO(1nj_zgY)dN~MWg#jX)QpNpBg_M}Y`Jh`>rGGK(Je}Kaha*93
zFi_7-goK2(D-0t5vk?1F2%91+QwHx>m>*#F)xy*A@h}4x_$ci1ruXCBhvZE6cX4EnfEQLEp;5rcD#_qM}=2|UiAaj88wh2aB!gqh0
zh4IOiwHc7VSgI
z^O7R^A7#go;=j5cW)S#mmsAAUZvde-|230K2
zkuhcTRQCAnNYyu3fDTq0YXRHv7icV_zqePAcZLc{MB$I4;HOVs+s39)bnK1LiJoiT|<-%Z&kPxeC$HOO1D5Bs7XD%6N@;k~(9u}ul
zc`BPjDlX_=*y`%7EiyLH6=w&tVXZHCKTqd&?`(4lbrrQpT7n>)+!yRM{og9}i3@n(
z*yLm#9W8Iv!GlR&UniNGnj%90LXSC%!V?7}^F^Ohi?==MfTn(A{v=LZL@H35J$b$;
zE!oE7ks119kPs6mI9z^i`P(XD?BI}f8g53&1zM2jP)2N}evJUKqtXim;~3kp;n?j}
zfIwCHf8@br`jaG`t*z9cOXfat!J@h-FhCdffZF63Cntoa#sE8S?dUDHmaI&k$RTSl
zHSbH^z%Zok{x78n)v^d*!Y$hbmB4C=?cyAhoT1^CerK3unqHYNw8^Lr{?#86(L;*+-|^ly^xOigKCB%C
zVp8|jq%M2}n=&50qN<7y4O<-fbRH*INeN>*xbZNXM6sPxQco}A*M(#RLec2oztQ?K
zM<2o2gA9G>;LcpH0c5Hq*!U1pZG<}3n3(Gp6ci8J2sn>5k^1ffHAUV;aK4j4d~CFB
zYqUMBJeoRh&DtM~Kb)SQr&lXY@#d=MS
ztX`!aECDb06za+Bwmpw#emmI%my>Li&i^h1jXyVJr~)q^1{%yN^dlqa$LiT#p{>s!De;?G)0LQ9dqwG_KaPJM;
z{Tr>*Iucwkl{OIoQWP-1X0V#j*LZC0e-b@y;^3f$#kM~1c5!z0WiKB!VfgEuzjhKm+jEB`0d3(K-na_~^+0x}p5Pq~{-82xFpAR(xYRX-c!*Hw*bT
zO?q8}?ym)2jj$NY8Oj4J*WXE#V1;m_quiIZVvNJdJQ%4;QAd2O1HvE)b+|zDU1Ki+
zqPWCTzD<(eA@82liL{$z*q$v#T&WfX75aoLWpglMR8#2~&`0%^7!AV9Ept-A$=zF-eHZ+qk82IzI<`XjGli1_242PuAU=eo8Q
z=Fj!bB@t7gwy|1YFyedoX7?C)x1_TO3a;fB{8_;qA8(HSbJ0u|uWW65r6SW9m{gS17})*r`r1Z
zQ#^qwEmU=uQn2P*#l%CMC@6*EsTDlw9SmcAW;MNG17Y{uX}aCf%!9)n9Y+Q&P6~!Xxo(oIn;X}a35w3m
z^RA-ww%{+r0|UjYME3Bl3BA+!p&N4o^0Zpv9k@B!zB_^@BfYsoQtzzh=NoqniIboo
ztq&{dE_*?c%2S0P(ocm_PQv=PC0NX4zq0A|HdLVj+q6bICP&;h!Vrw5$^M%WC?YL_
zvX(~BKwQSvz6@ee<+ClFT+Td6ZuuqZw)T;FeRL|E)bDx}oz!#J$Y|;{ozqwSQwHiy
zHVkNi9tcttxW%QvzM_-SgBj+TWDSr|m{fvEMyHltn*?WPd&&At{?k+U_n(ijt(czJ
z1z+6#{QZRD4MKK$Nz=N3v!xL-if5o;S0lv`(AHishiHK2kWD+QW=i|3-dX7-!r4Ek
zdt+QJx~+ZfCwLzt{Cc0tfi8SacV^tTgDzBBnhi?8OvE0CJk&oyz{UouH)UJ99*_kS`wfOM=P!H}FjQ^gsFj+;l}
zlowm@h_w}IOh3{uWZ)uxrSb4TOfV@u1kH0?bG?euR%ptcS9~eD_aBM@RVY^W9WH$!>w0Sw9a+nzhpIr
zXJ;l1Bl`Q?CHp30kK9Roch%G5gdg{W5kMLS(@N@V$SPoyCwsDC?wNssvsYmbN=m{@
x-SspYJeV5(?H*uk%0XU+jr89D_y~|?#d9fv
Date: Fri, 27 Dec 2024 17:26:54 +0900
Subject: [PATCH 2/2] [Update] Supporters: The members cards will be shown in
random order every 20 seconds.
---
.../setting_section/SettingSection.jsx | 10 +-
.../setting_box/supporters/Supporters.jsx | 182 ++++++++++++------
.../supporters/Supporters.module.scss | 10 +
src-ui/logics/configs/index.js | 1 +
.../configs/useSettingBoxScrollPosition.js | 28 +++
src-ui/store.js | 2 +
6 files changed, 175 insertions(+), 58 deletions(-)
create mode 100644 src-ui/logics/configs/useSettingBoxScrollPosition.js
diff --git a/src-ui/app/config_page/setting_section/SettingSection.jsx b/src-ui/app/config_page/setting_section/SettingSection.jsx
index cfa4c9a2..c39e6221 100644
--- a/src-ui/app/config_page/setting_section/SettingSection.jsx
+++ b/src-ui/app/config_page/setting_section/SettingSection.jsx
@@ -1,9 +1,17 @@
+import { useRef, useLayoutEffect, useEffect } from "react";
+
import styles from "./SettingSection.module.scss";
import { SettingBox } from "./setting_box/SettingBox";
+import { store } from "@store";
export const SettingSection = () => {
+ const scrollContainerRef = useRef(null);
+ useLayoutEffect(() => {
+ store.setting_box_scroll_container = scrollContainerRef;
+ }, []);
+
return (
-
+
diff --git a/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.jsx b/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.jsx
index 8c3d4a76..1d64b9c5 100644
--- a/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.jsx
+++ b/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.jsx
@@ -1,7 +1,12 @@
+import { useState, useEffect } from "react";
import styles from "./Supporters.module.scss";
import clsx from "clsx";
import { useTranslation } from "react-i18next";
+import {
+ useSettingBoxScrollPosition,
+} from "@logics_configs"
+
const supporter_images = import.meta.glob("@images/supporters/supporters_images/*.{png,jpg,jpeg,svg}", { eager: true });
const chato_expression_images = import.meta.glob("@images/supporters/chato_expressions/*.{png,jpg,jpeg,svg}", { eager: true });
import fanbox_img from "@images/supporters/c_fanbox_1620x580.png";
@@ -11,7 +16,6 @@ import kofi_preparing from "@images/supporters/kofi_preparing.png";
import ExternalLink from "@images/external_link.svg?react";
-
const mogu_count = 8;
const mochi_count = 3;
const fuwa_count = 4;
@@ -23,6 +27,7 @@ const default_icon_numbers = ["05", "06", "07", "11"];
const supporters_filenames = Array.from({ length: 23 }, (_, index) => `supporter_${String(index + 1).padStart(2, "0")}`);
const chato_expressions_filenames = Array.from({ length: 7 }, (_, index) => `chato_expression_${String(index + 1).padStart(2, "0")}`);
+const SHUFFLE_INTERVAL_TIME = 20000;
const shuffleArray = (array) => {
return array
.map((value) => ({ value, sort: Math.random() }))
@@ -30,38 +35,6 @@ const shuffleArray = (array) => {
.map(({ value }) => value);
};
-const getCategoryImages = (start, count) => {
- const category_images = supporters_filenames.slice(start, start + count);
- return shuffleArray(category_images);
-};
-
-const mogu_images = getCategoryImages(0, mogu_count);
-const mochi_images = getCategoryImages(mogu_count, mochi_count);
-const fuwa_images = getCategoryImages(mogu_count + mochi_count, fuwa_count);
-const basic_images = getCategoryImages(mogu_count + mochi_count + fuwa_count, basic_count);
-const former_images = getCategoryImages(mogu_count + mochi_count + fuwa_count + basic_count, former_count);
-const and_you_images = getCategoryImages(mogu_count + mochi_count + fuwa_count + basic_count + former_count, and_you_count);
-
-const getRandomImage = (images) => {
- const random_index = Math.floor(Math.random() * images.length);
- return images[random_index];
-};
-
-const getSupportersImageByFileName = (file_name) => {
- const image_path = Object.keys(supporter_images).find((path) => path.endsWith(file_name + ".png"));
- return image_path ? supporter_images[image_path]?.default : null;
-};
-
-const getChatoImageByFileName = (file_name) => {
- const image_path = Object.keys(chato_expression_images).find((path) => path.endsWith(file_name + ".png"));
- return image_path ? chato_expression_images[image_path]?.default : null;
-};
-
-const getRandomDelay = (min, max) => {
- const random_value = Math.random() * (max - min) + min;
- return `${random_value.toFixed(1)}s`;
-};
-
export const Supporters = () => {
return (
@@ -77,10 +50,9 @@ const SupportUsContainer = () => {
@@ -92,14 +64,115 @@ const SupportUsContainer = () => {
);
};
+const getRandomImage = (images) => {
+ const random_index = Math.floor(Math.random() * images.length);
+ return images[random_index];
+};
+
export const SupportersContainer = () => {
- const renderImages = (image_list, options={}) => {
- return image_list.map((file_name) => {
+
+ return (
+
+

+
{`VRCT3.0のアップデートに向けて、めちゃ大変な開発を支えてくれた "Early Supporters" です。\nThey are the 'Early Supporters' who supported us through the incredibly challenging development toward the VRCT3.0 update.`}
+
+
+
+
{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(in開発室) しいなは喜び庭駆け回っています!!!ふわもちもぐもぐです!ありがとうございます。これからもまだまだ進化するVRCTをどうかよろしくお願いします!\nThanks to everyone, Misha has been granted the privilege of sleeping in a proper bed (in the development room), and Shiina is so happy, running around the yard! Fuwa-mochi-mogu-mogu! Thank you so much! We hope you'll continue to support the ever-evolving VRCT!`}
+
+ );
+};
+
+const ProgressBar = () => {
+ const [is_active, setIsActive] = useState(false);
+
+ useEffect(() => {
+ setIsActive(true);
+ const interval = setInterval(() => {
+ setIsActive(false);
+ setTimeout(() => setIsActive(true), 50);
+ }, SHUFFLE_INTERVAL_TIME);
+
+ return () => clearInterval(interval);
+ }, []);
+
+ return (
+
+ );
+};
+
+const SupportsWrapper = () => {
+ const { saveScrollPosition, restoreScrollPosition } = useSettingBoxScrollPosition();
+ const [imagesState, setImagesState] = useState({
+ mogu_images: [],
+ mochi_images: [],
+ fuwa_images: [],
+ basic_images: [],
+ former_images: [],
+ and_you_images: [],
+ chato_images: [],
+ });
+
+ const shuffleImages = () => {
+ saveScrollPosition();
+ const getCategoryImages = (start, count) => {
+ const category_images = supporters_filenames.slice(start, start + count);
+ return shuffleArray(category_images);
+ };
+
+ const randomChatoImages = shuffleArray(
+ Array.from({ length: mogu_count + mochi_count + fuwa_count + basic_count + former_count }, () =>
+ getRandomImage(chato_expressions_filenames)
+ )
+ );
+
+ setImagesState({
+ mogu_images: getCategoryImages(0, mogu_count),
+ mochi_images: getCategoryImages(mogu_count, mochi_count),
+ fuwa_images: getCategoryImages(mogu_count + mochi_count, fuwa_count),
+ basic_images: getCategoryImages(mogu_count + mochi_count + fuwa_count, basic_count),
+ former_images: getCategoryImages(mogu_count + mochi_count + fuwa_count + basic_count, former_count),
+ and_you_images: getCategoryImages(mogu_count + mochi_count + fuwa_count + basic_count + former_count, and_you_count),
+ chato_images: randomChatoImages,
+ });
+ setTimeout(() => restoreScrollPosition(), 0);
+ };
+
+ useEffect(() => {
+ shuffleImages();
+ const interval = setInterval(() => {
+ shuffleImages();
+ }, SHUFFLE_INTERVAL_TIME);
+
+ return () => clearInterval(interval);
+ }, []);
+
+
+ const getSupportersImageByFileName = (file_name) => {
+ const image_path = Object.keys(supporter_images).find((path) => path.endsWith(file_name + ".png"));
+ return image_path ? supporter_images[image_path]?.default : null;
+ };
+
+ const getChatoImageByFileName = (file_name) => {
+ const image_path = Object.keys(chato_expression_images).find((path) => path.endsWith(file_name + ".png"));
+ return image_path ? chato_expression_images[image_path]?.default : null;
+ };
+
+ const getRandomDelay = (min, max) => {
+ const random_value = Math.random() * (max - min) + min;
+ return `${random_value.toFixed(1)}s`;
+ };
+
+
+ const renderImages = (image_list, chato_list, options = {}) => {
+ return image_list.map((file_name, index) => {
const img_src = getSupportersImageByFileName(file_name);
- const is_default_icon = default_icon_numbers.some((default_num) => file_name.endsWith(default_num));
- const chato_expression_src = is_default_icon
- ? getChatoImageByFileName(getRandomImage(chato_expressions_filenames))
- : null;
+ const is_default_icon = default_icon_numbers.some((element) => file_name.endsWith(element));
+ const chato_expression_src = is_default_icon ? getChatoImageByFileName(chato_list[index]) : null;
const random_delay = getRandomDelay(0.1, 6);
return img_src ? (
@@ -122,20 +195,15 @@ export const SupportersContainer = () => {
};
return (
-
-

-
{`VRCT3.0のアップデートに向けて、めちゃ大変な開発を支えてくれた "Early Supporters" です。\nThey are the 'Early Supporters' who supported us through the incredibly challenging development toward the VRCT3.0 update.`}
-
-
{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(in開発室) しいなは喜び庭駆け回っています!!!ふわもちもぐもぐです!ありがとうございます。これからもまだまだ進化するVRCTをどうかよろしくお願いします!\nThanks to everyone, Misha has been granted the privilege of sleeping in a proper bed (in the development room), and Shiina is so happy, running around the yard! Fuwa-mochi-mogu-mogu! Thank you so much! We hope you’ll continue to support the ever-evolving VRCT!`}
+
);
};
diff --git a/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.module.scss b/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.module.scss
index 7c0cfc16..4288b653 100644
--- a/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.module.scss
+++ b/src-ui/app/config_page/setting_section/setting_box/supporters/Supporters.module.scss
@@ -192,4 +192,14 @@
font-size: 1.4rem;
margin-top: 2rem;
color: var(--dark_300_color);
+}
+
+.progress_bar {
+ height: 0.2rem;
+ width: 0%;
+ &.progress_bar_active {
+ transition: width 20000ms linear;
+ background-color: var(--primary_400_color);
+ width: 100%;
+ }
}
\ No newline at end of file
diff --git a/src-ui/logics/configs/index.js b/src-ui/logics/configs/index.js
index 646d24c1..b8426255 100644
--- a/src-ui/logics/configs/index.js
+++ b/src-ui/logics/configs/index.js
@@ -54,4 +54,5 @@ export { useSendTextToOverlay } from "./vr/useSendTextToOverlay";
export { useOscIpAddress } from "./advanced_settings/useOscIpAddress";
export { useOscPort } from "./advanced_settings/useOscPort";
+export { useSettingBoxScrollPosition } from "./useSettingBoxScrollPosition";
export { useSoftwareVersion } from "./useSoftwareVersion";
\ No newline at end of file
diff --git a/src-ui/logics/configs/useSettingBoxScrollPosition.js b/src-ui/logics/configs/useSettingBoxScrollPosition.js
new file mode 100644
index 00000000..c0a19b98
--- /dev/null
+++ b/src-ui/logics/configs/useSettingBoxScrollPosition.js
@@ -0,0 +1,28 @@
+import { store, useStore_SettingBoxScrollPosition } from "@store";
+
+export const useSettingBoxScrollPosition = () => {
+ const { currentSettingBoxScrollPosition, updateSettingBoxScrollPosition, pendingSettingBoxScrollPosition } = useStore_SettingBoxScrollPosition();
+
+ const saveScrollPosition = () => {
+ if (store.setting_box_scroll_container.current) {
+ updateSettingBoxScrollPosition(store.setting_box_scroll_container.current.scrollTop);
+ }
+ };
+ const restoreScrollPosition = () => {
+
+ if (store.setting_box_scroll_container.current) {
+ updateSettingBoxScrollPosition((pre) => {
+ store.setting_box_scroll_container.current.scrollTop = pre.data;
+ return pre.data;
+ })
+ }
+
+ };
+
+ return {
+ saveScrollPosition,
+ restoreScrollPosition,
+ currentSettingBoxScrollPosition,
+ updateSettingBoxScrollPosition,
+ };
+};
\ No newline at end of file
diff --git a/src-ui/store.js b/src-ui/store.js
index a7950a2a..19d81081 100644
--- a/src-ui/store.js
+++ b/src-ui/store.js
@@ -16,6 +16,7 @@ import {
export const store = {
backend_subprocess: null,
config_page: null,
+ setting_box_scroll_container: null,
log_box_ref: null,
is_applied_init_message_box_height: false,
};
@@ -160,6 +161,7 @@ export const { atomInstance: Atom_IsOpenedLanguageSelector, useHook: useStore_Is
// Common
export const { atomInstance: Atom_SoftwareVersion, useHook: useStore_SoftwareVersion } = createAtomWithHook("-", "SoftwareVersion");
export const { atomInstance: Atom_SelectedConfigTabId, useHook: useStore_SelectedConfigTabId } = createAtomWithHook("device", "SelectedConfigTabId");
+export const { atomInstance: Atom_SettingBoxScrollPosition, useHook: useStore_SettingBoxScrollPosition } = createAtomWithHook(0, "SettingBoxScrollPosition");
// Designs
export const { atomInstance: Atom_IsOpenedDropdownMenu, useHook: useStore_IsOpenedDropdownMenu } = createAtomWithHook("", "IsOpenedDropdownMenu");