From 22ada89fa68b8ea2cb09951f05b4bc7eaeee8f9c Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 5 Mar 2025 23:22:22 +0900 Subject: [PATCH] [TMP] Plugins system. --- package-lock.json | 91 ++++ package.json | 2 + src-tauri/Cargo.lock | 507 +++++++++++++++++- src-tauri/Cargo.toml | 4 +- src-tauri/plugins/plugin_examples/index.jsx | 22 + .../main_container/MainContainer.jsx | 19 + src-tauri/src/main.rs | 21 +- src-tauri/tauri.conf.json | 15 +- src-ui/app/App.jsx | 2 + .../_app_controllers/PluginsController.jsx | 17 + src-ui/app/_app_controllers/index.js | 3 +- .../setting_box/SettingBox.jsx | 3 + .../setting_section/setting_box/index.js | 1 + .../setting_box/plugins/Plugins.jsx | 60 +++ .../setting_box/plugins/Plugins.module.scss | 5 + .../sidebar_section/SidebarSection.jsx | 1 + .../main_page/main_section/MainSection.jsx | 6 +- .../app/main_page/main_section/PluginHost.jsx | 20 + src-ui/logics/configs/index.js | 2 + src-ui/logics/configs/plugins/usePlugins.js | 190 +++++++ src-ui/store.js | 6 +- 21 files changed, 990 insertions(+), 7 deletions(-) create mode 100644 src-tauri/plugins/plugin_examples/index.jsx create mode 100644 src-tauri/plugins/plugin_examples/main_container/MainContainer.jsx create mode 100644 src-ui/app/_app_controllers/PluginsController.jsx create mode 100644 src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.jsx create mode 100644 src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.module.scss create mode 100644 src-ui/app/main_page/main_section/PluginHost.jsx create mode 100644 src-ui/logics/configs/plugins/usePlugins.js diff --git a/package-lock.json b/package-lock.json index ebda8372..8e87cde6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "tauri-app", "version": "0.0.0", "dependencies": { + "@babel/standalone": "^7.26.9", "@emotion/react": "11.14.0", "@emotion/styled": "11.14.0", "@mui/material": "6.2.0", @@ -20,6 +21,7 @@ "jotai": "2.10.3", "js-base64": "3.7.7", "js-yaml": "4.1.0", + "jszip": "^3.10.1", "react": "18.2.0", "react-dom": "18.2.0", "react-i18next": "15.2.0", @@ -271,6 +273,14 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/standalone": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.26.9.tgz", + "integrity": "sha512-UTeQKy0kzJwWRe55kT1uK4G9H6D0lS6G4207hCU/bDaOhA5t2aC0qHN6GmID0Axv3OFLNXm27NdqcWp+BXcGtA==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", @@ -2330,6 +2340,11 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -3424,6 +3439,11 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, "node_modules/immutable": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", @@ -3933,6 +3953,17 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3953,6 +3984,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4364,6 +4403,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4510,6 +4554,11 @@ "node": ">= 0.8.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -4659,6 +4708,25 @@ "node": ">=4" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/readdirp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", @@ -4836,6 +4904,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/safe-regex-test": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", @@ -4919,6 +4992,11 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5022,6 +5100,14 @@ "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", "dev": true }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", @@ -5338,6 +5424,11 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index 48ee7f7e..eafff304 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "release-all": "npm run release && npm run release-cuda" }, "dependencies": { + "@babel/standalone": "^7.26.9", "@emotion/react": "11.14.0", "@emotion/styled": "11.14.0", "@mui/material": "6.2.0", @@ -35,6 +36,7 @@ "jotai": "2.10.3", "js-base64": "3.7.7", "js-yaml": "4.1.0", + "jszip": "^3.10.1", "react": "18.2.0", "react-dom": "18.2.0", "react-i18next": "15.2.0", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 347908d6..861e96ec 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -6,7 +6,9 @@ version = 3 name = "VRCT" version = "0.0.0" dependencies = [ + "base64 0.22.1", "font-kit", + "reqwest", "serde", "serde_json", "tauri", @@ -218,6 +220,9 @@ name = "bytes" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +dependencies = [ + "serde", +] [[package]] name = "cairo-rs" @@ -729,7 +734,7 @@ dependencies = [ "rustc_version", "toml 0.8.19", "vswhom", - "winreg", + "winreg 0.52.0", ] [[package]] @@ -971,6 +976,12 @@ dependencies = [ "syn 2.0.94", ] +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + [[package]] name = "futures-task" version = "0.3.31" @@ -984,8 +995,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1297,6 +1311,25 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.7.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1361,12 +1394,86 @@ dependencies = [ "itoa 1.0.14", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + [[package]] name = "http-range" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" +[[package]] +name = "httparse" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.14", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -1613,6 +1720,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "itoa" version = "0.4.8" @@ -1849,6 +1962,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.8.2" @@ -1859,6 +1978,34 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.6.0" @@ -1998,6 +2145,50 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.94", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -2516,6 +2707,67 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "winreg 0.50.0", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2544,6 +2796,37 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.19" @@ -2565,6 +2848,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2577,6 +2869,39 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.22.0" @@ -2659,6 +2984,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.14", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.12.0" @@ -2784,6 +3121,16 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "soup2" version = "0.2.1" @@ -2812,6 +3159,12 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2881,6 +3234,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.13.1" @@ -2892,6 +3251,27 @@ dependencies = [ "syn 2.0.94", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "5.0.0" @@ -3000,6 +3380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf327e247698d3f39af8aa99401c9708384290d1f5c544bf5d251d44c2fea22" dependencies = [ "anyhow", + "bytes", "cocoa 0.24.1", "dirs-next", "dunce", @@ -3014,6 +3395,7 @@ dependencies = [ "heck 0.5.0", "http", "ignore", + "indexmap 1.9.3", "log", "objc", "once_cell", @@ -3024,6 +3406,7 @@ dependencies = [ "rand 0.8.5", "raw-window-handle", "regex", + "reqwest", "semver", "serde", "serde_json", @@ -3296,7 +3679,44 @@ checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", + "libc", + "mio", "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] @@ -3367,6 +3787,12 @@ dependencies = [ "winnow 0.6.22", ] +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.41" @@ -3428,6 +3854,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.17.0" @@ -3446,6 +3878,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -3491,6 +3929,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.0.11" @@ -3539,6 +3983,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3576,6 +4029,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.99" @@ -3605,6 +4071,29 @@ version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webkit2gtk" version = "0.18.2" @@ -3652,6 +4141,12 @@ dependencies = [ "system-deps 6.2.2", ] +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "webview2-com" version = "0.19.1" @@ -4058,6 +4553,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winreg" version = "0.52.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 06160d79..21966762 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -11,11 +11,13 @@ edition = "2021" tauri-build = { version = "1", features = [] } [dependencies] -tauri = { version = "1", features = [ "window-hide", "window-set-focus", "global-shortcut-all", "window-set-size", "window-set-position", "window-unmaximize", "window-close", "window-maximize", "window-minimize", "window-unminimize", "window-start-dragging", "window-set-decorations", "window-set-always-on-top", "shell-sidecar", "shell-open", "devtools"] } +tauri = { version = "1", features = [ "fs-read-file", "fs-create-dir", "fs-write-file", "fs-exists", "http-request", "fs-read-dir", "window-hide", "window-set-focus", "global-shortcut-all", "window-set-size", "window-set-position", "window-unmaximize", "window-close", "window-maximize", "window-minimize", "window-unminimize", "window-start-dragging", "window-set-decorations", "window-set-always-on-top", "shell-sidecar", "shell-open", "devtools"] } serde = { version = "1", features = ["derive"] } serde_json = "1" font-kit = "0.14.2" window-shadows = { git = "https://github.com/tauri-apps/window-shadows.git" } +reqwest = { version = "0.11", features = ["json", "rustls-tls"] } +base64 = "0.22.1" [features] diff --git a/src-tauri/plugins/plugin_examples/index.jsx b/src-tauri/plugins/plugin_examples/index.jsx new file mode 100644 index 00000000..a2b966b7 --- /dev/null +++ b/src-tauri/plugins/plugin_examples/index.jsx @@ -0,0 +1,22 @@ +import React from "react"; + +import { MainContainer } from "./main_container/MainContainer"; + +export const init = (plugin_context) => { + const { useHook: useStore_CountPluginState } = plugin_context.createAtomWithHook({ count: 6 }, "CountPluginState"); + + const EntryComponents = () => { + + return ( + + ); + + }; + + // UI の"main_section"拡張ポイントにコンポーネントを登録 + plugin_context.registerComponent({ + plugin_id: "dev_vrct_plugin_example_1", + location: "main_section", + component: EntryComponents, + }); +}; \ No newline at end of file diff --git a/src-tauri/plugins/plugin_examples/main_container/MainContainer.jsx b/src-tauri/plugins/plugin_examples/main_container/MainContainer.jsx new file mode 100644 index 00000000..91dcdf90 --- /dev/null +++ b/src-tauri/plugins/plugin_examples/main_container/MainContainer.jsx @@ -0,0 +1,19 @@ + + +export const MainContainer = ({useStore_CountPluginState}) => { + const { updateCountPluginState, currentCountPluginState } = useStore_CountPluginState(); + const incrementCount = () => { + updateCountPluginState((prev_value) => { + return { count: prev_value.data.count + 1 } + }); + }; + + return ( +
+

Dev Plugin Count: {currentCountPluginState?.data?.count}

+ +
+ ); +}; \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 163cc78a..36e07028 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -22,7 +22,7 @@ fn main() { event.window().set_size(tauri::Size::Physical(*new_inner_size)).unwrap(); } }) - .invoke_handler(tauri::generate_handler![get_font_list]) + .invoke_handler(tauri::generate_handler![get_font_list, download_zip_asset]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } @@ -45,4 +45,23 @@ async fn get_font_list() -> Vec { } font_families.into_iter().collect() +} + +#[tauri::command] +async fn download_zip_asset(url: String) -> Result { + use reqwest; + // reqwest のクライアントを作成 + let client = reqwest::Client::new(); + // GET リクエストを送信(リダイレクトも自動追従します) + let resp = client.get(&url) + .header("Accept", "application/octet-stream") + .send() + .await.map_err(|e| format!("Request error: {}", e))?; + if !resp.status().is_success() { + return Err(format!("HTTP error: {}", resp.status())); + } + // レスポンスのバイナリデータを取得 + let bytes = resp.bytes().await.map_err(|e| format!("Reading bytes error: {}", e))?; + // バイナリデータを base64 エンコードして返す + Ok(base64::encode(&bytes)) } \ No newline at end of file diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9b869f1a..6e6fca0c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -30,6 +30,18 @@ "globalShortcut": { "all": true }, + "fs": { + "readDir": true, + "readFile": true, + "exists": true, + "writeFile": true, + "createDir": true, + "scope": ["$RESOURCE/**", "**/src-tauri/target/debug/plugins/**"] + }, + "http": { + "request": true, + "scope": ["https://api.github.com/repos/**", "https://github.com/**"] + }, "shell": { "all": false, "open": true, @@ -74,7 +86,8 @@ "bin/VRCT-sidecar" ], "resources":{ - "bin/_internal": "_internal" + "bin/_internal": "_internal", + "plugins": "plugins" }, "windows": { "nsis": { diff --git a/src-ui/app/App.jsx b/src-ui/app/App.jsx index 03fa2f8c..fe4f5745 100644 --- a/src-ui/app/App.jsx +++ b/src-ui/app/App.jsx @@ -9,6 +9,7 @@ import { UiSizeController, FontFamilyController, TransparencyController, + PluginsController, } from "./_app_controllers/index.js"; import { WindowTitleBar } from "./window_title_bar/WindowTitleBar"; @@ -40,6 +41,7 @@ export const App = () => { + {(currentIsBackendReady.data === false || currentIsVrctAvailable.data === false) ? diff --git a/src-ui/app/_app_controllers/PluginsController.jsx b/src-ui/app/_app_controllers/PluginsController.jsx new file mode 100644 index 00000000..6ccc231b --- /dev/null +++ b/src-ui/app/_app_controllers/PluginsController.jsx @@ -0,0 +1,17 @@ +import { useEffect } from "react"; +import { usePlugins } from "@logics_configs"; + +// ホスト側でReactやjotaiをグローバル変数として提供 +import ReactModule from "react"; +if (typeof window !== "undefined") { + window.React = ReactModule; +} + +export const PluginsController = () => { + const { loadAllPlugins } = usePlugins(); + useEffect(() => { + loadAllPlugins(); + }, []); + + return null; +}; \ No newline at end of file diff --git a/src-ui/app/_app_controllers/index.js b/src-ui/app/_app_controllers/index.js index 547c78f4..d71c8b6e 100644 --- a/src-ui/app/_app_controllers/index.js +++ b/src-ui/app/_app_controllers/index.js @@ -5,4 +5,5 @@ export { UiLanguageController } from "./UiLanguageController"; export { ConfigPageCloseTriggerController } from "./ConfigPageCloseTriggerController"; export { UiSizeController } from "./UiSizeController"; export { FontFamilyController } from "./FontFamilyController"; -export { TransparencyController } from "./TransparencyController"; \ No newline at end of file +export { TransparencyController } from "./TransparencyController"; +export { PluginsController } from "./PluginsController"; \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/SettingBox.jsx b/src-ui/app/config_page/setting_section/setting_box/SettingBox.jsx index e61067a7..b149da0d 100644 --- a/src-ui/app/config_page/setting_section/setting_box/SettingBox.jsx +++ b/src-ui/app/config_page/setting_section/setting_box/SettingBox.jsx @@ -9,6 +9,7 @@ import { AdvancedSettings, Vr, Hotkeys, + Plugins, Supporters, AboutVrct, } from "@setting_box"; @@ -32,6 +33,8 @@ export const SettingBox = () => { return ; case "advanced_settings": return ; + case "plugins": + return ; case "supporters": return ; case "about_vrct": diff --git a/src-ui/app/config_page/setting_section/setting_box/index.js b/src-ui/app/config_page/setting_section/setting_box/index.js index dc5798c3..abb876ae 100644 --- a/src-ui/app/config_page/setting_section/setting_box/index.js +++ b/src-ui/app/config_page/setting_section/setting_box/index.js @@ -6,5 +6,6 @@ export { Others, VrcMicMuteSyncContainer } from "./others/Others"; export { AdvancedSettings } from "./advanced_settings/AdvancedSettings"; export { Vr } from "./vr/Vr"; export { Hotkeys } from "./hotkeys/Hotkeys"; +export { Plugins } from "./plugins/Plugins"; export { AboutVrct } from "./about_vrct/AboutVrct"; export { Supporters } from "./supporters/Supporters"; \ No newline at end of file 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 new file mode 100644 index 00000000..1954b48d --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.jsx @@ -0,0 +1,60 @@ +import React, { useState, useEffect } from "react"; +import { usePlugins } from "@logics_configs"; +import styles from "./Plugins.module.scss"; + +export const Plugins = () => { + return ( +
+ +
+ ); +}; + +const PluginDownloadContainer = () => { + const [plugin_list, set_plugin_list] = useState([]); + const [download_progress, set_download_progress] = useState({}); + + const { downloadAndExtractPlugin } = usePlugins(); + + useEffect(() => { + // GitHub上のJSONファイルからプラグインリストを取得 + const fetchPluginList = async () => { + try { + const response = await fetch( + "https://raw.githubusercontent.com/ShiinaSakamoto/vrct_plugins_list/main/vrct_plugins_list.json" + ); + if (!response.ok) { + throw new Error("Failed to fetch plugin list"); + } + const data = await response.json(); + set_plugin_list(data); + } catch (error) { + console.error("Error fetching plugin list:", error); + } + }; + fetchPluginList(); + }, []); + + const handleDownload = async (plugin) => { + await downloadAndExtractPlugin(plugin); + }; + + return ( +
+ {plugin_list.map((plugin) => ( +
+

{plugin.title}

+ + {download_progress[plugin.plugin_id] !== undefined && ( +
+ Download Progress: {download_progress[plugin.plugin_id].toFixed(0)}% +
+ )} +
+ ))} +
+ ); +}; + diff --git a/src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.module.scss b/src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.module.scss new file mode 100644 index 00000000..a49fed11 --- /dev/null +++ b/src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.module.scss @@ -0,0 +1,5 @@ +.container { + display: flex; + gap: 6.4rem; + flex-direction: column; +} \ No newline at end of file diff --git a/src-ui/app/config_page/sidebar_section/SidebarSection.jsx b/src-ui/app/config_page/sidebar_section/SidebarSection.jsx index 6938f97f..5339de0d 100644 --- a/src-ui/app/config_page/sidebar_section/SidebarSection.jsx +++ b/src-ui/app/config_page/sidebar_section/SidebarSection.jsx @@ -12,6 +12,7 @@ export const SidebarSection = () => { +
diff --git a/src-ui/app/main_page/main_section/MainSection.jsx b/src-ui/app/main_page/main_section/MainSection.jsx index a918f1dd..2dec160c 100644 --- a/src-ui/app/main_page/main_section/MainSection.jsx +++ b/src-ui/app/main_page/main_section/MainSection.jsx @@ -9,12 +9,16 @@ import { useStore_IsOpenedLanguageSelector } from "@store"; import { useLanguageSettings } from "@logics_main"; import { useEffect } from "react"; import { SubtitleSystemContainer } from "./subtitle_system_container/SubtitleSystemContainer"; + +import { PluginHost } from "./PluginHost"; + export const MainSection = () => { return (
- + {/* */} + {/* */}
diff --git a/src-ui/app/main_page/main_section/PluginHost.jsx b/src-ui/app/main_page/main_section/PluginHost.jsx new file mode 100644 index 00000000..8fc162ff --- /dev/null +++ b/src-ui/app/main_page/main_section/PluginHost.jsx @@ -0,0 +1,20 @@ +// PluginHost.jsx +import React from "react"; +import { useStore_LoadedPluginsList } from "@store"; + +// export const PluginHost = ({ location }) => { +export const PluginHost = () => { + const { currentLoadedPluginsList } = useStore_LoadedPluginsList(); + console.log(currentLoadedPluginsList.data); + + return ( +
+ {currentLoadedPluginsList.data + .filter((plugin) => plugin.location === "main_section") + .map((plugin, index) => { + const PluginComponent = plugin.component; + return PluginComponent ? : null; + })} +
+ ); +}; \ No newline at end of file diff --git a/src-ui/logics/configs/index.js b/src-ui/logics/configs/index.js index 22439030..85d7c553 100644 --- a/src-ui/logics/configs/index.js +++ b/src-ui/logics/configs/index.js @@ -60,6 +60,8 @@ export { useOscPort } from "./advanced_settings/useOscPort"; export { useSupporters } from "./supporters/useSupporters"; +export { usePlugins } from "./plugins/usePlugins"; + export { useSettingBoxScrollPosition } from "./useSettingBoxScrollPosition"; export { useSoftwareVersion } from "./useSoftwareVersion"; \ No newline at end of file diff --git a/src-ui/logics/configs/plugins/usePlugins.js b/src-ui/logics/configs/plugins/usePlugins.js new file mode 100644 index 00000000..41832944 --- /dev/null +++ b/src-ui/logics/configs/plugins/usePlugins.js @@ -0,0 +1,190 @@ + import { invoke } from '@tauri-apps/api/tauri'; + import { createAtomWithHook, useStore_LoadedPluginsList } from "@store"; + import { transform } from "@babel/standalone"; + import { writeFile, createDir, exists, readDir, BaseDirectory, readTextFile } from "@tauri-apps/api/fs"; + const dev_plugin_mapping = import.meta.glob("/src-tauri/plugins/**/index.jsx", { eager: true }); + import JSZip from "jszip"; + import { fetch as tauriFetch, ResponseType } from "@tauri-apps/api/http"; + + export const usePlugins = () => { + const { updateLoadedPluginsList } = useStore_LoadedPluginsList(); + + const plugin_context = { + registerComponent: ({ plugin_id, location, component }) => { + if (!plugin_id || !location || !component) { + return console.error("An invalid plugin was detected.", plugin_id, location, component); + } + updateLoadedPluginsList((prev) => { + const filtered = prev.data.filter(item => item.plugin_id !== plugin_id); + return [...filtered, { plugin_id, location, component }]; + }); + }, + createAtomWithHook: (...args) => createAtomWithHook(...args) + }; + + const asyncLoadPlugin = async (plugin_relative_path) => { + plugin_relative_path = "plugins/" + plugin_relative_path; + console.log("plugin_relative_path",plugin_relative_path); + + try { + const plugin_code = await readTextFile(plugin_relative_path, { dir: BaseDirectory.Resource, recursive: true }); + const cleanedCode = removeImportStatements(plugin_code); + const transpiled_code = transform(cleanedCode, { + presets: [ + ["env", { modules: false }], + "react" + ], + sourceType: "module" + }).code; + const blob = new Blob([transpiled_code], { type: "text/javascript" }); + const blob_url = URL.createObjectURL(blob); + const plugin_module = await import(/* @vite-ignore */ blob_url); + URL.revokeObjectURL(blob_url); + + if (plugin_module && plugin_module.init) { + plugin_module.init(plugin_context); + } + } catch (error) { + console.error("Failed to load plugin from", plugin_relative_path, error); + } + }; + + const loadAllPlugins = async () => { + if (import.meta.env.DEV) { + // ホットリロード対応 src-tauri以下にあるpluginsディレクトリから直接読み込み(開発用) + Object.entries(dev_plugin_mapping).forEach(([key, plugin_module]) => { + console.log(plugin_module); + if (plugin_module && plugin_module.init) { + plugin_module.init(plugin_context); + } + }); + } else { + try { + const plugin_files = await readDir("plugins", { dir: BaseDirectory.Resource, recursive: true }); + for (const target_dir of plugin_files) { + console.log(target_dir); + + const target_path = target_dir.name + "\\index.jsx"; + await asyncLoadPlugin(target_path, plugin_context); + } + } catch (error) { + console.error("Error loading plugins:", error); + } + } + }; + + const downloadAndExtractPlugin = async (plugin) => { + try { + const plugin_zip_url = await fetchLatestPluginZipUrl(plugin); + console.log("Latest plugin zip URL:", plugin_zip_url); + + // Rust コマンド経由で zip をダウンロード + const base64Zip = await invoke("download_zip_asset", { url: plugin_zip_url }); + // base64Zip は文字列なので、デコードして Uint8Array に変換 + const binaryString = atob(base64Zip); + const len = binaryString.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + // JSZip で zip を解凍 + const zip = await JSZip.loadAsync(bytes); + + // const plugin_dir_exists = await exists("plugins", { dir: BaseDirectory.Resource, recursive: true }); + await createDir("plugins/" + plugin.asset_name.replace(".zip", ""), { dir: BaseDirectory.Resource, recursive: true }); + // if (!plugin_dir_exists) { + // } + const target_plugin_path = "plugins/" + plugin.asset_name.replace(".zip", ""); + + + const filePromises = []; + zip.forEach((relativePath, zipEntry) => { + // .git 以下のファイルはスキップ + if (relativePath.startsWith(".git") || relativePath.includes("/.git/")) { + // console.log("Skipping .git file: " + relativePath); + return; + } + + + const filePath = target_plugin_path + "/" + relativePath; + + if (zipEntry.dir) { + // フォルダの場合、ディレクトリを作成 + filePromises.push( + createDir(filePath, { dir: BaseDirectory.Resource, recursive: true }).catch((err) => { + console.log(err); + + if (!err.message?.includes("already exists")) { + console.error("Failed to create directory:", filePath, err); + } + }) + ); + } else { + // ファイルの場合、ディレクトリを作成してから書き込む + const dirPath = filePath.substring(0, filePath.lastIndexOf("/")); // 親ディレクトリのパス + + const promise = createDir(dirPath, { dir: BaseDirectory.Resource, recursive: true }) + .catch((err) => { + if (!err.message?.includes("already exists")) { + console.error("Failed to create parent directory:", dirPath, err); + } + }) + .then(() => zipEntry.async("text")) + .then(async (fileData) => { + await writeFile(filePath, fileData, { dir: BaseDirectory.Resource, recursive: true }); + }); + + filePromises.push(promise); + } + }); + + await Promise.all(filePromises); + console.log("Plugin downloaded successfully."); + + const index_file_relative_path = plugin.asset_name.replace(".zip", "") + "/" + "index.jsx" + console.log("index_file_relative_path", index_file_relative_path); + + await asyncLoadPlugin(index_file_relative_path); + + console.log("Plugin loaded successfully."); + } catch (error) { + console.error("Error downloading and extracting plugin:", error); + } + }; + + // JSON内のURLから GitHub API を使って最新リリース情報を取得し、 + // assets 配列から plugin.asset_name に一致するアセットの browser_download_url を返す + const fetchLatestPluginZipUrl = async (plugin) => { + const api_url = plugin.url; + const response = await tauriFetch(api_url, { + method: "GET", + responseType: ResponseType.Json, + headers: { + "Accept": "application/vnd.github+json", + "User-Agent": "VRCTPluginApp" + } + }); + if (response.status !== 200) { + throw new Error("Failed to fetch latest release info, status: " + response.status); + } + const release_info = response.data; + const asset = release_info.assets.find((a) => a.name === plugin.asset_name); + if (!asset) { + throw new Error(`Asset ${plugin.asset_name} not found in the latest release`); + } + return asset.browser_download_url; + }; + + return { + loadAllPlugins, + downloadAndExtractPlugin, + }; +}; + +const removeImportStatements = (code) => { + return code + .split("\n") + .filter(line => !line.match(/^import\s+.*['"]react['"]/)) + .join("\n"); +}; diff --git a/src-ui/store.js b/src-ui/store.js index 9a2967c4..da4c1f45 100644 --- a/src-ui/store.js +++ b/src-ui/store.js @@ -34,7 +34,7 @@ const generatePropertyNames = (base_name) => ({ }); -const createAtomWithHook = (initialValue, base_name, options) => { +export const createAtomWithHook = (initialValue, base_name, options) => { const property_names = generatePropertyNames(base_name); const atomInstance = atom({ state: (options?.is_state_ok) ? "ok" : "pending", @@ -274,6 +274,10 @@ export const { atomInstance: Atom_Hotkeys, useHook: useStore_Hotkeys } = createA toggle_transcription_receive: null, }, "Hotkeys"); +// Plugins +export const { atomInstance: Atom_InstalledPluginsPath, useHook: useStore_InstalledPluginsPath } = createAtomWithHook([], "InstalledPluginsPath"); +export const { atomInstance: Atom_LoadedPluginsList, useHook: useStore_LoadedPluginsList } = createAtomWithHook([], "LoadedPluginsList"); + // Advanced Settings export const { atomInstance: Atom_OscIpAddress, useHook: useStore_OscIpAddress } = createAtomWithHook("127.0.0.1", "OscIpAddress"); export const { atomInstance: Atom_OscPort, useHook: useStore_OscPort } = createAtomWithHook("9000", "OscPort");