[Update] Config Page: Supporters: Update-able anytime.

Fetch the supporters data and images from the git repo, https://github.com/ShiinaSakamoto/vrct_supporters, via web.
This commit is contained in:
Sakamoto Shiina
2025-02-03 11:26:10 +09:00
parent 182c277ef5
commit 4fb675943f
57 changed files with 335 additions and 562 deletions

View File

@@ -1,40 +1,28 @@
import styles from "./SupportersContainer.module.scss";
import { useState, useEffect } from "react";
import vrct_supporters_title from "@images/supporters/vrct_supporters_title.png";
import { useEffect } from "react";
import { SupportersWrapper } from "./supporters_wrapper/SupportersWrapper";
import { clsx } from "clsx";
const SHUFFLE_INTERVAL_TIME = 20000;
import { useSupporters } from "@logics_configs";
import { supporters_images_url } from "@ui_configs";
export const SupportersContainer = () => {
const { currentSupportersData, asyncFetchSupportersData } = useSupporters();
useEffect(() => {
asyncFetchSupportersData();
}, []);
if (currentSupportersData.state === "error")
return <div>Failed to retrieve data.</div>;
if (currentSupportersData.state === "pending" || currentSupportersData.data === null)
return <div>Loading...</div>;
const supporters_settings = currentSupportersData.data.supporters_settings;
return (
<div className={styles.supporters_container}>
<img className={styles.vrct_supporters_title} src={vrct_supporters_title} />
<ProgressBar />
<SupportersWrapper />
<ProgressBar />
<img className={styles.vrct_supporters_title} src={`${supporters_images_url}/vrct_supporters_title.png`} />
<SupportersWrapper supporters_settings={supporters_settings}/>
<p className={styles.vrct_supporters_desc_end}>{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(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!`}</p>
</div>
);
};
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 (
<div
className={clsx(styles.progress_bar, {
[styles.progress_bar_active]: is_active,
})}
/>
);
};

View File

@@ -14,13 +14,3 @@
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%;
}
}

View File

@@ -6,222 +6,162 @@ import { shuffleArray, randomIntMinMax, randomMinMax } from "@utils";
import {
useSettingBoxScrollPosition,
} from "@logics_configs"
useSupporters,
} from "@logics_configs";
import json_data from "./data.json";
const target_supporting_month = "2025-01";
const calc_support_period = ["2024-10", "2024-11", "2024-12", "2025-01"];
import { supporters_images_url } from "@ui_configs";
const SHUFFLE_INTERVAL_TIME = 20000;
const and_you_data = {
supporter_id: "and_you",
};
const getImagePath = (images, file_name) => {
const image_path = Object.keys(images).find((path) => path.endsWith(`${file_name}.png`));
return image_path ? images[image_path]?.default : null;
};
const image_sets = {
supporter_cards: import.meta.glob("@images/supporters/supporter_cards/*.png", { eager: true }),
chato_expressions: import.meta.glob("@images/supporters/chato_expressions/*.png", { eager: true }),
supporters_labels: import.meta.glob("@images/supporters/supporters_labels/*.png", { eager: true }),
supporters_icons: import.meta.glob("@images/supporters/supporters_icons/*.png", { eager: true }),
supporter_cards: `${supporters_images_url}/supporter_cards/`,
chato_expressions: `${supporters_images_url}/chato_expressions/`,
supporters_labels: `${supporters_images_url}/supporters_labels/`,
supporters_icons: `${supporters_images_url}/supporters_icons/`,
};
const getSupporterCard = (plan_name) => {
const card_map = {
"もぐもぐ_2000": "mogu_card",
"もちもち_1000": "mochi_card",
"ふわふわ_500": "fuwa_card",
"Basic_300": "basic_card",
"mogu_2000": "mogu_card",
"mochi_1000": "mochi_card",
"fuwa_500": "fuwa_card",
"basic_300": "basic_card",
};
return getImagePath(image_sets.supporter_cards, card_map[plan_name] || "basic_card");
if (!card_map[plan_name]) return `${image_sets.supporter_cards}basic_card.png`;
return `${image_sets.supporter_cards}${card_map[plan_name]}.png`;
};
const getChatoExpressionsPath = (file_name) =>
getImagePath(image_sets.chato_expressions, file_name);
const getChatoExpressionsPath = (file_name) => `${image_sets.chato_expressions}${file_name}.png`;
const getSupportersLabelsPath = (file_name) => `${image_sets.supporters_labels}${file_name}.png`;
const getSupportersIconsPath = (file_name) => `${image_sets.supporters_icons}${file_name}.png`;
const getSupportersLabelsPath = (file_name) =>
getImagePath(image_sets.supporters_labels, file_name);
const getSupportersIconsPath = (file_name) =>
getImagePath(image_sets.supporters_icons, file_name);
const chato_ex_count = Object.keys(image_sets.chato_expressions).length;
export const SupportersWrapper = () => {
export const SupportersWrapper = ({supporters_settings}) => {
const { saveScrollPosition, restoreScrollPosition } = useSettingBoxScrollPosition();
const { currentSupportersData } = useSupporters();
let credit_pending_count = 0;
const filtered_data = json_data.filter((supporter) => {
if (!supporter.supporter_id) return false;
const [json_data, setJsonData] = useState();
const [supportersData, setSupportersData] = useState([]);
const [chatoExpressions, setChatoExpressions] = useState([]);
const months = Object.keys(supporter).filter((key) => key.match(/^\d{4}-\d{2}$/));
const has_valid_month = months.some((month) => supporter[month]);
if (!has_valid_month) return false;
const basic_300_months = months.filter((month) => supporter[month] === "Basic_300");
const has_special_plan = months.some((month) => ["ふわふわ_500", "もちもち_1000", "もぐもぐ_2000"].includes(supporter[month]));
if (basic_300_months.length === 1 && !has_special_plan) {
credit_pending_count++;
return false;
}
return true;
});
const grouped_data = {
もぐもぐ_2000: [],
もちもち_1000: [],
ふわふわ_500: [],
Basic_300: [],
empty: [],
and_you: [],
};
filtered_data.forEach((supporter) => {
const value = supporter[target_supporting_month] || "empty";
if (grouped_data[value]) {
grouped_data[value].push(supporter);
} else {
grouped_data["empty"].push(supporter);
}
});
const [supportersData, setSupportersData] = useState(() => [
...grouped_data["もぐもぐ_2000"],
...grouped_data["もちもち_1000"],
...grouped_data["ふわふわ_500"],
...grouped_data["Basic_300"],
...grouped_data["empty"],
and_you_data,
]);
useEffect(() => {
setJsonData(currentSupportersData.data);
}, [currentSupportersData.data]);
const [chatoExpressions, setChatoExpressions] = useState(() =>
supportersData.map(() =>
getChatoExpressionsPath(`chato_expression_${randomIntMinMax(1, chato_ex_count)}`)
)
);
const target_supporting_month = supporters_settings.target_supporting_month;
const calc_support_period = supporters_settings.calc_support_period;
const chato_ex_count = supporters_settings.chato_ex_count;
const last_updated_local_date = new Date(supporters_settings.last_updated_utc_date)?.toString();
const recalcAndUpdateSupporters = useCallback(() => {
if (!json_data) return;
let credit_pending_count = 0;
const newGroupedData = {
"mogu_2000": [],
"mochi_1000": [],
"fuwa_500": [],
"basic_300": [],
"empty": [],
"and_you": [],
};
const filtered_data = json_data.supporters_data.filter((supporter) => {
if (!supporter.supporter_id) return false;
const months = Object.keys(supporter).filter((key) =>
key.match(/^\d{4}-\d{2}$/)
);
const has_valid_month = months.some((month) => supporter[month]);
if (!has_valid_month) return false;
const basic_300_months = months.filter(
(month) => supporter[month] === "basic_300"
);
const has_special_plan = months.some((month) =>
["fuwa_500", "mochi_1000", "mogu_2000"].includes(supporter[month])
);
if (basic_300_months.length === 1 && !has_special_plan) {
credit_pending_count++;
return false;
}
return true;
});
filtered_data.forEach((supporter) => {
const value = supporter[target_supporting_month] || "empty";
if (newGroupedData[value]) {
newGroupedData[value].push(supporter);
} else {
newGroupedData["empty"].push(supporter);
}
});
const shuffleSupporters = useCallback(() => {
saveScrollPosition();
const newSupportersData = [
...shuffleArray(grouped_data["もぐもぐ_2000"]),
...shuffleArray(grouped_data["もちもち_1000"]),
...shuffleArray(grouped_data["ふわふわ_500"]),
...shuffleArray(grouped_data["Basic_300"]),
...shuffleArray(grouped_data["empty"]),
...shuffleArray(newGroupedData["mogu_2000"]),
...shuffleArray(newGroupedData["mochi_1000"]),
...shuffleArray(newGroupedData["fuwa_500"]),
...shuffleArray(newGroupedData["basic_300"]),
...shuffleArray(newGroupedData["empty"]),
and_you_data,
];
setSupportersData(newSupportersData);
setSupportersData(newSupportersData);
setChatoExpressions(
newSupportersData.map(() =>
getChatoExpressionsPath(`chato_expression_${randomIntMinMax(1, chato_ex_count)}`)
getChatoExpressionsPath(
`chato_expression_${randomIntMinMax(1, chato_ex_count)}`
)
)
);
setTimeout(() => restoreScrollPosition(), 0);
}, [grouped_data]);
const renderImages = () => {
return supportersData.map((item, index) => {
const target_plan = item[target_supporting_month];
const img_src = getSupporterCard(target_plan);
const is_default_icon = item.supporter_icon_id === "";
const is_icon_plan = ["もぐもぐ_2000", "もちもち_1000"].includes(target_plan);
const is_and_you = item.supporter_id === "and_you";
const random_delay = `${randomMinMax(0.1, 6).toFixed(1)}s`;
const file_name = is_and_you ? "and_you" : `supporter_${item.supporter_id}`;
const label_img_src = getSupportersLabelsPath(file_name);
const icon_img_src = getSupportersIconsPath(`supporter_icon_${item.supporter_icon_id}`);
const supporter_label_component_classname = clsx(styles.supporter_label_component, {
[styles.is_icon_plan]: is_icon_plan,
});
const supporterLabelComponent = () => (
<div className={supporter_label_component_classname}>
{is_icon_plan && (
<div className={styles.supporter_icon_wrapper}>
{is_default_icon ? (
<img
className={styles.default_chato_expression_image}
src={chatoExpressions[index]}
/>
) : (
<img className={styles.supporter_icon} src={icon_img_src} />
)}
</div>
)}
<img className={styles.supporter_label_image} src={label_img_src} />
</div>
);
const supporter_image_wrapper_classname = clsx(styles.supporter_image_wrapper, {
[styles.mogu_image]: target_plan === "もぐもぐ_2000",
});
return is_and_you ? (
<a href="#support_us_container" key={item.supporter_id}>
<div className={styles.supporter_image_container}>
<div
className={supporter_image_wrapper_classname}
style={{ "--delay": random_delay }}
>
<img className={styles.supporter_image} src={img_src} />
{supporterLabelComponent()}
<AndYouIcon />
</div>
</div>
</a>
): img_src ? (
<div key={item.supporter_id} className={styles.supporter_image_container}>
<div
className={supporter_image_wrapper_classname}
style={{ "--delay": random_delay }}
>
<img className={styles.supporter_image} src={img_src} />
{supporterLabelComponent()}
</div>
<SupporterPeriodContainer settings={item}/>
</div>
) : null;
});
};
}, [json_data]);
useEffect(() => {
recalcAndUpdateSupporters();
}, [json_data, recalcAndUpdateSupporters]);
const shuffleSupporters = useCallback(() => {
if (!json_data) return;
saveScrollPosition();
recalcAndUpdateSupporters();
setTimeout(() => restoreScrollPosition(), 0);
}, [json_data, recalcAndUpdateSupporters, saveScrollPosition, restoreScrollPosition]);
useEffect(() => {
shuffleSupporters();
const interval = setInterval(() => {
shuffleSupporters();
}, SHUFFLE_INTERVAL_TIME);
return () => clearInterval(interval);
}, []);
}, [shuffleSupporters]);
return (
<div className={styles.supporters_wrapper}>{renderImages()}</div>
<div className={styles.container}>
<ProgressBar />
<div className={styles.supporters_wrapper}>
<SupporterCardsComponent
supportersData={supportersData}
chatoExpressions={chatoExpressions}
target_supporting_month={target_supporting_month}
calc_support_period={calc_support_period}
/>
</div>
<p className={styles.last_updated_local_date}>{`Last updated date:\n${last_updated_local_date}`}</p>
<ProgressBar />
</div>
);
};
const AndYouIcon = () => {
return (
<>
@@ -237,29 +177,136 @@ const AndYouIcon = () => {
);
};
const SupporterPeriodContainer = ({settings}) => {
const SupporterCardsComponent = ({ supportersData, chatoExpressions, target_supporting_month, calc_support_period }) => {
return supportersData.map((item, index) => {
const target_plan = item[target_supporting_month];
const img_src = getSupporterCard(target_plan);
const is_and_you = item.supporter_id === "and_you";
const random_delay = `${randomMinMax(0.1, 6).toFixed(1)}s`;
const supporter_image_wrapper_classname = clsx(
styles.supporter_image_wrapper,
{
[styles.mogu_image]: target_plan === "mogu_2000",
}
);
return is_and_you ? (
<a href="#support_us_container" key={item.supporter_id}>
<div className={styles.supporter_image_container}>
<div
className={supporter_image_wrapper_classname}
style={{ "--delay": random_delay }}
>
<img
className={styles.supporter_image}
src={img_src}
alt="supporter"
/>
<SupporterLabelComponent
target_plan={target_plan}
item={item}
chatoExpressions={chatoExpressions}
/>
<AndYouIcon />
</div>
</div>
</a>
) : img_src ? (
<div key={item.supporter_id} className={styles.supporter_image_container}>
<div
className={supporter_image_wrapper_classname}
style={{ "--delay": random_delay }}
>
<img
className={styles.supporter_image}
src={img_src}
alt="supporter"
/>
<SupporterLabelComponent
target_plan={target_plan}
item={item}
chato_src={chatoExpressions[index]}
index={index}
/>
</div>
<SupporterPeriodContainer settings={item} calc_support_period={calc_support_period}/>
</div>
) : null;
});
};
const SupporterLabelComponent = ({ item, target_plan, chato_src }) => {
const is_icon_plan = ["mogu_2000", "mochi_1000"].includes(
target_plan
);
const supporter_label_component_classname = clsx(
styles.supporter_label_component,
{
[styles.is_icon_plan]: is_icon_plan,
}
);
const is_and_you = item.supporter_id === "and_you";
const is_default_icon = item.supporter_icon_id === "";
const file_name = is_and_you ? "and_you" : `supporter_${item.supporter_id}`;
const label_img_src = getSupportersLabelsPath(file_name);
const icon_img_src = getSupportersIconsPath(
`supporter_icon_${item.supporter_icon_id}`
);
return (
<div className={supporter_label_component_classname}>
{is_icon_plan && (
<div className={styles.supporter_icon_wrapper}>
{is_default_icon ? (
<img
className={styles.default_chato_expression_image}
src={chato_src}
alt="chato expression"
/>
) : (
<img
className={styles.supporter_icon}
src={icon_img_src}
alt="supporter icon"
/>
)}
</div>
)}
<img
className={styles.supporter_label_image}
src={label_img_src}
alt="supporter label"
/>
</div>
);
};
const SupporterPeriodContainer = ({ settings, calc_support_period }) => {
const period_data = extractKeys(settings, calc_support_period);
return (
<div className={styles.supporter_period_container}>
{Object.entries(period_data).map(([key, item], index) => {
if (item === "") return null;
const class_name = clsx(styles.period_box, {
[styles.mogu_bar]: item === "もぐもぐ_2000",
[styles.mochi_bar]: item === "もちもち_1000",
[styles.fuwa_bar]: item === "ふわふわ_500",
[styles.basic_bar]: item === "Basic_300",
[styles.mogu_bar]: item === "mogu_2000",
[styles.mochi_bar]: item === "mochi_1000",
[styles.fuwa_bar]: item === "fuwa_500",
[styles.basic_bar]: item === "basic_300",
});
return <div key={index} className={class_name}></div>
return <div key={index} className={class_name}></div>;
})}
</div>
);
};
const extractKeys = (data, keys_to_extract) => {
const result = {};
for (const key of keys_to_extract) {
@@ -269,3 +316,30 @@ const extractKeys = (data, keys_to_extract) => {
}
return result;
};
const ProgressBar = () => {
const [is_active, setIsActive] = useState(false);
useEffect(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
setIsActive(true);
});
});
const interval = setInterval(() => {
setIsActive(false);
setTimeout(() => setIsActive(true), 50);
}, SHUFFLE_INTERVAL_TIME);
return () => clearInterval(interval);
}, []);
return (
<div
className={clsx(styles.progress_bar, {
[styles.progress_bar_active]: is_active,
})}
/>
);
};

View File

@@ -1,3 +1,11 @@
.container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 2rem;
}
.supporters_wrapper {
display: flex;
justify-content: center;
@@ -219,3 +227,21 @@
background-color: var(--dark_800_color);
}
}
.progress_bar {
height: 0.2rem;
width: 0%;
&.progress_bar_active {
transition: width 20000ms linear;
background-color: var(--primary_400_color);
width: 100%;
}
}
.last_updated_local_date {
font-size: 1rem;
color: var(--dark_800_color);
width: 100%;
text-align: end;
}

View File

@@ -1,343 +0,0 @@
[
{
"supporter_id": 1,
"supporter_icon_id": "",
"2024-10": "Basic_300",
"2024-11": "Basic_300",
"2024-12": "Basic_300",
"2025-01": "Basic_300",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 2,
"supporter_icon_id": 8,
"2024-10": "もぐもぐ_2000",
"2024-11": "もぐもぐ_2000",
"2024-12": "もぐもぐ_2000",
"2025-01": "もぐもぐ_2000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 3,
"supporter_icon_id": 4,
"2024-10": "もちもち_1000",
"2024-11": "もちもち_1000",
"2024-12": "もちもち_1000",
"2025-01": "もちもち_1000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 4,
"supporter_icon_id": "",
"2024-10": "もぐもぐ_2000",
"2024-11": "もぐもぐ_2000",
"2024-12": "もぐもぐ_2000",
"2025-01": "もぐもぐ_2000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 5,
"supporter_icon_id": "",
"2024-10": "ふわふわ_500",
"2024-11": "ふわふわ_500",
"2024-12": "ふわふわ_500",
"2025-01": "Basic_300",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 6,
"supporter_icon_id": "",
"2024-10": "ふわふわ_500",
"2024-11": "ふわふわ_500",
"2024-12": "ふわふわ_500",
"2025-01": "ふわふわ_500",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 7,
"supporter_icon_id": "",
"2024-10": "ふわふわ_500",
"2024-11": "ふわふわ_500",
"2024-12": "ふわふわ_500",
"2025-01": "ふわふわ_500",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 8,
"supporter_icon_id": "",
"2024-10": "Basic_300",
"2024-11": "Basic_300",
"2024-12": "Basic_300",
"2025-01": "",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 9,
"supporter_icon_id": 6,
"2024-10": "もぐもぐ_2000",
"2024-11": "もぐもぐ_2000",
"2024-12": "もぐもぐ_2000",
"2025-01": "もぐもぐ_2000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 10,
"supporter_icon_id": "",
"2024-10": "Basic_300",
"2024-11": "Basic_300",
"2024-12": "Basic_300",
"2025-01": "Basic_300",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 11,
"supporter_icon_id": "",
"2024-10": "もぐもぐ_2000",
"2024-11": "もぐもぐ_2000",
"2024-12": "もぐもぐ_2000",
"2025-01": "もぐもぐ_2000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 12,
"supporter_icon_id": "",
"2024-10": "もぐもぐ_2000",
"2024-11": "もぐもぐ_2000",
"2024-12": "もぐもぐ_2000",
"2025-01": "もぐもぐ_2000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 13,
"supporter_icon_id": "",
"2024-10": "Basic_300",
"2024-11": "Basic_300",
"2024-12": "Basic_300",
"2025-01": "Basic_300",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 14,
"supporter_icon_id": 2,
"2024-10": "もちもち_1000",
"2024-11": "もちもち_1000",
"2024-12": "もちもち_1000",
"2025-01": "もちもち_1000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 15,
"supporter_icon_id": 5,
"2024-10": "もぐもぐ_2000",
"2024-11": "もぐもぐ_2000",
"2024-12": "もぐもぐ_2000",
"2025-01": "もぐもぐ_2000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 16,
"supporter_icon_id": "",
"2024-10": "もぐもぐ_2000",
"2024-11": "",
"2024-12": "",
"2025-01": "",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 17,
"supporter_icon_id": "",
"2024-10": "もちもち_1000",
"2024-11": "もちもち_1000",
"2024-12": "もちもち_1000",
"2025-01": "もちもち_1000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 18,
"supporter_icon_id": "",
"2024-10": "Basic_300",
"2024-11": "Basic_300",
"2024-12": "",
"2025-01": "",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 19,
"supporter_icon_id": 1,
"2024-10": "もぐもぐ_2000",
"2024-11": "もぐもぐ_2000",
"2024-12": "もぐもぐ_2000",
"2025-01": "もぐもぐ_2000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 20,
"supporter_icon_id": "",
"2024-10": "Basic_300",
"2024-11": "Basic_300",
"2024-12": "Basic_300",
"2025-01": "ふわふわ_500",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 21,
"supporter_icon_id": "",
"2024-10": "",
"2024-11": "ふわふわ_500",
"2024-12": "ふわふわ_500",
"2025-01": "ふわふわ_500",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 22,
"supporter_icon_id": 3,
"2024-10": "",
"2024-11": "",
"2024-12": "もぐもぐ_2000",
"2025-01": "もぐもぐ_2000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 23,
"supporter_icon_id": "",
"2024-10": "",
"2024-11": "",
"2024-12": "",
"2025-01": "ふわふわ_500",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 24,
"supporter_icon_id": 7,
"2024-10": "",
"2024-11": "",
"2024-12": "",
"2025-01": "もぐもぐ_2000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 25,
"supporter_icon_id": 9,
"2024-10": "",
"2024-11": "",
"2024-12": "",
"2025-01": "もぐもぐ_2000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 26,
"supporter_icon_id": "",
"2024-10": "",
"2024-11": "",
"2024-12": "",
"2025-01": "もちもち_1000",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 27,
"supporter_icon_id": "",
"2024-10": "",
"2024-11": "",
"2024-12": "",
"2025-01": "Basic_300",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 28,
"supporter_icon_id": "",
"2024-10": "",
"2024-11": "",
"2024-12": "",
"2025-01": "Basic_300",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 29,
"supporter_icon_id": "",
"2024-10": "",
"2024-11": "",
"2024-12": "",
"2025-01": "",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": 30,
"supporter_icon_id": "",
"2024-10": "",
"2024-11": "",
"2024-12": "",
"2025-01": "",
"2025-02": "",
"2025-03": "",
"2025-04": ""
},
{
"supporter_id": "",
"supporter_icon_id": "",
"2024-10": "",
"2024-11": "",
"2024-12": "",
"2025-01": "",
"2025-02": "",
"2025-03": "",
"2025-04": ""
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -56,5 +56,9 @@ export { useHotkeys } from "./hotkeys/useHotkeys";
export { useOscIpAddress } from "./advanced_settings/useOscIpAddress";
export { useOscPort } from "./advanced_settings/useOscPort";
export { useSupporters } from "./supporters/useSupporters";
export { useSettingBoxScrollPosition } from "./useSettingBoxScrollPosition";
export { useSoftwareVersion } from "./useSoftwareVersion";

View File

@@ -0,0 +1,29 @@
import { useStore_SupportersData } from "@store";
import { supporters_data_url } from "@ui_configs";
export const useSupporters = () => {
const { currentSupportersData, updateSupportersData, pendingSupportersData, errorSupportersData } = useStore_SupportersData();
const asyncFetchSupportersData = async () => {
if (currentSupportersData.state === "pending") return;
pendingSupportersData();
try {
const res = await fetch(supporters_data_url);
// const res = await fetch(supporters_data_url, { cache: "no-store" });
if (!res.ok) {
throw new Error("Network response was not ok");
}
const data = await res.json();
updateSupportersData(data);
} catch (error) {
console.error("Error fetching supporters' data:", error);
errorSupportersData();
}
};
return {
asyncFetchSupportersData,
currentSupportersData,
updateSupportersData,
pendingSupportersData,
};
};

View File

@@ -280,5 +280,7 @@ export const { atomInstance: Atom_OscPort, useHook: useStore_OscPort } = createA
export const { atomInstance: Atom_IsOpenedTranslatorSelector, useHook: useStore_IsOpenedTranslatorSelector } = createAtomWithHook(false, "IsOpenedTranslatorSelector");
export const { atomInstance: Atom_SupportersData, useHook: useStore_SupportersData } = createAtomWithHook(null, "SupportersData", {is_state_ok: true});
export const { atomInstance: Atom_VrctPosterIndex, useHook: useStore_VrctPosterIndex } = createAtomWithHook(0, "VrctPosterIndex");
export const { atomInstance: Atom_PosterShowcaseWorldPageIndex, useHook: useStore_PosterShowcaseWorldPageIndex } = createAtomWithHook(0, "PosterShowcaseWorldPageIndex");

View File

@@ -75,3 +75,6 @@ export const whisper_weight_type_status = [
{ id: "large-v2", label: "large-v2", is_downloaded: false, progress: null },
{ id: "large-v3", label: "large-v3", is_downloaded: false, progress: null },
];
export const supporters_data_url = "https://shiinasakamoto.github.io/vrct_supporters/assets/supporters/data.json";
export const supporters_images_url = "https://ShiinaSakamoto.github.io/vrct_supporters/assets/supporters";