diff --git a/src-python/controller.py b/src-python/controller.py index 02ba5e34..faa659b2 100644 --- a/src-python/controller.py +++ b/src-python/controller.py @@ -447,11 +447,11 @@ class Controller: return {"status":200, "result":config.VERSION} def checkSoftwareUpdated(self) -> dict: - update_flag = model.checkSoftwareUpdated() + software_update_info = model.checkSoftwareUpdated() self.run( 200, - self.run_mapping["update_software_flag"], - update_flag, + self.run_mapping["software_update_info"], + software_update_info, ) @staticmethod diff --git a/src-python/mainloop.py b/src-python/mainloop.py index edd5356a..5c5fb744 100644 --- a/src-python/mainloop.py +++ b/src-python/mainloop.py @@ -38,7 +38,7 @@ run_mapping = { "mic_device_list":"/run/mic_device_list", "speaker_device_list":"/run/speaker_device_list", - "update_software_flag":"/run/update_software_flag", + "software_update_info":"/run/software_update_info", "initialization_progress":"/run/initialization_progress", "initialization_complete":"/run/initialization_complete", diff --git a/src-python/model.py b/src-python/model.py index f393314d..afe0d565 100644 --- a/src-python/model.py +++ b/src-python/model.py @@ -320,6 +320,7 @@ class Model: def checkSoftwareUpdated(): # check update update_flag = False + version = "" try: response = requests_get(config.GITHUB_URL) json_data = response.json() @@ -331,7 +332,10 @@ class Model: update_flag = True except Exception: errorLogging() - return update_flag + return { + "is_update_available": update_flag, + "new_version": version, + } @staticmethod def updateSoftware(): diff --git a/src-ui/app/_app_controllers/PluginsController.jsx b/src-ui/app/_app_controllers/PluginsController.jsx index ec181bb9..2fad4376 100644 --- a/src-ui/app/_app_controllers/PluginsController.jsx +++ b/src-ui/app/_app_controllers/PluginsController.jsx @@ -14,6 +14,7 @@ export const PluginsController = ({ fetchPluginsHasRunRef }) => { currentPluginsData, updatePluginsData, currentSavedPluginsStatus, + updateIsPluginsInitialized, } = usePlugins(); useEffect(() => { @@ -26,40 +27,55 @@ export const PluginsController = ({ fetchPluginsHasRunRef }) => { const info_map = new Map(info_array.map(info => [info.plugin_id, info])); const prev_map = new Map(prev.data.map(item => [item.plugin_id, item])); - // info_array にある各アイテムについて、prev.data に同じ plugin_id があればマージ - const merged = info_array.map(info => { - if (prev_map.has(info.plugin_id)) { - return { - ...info, - latest_plugin_version: info.plugin_version, - ...prev_map.get(info.plugin_id), + const new_data = []; + for (const info of info_array) { + let new_plugin_info = {}; + if (prev_map.has(info.plugin_id)) { // plugin_id 登録済み + const target_downloaded_plugin = prev_map.get(info.plugin_id); + if (target_downloaded_plugin.is_downloaded) { // 既にダウンロード済み + const is_latest_version_available = !(target_downloaded_plugin.plugin_version === info.plugin_version); + + new_plugin_info = { + is_downloaded: true, + is_latest_version_already: (target_downloaded_plugin.downloaded_plugin_info?.plugin_version === info.plugin_version), + is_latest_version_available: is_latest_version_available, + latest_plugin_info: { ...info }, + ...target_downloaded_plugin, + }; + } else { // infoにもあり登録済みだがダウンロードされていない + new_plugin_info = { + is_downloaded: false, + is_latest_version_already: false, + is_latest_version_available: info.is_latest_version_available, + latest_plugin_info: { ...info }, + ...target_downloaded_plugin, + } + } + } else { // 未ダウンロード + new_plugin_info = { + plugin_id: info.plugin_id, + is_downloaded: false, + is_latest_version_already: false, + latest_plugin_info: { ...info }, }; } - return { - ...info, - latest_plugin_version: info.plugin_version, - }; - }); + new_data.push(new_plugin_info); + } // prev.data にのみ存在するアイテム = latest plugin infoには存在しない // を追加し、is_outdated: true を付与 prev.data.forEach(item => { if (!info_map.has(item.plugin_id)) { - merged.push({ ...item, is_outdated: true }); + new_data.push({ ...item, is_outdated: true }); } }); - let new_value = []; - for (const plugin of merged) { - if (plugin.downloaded_plugin_version !== plugin.latest_plugin_version && plugin.is_plugin_supported) { - plugin.is_latest_version_available = true; - } else { - plugin.is_latest_version_available = false; + new_data.forEach(plugin => { + if (!plugin.is_outdated) { + plugin.is_latest_version_available = (plugin.latest_plugin_info.is_plugin_supported); } - new_value.push(plugin); - } - - return new_value; + }); + return new_data; }); } catch (error) { console.error(error); @@ -68,6 +84,7 @@ export const PluginsController = ({ fetchPluginsHasRunRef }) => { if (!fetchPluginsHasRunRef.current) { loadPlugins(); + updateIsPluginsInitialized(true); } return () => fetchPluginsHasRunRef.current = true; }, []); @@ -76,13 +93,13 @@ export const PluginsController = ({ fetchPluginsHasRunRef }) => { useEffect(() => { updatePluginsData(prev => { // currentSavedPluginsStatus.data の各要素を Map 化して plugin_id で参照 - const savedMap = new Map(currentSavedPluginsStatus.data.map(saved => [saved.plugin_id, saved])); + const saved_map = new Map(currentSavedPluginsStatus.data.map(saved => [saved.plugin_id, saved])); const prev_map = new Map(prev.data.map(item => [item.plugin_id, item])); - // prev.data にある各アイテムについて、保存済みの状態情報があればマージ const merged = prev.data.map(item => { - if (savedMap.has(item.plugin_id)) { - return { ...item, is_enabled: savedMap.get(item.plugin_id).is_enabled }; + + if (saved_map.has(item.plugin_id)) { + return { ...item, is_enabled: saved_map.get(item.plugin_id).is_enabled }; } return item; }); @@ -93,7 +110,6 @@ export const PluginsController = ({ fetchPluginsHasRunRef }) => { merged.push({ plugin_id: saved.plugin_id, is_enabled: saved.is_enabled }); } }); - return merged; }); }, [currentSavedPluginsStatus]); diff --git a/src-ui/app/config_page/setting_section/setting_box/_components/plugins_control_component/PluginsControlComponent.jsx b/src-ui/app/config_page/setting_section/setting_box/_components/plugins_control_component/PluginsControlComponent.jsx index a8f70b73..0fd7c33a 100644 --- a/src-ui/app/config_page/setting_section/setting_box/_components/plugins_control_component/PluginsControlComponent.jsx +++ b/src-ui/app/config_page/setting_section/setting_box/_components/plugins_control_component/PluginsControlComponent.jsx @@ -1,31 +1,136 @@ +import React from "react"; import { SwitchBox } from "../index"; import { _DownloadButton } from "../_atoms/_download_button/_DownloadButton"; import styles from "./PluginsControlComponent.module.scss"; -export const PluginsControlComponent = ({ variable_state, plugin_status, toggleFunction, downloadStartFunction }) => { - const { plugin_id, is_pending, is_downloaded, is_enabled, is_latest_version_available, is_plugin_supported, is_outdated } = plugin_status; - +// メインのコントロールコンポーネント。ダウンロード済み / 未ダウンロードで分岐して表示する +export const PluginsControlComponent = ({ + variable_state, + plugin_status, + toggleFunction, + downloadStartFunction, +}) => { + // 共通オプション(各子コンポーネントに引き回す情報) const option = { - id: plugin_id, - is_pending: is_pending, - is_downloaded: is_downloaded, - data: is_enabled, - update_button: is_downloaded && is_latest_version_available, + id: plugin_status.plugin_id, + is_pending: plugin_status.is_pending, + is_downloaded: plugin_status.is_downloaded, + data: plugin_status.is_enabled, + update_button: plugin_status.is_downloaded && plugin_status.is_latest_version_available, state: variable_state, progress: null, }; - const togglePlugin = () => toggleFunction(plugin_id); - const is_turn_on_able = is_downloaded && (is_plugin_supported || is_outdated); + if (plugin_status.is_downloaded) { + return ( + + ); + } else { + return ( + + ); + } +}; - return ( -
- {is_plugin_supported ? ( +// ------------------------- +// ダウンロード済みのプラグイン用コンポーネント +// 状態により以下の分岐を行う +// ・ is_latest_version_already が true なら「最新版を使用中」 +// ・ is_latest_version_already が false かつ is_latest_version_available が true なら「最新版を利用可能」(アップデートボタン+スイッチ) +// ・ それ以外(is_latest_version_already:false && is_latest_version_available: false)なら、desc等の情報とスイッチのみ表示 +const DownloadedPluginControl = ({ + option, + plugin_status, + toggleFunction, + downloadStartFunction, +}) => { + // on/off トグル時の処理 + const togglePlugin = () => { + toggleFunction(plugin_status.plugin_id); + }; + + + // ダウンロード済みの場合、ダウンロードされた情報からタイトルやバージョンを取得 + const title = plugin_status.downloaded_plugin_info?.title || plugin_status.latest_plugin_info.title; + const current_version = + plugin_status.downloaded_plugin_info?.plugin_version || plugin_status.latest_plugin_info.plugin_version; + + // コンポーネントごとに表示内容を分岐 + if (plugin_status.is_latest_version_already) { + // 最新版が既に使用中 + return ( +
+

{title}

+

現在のバージョン: {current_version}

+

最新版を使用中

+ +
+ ); + } else if (plugin_status.is_latest_version_available) { + // 最新版の利用可能なお知らせとアップデートボタン+スイッチ + return ( +
+

{title}

+

現在のバージョン: {current_version}

+

最新版を利用可能

<_DownloadButton option={option} downloadStartFunction={downloadStartFunction} /> - ) : ( -
Downloaded but outdated.
- )} - {is_turn_on_able && } -
- ); -}; \ No newline at end of file + +
+ ); + } else { + // 最新版利用可能ではないがダウンロード済み + // ※「desc」に関しては、情報があればplugin_status.descやlatest_plugin_info.descを利用してください + const desc = + plugin_status.latest_plugin_info.desc || + "追加情報がありません。"; + return ( +
+

{title}

+

現在のバージョン: {current_version}

+

{desc}

+ +
+ ); + } +}; + +// ------------------------- +// 未ダウンロードのプラグイン用コンポーネント +// 状態により以下の分岐を行う +// ・ is_latest_version_available が true なら:info_title, 最新バージョン情報とダウンロードボタン +// ・ is_latest_version_available が false なら:info_title, 最新バージョン情報と「現在利用不可」 +const NotDownloadedPluginControl = ({ option, plugin_status, downloadStartFunction }) => { + const title = plugin_status.latest_plugin_info.title; + const latest_version = plugin_status.latest_plugin_info.plugin_version; + // ※未ダウンロードの場合、current_versionは「未ダウンロード」や「なし」といった扱いとする + const current_version = "未ダウンロード"; + + if (plugin_status.is_latest_version_available) { + return ( +
+

