[Update/Refactor] Fetch and show the plugins info list.
Refactor some functions. Try to fetch functions from github api just once when vrct started.(It used to every time plugin tab has opened so easy to reach to the api limit)
This commit is contained in:
@@ -41,7 +41,6 @@ export const App = () => {
|
||||
<FontFamilyController />
|
||||
<TransparencyController />
|
||||
<WindowGeometryController />
|
||||
<PluginsController />
|
||||
|
||||
{(currentIsBackendReady.data === false || currentIsVrctAvailable.data === false)
|
||||
? <SplashComponent />
|
||||
@@ -57,6 +56,7 @@ const Contents = () => {
|
||||
const { currentIsSoftwareUpdating } = useIsSoftwareUpdating();
|
||||
return (
|
||||
<>
|
||||
<PluginsController />
|
||||
<SubtitlesController />
|
||||
|
||||
<WindowTitleBar />
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { usePlugins } from "@logics_configs";
|
||||
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
if (typeof window !== "undefined") {
|
||||
window.React = React;
|
||||
}
|
||||
|
||||
export const PluginsController = () => {
|
||||
const { loadAllPlugins } = usePlugins();
|
||||
const hasRunRef = useRef(false);
|
||||
const {
|
||||
loadAllPlugins,
|
||||
asyncUpdatePluginInfoList,
|
||||
} = usePlugins();
|
||||
|
||||
useEffect(() => {
|
||||
loadAllPlugins();
|
||||
if (!hasRunRef.current) {
|
||||
asyncUpdatePluginInfoList().then(() => {
|
||||
loadAllPlugins();
|
||||
});
|
||||
}
|
||||
return () => hasRunRef.current = true;
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import styles from "./_DownloadButton.module.scss";
|
||||
|
||||
export const _DownloadButton = ({option, ...props}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const renderContent = () => {
|
||||
const circular_progress = Math.floor(option.progress / 10) * 10;
|
||||
|
||||
switch (true) {
|
||||
case option.progress !== null:
|
||||
return (
|
||||
<>
|
||||
<CircularProgress
|
||||
variant={(option.progress === 100) ? "indeterminate" : "determinate"}
|
||||
value={circular_progress}
|
||||
size="3rem"
|
||||
sx={{ color: "var(--primary_300_color)" }}
|
||||
/>
|
||||
<p className={styles.progress_label}>{`${Math.round(option.progress)}%`}</p>
|
||||
</>
|
||||
);
|
||||
case option.is_pending:
|
||||
return <CircularProgress size="3rem" sx={{ color: "var(--dark_600_color)" }}/>;
|
||||
case !option.is_downloaded:
|
||||
return (
|
||||
<button
|
||||
className={styles.download_button}
|
||||
onClick={() => props.downloadStartFunction(option.id)}
|
||||
>
|
||||
<p className={styles.download_button_label}>{t("config_page.model_download_button_label")}</p>
|
||||
</button>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return <div className={styles.download_container}>{renderContent()}</div>;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
.download_container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 8rem;
|
||||
}
|
||||
|
||||
.download_button {
|
||||
pointer-events: auto;
|
||||
background-color: var(--dark_800_color);
|
||||
padding: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
&:hover {
|
||||
background-color: var(--dark_750_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
}
|
||||
.download_button_label {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.progress_label {
|
||||
position: absolute;
|
||||
font-size: 1rem;
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import styles from "./DownloadModels.module.scss";
|
||||
import {
|
||||
RadioButton,
|
||||
} from "../index";
|
||||
import { _DownloadButton } from "../_atoms/_download_button/_DownloadButton";
|
||||
|
||||
export const DownloadModels = (props) => {
|
||||
const options = props.options.map(item => ({
|
||||
@@ -19,47 +17,9 @@ export const DownloadModels = (props) => {
|
||||
options={options}
|
||||
checked_variable={props.checked_variable}
|
||||
column={true}
|
||||
ChildComponent={ModelSelector}
|
||||
ChildComponent={_DownloadButton}
|
||||
downloadStartFunction={props.downloadStartFunction}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ModelSelector = ({option, ...props}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const renderContent = () => {
|
||||
const circular_progress = Math.floor(option.progress / 10) * 10;
|
||||
|
||||
switch (true) {
|
||||
case option.progress !== null:
|
||||
return (
|
||||
<>
|
||||
<CircularProgress
|
||||
variant={(option.progress === 100) ? "indeterminate" : "determinate"}
|
||||
value={circular_progress}
|
||||
size="3rem"
|
||||
sx={{ color: "var(--primary_300_color)" }}
|
||||
/>
|
||||
<p className={styles.progress_label}>{`${Math.round(option.progress)}%`}</p>
|
||||
</>
|
||||
);
|
||||
case option.is_pending:
|
||||
return <CircularProgress size="3rem" sx={{ color: "var(--dark_600_color)" }}/>;
|
||||
case !option.is_downloaded:
|
||||
return (
|
||||
<button
|
||||
className={styles.download_button}
|
||||
onClick={() => props.downloadStartFunction(option.id)}
|
||||
>
|
||||
<p className={styles.download_button_label}>{t("config_page.model_download_button_label")}</p>
|
||||
</button>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return <div className={styles.download_container}>{renderContent()}</div>;
|
||||
};
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
.download_container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 8rem;
|
||||
}
|
||||
|
||||
.download_button {
|
||||
pointer-events: auto;
|
||||
background-color: var(--dark_800_color);
|
||||
padding: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
&:hover {
|
||||
background-color: var(--dark_750_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
}
|
||||
.download_button_label {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.progress_label {
|
||||
position: absolute;
|
||||
font-size: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
SwitchBox,
|
||||
} from "../index";
|
||||
import { _DownloadButton } from "../_atoms/_download_button/_DownloadButton";
|
||||
|
||||
export const DownloadPlugins = ({plugin_info, ...props}) => {
|
||||
|
||||
const option = {
|
||||
is_pending: plugin_info.is_pending,
|
||||
is_downloaded: plugin_info.is_downloaded,
|
||||
}
|
||||
console.log(plugin_info);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* <SwitchBox
|
||||
variable={currentEnableAutoMicSelect}
|
||||
toggleFunction={toggleEnableAutoMicSelect}
|
||||
/> */}
|
||||
{plugin_info.is_plugin_supported ?
|
||||
<_DownloadButton
|
||||
option={option}
|
||||
downloadStartFunction={props.downloadStartFunction}
|
||||
/>
|
||||
:
|
||||
<div>
|
||||
Unavailable
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -11,4 +11,5 @@ export { Slider } from "./slider/Slider";
|
||||
export { SwitchBox } from "./switch_box/SwitchBox";
|
||||
export { ThresholdComponent } from "./threshold_component/ThresholdComponent";
|
||||
export { WordFilter, WordFilterListToggleComponent } from "./word_filter/WordFilter";
|
||||
export { DownloadModels } from "./download_models/DownloadModels";
|
||||
export { DownloadModels } from "./download_models/DownloadModels";
|
||||
export { DownloadPlugins } from "./download_plugins/DownloadPlugins";
|
||||
@@ -1,12 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import semver from "semver";
|
||||
import { usePlugins } from "@logics_configs";
|
||||
import styles from "./Plugins.module.scss";
|
||||
import { fetch as tauriFetch, ResponseType } from "@tauri-apps/api/http";
|
||||
|
||||
const MAIN_VRCT_VERSION = "3.0.5";
|
||||
// PLUGIN_LIST_URL は中央リポジトリにある、各プラグインの plugin_info.json への URL の配列を保持する JSON の URL
|
||||
const PLUGIN_LIST_URL = "https://raw.githubusercontent.com/ShiinaSakamoto/vrct_plugins_list/main/vrct_plugins_list.json";
|
||||
import { DownloadPlugins } from "../_components";
|
||||
|
||||
export const Plugins = () => {
|
||||
return (
|
||||
@@ -17,146 +12,85 @@ export const Plugins = () => {
|
||||
};
|
||||
|
||||
const PluginDownloadContainer = () => {
|
||||
const [plugin_list, set_plugin_list] = useState([]);
|
||||
const [download_progress, set_download_progress] = useState({});
|
||||
const { downloadAndExtractPlugin } = usePlugins();
|
||||
const {
|
||||
downloadAndExtractPlugin,
|
||||
currentPluginsInfoList,
|
||||
updatePluginsInfoList,
|
||||
} = usePlugins();
|
||||
|
||||
useEffect(() => {
|
||||
async function asyncFetchPluginInfoList() {
|
||||
try {
|
||||
// tauriFetch を使用して vrct_plugins_list.json を取得(CORS 対策)
|
||||
const response = await tauriFetch(PLUGIN_LIST_URL, {
|
||||
method: "GET",
|
||||
responseType: ResponseType.Json,
|
||||
headers: { "Cache-Control": "no-cache" }
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Failed to fetch plugin list, status: " + response.status);
|
||||
const downloadStartFunction = async (plugin) => {
|
||||
updatePluginsInfoList((old_value) => {
|
||||
const new_value = old_value.data.map(d => {
|
||||
if (d.plugin_id === plugin.plugin_id) {
|
||||
d.is_pending = true;
|
||||
}
|
||||
// 取得される plugin_list.json は各プラグインの plugin_info.json への raw URL の配列とする
|
||||
const plugins_data = response.data;
|
||||
const updated_list = await Promise.all(
|
||||
plugins_data.map(async (plugin_data) => {
|
||||
try {
|
||||
const plugin_manifest = await asyncFetchPluginManifest(plugin_data.url);
|
||||
return { ...plugin_manifest };
|
||||
} catch (error) {
|
||||
console.error("Error fetching manifest for URL:", plugin_data.url, error);
|
||||
// エラー発生時は、plugin_data.title とエラーメッセージを返す
|
||||
return {
|
||||
title: plugin_data.title,
|
||||
plugin_id: plugin_data.plugin_id || plugin_data.title,
|
||||
error: error.message,
|
||||
url: plugin_data.url
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
set_plugin_list(updated_list);
|
||||
} catch (error) {
|
||||
console.error("Error fetching plugin info list:", error);
|
||||
}
|
||||
}
|
||||
asyncFetchPluginInfoList();
|
||||
}, []);
|
||||
|
||||
const handleDownload = async (plugin) => {
|
||||
return d;
|
||||
});
|
||||
return new_value;
|
||||
});
|
||||
await downloadAndExtractPlugin(plugin);
|
||||
updatePluginsInfoList((old_value) => {
|
||||
const new_value = old_value.data.map(d => {
|
||||
if (d.plugin_id === plugin.plugin_id) {
|
||||
d.is_pending = false;
|
||||
}
|
||||
return d;
|
||||
});
|
||||
return new_value;
|
||||
});
|
||||
};
|
||||
|
||||
const plugin_list = currentPluginsInfoList.data;
|
||||
// const plugin_list = [
|
||||
// {
|
||||
// title: "VRCT Example Plugins 1",
|
||||
// plugin_id: "vrct_plugin_example_1",
|
||||
// asset_name: "vrct_plugin_example_1.zip",
|
||||
// plugin_version: "0.0.6",
|
||||
// min_supported_vrct_version: "3.0.4",
|
||||
// max_supported_vrct_version: "3.0.6",
|
||||
// is_plugin_supported: true,
|
||||
// // url: manifest_url
|
||||
// },
|
||||
// {
|
||||
// title: "VRCT Example Plugins 2",
|
||||
// plugin_id: "vrct_plugin_example_2",
|
||||
// asset_name: "vrct_plugin_example_2.zip",
|
||||
// plugin_version: "0.0.1",
|
||||
// min_supported_vrct_version: "3.0.4",
|
||||
// max_supported_vrct_version: "3.0.7",
|
||||
// is_plugin_supported: true,
|
||||
// // url: manifest_url
|
||||
// },
|
||||
// ];
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.plugins_list_container}>
|
||||
{plugin_list.map((plugin) => (
|
||||
<div key={plugin.plugin_id}>
|
||||
<h3>{plugin.title}</h3>
|
||||
<h4>{plugin.plugin_id}</h4>
|
||||
<div key={plugin.plugin_id} className={styles.plugin_wrapper}>
|
||||
<p className={styles.title}>{plugin.title}</p>
|
||||
<p className={styles.plugin_id}>{plugin.plugin_id}</p>
|
||||
{plugin.error ? (
|
||||
<p style={{ color: "red" }}>Error: {plugin.error}</p>
|
||||
) : (
|
||||
<>
|
||||
<p>Version: {plugin.plugin_version}</p>
|
||||
<p>
|
||||
Compatible: {plugin.min_compatible_version} ~ {plugin.max_compatible_version}
|
||||
</p>
|
||||
<button onClick={() => handleDownload(plugin)}>
|
||||
Download and Load Plugin
|
||||
</button>
|
||||
{download_progress[plugin.plugin_id] !== undefined && (
|
||||
<div>
|
||||
Download Progress: {download_progress[plugin.plugin_id].toFixed(0)}%
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<div className={styles.plugin_info_wrapper}>
|
||||
<div className={styles.plugin_info}>
|
||||
<p>Version: {plugin.plugin_version}</p>
|
||||
<p>
|
||||
Compatible: {plugin.min_supported_vrct_version} ~ {plugin.max_supported_vrct_version}
|
||||
</p>
|
||||
</div>
|
||||
<DownloadPlugins
|
||||
plugin_info={plugin}
|
||||
downloadStartFunction={downloadStartFunction}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// GitHub Releases の latest 情報から plugin_info.json を取得する(tauriFetch を使用)
|
||||
async function asyncFetchPluginManifest(manifest_url) {
|
||||
// リリース情報を取得
|
||||
const release_response = await tauriFetch(manifest_url, {
|
||||
method: "GET",
|
||||
responseType: ResponseType.Json,
|
||||
headers: {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"User-Agent": "VRCTPluginApp"
|
||||
}
|
||||
});
|
||||
if (release_response.status !== 200) {
|
||||
throw new Error(`Failed to fetch release info from ${manifest_url}`);
|
||||
}
|
||||
const release_data = release_response.data;
|
||||
// assets 内に plugin_info.json があるかチェック
|
||||
const manifest_asset = release_data.assets.find(asset => asset.name === "plugin_info.json");
|
||||
if (!manifest_asset) {
|
||||
throw new Error("plugin_info.json not found in release assets");
|
||||
}
|
||||
// plugin_info.json の内容を取得
|
||||
const manifest_response = await tauriFetch(manifest_asset.browser_download_url, {
|
||||
method: "GET",
|
||||
responseType: ResponseType.Json,
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"User-Agent": "VRCTPluginApp",
|
||||
"Cache-Control": "no-cache"
|
||||
}
|
||||
});
|
||||
if (manifest_response.status !== 200) {
|
||||
throw new Error(`Failed to fetch plugin_info.json from ${manifest_asset.browser_download_url}`);
|
||||
}
|
||||
const plugin_manifest = manifest_response.data;
|
||||
return {
|
||||
title: plugin_manifest.title,
|
||||
plugin_id: plugin_manifest.plugin_id,
|
||||
plugin_version: plugin_manifest.plugin_version,
|
||||
min_compatible_version: plugin_manifest.min_compatible_version,
|
||||
max_compatible_version: plugin_manifest.max_compatible_version,
|
||||
asset_name: plugin_manifest.asset_name,
|
||||
url: manifest_url
|
||||
};
|
||||
}
|
||||
|
||||
export { PluginDownloadContainer };
|
||||
|
||||
|
||||
|
||||
// // プラグインのマニフェスト(plugin.json から取得した情報の例)
|
||||
// const plugin_manifest = {
|
||||
// compatible_lower_version: "3.0.4",
|
||||
// compatible_upper_version: "3.0.6",
|
||||
// // 他の情報...
|
||||
// };
|
||||
|
||||
// const isPluginCompatible = (main_version, lower_version, upper_version) => {
|
||||
// // lower_version 以上かつ upper_version 以下なら互換性ありと判定
|
||||
// return semver.gte(main_version, lower_version) && semver.lte(main_version, upper_version);
|
||||
// };
|
||||
|
||||
// if (isPluginCompatible(currentSoftwareVersion.data, plugin_manifest.compatible_lower_version, plugin_manifest.compatible_upper_version)) {
|
||||
// console.log("プラグインは互換性があります。");
|
||||
// } else {
|
||||
// console.error("プラグインは現在の VRCT バージョンと互換性がありません。");
|
||||
// }
|
||||
};
|
||||
@@ -2,4 +2,34 @@
|
||||
display: flex;
|
||||
gap: 6.4rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.plugins_list_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.plugin_wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2rem;
|
||||
&:not(:last-child) {
|
||||
border-bottom: 0.1rem solid var(--dark_750_color);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.plugin_id {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.download_button {
|
||||
background-color: var(--dark_750_color);
|
||||
padding: 0.4rem 0.6rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
// PluginHost.jsx
|
||||
import React from "react";
|
||||
import { useStore_LoadedPluginsList } from "@store";
|
||||
import { usePlugins } from "@logics_configs";
|
||||
|
||||
// export const PluginHost = ({ location }) => {
|
||||
export const PluginHost = () => {
|
||||
const { currentLoadedPluginsList } = useStore_LoadedPluginsList();
|
||||
const { currentLoadedPluginsList } = usePlugins();
|
||||
console.log(currentLoadedPluginsList.data);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user