{title}

+

現在のバージョン: {current_version}

+

最新バージョン: {latest_version}

+ <_DownloadButton option={option} downloadStartFunction={downloadStartFunction} /> +
+ ); + } else { + return ( +
+

{title}

+

現在のバージョン: {current_version}

+

最新バージョン: {latest_version}

+

現在利用不可

+
+ ); + } +}; diff --git a/src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.jsx b/src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.jsx index 4c54df95..2a3fd42d 100644 --- a/src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.jsx +++ b/src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.jsx @@ -1,9 +1,15 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import { usePlugins } from "@logics_configs"; import styles from "./Plugins.module.scss"; -import { PluginsControlComponent } from "../_components"; +import { PluginsControlComponent } from "../_components/plugins_control_component/PluginsControlComponent"; export const Plugins = () => { + const { + currentIsPluginsInitialized, + } = usePlugins(); + + if (!currentIsPluginsInitialized.data) return null; + return (
@@ -11,6 +17,7 @@ export const Plugins = () => { ); }; + const PluginDownloadContainer = () => { const { downloadAndExtractPlugin, @@ -18,40 +25,29 @@ const PluginDownloadContainer = () => { updatePluginsData, currentSavedPluginsStatus, setSavedPluginsStatus, + handlePendingPlugin, } = usePlugins(); - + // ダウンロード開始時の状態更新処理 const downloadStartFunction = async (target_plugin_id) => { - updatePluginsData((old_value) => { - const new_value = old_value.data.map(d => { - if (d.plugin_id === target_plugin_id) { - d.is_pending = true; - } - return d; - }); - return new_value; - }); - const target_plugin_info = currentPluginsData.data.find(d => d.plugin_id === target_plugin_id); + handlePendingPlugin(target_plugin_id, true); + + const target_plugin_info = currentPluginsData.data.find( + (d) => d.plugin_id === target_plugin_id + ); downloadAndExtractPlugin(target_plugin_info).then(() => { - updatePluginsData((old_value) => { - const new_value = old_value.data.map(d => { - if (d.plugin_id === target_plugin_id) { - d.is_pending = false; - d.is_downloaded = true; - } - return d; - }); - return new_value; - }); - }) + handlePendingPlugin(target_plugin_id, false); + }); }; - + // プラグインのオンオフ切り替え処理 const toggleFunction = (target_plugin_id) => { - const is_exists = currentSavedPluginsStatus.data.some(d => d.plugin_id === target_plugin_id); + const is_exists = currentSavedPluginsStatus.data.some( + (d) => d.plugin_id === target_plugin_id + ); let new_value = []; if (is_exists) { - new_value = currentSavedPluginsStatus.data.map(d => { + new_value = currentSavedPluginsStatus.data.map((d) => { if (d.plugin_id === target_plugin_id) { d.is_enabled = !d.is_enabled; } @@ -65,40 +61,45 @@ const PluginDownloadContainer = () => { }); } - - // currentPluginsData.data で、is_downloaded が true のものだけ残す - new_value = new_value.filter(item => { - return currentPluginsData.data.some(plugin => plugin.plugin_id === item.plugin_id && plugin.is_downloaded); - }); + // 「currentPluginsData.data」でis_downloadedがtrueのものだけ残す + new_value = new_value.filter((item) => + currentPluginsData.data.some( + (plugin) => plugin.plugin_id === item.plugin_id && plugin.is_downloaded + ) + ); setSavedPluginsStatus(new_value); - } + }; const variable_state = currentSavedPluginsStatus.state; - return (
{currentPluginsData.data.map((plugin) => (
-

{plugin.title}

+

+ {plugin.is_downloaded + ? plugin.downloaded_plugin_info?.title || plugin.latest_plugin_info.title + : plugin.latest_plugin_info.title} +

{plugin.plugin_id}

{plugin.error ? (

Error: {plugin.error}

) : (
-

Downloaded Version: {plugin.downloaded_plugin_version}

-

Latest Version: {plugin.latest_plugin_version}

+ {/* 状態に応じた情報表示(例:バージョン等) */}

- Compatible: {plugin.min_supported_vrct_version} ~ {plugin.max_supported_vrct_version} + {plugin.is_downloaded + ? `現在のバージョン: ${plugin.downloaded_plugin_info?.plugin_version}` + : `最新バージョン: ${plugin.latest_plugin_info.plugin_version}`}

)} @@ -106,4 +107,4 @@ const PluginDownloadContainer = () => { ))}
); -}; \ No newline at end of file +}; diff --git a/src-ui/app/config_page/version_label/VersionLabel.jsx b/src-ui/app/config_page/version_label/VersionLabel.jsx index be095e5c..95abd94d 100644 --- a/src-ui/app/config_page/version_label/VersionLabel.jsx +++ b/src-ui/app/config_page/version_label/VersionLabel.jsx @@ -3,8 +3,7 @@ import { useState } from "react"; import clsx from "clsx"; import styles from "./VersionLabel.module.scss"; -import { useSoftwareVersion } from "@logics_configs"; -import { useComputeMode } from "@logics_common"; +import { useSoftwareVersion, useComputeMode } from "@logics_common"; import CopySvg from "@images/copy.svg?react"; import CheckMarkSvg from "@images/check_mark.svg?react"; diff --git a/src-ui/app/main_page/main_section/MainSection.jsx b/src-ui/app/main_page/main_section/MainSection.jsx index 0eaf272d..e89071a8 100644 --- a/src-ui/app/main_page/main_section/MainSection.jsx +++ b/src-ui/app/main_page/main_section/MainSection.jsx @@ -16,7 +16,7 @@ import { usePlugins } from "@logics_configs"; export const MainSection = () => { const { currentPluginsData } = usePlugins(); - const render_plugins = currentPluginsData.data.filter((plugin) => plugin.is_enabled && plugin.location === "main_section"); + const render_plugins = currentPluginsData.data.filter((plugin) => (plugin.is_downloaded && plugin.is_enabled && plugin.downloaded_plugin_info.location === "main_section")); return (
diff --git a/src-ui/app/main_page/main_section/top_bar/right_side_components/RightSideComponents.jsx b/src-ui/app/main_page/main_section/top_bar/right_side_components/RightSideComponents.jsx index 995266e2..1917d523 100644 --- a/src-ui/app/main_page/main_section/top_bar/right_side_components/RightSideComponents.jsx +++ b/src-ui/app/main_page/main_section/top_bar/right_side_components/RightSideComponents.jsx @@ -4,7 +4,7 @@ import RefreshSvg from "@images/refresh.svg?react"; import HelpSvg from "@images/help.svg?react"; import { useStore_OpenedQuickSetting } from "@store"; -import { useIsSoftwareUpdateAvailable } from "@logics_common"; +import { useSoftwareVersion } from "@logics_common"; import { useIsEnabledOverlaySmallLog, useIsEnabledOverlayLargeLog, useEnableVrcMicMuteSync } from "@logics_configs"; import { OpenQuickSettingButton } from "./_buttons/OpenQuickSettingButton"; @@ -66,9 +66,9 @@ const OpenVrcMicMuteSyncQuickSetting = () => { }; const SoftwareUpdateAvailableButton = () => { - const { currentIsSoftwareUpdateAvailable } = useIsSoftwareUpdateAvailable(); + const { currentLatestSoftwareVersionInfo } = useSoftwareVersion(); const { t } = useTranslation(); - if (currentIsSoftwareUpdateAvailable.data === false) return null; + if (currentLatestSoftwareVersionInfo.data.is_update_available === false) return null; const { updateOpenedQuickSetting } = useStore_OpenedQuickSetting(); diff --git a/src-ui/app/modal_controller/update_modal/UpdateModal.jsx b/src-ui/app/modal_controller/update_modal/UpdateModal.jsx index 94335cd2..4fef1c71 100644 --- a/src-ui/app/modal_controller/update_modal/UpdateModal.jsx +++ b/src-ui/app/modal_controller/update_modal/UpdateModal.jsx @@ -1,8 +1,14 @@ import styles from "./UpdateModal.module.scss"; import { useTranslation } from "react-i18next"; import { useStore_OpenedQuickSetting } from "@store"; -import { useComputeMode, useUpdateSoftware } from "@logics_common"; -import { useIsSoftwareUpdating, useIsSoftwareUpdateAvailable } from "@logics_common"; +import { usePlugins } from "@logics_configs"; +import { + useComputeMode, + useUpdateSoftware, + useIsSoftwareUpdating, + useSoftwareVersion, +} from "@logics_common"; + import clsx from "clsx"; export const UpdateModal = () => { @@ -11,9 +17,13 @@ export const UpdateModal = () => { const { updateSoftware, updateSoftware_CUDA } = useUpdateSoftware(); const { updateIsSoftwareUpdating } = useIsSoftwareUpdating(); const { currentComputeMode } = useComputeMode(); - const { currentIsSoftwareUpdateAvailable } = useIsSoftwareUpdateAvailable(); + const { currentLatestSoftwareVersionInfo } = useSoftwareVersion(); + const { isAnyPluginEnabled } = usePlugins(); - const is_latest_version_already = currentIsSoftwareUpdateAvailable.data === false; + console.log(isAnyPluginEnabled()); + + + const is_latest_version_already = currentLatestSoftwareVersionInfo.data.is_update_available === false; const is_cpu_version = currentComputeMode.data === "cpu"; const onClickUpdateSoftware = () => { @@ -37,6 +47,7 @@ export const UpdateModal = () => { return (
+ {isAnyPluginEnabled() && }
@@ -85,4 +96,20 @@ const CurrentVersionLabel = (props) => { return

{t("update_modal.is_latest_version_already")}

; } return

{t("update_modal.is_current_compute_device")}

; +}; + +const PluginUpdateNotification = () => { + const { enabledPluginsList } = usePlugins(); + + const incompatible_plugins_list = enabledPluginsList(); + + return ( +
+ {incompatible_plugins_list.map(plugin => { + console.log(plugin); + + return

{plugin.title}

+ })} +
+ ); }; \ No newline at end of file diff --git a/src-ui/logics/common/index.js b/src-ui/logics/common/index.js index 5feee60e..e95bae8f 100644 --- a/src-ui/logics/common/index.js +++ b/src-ui/logics/common/index.js @@ -1,9 +1,9 @@ +export { useSoftwareVersion } from "./useSoftwareVersion"; export { useComputeMode } from "./useComputeMode"; export { useInitProgress } from "./useInitProgress"; export { useIsBackendReady } from "./useIsBackendReady"; export { useWindow } from "./useWindow"; export { useIsOpenedConfigPage } from "./useIsOpenedConfigPage"; -export { useIsSoftwareUpdateAvailable } from "./useIsSoftwareUpdateAvailable"; export { useIsSoftwareUpdating } from "./useIsSoftwareUpdating"; export { useNotificationStatus } from "./useNotificationStatus"; export { useOpenFolder } from "./useOpenFolder"; diff --git a/src-ui/logics/common/useIsSoftwareUpdateAvailable.js b/src-ui/logics/common/useIsSoftwareUpdateAvailable.js deleted file mode 100644 index 569a3949..00000000 --- a/src-ui/logics/common/useIsSoftwareUpdateAvailable.js +++ /dev/null @@ -1,10 +0,0 @@ -import { useStore_IsSoftwareUpdateAvailable } from "@store"; - -export const useIsSoftwareUpdateAvailable = () => { - const { currentIsSoftwareUpdateAvailable, updateIsSoftwareUpdateAvailable } = useStore_IsSoftwareUpdateAvailable(); - - return { - currentIsSoftwareUpdateAvailable, - updateIsSoftwareUpdateAvailable, - }; -}; \ No newline at end of file diff --git a/src-ui/logics/common/useSoftwareVersion.js b/src-ui/logics/common/useSoftwareVersion.js new file mode 100644 index 00000000..18f24a7f --- /dev/null +++ b/src-ui/logics/common/useSoftwareVersion.js @@ -0,0 +1,40 @@ +import semver from "semver"; + +import { useStore_SoftwareVersion, useStore_LatestSoftwareVersionInfo } from "@store"; +import { useStdoutToPython } from "@logics/useStdoutToPython"; + +export const useSoftwareVersion = () => { + const { asyncStdoutToPython } = useStdoutToPython(); + const { currentLatestSoftwareVersionInfo, updateLatestSoftwareVersionInfo } = useStore_LatestSoftwareVersionInfo(); + const { currentSoftwareVersion, updateSoftwareVersion, pendingSoftwareVersion } = useStore_SoftwareVersion(); + + const getSoftwareVersion = () => { + pendingSoftwareVersion(); + asyncStdoutToPython("/get/data/version"); + }; + + 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); + }; + + const checkVrctVerCompatibility = (min_version, max_version) => { + const current_vrct_version = currentSoftwareVersion.data; + const latest_vrct_version = currentLatestSoftwareVersionInfo.data.new_version; + + const is_plugin_supported = isPluginCompatible(current_vrct_version, min_version, max_version); + const is_plugin_supported_latest_vrct = isPluginCompatible(latest_vrct_version, min_version, max_version); + return { is_plugin_supported, is_plugin_supported_latest_vrct }; + }; + + return { + currentSoftwareVersion, + getSoftwareVersion, + updateSoftwareVersion, + + currentLatestSoftwareVersionInfo, + updateLatestSoftwareVersionInfo, + + checkVrctVerCompatibility, + }; +}; \ No newline at end of file diff --git a/src-ui/logics/configs/index.js b/src-ui/logics/configs/index.js index 85d7c553..912e2a7d 100644 --- a/src-ui/logics/configs/index.js +++ b/src-ui/logics/configs/index.js @@ -62,6 +62,4 @@ export { useSupporters } from "./supporters/useSupporters"; export { usePlugins } from "./plugins/usePlugins"; - -export { useSettingBoxScrollPosition } from "./useSettingBoxScrollPosition"; -export { useSoftwareVersion } from "./useSoftwareVersion"; \ No newline at end of file +export { useSettingBoxScrollPosition } from "./useSettingBoxScrollPosition"; \ No newline at end of file diff --git a/src-ui/logics/configs/plugins/usePlugins.js b/src-ui/logics/configs/plugins/usePlugins.js index 22137654..d4910f46 100644 --- a/src-ui/logics/configs/plugins/usePlugins.js +++ b/src-ui/logics/configs/plugins/usePlugins.js @@ -1,10 +1,9 @@ -import semver from "semver"; - import { invoke } from "@tauri-apps/api/tauri"; import { createAtomWithHook, useStore_SavedPluginsStatus, useStore_PluginsData, + useStore_IsPluginsInitialized, } from "@store"; import { useStdoutToPython } from "@logics/useStdoutToPython"; @@ -21,8 +20,7 @@ dev_plugins.forEach(async ({entry_path}) => { import JSZip from "jszip"; -import { useFetch } from "@logics_common"; -import { useSoftwareVersion } from "@logics_configs"; +import { useFetch, useSoftwareVersion } from "@logics_common"; import * as logics_configs from "@logics_configs"; import * as logics_main from "@logics_main"; @@ -36,30 +34,66 @@ export const usePlugins = () => { const { asyncStdoutToPython } = useStdoutToPython(); const { currentSavedPluginsStatus, updateSavedPluginsStatus, pendingSavedPluginsStatus } = useStore_SavedPluginsStatus(); const { currentPluginsData, updatePluginsData, pendingPluginsData } = useStore_PluginsData(); - const { currentSoftwareVersion } = useSoftwareVersion(); + const { currentIsPluginsInitialized, updateIsPluginsInitialized, pendingIsPluginsInitialized } = useStore_IsPluginsInitialized(); + const { checkVrctVerCompatibility } = useSoftwareVersion(); const { asyncTauriFetchGithub } = useFetch(); - const generatePluginContext= (plugin_info) => { + const generatePluginContext= (downloaded_plugin_info) => { const plugin_context = { registerComponent: (component) => { - if (!plugin_info.plugin_id || !plugin_info.location || !component) { - return console.error("An invalid plugin was detected.", plugin_info.plugin_id, plugin_info.location, component); + if (!downloaded_plugin_info.plugin_id || !downloaded_plugin_info.location || !component) { + return console.error("An invalid plugin was detected.", downloaded_plugin_info.plugin_id, downloaded_plugin_info.location, component); } updatePluginsData(prev => { - const new_value = prev.data.map(old_value => - old_value.plugin_id === plugin_info.plugin_id - ? { - ...old_value, - ...plugin_info, - downloaded_plugin_version: plugin_info.plugin_version, + const prev_map = new Map(prev.data.map(item => [item.plugin_id, item])); + const new_data = []; + let new_value = {}; + + if (!prev_map.has(downloaded_plugin_info.plugin_id)) { // 未ダウンロード 新規登録 + new_value = { + plugin_id: downloaded_plugin_info.plugin_id, + component: component, + is_downloaded: true, + is_latest_version_available: false, + downloaded_plugin_info: { + ...downloaded_plugin_info, + component: component, + is_plugin_supported: is_plugin_supported, + is_plugin_supported_latest_vrct: is_plugin_supported_latest_vrct, + }, + }; + return [...prev.data, new_value]; + } + + for (const old_plugin_data of prev.data) { + const { is_plugin_supported, is_plugin_supported_latest_vrct } = checkVrctVerCompatibility(downloaded_plugin_info.min_supported_vrct_version, downloaded_plugin_info.max_supported_vrct_version); + + if (prev_map.has(downloaded_plugin_info.plugin_id) && old_plugin_data.plugin_id === downloaded_plugin_info.plugin_id) { // ダウンロード済み + + + const target_prev_plugin = prev_map.get(downloaded_plugin_info.plugin_id); + const is_latest_version_available = (target_prev_plugin.is_downloaded) && !(downloaded_plugin_info.plugin_version === target_prev_plugin.latest_plugin_info.plugin_version); + + new_value = { + ...target_prev_plugin, + plugin_id: downloaded_plugin_info.plugin_id, component: component, is_downloaded: true, - is_latest_version_available: false, - } : old_value - ); - - return new_value; + is_latest_version_available: is_latest_version_available, + downloaded_plugin_info: { + ...downloaded_plugin_info, + component: component, + is_plugin_supported: is_plugin_supported, + is_plugin_supported_latest_vrct: is_plugin_supported_latest_vrct, + }, + }; + } else { + new_value = old_plugin_data; + } + new_data.push(new_value); + } + return new_data; }); }, createAtomWithHook: (...args) => createAtomWithHook(...args), @@ -70,11 +104,11 @@ export const usePlugins = () => { const asyncLoadPlugin = async (plugin_folder_relative_path) => { const init_path = "plugins/" + plugin_folder_relative_path + "/index.esm.js"; - const plugin_info_path = "plugins/" + plugin_folder_relative_path + "/plugin_info.json"; + const downloaded_plugin_info_path = "plugins/" + plugin_folder_relative_path + "/plugin_info.json"; const plugin_css_path = "plugins/" + plugin_folder_relative_path + "/main.css"; try { - const plugin_info_json = await readTextFile(plugin_info_path, { dir: BaseDirectory.Resource, recursive: true }); - const plugin_info = JSON.parse(plugin_info_json); + const downloaded_plugin_info_json = await readTextFile(downloaded_plugin_info_path, { dir: BaseDirectory.Resource, recursive: true }); + const downloaded_plugin_info = JSON.parse(downloaded_plugin_info_json); const plugin_code = await readTextFile(init_path, { dir: BaseDirectory.Resource, recursive: true }); const cleaned_code = removeImportStatements(plugin_code); @@ -91,7 +125,7 @@ export const usePlugins = () => { URL.revokeObjectURL(blob_url); if (plugin_module && plugin_module.init) { - plugin_module.init(generatePluginContext(plugin_info)); + plugin_module.init(generatePluginContext(downloaded_plugin_info)); } await loadPluginCSS(plugin_css_path); @@ -102,16 +136,16 @@ export const usePlugins = () => { const asyncLoadAllPlugins = async () => { if (!import.meta.env.DEV) { - imported_dev_plugins.forEach(({ index, plugin_info }) => { - if (!index || !plugin_info) { - console.error("Invalid development plugin detected", index, plugin_info); + imported_dev_plugins.forEach(({ index, downloaded_plugin_info }) => { + if (!index || !downloaded_plugin_info) { + console.error("Invalid development plugin detected", index, downloaded_plugin_info); return; } - const plugin_context = generatePluginContext(plugin_info); + const plugin_context = generatePluginContext(downloaded_plugin_info); if (index.init) { index.init(plugin_context); } else { - console.error("Plugin missing init function", plugin_info); + console.error("Plugin missing init function", downloaded_plugin_info); } }); } else { @@ -128,8 +162,9 @@ export const usePlugins = () => { }; const downloadAndExtractPlugin = async (plugin) => { + const latest_plugin_info = plugin.latest_plugin_info; try { - const plugin_zip_url = await fetchLatestPluginZipUrl(plugin); + const plugin_zip_url = await fetchLatestPluginZipUrl(latest_plugin_info); // Rust コマンド経由で ZIP をダウンロード const base64_zip = await invoke("download_zip_asset", { url: plugin_zip_url }); // base64_zip をデコードして Uint8Array に変換 @@ -144,7 +179,7 @@ export const usePlugins = () => { const zip = await JSZip.loadAsync(bytes); // 展開先ディレクトリのパス(例:"plugins/" とする) - const target_plugin_path = "plugins/" + plugin.plugin_id; + const target_plugin_path = "plugins/" + latest_plugin_info.plugin_id; // 既に存在する場合は削除してから新規作成 if (await exists(target_plugin_path, { dir: BaseDirectory.Resource, recursive: true })) { await removeDir(target_plugin_path, { dir: BaseDirectory.Resource, recursive: true }); @@ -220,13 +255,13 @@ export const usePlugins = () => { plugins_data.map(async (plugin_data) => { try { const plugin_info = await asyncFetchPluginInfo(plugin_data.url); - return { ...plugin_info }; + return plugin_info; } catch (error) { console.error("Error fetching plugin info for URL:", plugin_data.url, error); - // エラー発生時は、plugin_data.title とエラーメッセージを返す return { title: plugin_data.title, plugin_id: plugin_data.plugin_id || plugin_data.title, + is_error: true, error: error.message, url: plugin_data.url }; @@ -254,14 +289,8 @@ export const usePlugins = () => { } const plugin_info = plugin_info_json_response.data; - const isPluginCompatible = (main_version, lower_version, upper_version) => { - console.log(main_version, lower_version, upper_version); - // lower_version 以上かつ upper_version 以下なら互換性ありと判定 - return semver.gte(main_version, lower_version) && semver.lte(main_version, upper_version); - }; - - const is_plugin_supported = isPluginCompatible(currentSoftwareVersion.data, plugin_info.min_supported_vrct_version, plugin_info.max_supported_vrct_version); + const { is_plugin_supported, is_plugin_supported_latest_vrct } = checkVrctVerCompatibility(plugin_info.min_supported_vrct_version, plugin_info.max_supported_vrct_version); return { title: plugin_info.title, @@ -270,21 +299,47 @@ export const usePlugins = () => { min_supported_vrct_version: plugin_info.min_supported_vrct_version, max_supported_vrct_version: plugin_info.max_supported_vrct_version, is_plugin_supported: is_plugin_supported, + is_plugin_supported_latest_vrct: is_plugin_supported_latest_vrct, asset_name: plugin_info.asset_name, url: plugin_info_asset_url }; } + const handlePendingPlugin = (target_plugin_id, is_pending) => { + updatePluginsData((old_value) => { + const new_value = old_value.data.map((d) => { + if (d.plugin_id === target_plugin_id) { + d.is_pending = is_pending; + } + return d; + }); + return new_value; + }); + }; + + + const setSavedPluginsStatus = (plugins_status) => { pendingSavedPluginsStatus(); asyncStdoutToPython("/set/data/plugins_status", plugins_status); }; + const isAnyPluginEnabled = () => { + return currentPluginsData.data.some(plugin => plugin.is_enabled); + }; + + const enabledPluginsList = () => { + return currentPluginsData.data.filter(plugin => plugin.is_enabled); + } + return { asyncFetchPluginsInfo, + isAnyPluginEnabled, + enabledPluginsList, + asyncLoadAllPlugins, downloadAndExtractPlugin, @@ -294,7 +349,12 @@ export const usePlugins = () => { currentPluginsData, updatePluginsData, + currentIsPluginsInitialized, + updateIsPluginsInitialized, + setSavedPluginsStatus, + + handlePendingPlugin, }; }; diff --git a/src-ui/logics/configs/useSoftwareVersion.js b/src-ui/logics/configs/useSoftwareVersion.js deleted file mode 100644 index c81b81c2..00000000 --- a/src-ui/logics/configs/useSoftwareVersion.js +++ /dev/null @@ -1,18 +0,0 @@ -import { useStore_SoftwareVersion } from "@store"; -import { useStdoutToPython } from "@logics/useStdoutToPython"; - -export const useSoftwareVersion = () => { - const { asyncStdoutToPython } = useStdoutToPython(); - const { currentSoftwareVersion, updateSoftwareVersion, pendingSoftwareVersion } = useStore_SoftwareVersion(); - - const getSoftwareVersion = () => { - pendingSoftwareVersion(); - asyncStdoutToPython("/get/data/version"); - }; - - return { - currentSoftwareVersion, - getSoftwareVersion, - updateSoftwareVersion, - }; -}; \ No newline at end of file diff --git a/src-ui/logics/useReceiveRoutes.js b/src-ui/logics/useReceiveRoutes.js index 4575c3d8..b73fe62e 100644 --- a/src-ui/logics/useReceiveRoutes.js +++ b/src-ui/logics/useReceiveRoutes.js @@ -8,13 +8,13 @@ import { useNotificationStatus, useHandleNetworkConnection, + useSoftwareVersion, useComputeMode, useInitProgress, useIsBackendReady, useWindow, useMessage, useVolume, - useIsSoftwareUpdateAvailable, } from "@logics_common"; import { @@ -26,7 +26,6 @@ import { } from "@logics_main"; import { - useSoftwareVersion, useEnableAutoMicSelect, useEnableAutoSpeakerSelect, useMicHostList, @@ -105,7 +104,7 @@ export const useReceiveRoutes = () => { addSentMessageLog, addReceivedMessageLog, } = useMessage(); - const { updateIsSoftwareUpdateAvailable } = useIsSoftwareUpdateAvailable(); + const { updateLatestSoftwareVersionInfo } = useSoftwareVersion(); const { updateSoftwareVersion } = useSoftwareVersion(); const { updateEnableAutoMicSelect } = useEnableAutoMicSelect(); const { updateEnableAutoSpeakerSelect } = useEnableAutoSpeakerSelect(); @@ -207,7 +206,7 @@ export const useReceiveRoutes = () => { "/set/data/main_window_geometry": () => {}, "/run/open_filepath_logs": () => console.log("Opened Directory, Message Logs"), "/run/open_filepath_config_file": () => console.log("Opened Directory, Config File"), - "/run/update_software_flag": updateIsSoftwareUpdateAvailable, + "/run/software_update_info": updateLatestSoftwareVersionInfo, "/run/connected_network": handleNetworkConnection, // Main Page diff --git a/src-ui/store.js b/src-ui/store.js index 7e88792b..5adbb4c7 100644 --- a/src-ui/store.js +++ b/src-ui/store.js @@ -114,7 +114,10 @@ export const { atomInstance: Atom_MainFunctionsStateMemory, useHook: useStore_Ma transcription_receive: false, }, "MainFunctionsStateMemory"); export const { atomInstance: Atom_OpenedQuickSetting, useHook: useStore_OpenedQuickSetting } = createAtomWithHook("", "OpenedQuickSetting"); -export const { atomInstance: Atom_IsSoftwareUpdateAvailable, useHook: useStore_IsSoftwareUpdateAvailable } = createAtomWithHook(false, "IsSoftwareUpdateAvailable"); +export const { atomInstance: Atom_LatestSoftwareVersionInfo, useHook: useStore_LatestSoftwareVersionInfo } = createAtomWithHook({ + is_update_available: false, + new_version: "", +}, "LatestSoftwareVersionInfo"); export const { atomInstance: Atom_InitProgress, useHook: useStore_InitProgress } = createAtomWithHook(0, "InitProgress"); export const { atomInstance: Atom_IsBreakPoint, useHook: useStore_IsBreakPoint } = createAtomWithHook(false, "IsBreakPoint"); export const { atomInstance: Atom_IsSoftwareUpdating, useHook: useStore_IsSoftwareUpdating } = createAtomWithHook(false, "IsSoftwareUpdating"); @@ -275,7 +278,7 @@ export const { atomInstance: Atom_Hotkeys, useHook: useStore_Hotkeys } = createA }, "Hotkeys"); // Plugins -// export const { atomInstance: Atom_LoadedPluginsList, useHook: useStore_LoadedPluginsList } = createAtomWithHook([], "LoadedPluginsList"); +export const { atomInstance: Atom_IsPluginsInitialized, useHook: useStore_IsPluginsInitialized } = createAtomWithHook(false, "IsPluginsInitialized"); export const { atomInstance: Atom_SavedPluginsStatus, useHook: useStore_SavedPluginsStatus } = createAtomWithHook([], "SavedPluginsStatus"); export const { atomInstance: Atom_PluginsData, useHook: useStore_PluginsData } = createAtomWithHook([], "PluginsData");