Merge branch 'develop'
3
.gitignore
vendored
@@ -43,4 +43,5 @@ dist-ssr
|
||||
.venv
|
||||
|
||||
# Customize
|
||||
/build
|
||||
/build
|
||||
error.txt
|
||||
116
README.md
@@ -1,116 +0,0 @@
|
||||
<div align="center">
|
||||
|
||||
<picture>
|
||||
<source srcset="docs/vrct_logo_white.png" media="(prefers-color-scheme: dark)" width="50%">
|
||||
<source srcset="docs/vrct_logo_black.png" media="(prefers-color-scheme: light)" width="50%">
|
||||
<img src="docs/vrct_logo.png" alt="VRCT Logo" width="50%">
|
||||
</picture>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
[](https://github.com/misyaguziya/VRCT/releases)
|
||||
[](https://github.com/misyaguziya/VRCT/releases)
|
||||
[](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
|
||||
[](https://misyaguziya.booth.pm/items/5155325)
|
||||
[](https://github.com/sponsors/misyaguziya)
|
||||
|
||||
<h3>
|
||||
Become a VRCT Supporter on:
|
||||
</h3>
|
||||
|
||||
<a href="https://vrct-dev.fanbox.cc">
|
||||
<picture>
|
||||
<source srcset="docs/pixiv_fanbox_white.png" media="(prefers-color-scheme: dark)" height="18px">
|
||||
<source srcset="docs/pixiv_fanbox_black.png" media="(prefers-color-scheme: light)" height="18px">
|
||||
<img src="docs/pixiv_fanbox_black.png" alt="PIXIV FANBOX" height="18px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<a href="https://patreon.com/vrct_dev">
|
||||
<picture>
|
||||
<source srcset="docs/patreon_logo_white.png" media="(prefers-color-scheme: dark)" height="22px">
|
||||
<source srcset="docs/patreon_logo_black.png" media="(prefers-color-scheme: light)" height="22px">
|
||||
<img src="docs/patreon_logo_black.png" alt="Patreon" height="22px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<a href="https://ko-fi.com/vrct_dev">
|
||||
<picture>
|
||||
<img src="docs/kofi_logo.png" alt="Ko-fi" height="22px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<br>
|
||||
|
||||
<picture>
|
||||
<source srcset="docs/supporter_section_border_d.png" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="docs/supporter_section_border_l.png" media="(prefers-color-scheme: light)">
|
||||
<img src="docs/supporter_section_border_d.png" alt="Supporter Section Border">
|
||||
</picture>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
| **English** | [日本語](./README.ja.md) | [한국어](./README.ko.md) | [繁體中文](./README.zh-Hant.md) |
|
||||
|
||||
<h3>
|
||||
VRCT is software that supports VRChat conversations with translation and transcription.
|
||||
</h3>
|
||||
|
||||

|
||||
|
||||
<div align="left">
|
||||
|
||||
# Download & Install
|
||||
Download from anywhere you like.
|
||||
- [Github.com](https://github.com/misyaguziya/VRCT/releases/)
|
||||
- [BOOTH.pm](https://misyaguziya.booth.pm/items/5155325)
|
||||
|
||||
Just download and run the exe.
|
||||
|
||||
# What is VRCT?
|
||||
VRCT is software that supports conversations between people who speak different languages by providing chat or voice translation.
|
||||
These features are designed for use within VRChat.
|
||||
*Although not supported, it is also used for other purposes such as watching movies.
|
||||
|
||||
VRCT supports your conversations with
|
||||
- 💬 **Send chat to VRChat**
|
||||
- 🌐 **Translation**
|
||||
- 🎙 **Transcription of audio from microphone**
|
||||
- 🔈 **Transcription of audio from Speaker**
|
||||
|
||||
# Documents
|
||||
Initial setup, basic functions, and other features are also described.
|
||||
- [Documents Link](https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246?pvs=4)
|
||||
|
||||
# How to Use (YouTube)
|
||||
<div align="center">
|
||||
|
||||
[](https://www.youtube.com/watch?v=rUTad037n8Q)
|
||||
|
||||
<div align="left">
|
||||
|
||||
# If you want to run it in python
|
||||
1. Install the following version of python.
|
||||
`python version 3.11.5`
|
||||
2. Install package and run main.py.
|
||||
```bash
|
||||
./install.bat
|
||||
python main.py
|
||||
```
|
||||
|
||||
## Author
|
||||
- [みしゃ(misyaguzi)](https://github.com/misyaguziya) (Main Development)
|
||||
- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) (UI/UX, UI multilingual support)
|
||||
- [レラ](https://github.com/soumt-r) (Technical Support)
|
||||
- [どね](https://twitter.com/done_vrc) (Logo Design)
|
||||
|
||||
## Thanks to our contributors
|
||||
<a href="https://github.com/misyaguziya/VRCT/graphs/contributors" target="_blank">
|
||||
<img src="https://contrib.rocks/image?repo=misyaguziya/VRCT" />
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
VRCT is not endorsed by VRChat and does not reflect the views or opinions of VRChat or anyone officially involved in producing or managing VRChat properties. VRChat and all associated properties are trademarks or registered trademarks of VRChat Inc. VRChat © VRChat Inc.
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 182 B After Width: | Height: | Size: 182 B |
|
Before Width: | Height: | Size: 186 B After Width: | Height: | Size: 186 B |
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
107
docs/readmes/README.en.md
Normal file
@@ -0,0 +1,107 @@
|
||||
<div align="center">
|
||||
|
||||
<picture>
|
||||
<source srcset="/docs/img/vrct_logo_white.png" media="(prefers-color-scheme: dark)" width="50%">
|
||||
<source srcset="/docs/img/vrct_logo_black.png" media="(prefers-color-scheme: light)" width="50%">
|
||||
<img src="/docs/img/vrct_logo.png" alt="VRCT Logo" width="50%">
|
||||
</picture>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
[](https://github.com/misyaguziya/VRCT/releases)
|
||||
[](https://github.com/misyaguziya/VRCT/releases)
|
||||
[](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
|
||||
[](https://misyaguziya.booth.pm/items/5155325)
|
||||
[](https://github.com/sponsors/misyaguziya)
|
||||
|
||||
<h3>
|
||||
Become a VRCT Supporter on:
|
||||
</h3>
|
||||
|
||||
<a href="https://vrct-dev.fanbox.cc">
|
||||
<picture>
|
||||
<source srcset="/docs/img/pixiv_fanbox_white.png" media="(prefers-color-scheme: dark)" height="18px">
|
||||
<source srcset="/docs/img/pixiv_fanbox_black.png" media="(prefers-color-scheme: light)" height="18px">
|
||||
<img src="/docs/img/pixiv_fanbox_black.png" alt="PIXIV FANBOX" height="18px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<a href="https://patreon.com/vrct_dev">
|
||||
<picture>
|
||||
<source srcset="/docs/img/patreon_logo_white.png" media="(prefers-color-scheme: dark)" height="22px">
|
||||
<source srcset="/docs/img/patreon_logo_black.png" media="(prefers-color-scheme: light)" height="22px">
|
||||
<img src="/docs/img/patreon_logo_black.png" alt="Patreon" height="22px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<a href="https://ko-fi.com/vrct_dev">
|
||||
<picture>
|
||||
<img src="/docs/img/kofi_logo.png" alt="Ko-fi" height="22px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<br>
|
||||
|
||||
<picture>
|
||||
<source srcset="/docs/img/supporter_section_border_d.png" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="/docs/img/supporter_section_border_l.png" media="(prefers-color-scheme: light)">
|
||||
<img src="/docs/img/supporter_section_border_d.png" alt="Supporter Section Border">
|
||||
</picture>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
| **English** | [日本語](/docs/readmes/README.ja.md) | [한국어](/docs/readmes/README.ko.md) | [繁體中文](/docs/readmes/README.zh-Hant.md) |
|
||||
|
||||
<h3>
|
||||
VRCT is software that supports VRChat conversations with translation and transcription.
|
||||
</h3>
|
||||
|
||||

|
||||
|
||||
<div align="left">
|
||||
|
||||
# Download & Install
|
||||
Download from anywhere you like.
|
||||
- [Github.com](https://github.com/misyaguziya/VRCT/releases/)
|
||||
- [BOOTH.pm](https://misyaguziya.booth.pm/items/5155325)
|
||||
|
||||
Just download and run the exe.
|
||||
|
||||
# What is VRCT?
|
||||
VRCT is software that supports conversations between people who speak different languages by providing chat or voice translation.
|
||||
These features are designed for use within VRChat.
|
||||
*Although not supported, it is also used for other purposes such as watching movies.
|
||||
|
||||
VRCT supports your conversations with
|
||||
- 💬 **Send chat to VRChat**
|
||||
- 🌐 **Translation**
|
||||
- 🎙 **Transcription of audio from microphone**
|
||||
- 🔈 **Transcription of audio from Speaker**
|
||||
|
||||
# Documents
|
||||
Initial setup, basic functions, and other features are also described.
|
||||
- [Documents Link](https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246?pvs=4)
|
||||
|
||||
# How to Use (YouTube)
|
||||
<div align="center">
|
||||
|
||||
[](https://www.youtube.com/watch?v=rUTad037n8Q)
|
||||
|
||||
<div align="left">
|
||||
|
||||
## Author
|
||||
- [みしゃ(misyaguzi)](https://github.com/misyaguziya) (Main Development)
|
||||
- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) (UI/UX, UI multilingual support)
|
||||
- [レラ](https://github.com/soumt-r) (Technical Advisor)
|
||||
- [どね](https://twitter.com/done_vrc) (Logo Design)
|
||||
|
||||
## Thanks to our contributors
|
||||
<a href="https://github.com/misyaguziya/VRCT/graphs/contributors" target="_blank">
|
||||
<img src="https://contrib.rocks/image?repo=misyaguziya/VRCT" />
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
VRCT is not endorsed by VRChat and does not reflect the views or opinions of VRChat or anyone officially involved in producing or managing VRChat properties. VRChat and all associated properties are trademarks or registered trademarks of VRChat Inc. VRChat © VRChat Inc.
|
||||
@@ -1,9 +1,9 @@
|
||||
<div align="center">
|
||||
|
||||
<picture>
|
||||
<source srcset="docs/vrct_logo_white.png" media="(prefers-color-scheme: dark)" width="50%">
|
||||
<source srcset="docs/vrct_logo_black.png" media="(prefers-color-scheme: light)" width="50%">
|
||||
<img src="docs/vrct_logo.png" alt="VRCT Logo" width="50%">
|
||||
<source srcset="/docs/img/vrct_logo_white.png" media="(prefers-color-scheme: dark)" width="50%">
|
||||
<source srcset="/docs/img/vrct_logo_black.png" media="(prefers-color-scheme: light)" width="50%">
|
||||
<img src="/docs/img/vrct_logo.png" alt="VRCT Logo" width="50%">
|
||||
</picture>
|
||||
|
||||
<br>
|
||||
@@ -21,44 +21,44 @@ Become a VRCT Supporter on:
|
||||
|
||||
<a href="https://vrct-dev.fanbox.cc">
|
||||
<picture>
|
||||
<source srcset="docs/pixiv_fanbox_white.png" media="(prefers-color-scheme: dark)" height="18px">
|
||||
<source srcset="docs/pixiv_fanbox_black.png" media="(prefers-color-scheme: light)" height="18px">
|
||||
<img src="docs/pixiv_fanbox_black.png" alt="PIXIV FANBOX" height="18px">
|
||||
<source srcset="/docs/img/pixiv_fanbox_white.png" media="(prefers-color-scheme: dark)" height="18px">
|
||||
<source srcset="/docs/img/pixiv_fanbox_black.png" media="(prefers-color-scheme: light)" height="18px">
|
||||
<img src="/docs/img/pixiv_fanbox_black.png" alt="PIXIV FANBOX" height="18px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<a href="https://patreon.com/vrct_dev">
|
||||
<picture>
|
||||
<source srcset="docs/patreon_logo_white.png" media="(prefers-color-scheme: dark)" height="22px">
|
||||
<source srcset="docs/patreon_logo_black.png" media="(prefers-color-scheme: light)" height="22px">
|
||||
<img src="docs/patreon_logo_black.png" alt="Patreon" height="22px">
|
||||
<source srcset="/docs/img/patreon_logo_white.png" media="(prefers-color-scheme: dark)" height="22px">
|
||||
<source srcset="/docs/img/patreon_logo_black.png" media="(prefers-color-scheme: light)" height="22px">
|
||||
<img src="/docs/img/patreon_logo_black.png" alt="Patreon" height="22px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<a href="https://ko-fi.com/vrct_dev">
|
||||
<picture>
|
||||
<img src="docs/kofi_logo.png" alt="Ko-fi" height="22px">
|
||||
<img src="/docs/img/kofi_logo.png" alt="Ko-fi" height="22px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<br>
|
||||
|
||||
<picture>
|
||||
<source srcset="docs/supporter_section_border_d.png" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="docs/supporter_section_border_l.png" media="(prefers-color-scheme: light)">
|
||||
<img src="docs/supporter_section_border_d.png" alt="Supporter Section Border">
|
||||
<source srcset="/docs/img/supporter_section_border_d.png" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="/docs/img/supporter_section_border_l.png" media="(prefers-color-scheme: light)">
|
||||
<img src="/docs/img/supporter_section_border_d.png" alt="Supporter Section Border">
|
||||
</picture>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
| [English](./README.md) | **日本語** | [한국어](./README.ko.md) | [繁體中文](./README.zh-Hant.md) |
|
||||
| [English](/docs/readmes/README.en.md) | **日本語** | [한국어](/docs/readmes/README.ko.md) | [繁體中文](/docs/readmes/README.zh-Hant.md) |
|
||||
|
||||
<h3>
|
||||
VRCTは翻訳や文字起こしでVRChatの会話をサポートするソフトウェアです。
|
||||
</h3>
|
||||
|
||||

|
||||

|
||||
|
||||
<div align="left">
|
||||
|
||||
@@ -91,15 +91,6 @@ VRCTはあなたの会話を以下でサポートをします。
|
||||
|
||||
<div align="left">
|
||||
|
||||
# pythonで実行したい場合
|
||||
1. 以下のバージョンのpythonをインストールしてください。
|
||||
`python version 3.11.5`
|
||||
2. packageのインストールとmain.pyを起動してください。
|
||||
```bash
|
||||
./install.bat
|
||||
python main.py
|
||||
```
|
||||
|
||||
## Author
|
||||
- [みしゃ(misyaguzi)](https://github.com/misyaguziya) (メイン開発)
|
||||
- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) (UI/UX, UI多言語対応)
|
||||
@@ -1,9 +1,9 @@
|
||||
<div align="center">
|
||||
|
||||
<picture>
|
||||
<source srcset="docs/vrct_logo_white.png" media="(prefers-color-scheme: dark)" width="50%">
|
||||
<source srcset="docs/vrct_logo_black.png" media="(prefers-color-scheme: light)" width="50%">
|
||||
<img src="docs/vrct_logo.png" alt="VRCT Logo" width="50%">
|
||||
<source srcset="/docs/img/vrct_logo_white.png" media="(prefers-color-scheme: dark)" width="50%">
|
||||
<source srcset="/docs/img/vrct_logo_black.png" media="(prefers-color-scheme: light)" width="50%">
|
||||
<img src="/docs/img/vrct_logo.png" alt="VRCT Logo" width="50%">
|
||||
</picture>
|
||||
|
||||
<br>
|
||||
@@ -21,44 +21,44 @@ Become a VRCT Supporter on:
|
||||
|
||||
<a href="https://vrct-dev.fanbox.cc">
|
||||
<picture>
|
||||
<source srcset="docs/pixiv_fanbox_white.png" media="(prefers-color-scheme: dark)" height="18px">
|
||||
<source srcset="docs/pixiv_fanbox_black.png" media="(prefers-color-scheme: light)" height="18px">
|
||||
<img src="docs/pixiv_fanbox_black.png" alt="PIXIV FANBOX" height="18px">
|
||||
<source srcset="/docs/img/pixiv_fanbox_white.png" media="(prefers-color-scheme: dark)" height="18px">
|
||||
<source srcset="/docs/img/pixiv_fanbox_black.png" media="(prefers-color-scheme: light)" height="18px">
|
||||
<img src="/docs/img/pixiv_fanbox_black.png" alt="PIXIV FANBOX" height="18px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<a href="https://patreon.com/vrct_dev">
|
||||
<picture>
|
||||
<source srcset="docs/patreon_logo_white.png" media="(prefers-color-scheme: dark)" height="22px">
|
||||
<source srcset="docs/patreon_logo_black.png" media="(prefers-color-scheme: light)" height="22px">
|
||||
<img src="docs/patreon_logo_black.png" alt="Patreon" height="22px">
|
||||
<source srcset="/docs/img/patreon_logo_white.png" media="(prefers-color-scheme: dark)" height="22px">
|
||||
<source srcset="/docs/img/patreon_logo_black.png" media="(prefers-color-scheme: light)" height="22px">
|
||||
<img src="/docs/img/patreon_logo_black.png" alt="Patreon" height="22px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<a href="https://ko-fi.com/vrct_dev">
|
||||
<picture>
|
||||
<img src="docs/kofi_logo.png" alt="Ko-fi" height="22px">
|
||||
<img src="/docs/img/kofi_logo.png" alt="Ko-fi" height="22px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<br>
|
||||
|
||||
<picture>
|
||||
<source srcset="docs/supporter_section_border_d.png" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="docs/supporter_section_border_l.png" media="(prefers-color-scheme: light)">
|
||||
<img src="docs/supporter_section_border_d.png" alt="Supporter Section Border">
|
||||
<source srcset="/docs/img/supporter_section_border_d.png" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="/docs/img/supporter_section_border_l.png" media="(prefers-color-scheme: light)">
|
||||
<img src="/docs/img/supporter_section_border_d.png" alt="Supporter Section Border">
|
||||
</picture>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
| [English](./README.md) | [日本語](./README.ja.md) | **한국어** | [繁體中文](./README.zh-Hant.md) |
|
||||
| [English](/docs/readmes/README.en.md) | [日本語](/docs/readmes/README.ja.md) | **한국어** | [繁體中文](/docs/readmes/README.zh-Hant.md) |
|
||||
|
||||
<h3>
|
||||
VRCT는 음성인식 및 번역 기능을 통해 VRChat의 대화를 지원하는 소프트웨어입니다.
|
||||
</h3>
|
||||
|
||||

|
||||

|
||||
|
||||
<div align="left">
|
||||
|
||||
@@ -91,15 +91,6 @@ VRCT는 다음과 같이 당신의 대화를 도와드려요.
|
||||
|
||||
<div align="left">
|
||||
|
||||
# python으로 실행하고 싶은 경우
|
||||
1. 다음 버전의 python을 설치합니다.
|
||||
`python version 3.11.5`
|
||||
2. 패키지를 설치하고 main.py를 실행합니다.
|
||||
```bash
|
||||
./install.bat
|
||||
python main.py
|
||||
```
|
||||
|
||||
## Author
|
||||
- [みしゃ(misyaguzi)](https://github.com/misyaguziya) (주요 개발)
|
||||
- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) (UI/UX, UI 다국어 지원)
|
||||
@@ -1,9 +1,9 @@
|
||||
<div align="center">
|
||||
|
||||
<picture>
|
||||
<source srcset="docs/vrct_logo_white.png" media="(prefers-color-scheme: dark)" width="50%">
|
||||
<source srcset="docs/vrct_logo_black.png" media="(prefers-color-scheme: light)" width="50%">
|
||||
<img src="docs/vrct_logo.png" alt="VRCT Logo" width="50%">
|
||||
<source srcset="/docs/img/vrct_logo_white.png" media="(prefers-color-scheme: dark)" width="50%">
|
||||
<source srcset="/docs/img/vrct_logo_black.png" media="(prefers-color-scheme: light)" width="50%">
|
||||
<img src="/docs/img/vrct_logo.png" alt="VRCT Logo" width="50%">
|
||||
</picture>
|
||||
|
||||
<br>
|
||||
@@ -21,44 +21,44 @@ Become a VRCT Supporter on:
|
||||
|
||||
<a href="https://vrct-dev.fanbox.cc">
|
||||
<picture>
|
||||
<source srcset="docs/pixiv_fanbox_white.png" media="(prefers-color-scheme: dark)" height="18px">
|
||||
<source srcset="docs/pixiv_fanbox_black.png" media="(prefers-color-scheme: light)" height="18px">
|
||||
<img src="docs/pixiv_fanbox_black.png" alt="PIXIV FANBOX" height="18px">
|
||||
<source srcset="/docs/img/pixiv_fanbox_white.png" media="(prefers-color-scheme: dark)" height="18px">
|
||||
<source srcset="/docs/img/pixiv_fanbox_black.png" media="(prefers-color-scheme: light)" height="18px">
|
||||
<img src="/docs/img/pixiv_fanbox_black.png" alt="PIXIV FANBOX" height="18px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<a href="https://patreon.com/vrct_dev">
|
||||
<picture>
|
||||
<source srcset="docs/patreon_logo_white.png" media="(prefers-color-scheme: dark)" height="22px">
|
||||
<source srcset="docs/patreon_logo_black.png" media="(prefers-color-scheme: light)" height="22px">
|
||||
<img src="docs/patreon_logo_black.png" alt="Patreon" height="22px">
|
||||
<source srcset="/docs/img/patreon_logo_white.png" media="(prefers-color-scheme: dark)" height="22px">
|
||||
<source srcset="/docs/img/patreon_logo_black.png" media="(prefers-color-scheme: light)" height="22px">
|
||||
<img src="/docs/img/patreon_logo_black.png" alt="Patreon" height="22px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<a href="https://ko-fi.com/vrct_dev">
|
||||
<picture>
|
||||
<img src="docs/kofi_logo.png" alt="Ko-fi" height="22px">
|
||||
<img src="/docs/img/kofi_logo.png" alt="Ko-fi" height="22px">
|
||||
</picture>
|
||||
</a> 
|
||||
|
||||
<br>
|
||||
|
||||
<picture>
|
||||
<source srcset="docs/supporter_section_border_d.png" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="docs/supporter_section_border_l.png" media="(prefers-color-scheme: light)">
|
||||
<img src="docs/supporter_section_border_d.png" alt="Supporter Section Border">
|
||||
<source srcset="/docs/img/supporter_section_border_d.png" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="/docs/img/supporter_section_border_l.png" media="(prefers-color-scheme: light)">
|
||||
<img src="/docs/img/supporter_section_border_d.png" alt="Supporter Section Border">
|
||||
</picture>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
| [English](./README.md) | [日本語](./README.ja.md) | [한국어](./README.ko.md) | **繁體中文** |
|
||||
| [English](/docs/readmes/README.en.md) | [日本語](/docs/readmes/README.ja.md) | [한국어](/docs/readmes/README.ko.md) | **繁體中文** |
|
||||
|
||||
<h3>
|
||||
VRCT 是一個支援 VRChat 對話翻譯和紀錄的軟體。
|
||||
</h3>
|
||||
|
||||

|
||||

|
||||
|
||||
<div align="left">
|
||||
|
||||
@@ -90,15 +90,6 @@ VRCT 可以:
|
||||
|
||||
<div align="left">
|
||||
|
||||
# 原始碼啟動
|
||||
1. 安裝此版本的 Python。
|
||||
`python version 3.11.5`
|
||||
2. 安裝 package 並啟動 main.py。
|
||||
```bash
|
||||
./install.bat
|
||||
python main.py
|
||||
```
|
||||
|
||||
## 作者
|
||||
- [みしゃ(misyaguzi)](https://github.com/misyaguziya) (主要開發)
|
||||
- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) (UI/UX, UI 多語系支援)
|
||||
@@ -3,8 +3,8 @@ python -m venv .venv_cuda
|
||||
|
||||
call .venv/Scripts/activate
|
||||
python.exe -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install --no-cache-dir --force-reinstall -r requirements.txt
|
||||
|
||||
call .venv_cuda/Scripts/activate
|
||||
python.exe -m pip install --upgrade pip
|
||||
pip install -r requirements_cuda.txt
|
||||
pip install --no-cache-dir --force-reinstall -r requirements_cuda.txt
|
||||
@@ -243,4 +243,31 @@ config_page:
|
||||
open_config_filepath:
|
||||
label: "Open Config File"
|
||||
switch_compute_device:
|
||||
label: "Switch VRCT To CPU/GPU Version"
|
||||
label: "Switch VRCT To CPU/GPU Version"
|
||||
section_label_plugins: Plugins # Exception, It'll be moved later.
|
||||
|
||||
plugins:
|
||||
downloaded_version: "Downloaded version: {{downloaded_version}}"
|
||||
latest_version: "Latest version: {{latest_version}}"
|
||||
available_after_updating: "Available after updating to the latest version"
|
||||
unavailable_downloaded: "Currently unavailable due to incompatibility with the VRCT version in use"
|
||||
no_latest_info: "Unable to retrieve the latest information"
|
||||
using_latest_version: "Using the latest version"
|
||||
available_latest_version: "Latest version available"
|
||||
unavailable_latest_version: "Latest version currently unavailable"
|
||||
available_in_latest_vrct_version: "Available in the latest VRCT version"
|
||||
unavailable_not_downloaded: "Currently unavailable"
|
||||
|
||||
plugin_notifications:
|
||||
downloading: Downloading the plugin.
|
||||
downloaded_success: Downloaded successfully.
|
||||
downloaded_error: Download failed.
|
||||
|
||||
updating: Updating the plugin.
|
||||
updated_success: Updated successfully.
|
||||
updated_error: Update failed.
|
||||
|
||||
disabled_out_of_support: The plugin has been disabled. It's not supported on this VRCT version.
|
||||
|
||||
is_enabled: The plugin has enabled.
|
||||
is_disabled: The plugin has disabled.
|
||||
@@ -243,4 +243,31 @@ config_page:
|
||||
open_config_filepath:
|
||||
label: "設定ファイルを開く"
|
||||
switch_compute_device:
|
||||
label: "VRCT CPU/GPUバージョンの切り替え"
|
||||
label: "VRCT CPU/GPUバージョンの切り替え"
|
||||
section_label_plugins: プラグイン # Exception, It'll be moved later.
|
||||
|
||||
plugins:
|
||||
downloaded_version: "ダウンロード済バージョン: {{downloaded_version}}"
|
||||
latest_version: "最新バージョン: {{latest_version}}"
|
||||
available_after_updating: 最新版にアップデート後 利用可能
|
||||
unavailable_downloaded: 現在利用不可 使用中VRCTバージョンとの互換性なし
|
||||
no_latest_info: 最新情報が取得できません
|
||||
using_latest_version: 最新版を使用中
|
||||
available_latest_version: 最新版を利用可能
|
||||
unavailable_latest_version: 最新版は現在利用不可
|
||||
available_in_latest_vrct_version: VRCT最新版で利用可能
|
||||
unavailable_not_downloaded: 現在利用不可
|
||||
|
||||
plugin_notifications:
|
||||
downloading: プラグインをダウンロード中。
|
||||
downloaded_success: プラグインのダウンロードが完了しました。
|
||||
downloaded_error: プラグインのダウンロードに失敗しました。
|
||||
|
||||
updating: プラグインをアップデート中。
|
||||
updated_success: プラグインのアップデートが完了しました。
|
||||
updated_error: プラグインのアップデートに失敗しました。
|
||||
|
||||
disabled_out_of_support: 現在のバージョンとの互換性がありません。プラグインを無効にしました。
|
||||
|
||||
is_enabled: プラグインを有効にしました。
|
||||
is_disabled: プラグインを無効にしました。
|
||||
78
map-stack.js
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env node
|
||||
// 使用例: node map-stack.js
|
||||
// カレントディレクトリの error.txt を読み込み、各エラーフレームから
|
||||
// 対応するソースマップ(dist/assets/ 以下の *.js.map)を用いて元の位置情報を出力する
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { SourceMapConsumer } from "source-map";
|
||||
|
||||
// 各スタックフレームにマッチする正規表現
|
||||
const FRAME_REGEX = /^\s*at\s+(.*?)\s+\((.*):(\d+):(\d+)\)$/;
|
||||
|
||||
// スタックトレースのパース関数
|
||||
const parseStackTrace = (text) => {
|
||||
return text
|
||||
.split(/\r?\n/)
|
||||
.map((line) => {
|
||||
const match = line.match(FRAME_REGEX);
|
||||
if (match) {
|
||||
return {
|
||||
original: line,
|
||||
functionName: match[1],
|
||||
file: match[2],
|
||||
line: Number(match[3]),
|
||||
column: Number(match[4])
|
||||
};
|
||||
} else {
|
||||
return { original: line };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// エラースタックをソースマップから逆引きする関数(位置情報のみを表示)
|
||||
const mapStackTrace = async () => {
|
||||
const errorTxtPath = path.resolve(process.cwd(), "error.txt");
|
||||
const stackTraceText = fs.readFileSync(errorTxtPath, "utf8");
|
||||
const frames = parseStackTrace(stackTraceText);
|
||||
|
||||
const consumerMap = new Map();
|
||||
|
||||
const mappedFrames = await Promise.all(frames.map(async (frame) => {
|
||||
if (frame.file && frame.line && frame.column) {
|
||||
const relativeFile = frame.file.replace(/^\//, ""); // 例: "assets/main-Td8-sruo.js"
|
||||
const mapFilePath = path.resolve(process.cwd(), "dist", relativeFile + ".map");
|
||||
let consumer = consumerMap.get(mapFilePath);
|
||||
if (!consumer) {
|
||||
const rawSourceMap = fs.readFileSync(mapFilePath, "utf8");
|
||||
consumer = await new SourceMapConsumer(rawSourceMap);
|
||||
consumerMap.set(mapFilePath, consumer);
|
||||
}
|
||||
const pos = consumer.originalPositionFor({
|
||||
line: frame.line,
|
||||
column: frame.column
|
||||
});
|
||||
|
||||
if (pos && pos.source && pos.line != null && pos.column != null) {
|
||||
return ` at ${frame.functionName} (${pos.source}:${pos.line}:${pos.column})`;
|
||||
} else {
|
||||
return frame.original;
|
||||
}
|
||||
} else {
|
||||
return frame.original;
|
||||
}
|
||||
}));
|
||||
|
||||
consumerMap.forEach((consumer) => consumer.destroy());
|
||||
return mappedFrames.join("\n");
|
||||
};
|
||||
|
||||
mapStackTrace()
|
||||
.then((mapped) => {
|
||||
console.log("--- Mapped Stack Trace ---");
|
||||
console.log(mapped);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
12264
package-lock.json
generated
110
package.json
@@ -1,50 +1,60 @@
|
||||
{
|
||||
"name": "tauri-app",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"setup-python": "install.bat",
|
||||
"build-python": "build.bat",
|
||||
"build-python-cuda": "build_cuda.bat",
|
||||
"vite": "vite",
|
||||
"vite-build": "vite build",
|
||||
"vite-preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"tauri-dev": "tauri dev",
|
||||
"clean": "python clean.py",
|
||||
"dev": "npm run build-python && npm run dev-ui",
|
||||
"dev-cuda": "npm run build-python-cuda && npm run dev-ui",
|
||||
"dev-ui": "npm-run-all --parallel vite tauri-dev",
|
||||
"build": "npm run clean && npm run build-python && npm run vite-build && npm run tauri build",
|
||||
"build-cuda": "npm run clean && npm run build-python-cuda && npm run vite-build && npm run tauri build",
|
||||
"release": "npm run build && python zip.py --zip_name VRCT.zip",
|
||||
"release-cuda": "npm run build-cuda && python zip.py --zip_name VRCT_cuda.zip",
|
||||
"release-all": "npm run release && npm run release-cuda"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "11.14.0",
|
||||
"@emotion/styled": "11.14.0",
|
||||
"@mui/material": "6.2.0",
|
||||
"@tauri-apps/api": "1.6.0",
|
||||
"@vitejs/plugin-react": "4.3.4",
|
||||
"clsx": "2.1.1",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-react": "7.37.2",
|
||||
"i18next": "24.1.0",
|
||||
"jotai": "2.10.3",
|
||||
"js-base64": "3.7.7",
|
||||
"js-yaml": "4.1.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-i18next": "15.2.0",
|
||||
"react-resizable-layout": "0.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "1.6.3",
|
||||
"npm-run-all": "4.1.5",
|
||||
"sass": "1.79.4",
|
||||
"vite": "6.0.3",
|
||||
"vite-plugin-svgr": "4.3.0"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "vrct",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"setup-python": "install.bat",
|
||||
"build-python": "build.bat",
|
||||
"build-python-cuda": "build_cuda.bat",
|
||||
"vite": "vite",
|
||||
"vite-build": "vite build",
|
||||
"vite-preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"tauri-dev": "tauri dev",
|
||||
"clean": "python clean.py",
|
||||
"dev": "npm run build-python && npm run dev-ui",
|
||||
"dev-cuda": "npm run build-python-cuda && npm run dev-ui",
|
||||
"dev-ui": "npm-run-all --parallel vite tauri-dev",
|
||||
"build": "npm run clean && npm run build-python && npm run vite-build && npm run tauri build",
|
||||
"build-cuda": "npm run clean && npm run build-python-cuda && npm run vite-build && npm run tauri build",
|
||||
"release": "npm run build && python zip.py --zip_name VRCT.zip",
|
||||
"release-cuda": "npm run build-cuda && python zip.py --zip_name VRCT_cuda.zip",
|
||||
"release-all": "npm run release && npm run release-cuda"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/standalone": "7.27.0",
|
||||
"@emotion/react": "11.14.0",
|
||||
"@emotion/styled": "11.14.0",
|
||||
"@mui/material": "7.0.2",
|
||||
"@tauri-apps/api": "2.5.0",
|
||||
"@tauri-apps/plugin-fs": "2.2.1",
|
||||
"@tauri-apps/plugin-global-shortcut": "2.2.0",
|
||||
"@tauri-apps/plugin-http": "2.4.3",
|
||||
"@tauri-apps/plugin-opener": "2.2.6",
|
||||
"@tauri-apps/plugin-shell": "2.2.1",
|
||||
"clsx": "2.1.1",
|
||||
"i18next": "25.0.1",
|
||||
"jotai": "2.12.3",
|
||||
"js-base64": "3.7.7",
|
||||
"jszip": "3.10.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-error-boundary": "5.0.0",
|
||||
"react-i18next": "15.5.1",
|
||||
"react-resizable-layout": "0.7.2",
|
||||
"sass": "1.79.4",
|
||||
"semver": "7.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-yaml": "4.1.2",
|
||||
"@tauri-apps/cli": "2.5.0",
|
||||
"@vitejs/plugin-react": "4.4.1",
|
||||
"eslint": "9.25.1",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"npm-run-all": "4.1.5",
|
||||
"source-map": "0.7.4",
|
||||
"vite": "6.3.4",
|
||||
"vite-plugin-svgr": "4.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
torch==2.2.2
|
||||
faster-whisper==1.0.3
|
||||
ctranslate2==4.3.1
|
||||
torch==2.7.0
|
||||
faster-whisper==1.1.1
|
||||
ctranslate2==4.6.0
|
||||
transformers==4.40.2
|
||||
pillow == 10.0.0
|
||||
PyAudioWPatch == 0.2.12.6
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
torch==2.2.2
|
||||
--extra-index-url https://download.pytorch.org/whl/cu121
|
||||
faster-whisper==1.0.3
|
||||
ctranslate2==4.3.1
|
||||
torch==2.7.0
|
||||
--extra-index-url https://download.pytorch.org/whl/cu128
|
||||
faster-whisper==1.1.1
|
||||
ctranslate2==4.6.0
|
||||
transformers==4.40.2
|
||||
pillow == 10.0.0
|
||||
PyAudioWPatch == 0.2.12.6
|
||||
|
||||
@@ -555,6 +555,18 @@ class Config:
|
||||
self._HOTKEYS[key] = value
|
||||
self.saveConfig(inspect.currentframe().f_code.co_name, self.HOTKEYS, immediate_save=True)
|
||||
|
||||
@property
|
||||
@json_serializable('PLUGINS_STATUS')
|
||||
def PLUGINS_STATUS(self):
|
||||
return self._PLUGINS_STATUS
|
||||
|
||||
@PLUGINS_STATUS.setter
|
||||
def PLUGINS_STATUS(self, value):
|
||||
if isinstance(value, list):
|
||||
if all(isinstance(item, dict) for item in value):
|
||||
self._PLUGINS_STATUS = value
|
||||
self.saveConfig(inspect.currentframe().f_code.co_name, self.PLUGINS_STATUS, immediate_save=True)
|
||||
|
||||
@property
|
||||
@json_serializable('MIC_AVG_LOGPROB')
|
||||
def MIC_AVG_LOGPROB(self):
|
||||
@@ -944,7 +956,7 @@ class Config:
|
||||
|
||||
def init_config(self):
|
||||
# Read Only
|
||||
self._VERSION = "3.0.5"
|
||||
self._VERSION = "3.1.0"
|
||||
if getattr(sys, 'frozen', False):
|
||||
self._PATH_LOCAL = os_path.dirname(sys.executable)
|
||||
else:
|
||||
@@ -1068,6 +1080,7 @@ class Config:
|
||||
"toggle_transcription_send": None,
|
||||
"toggle_transcription_receive": None,
|
||||
}
|
||||
self._PLUGINS_STATUS = []
|
||||
self._MIC_AVG_LOGPROB = -0.8
|
||||
self._MIC_NO_SPEECH_PROB = 0.6
|
||||
self._AUTO_SPEAKER_SELECT = True
|
||||
|
||||
@@ -321,7 +321,14 @@ class Controller:
|
||||
elif isinstance(message, str) and len(message) > 0:
|
||||
translation = []
|
||||
transliteration = []
|
||||
if model.detectRepeatReceiveMessage(message):
|
||||
if model.checkKeywords(message):
|
||||
self.run(
|
||||
200,
|
||||
self.run_mapping["word_filter"],
|
||||
{"message":f"Detected by word filter: {message}"},
|
||||
)
|
||||
return
|
||||
elif model.detectRepeatReceiveMessage(message):
|
||||
return
|
||||
elif config.ENABLE_TRANSLATION is False:
|
||||
pass
|
||||
@@ -447,11 +454,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
|
||||
@@ -1061,6 +1068,15 @@ class Controller:
|
||||
config.HOTKEYS = data
|
||||
return {"status":200, "result":config.HOTKEYS}
|
||||
|
||||
@staticmethod
|
||||
def getPluginsStatus(*args, **kwargs) -> dict:
|
||||
return {"status":200, "result":config.PLUGINS_STATUS}
|
||||
|
||||
@staticmethod
|
||||
def setPluginsStatus(data, *args, **kwargs) -> dict:
|
||||
config.PLUGINS_STATUS = data
|
||||
return {"status":200, "result":config.PLUGINS_STATUS}
|
||||
|
||||
@staticmethod
|
||||
def getSpeakerAvgLogprob(*args, **kwargs) -> dict:
|
||||
return {"status":200, "result":config.SPEAKER_AVG_LOGPROB}
|
||||
|
||||
@@ -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",
|
||||
@@ -190,6 +190,9 @@ mapping = {
|
||||
"/get/data/hotkeys": {"status": True, "variable":controller.getHotkeys},
|
||||
"/set/data/hotkeys": {"status": True, "variable":controller.setHotkeys},
|
||||
|
||||
"/get/data/plugins_status": {"status": True, "variable":controller.getPluginsStatus},
|
||||
"/set/data/plugins_status": {"status": True, "variable":controller.setPluginsStatus},
|
||||
|
||||
"/get/data/mic_avg_logprob": {"status": True, "variable":controller.getMicAvgLogprob},
|
||||
"/set/data/mic_avg_logprob": {"status": True, "variable":controller.setMicAvgLogprob},
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -24,6 +24,7 @@ class OSCHandler:
|
||||
self.osc_server_ip_address = ip_address
|
||||
self.http_port = None
|
||||
self.osc_server_port = None
|
||||
self.browser = None
|
||||
|
||||
def setOscIpAddress(self, ip_address:str) -> None:
|
||||
self.osc_ip_address = ip_address
|
||||
@@ -45,18 +46,26 @@ class OSCHandler:
|
||||
def getOSCParameterValue(self, address:str) -> Any:
|
||||
value = None
|
||||
try:
|
||||
browser = OSCQueryBrowser()
|
||||
sleep(1)
|
||||
service = browser.find_service_by_name(self.osc_server_name)
|
||||
# browserインスタンスを再利用し、毎回の生成と破棄を避ける
|
||||
if self.browser is None:
|
||||
self.browser = OSCQueryBrowser()
|
||||
sleep(1) # 初回のみスリープ
|
||||
|
||||
service = self.browser.find_service_by_name(self.osc_server_name)
|
||||
if service is not None:
|
||||
osc_query_client = OSCQueryClient(service)
|
||||
mute_self_node = osc_query_client.query_node(address)
|
||||
value = mute_self_node.value[0]
|
||||
browser.zc.close()
|
||||
browser.browser.cancel()
|
||||
|
||||
except Exception:
|
||||
errorLogging()
|
||||
# エラー発生時にbrowserをリセットして次回再初期化
|
||||
if self.browser is not None:
|
||||
try:
|
||||
self.browser.zc.close()
|
||||
self.browser.browser.cancel()
|
||||
except Exception:
|
||||
pass
|
||||
self.browser = None
|
||||
return value
|
||||
|
||||
def getOSCParameterMuteSelf(self) -> bool:
|
||||
@@ -82,7 +91,8 @@ class OSCHandler:
|
||||
sleep(1)
|
||||
|
||||
def oscServerServe(self) -> None:
|
||||
self.osc_server.serve_forever(2)
|
||||
# ポーリング間隔を長くして(2秒から10秒に)CPUの使用率を削減
|
||||
self.osc_server.serve_forever(10)
|
||||
|
||||
def oscServerStop(self) -> None:
|
||||
if isinstance(self.osc_server, osc_server.ThreadingOSCUDPServer):
|
||||
@@ -91,6 +101,14 @@ class OSCHandler:
|
||||
if isinstance(self.osc_query_service, OSCQueryService):
|
||||
self.osc_query_service.http_server.shutdown()
|
||||
self.osc_query_service = None
|
||||
# browserがある場合はクリーンアップ
|
||||
if self.browser is not None:
|
||||
try:
|
||||
self.browser.zc.close()
|
||||
self.browser.browser.cancel()
|
||||
except Exception:
|
||||
pass
|
||||
self.browser = None
|
||||
|
||||
if __name__ == "__main__":
|
||||
handler = OSCHandler()
|
||||
|
||||
3765
src-tauri/Cargo.lock
generated
@@ -7,17 +7,27 @@ edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
# The `_lib` suffix may seem redundant but it is necessary
|
||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||
name = "vrct_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1", features = [] }
|
||||
tauri-build = { version = "=2.2.0", 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"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
font-kit = "0.14.2"
|
||||
window-shadows = { git = "https://github.com/tauri-apps/window-shadows.git" }
|
||||
tauri = { version = "=2.5.1", features = ["devtools"] }
|
||||
tauri-plugin-opener = "=2.2.6"
|
||||
serde = { version = "=1.0.219", features = ["derive"] }
|
||||
serde_json = "=1.0.140"
|
||||
tauri-plugin-fs = "=2.2.1"
|
||||
tauri-plugin-http = "=2.4.3"
|
||||
tauri-plugin-shell = "2.2.1"
|
||||
font-kit = "=0.14.2"
|
||||
reqwest = "=0.12.15"
|
||||
base64 = "=0.22.1"
|
||||
|
||||
|
||||
[features]
|
||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-global-shortcut = "=2.2.0"
|
||||
|
||||
10
src-tauri/capabilities/default.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default"
|
||||
]
|
||||
}
|
||||
63
src-tauri/capabilities/vrct_capability.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "vrct-capability",
|
||||
"description": "VRCT main window capabilities",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:window:default",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-set-position",
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-set-always-on-top",
|
||||
"core:window:allow-maximize",
|
||||
"core:window:allow-unmaximize",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-unminimize",
|
||||
"core:window:allow-set-focus",
|
||||
|
||||
"global-shortcut:allow-is-registered",
|
||||
"global-shortcut:allow-register",
|
||||
"global-shortcut:allow-register-all",
|
||||
"global-shortcut:allow-unregister",
|
||||
"global-shortcut:allow-unregister-all",
|
||||
|
||||
"fs:default",
|
||||
"fs:allow-write-file",
|
||||
"fs:allow-remove",
|
||||
"fs:allow-resource-read-recursive",
|
||||
"fs:allow-resource-meta-recursive",
|
||||
{
|
||||
"identifier": "fs:scope",
|
||||
"allow": [
|
||||
{ "path": "$RESOURCE/plugins/**" },
|
||||
{ "path": "src-tauri/target/debug/plugins/**" }
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"identifier": "http:default",
|
||||
"allow": [
|
||||
{ "url": "https://api.github.com/repos/**" },
|
||||
{ "url": "https://github.com/**" },
|
||||
{ "url": "https://raw.githubusercontent.com/ShiinaSakamoto/vrct_plugins_list/main/vrct_plugins_list.json" },
|
||||
{ "url": "https://raw.githubusercontent.com/ShiinaSakamoto/vrct_plugins_list/main/dev_vrct_plugins_list.json" }
|
||||
]
|
||||
},
|
||||
|
||||
"shell:allow-open",
|
||||
"shell:allow-stdin-write",
|
||||
{
|
||||
"identifier": "shell:allow-spawn",
|
||||
"allow": [
|
||||
{ "name": "bin/VRCT-sidecar", "sidecar": true, "args": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "shell:allow-execute",
|
||||
"allow": [
|
||||
{ "name": "bin/VRCT-sidecar", "sidecar": true, "args": true }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -588,25 +588,9 @@ Section Install
|
||||
|
||||
!insertmacro CheckIfAppIsRunning
|
||||
|
||||
; ; Copy main executable
|
||||
; File "${MAINBINARYSRCPATH}"
|
||||
|
||||
; ; Copy resources
|
||||
; {{#each resources_dirs}}
|
||||
; CreateDirectory "$INSTDIR\\{{this}}"
|
||||
; {{/each}}
|
||||
; {{#each resources}}
|
||||
; File /a "/oname={{this.[1]}}" "{{unescape-dollar-sign @key}}"
|
||||
; {{/each}}
|
||||
|
||||
; ; Copy external binaries
|
||||
; {{#each binaries}}
|
||||
; File /a "/oname={{this}}" "{{unescape-dollar-sign @key}}"
|
||||
; {{/each}}
|
||||
|
||||
!addplugindir "..\..\..\..\nsis\plugins\x86-unicode"
|
||||
; 指定のURLからファイルをダウンロード
|
||||
!define SOFTWARE_RELEASE_URL "https://huggingface.co/misyaguziya/VRCT/resolve/main"
|
||||
!define SOFTWARE_RELEASE_URL "https://huggingface.co/ms-software/VRCT/resolve/main"
|
||||
!define SOFTWARE_DOWNLOAD_FILENAME "VRCT.zip"
|
||||
Var /GLOBAL i
|
||||
Var /GLOBAL cmder_dl
|
||||
|
||||
1
src-tauri/plugins/index.js
Normal file
@@ -0,0 +1 @@
|
||||
// This is for preserving plugins folder. It will be detected and created 'plugins' folder to target/debug/ by tauri build. do not delete it.
|
||||
64
src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use tauri::Manager;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
let _main_window = app.get_webview_window("main").unwrap(); // `main_window` is declared here for all builds
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{ _main_window.open_devtools(); }
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![get_font_list, download_zip_asset])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
|
||||
use font_kit::{source::SystemSource};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_font_list() -> Vec<String> {
|
||||
let source = SystemSource::new();
|
||||
let mut font_families = HashSet::new();
|
||||
|
||||
if let Ok(fonts) = source.all_fonts() {
|
||||
for font in fonts {
|
||||
if let Ok(info) = font.load() {
|
||||
font_families.insert(info.family_name().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
font_families.into_iter().collect()
|
||||
}
|
||||
|
||||
|
||||
use base64::engine::general_purpose::STANDARD as BASE64;
|
||||
use base64::Engine;
|
||||
|
||||
#[tauri::command]
|
||||
async fn download_zip_asset(url: String) -> Result<String, String> {
|
||||
use reqwest;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
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))?;
|
||||
|
||||
Ok(BASE64.encode(&bytes))
|
||||
}
|
||||
@@ -1,48 +1,6 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use tauri::Manager;
|
||||
use window_shadows::set_shadow;
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
let main_window = app.get_window("main").unwrap(); // `main_window` is declared here for all builds
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{ main_window.open_devtools(); }
|
||||
|
||||
#[cfg(any(windows, target_os = "macos"))]
|
||||
set_shadow(main_window, true).unwrap();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.on_window_event(|event| { // This is for fix the bug that the window scaling issue when dragging between monitors.
|
||||
if let tauri::WindowEvent::ScaleFactorChanged { new_inner_size, .. } = event.event() {
|
||||
event.window().set_size(tauri::Size::Physical(*new_inner_size)).unwrap();
|
||||
}
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![get_font_list])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
vrct_lib::run()
|
||||
}
|
||||
|
||||
|
||||
use font_kit::{source::SystemSource};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_font_list() -> Vec<String> {
|
||||
let source = SystemSource::new();
|
||||
let mut font_families = HashSet::new();
|
||||
|
||||
if let Ok(fonts) = source.all_fonts() {
|
||||
for font in fonts {
|
||||
if let Ok(info) = font.load() {
|
||||
font_families.insert(info.family_name().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
font_families.into_iter().collect()
|
||||
}
|
||||
@@ -1,44 +1,16 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "VRCT",
|
||||
"version": "3.0.0",
|
||||
"identifier": "com.vrct.app",
|
||||
"build": {
|
||||
"beforeDevCommand": "",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"beforeBuildCommand": "",
|
||||
"devPath": "http://localhost:1420",
|
||||
"distDir": "../dist"
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"package": {
|
||||
"productName": "VRCT",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": false,
|
||||
"window": {
|
||||
"all": false,
|
||||
"setAlwaysOnTop": true,
|
||||
"setFocus": true,
|
||||
"setDecorations": true,
|
||||
"close": true,
|
||||
"hide": true,
|
||||
"setPosition": true,
|
||||
"setSize": true,
|
||||
"maximize": true,
|
||||
"minimize": true,
|
||||
"unmaximize": true,
|
||||
"unminimize": true,
|
||||
"startDragging": true
|
||||
},
|
||||
"globalShortcut": {
|
||||
"all": true
|
||||
},
|
||||
"shell": {
|
||||
"all": false,
|
||||
"open": true,
|
||||
"sidecar": true,
|
||||
"scope": [
|
||||
{ "name": "bin/VRCT-sidecar", "sidecar": true, "args": true }
|
||||
]
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"enableGTKAppId": false,
|
||||
"windows": [{
|
||||
"title": "VRCT",
|
||||
"center": true,
|
||||
@@ -47,36 +19,40 @@
|
||||
"minWidth": 400,
|
||||
"minHeight": 200,
|
||||
"transparent": true,
|
||||
"decorations": false
|
||||
"decorations": false,
|
||||
"shadow": false
|
||||
}],
|
||||
"security": { "csp": null },
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "nsis",
|
||||
"identifier": "com.vrct.dev",
|
||||
"publisher": "m's software",
|
||||
"copyright": "Copyright m's software",
|
||||
"shortDescription": "VRCT",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"externalBin": [
|
||||
"bin/VRCT-sidecar"
|
||||
],
|
||||
"resources": {
|
||||
"bin/_internal": "_internal"
|
||||
},
|
||||
"windows": {
|
||||
"nsis": {
|
||||
"template": "nsis/template.nsi",
|
||||
"license": "../LICENSE",
|
||||
"installMode": "currentUser",
|
||||
"displayLanguageSelector": true
|
||||
}
|
||||
"security": {
|
||||
"csp": null,
|
||||
"capabilities": ["default", "vrct-capability"]
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "nsis",
|
||||
"publisher": "m's software",
|
||||
"copyright": "Copyright m's software",
|
||||
"licenseFile": "../LICENSE",
|
||||
"shortDescription": "VRCT",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"externalBin": [
|
||||
"bin/VRCT-sidecar"
|
||||
],
|
||||
"resources": {
|
||||
"bin/_internal": "_internal",
|
||||
"plugins": "plugins"
|
||||
},
|
||||
"windows": {
|
||||
"nsis": {
|
||||
"template": "nsis/template.nsi",
|
||||
"installMode": "currentUser",
|
||||
"displayLanguageSelector": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
UiSizeController,
|
||||
FontFamilyController,
|
||||
TransparencyController,
|
||||
CornerRadiusController,
|
||||
PluginsController,
|
||||
} from "./_app_controllers/index.js";
|
||||
|
||||
import { WindowTitleBar } from "./window_title_bar/WindowTitleBar";
|
||||
@@ -20,39 +22,45 @@ import { ModalController } from "./modal_controller/ModalController";
|
||||
import { SnackbarController } from "./snackbar_controller/SnackbarController";
|
||||
import styles from "./App.module.scss";
|
||||
import { useIsBackendReady, useIsSoftwareUpdating, useIsVrctAvailable, useWindow } from "@logics_common";
|
||||
import { AppErrorBoundary } from "./error_boundary/AppErrorBoundary";
|
||||
|
||||
export const App = () => {
|
||||
const { currentIsVrctAvailable } = useIsVrctAvailable();
|
||||
const { currentIsBackendReady } = useIsBackendReady();
|
||||
const { WindowGeometryController } = useWindow();
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<KeyEventController />
|
||||
<StartPythonController />
|
||||
<GlobalHotKeyController />
|
||||
<UiLanguageController />
|
||||
<ConfigPageCloseTriggerController />
|
||||
<UiSizeController />
|
||||
<FontFamilyController />
|
||||
<TransparencyController />
|
||||
<WindowGeometryController />
|
||||
<AppErrorBoundary >
|
||||
<KeyEventController />
|
||||
<StartPythonController />
|
||||
<GlobalHotKeyController />
|
||||
<UiLanguageController />
|
||||
<ConfigPageCloseTriggerController />
|
||||
<UiSizeController />
|
||||
<FontFamilyController />
|
||||
<TransparencyController />
|
||||
<CornerRadiusController />
|
||||
|
||||
{(currentIsBackendReady.data === false || currentIsVrctAvailable.data === false)
|
||||
? <SplashComponent />
|
||||
: <Contents key={i18n.language}/>
|
||||
}
|
||||
{(currentIsBackendReady.data === false || currentIsVrctAvailable.data === false)
|
||||
? <SplashComponent />
|
||||
: <Contents key={i18n.language} />
|
||||
}
|
||||
|
||||
<SnackbarController />
|
||||
<SnackbarController />
|
||||
</AppErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Contents = () => {
|
||||
const { WindowGeometryController } = useWindow();
|
||||
const { currentIsSoftwareUpdating } = useIsSoftwareUpdating();
|
||||
return (
|
||||
<>
|
||||
<WindowGeometryController />
|
||||
<PluginsController />
|
||||
|
||||
<WindowTitleBar />
|
||||
{currentIsSoftwareUpdating.data === false
|
||||
?
|
||||
|
||||
53
src-ui/app/_app_controllers/CornerRadiusController.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
|
||||
export const CornerRadiusController = () => {
|
||||
const [is_win11, setIsWin11] = useState(false);
|
||||
const [is_maximized, setIsMaximized] = useState(false);
|
||||
|
||||
// OS 判定(Win11 なら platformVersion の major ≥13)
|
||||
useEffect(() => {
|
||||
if (navigator.userAgentData?.getHighEntropyValues) {
|
||||
navigator.userAgentData
|
||||
.getHighEntropyValues(["platformVersion"])
|
||||
.then(({ platformVersion }) => {
|
||||
const major = parseInt(platformVersion.split(".")[0], 10);
|
||||
setIsWin11(major >= 13);
|
||||
})
|
||||
.catch(() => {
|
||||
setIsWin11(false);
|
||||
})
|
||||
} else {
|
||||
// フォールバックで Win10 扱い
|
||||
setIsWin11(false);
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
let unlisten;
|
||||
const setup = async () => {
|
||||
const window = await getCurrentWindow();
|
||||
// 初期状態取得
|
||||
setIsMaximized(await window.isMaximized());
|
||||
// リサイズ時にも再取得
|
||||
const updateMax = () => window.isMaximized().then(setIsMaximized);
|
||||
unlisten = await window.listen("tauri://resize", updateMax);
|
||||
}
|
||||
setup();
|
||||
|
||||
return () => {
|
||||
if (unlisten) {
|
||||
unlisten();
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 角丸の適用
|
||||
useEffect(() => {
|
||||
const radius = is_win11 && !is_maximized ? "10px" : "0";
|
||||
document.body.style.borderRadius = radius;
|
||||
}, [is_win11, is_maximized]);
|
||||
|
||||
return null;;
|
||||
}
|
||||
24
src-ui/app/_app_controllers/PluginsController.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import * as reactI18next from "react-i18next";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.React = React;
|
||||
window.clsx = clsx;
|
||||
window.reactI18next = reactI18next;
|
||||
}
|
||||
|
||||
import { LoadPluginsController } from "./plugins_controllers/LoadPluginsController";
|
||||
import { FetchLatestPluginsDataController } from "./plugins_controllers/FetchLatestPluginsDataController";
|
||||
import { MergePluginsController } from "./plugins_controllers/MergePluginsController";
|
||||
|
||||
export const PluginsController = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<MergePluginsController />
|
||||
<LoadPluginsController />
|
||||
<FetchLatestPluginsDataController />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useStartPython } from "@logics/useStartPython";
|
||||
import { useStdoutToPython } from "@logics/useStdoutToPython";
|
||||
|
||||
@@ -5,4 +5,6 @@ export { UiLanguageController } from "./UiLanguageController";
|
||||
export { ConfigPageCloseTriggerController } from "./ConfigPageCloseTriggerController";
|
||||
export { UiSizeController } from "./UiSizeController";
|
||||
export { FontFamilyController } from "./FontFamilyController";
|
||||
export { TransparencyController } from "./TransparencyController";
|
||||
export { TransparencyController } from "./TransparencyController";
|
||||
export { PluginsController } from "./PluginsController";
|
||||
export { CornerRadiusController } from "./CornerRadiusController";
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useEffect } from "react";
|
||||
import { usePlugins } from "@logics_configs";
|
||||
|
||||
export const FetchLatestPluginsDataController = () => {
|
||||
const {
|
||||
asyncFetchPluginsInfo,
|
||||
isAnyPluginEnabled_Init,
|
||||
} = usePlugins();
|
||||
|
||||
useEffect(() => {
|
||||
if (isAnyPluginEnabled_Init()) {
|
||||
asyncFetchPluginsInfo();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useEffect } from "react";
|
||||
import { usePlugins } from "@logics_configs";
|
||||
import { store } from "@store";
|
||||
|
||||
export const LoadPluginsController = () => {
|
||||
const {
|
||||
asyncLoadAllPlugins,
|
||||
} = usePlugins();
|
||||
|
||||
const asyncInitLoadPlugins = async () => {
|
||||
try {
|
||||
await asyncLoadAllPlugins();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!store.is_initialized_load_plugin) {
|
||||
asyncInitLoadPlugins();
|
||||
store.is_initialized_load_plugin = true;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,205 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { store } from "@store";
|
||||
import { usePlugins } from "@logics_configs";
|
||||
import { useSoftwareVersion } from "@logics_common";
|
||||
import { useNotificationStatus } from "@logics_common";
|
||||
|
||||
export const MergePluginsController = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
currentLoadedPlugins,
|
||||
updatePluginsData,
|
||||
currentPluginsData,
|
||||
currentFetchedPluginsInfo,
|
||||
currentSavedPluginsStatus,
|
||||
downloadAndExtractPlugin,
|
||||
setTargetSavedPluginsStatus_Init,
|
||||
} = usePlugins();
|
||||
const { checkVrctVerCompatibility } = useSoftwareVersion();
|
||||
const { showNotification_Success, showNotification_Error } = useNotificationStatus();
|
||||
|
||||
// downloaded, fetched, saved の各情報をまとめてマージ
|
||||
useEffect(() => {
|
||||
const mergePluginData = () => {
|
||||
updatePluginsData(prev => {
|
||||
// downloaded, fetched, 保存済み状態のMapをそれぞれ作成(plugin_id をキー)
|
||||
const downloaded_map = new Map(
|
||||
currentLoadedPlugins.data.map(info => [info.plugin_id, info])
|
||||
);
|
||||
const fetched_map = new Map(
|
||||
currentFetchedPluginsInfo.data.map(info => [info.plugin_id, info])
|
||||
);
|
||||
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])
|
||||
);
|
||||
|
||||
// union_keys: Saved以外の情報に対して重複なくキーを取得する
|
||||
const union_keys = new Set([
|
||||
...downloaded_map.keys(),
|
||||
...fetched_map.keys(),
|
||||
...prev_map.keys(),
|
||||
]);
|
||||
|
||||
const new_data = [];
|
||||
for (const id of union_keys) {
|
||||
const downloaded = downloaded_map.get(id);
|
||||
const fetched = fetched_map.get(id);
|
||||
const prev_plugin = prev_map.get(id);
|
||||
let plugin = {};
|
||||
|
||||
if (downloaded) {
|
||||
// ダウンロード済み情報に対してサポート確認
|
||||
const { is_plugin_supported, is_plugin_supported_latest_vrct } =
|
||||
checkVrctVerCompatibility(
|
||||
downloaded.min_supported_vrct_version,
|
||||
downloaded.max_supported_vrct_version
|
||||
);
|
||||
plugin = {
|
||||
// prevの情報があれば引き継ぎつつ上書き
|
||||
...(prev_plugin || {}),
|
||||
plugin_id: downloaded.plugin_id,
|
||||
component: downloaded.component,
|
||||
is_downloaded: true,
|
||||
downloaded_plugin_info: {
|
||||
...downloaded,
|
||||
is_plugin_supported,
|
||||
is_plugin_supported_latest_vrct,
|
||||
},
|
||||
};
|
||||
|
||||
if (fetched) {
|
||||
const is_latest_version_available =
|
||||
(downloaded.plugin_version !== fetched.plugin_version && fetched.is_plugin_supported);
|
||||
plugin = {
|
||||
...plugin,
|
||||
is_outdated: false,
|
||||
latest_plugin_info: { ...fetched },
|
||||
is_latest_version_available:
|
||||
plugin.is_downloaded && is_latest_version_available,
|
||||
is_latest_version_already:
|
||||
downloaded.plugin_version === fetched.plugin_version,
|
||||
};
|
||||
} else {
|
||||
// フェッチ情報がない場合の初期状態
|
||||
plugin = {
|
||||
...plugin,
|
||||
is_latest_version_available: false,
|
||||
is_latest_version_already: true,
|
||||
};
|
||||
}
|
||||
} else if (fetched) {
|
||||
// フェッチ情報のみの場合は、ダウンロードしていない初期状態
|
||||
plugin = {
|
||||
...(prev_plugin || {}),
|
||||
plugin_id: fetched.plugin_id,
|
||||
is_downloaded: false,
|
||||
is_latest_version_available: fetched.is_plugin_supported,
|
||||
is_latest_version_already: false,
|
||||
is_outdated: false,
|
||||
latest_plugin_info: { ...fetched },
|
||||
};
|
||||
} else if (prev_plugin) {
|
||||
// 既存情報のみ存在する場合は outdated フラグを付与
|
||||
plugin = { ...prev_plugin, is_outdated: true };
|
||||
}
|
||||
// いずれかの情報がある場合のみ new_data に追加
|
||||
if (plugin.plugin_id) {
|
||||
new_data.push(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存済み状態(currentSavedPluginsStatus)のマージ
|
||||
// ・new_dataに存在する各プラグインに対して、保存済みの is_enabled を上書き
|
||||
new_data.forEach(plugin => {
|
||||
if (saved_map.has(plugin.plugin_id)) {
|
||||
plugin.is_enabled = saved_map.get(plugin.plugin_id).is_enabled;
|
||||
}
|
||||
});
|
||||
// ・prev.data には存在せず、保存済み情報にのみある場合は追加
|
||||
for (const [id, saved] of saved_map.entries()) {
|
||||
if (!new_data.some(item => item.plugin_id === id)) {
|
||||
new_data.push({ plugin_id: saved.plugin_id, is_enabled: saved.is_enabled });
|
||||
}
|
||||
}
|
||||
|
||||
return new_data;
|
||||
});
|
||||
};
|
||||
|
||||
mergePluginData();
|
||||
}, [currentFetchedPluginsInfo.data, currentLoadedPlugins.data, currentSavedPluginsStatus]);
|
||||
|
||||
|
||||
|
||||
// --- 自動アップデート(ダウンロード処理)---
|
||||
// ※downloadAndExtractPlugin の重複実行を防ぐため、実行中の plugin_id を useRef で管理
|
||||
const downloadingRef = useRef(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentPluginsData.data.length) return;
|
||||
// マージ結果の currentPluginsData.data を元にダウンロード処理をチェック
|
||||
currentPluginsData.data.forEach(plugin => {
|
||||
if (plugin.is_downloaded &&
|
||||
plugin.is_enabled &&
|
||||
!plugin.downloaded_plugin_info.is_plugin_supported &&
|
||||
!plugin.is_latest_version_already &&
|
||||
plugin.is_latest_version_available
|
||||
) {
|
||||
if (!downloadingRef.current.has(plugin.plugin_id)) {
|
||||
showNotification_Success(t("plugin_notifications.updating"));
|
||||
downloadingRef.current.add(plugin.plugin_id);
|
||||
const target_plugin_id = plugin.plugin_id;
|
||||
downloadAndExtractPlugin(plugin)
|
||||
.then(() => {
|
||||
console.log(`Plugin ${target_plugin_id} updated successfully`);
|
||||
downloadingRef.current.delete(target_plugin_id);
|
||||
showNotification_Success(t("plugin_notifications.updated_success"));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Plugin ${target_plugin_id} update failed`, error);
|
||||
downloadingRef.current.delete(target_plugin_id);
|
||||
showNotification_Error(t("plugin_notifications.updated_error"));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [currentPluginsData.data]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
// ダウンロード済みかつ有効なプラグインで、サポート対象でない場合は無効化
|
||||
if (store.is_initialized_fetched_plugin_info) {
|
||||
updatePluginsData(prev => {
|
||||
prev.data.forEach(plugin => {
|
||||
if (plugin.is_downloaded && plugin.is_enabled) {
|
||||
if (
|
||||
!plugin.downloaded_plugin_info.is_plugin_supported &&
|
||||
plugin.latest_plugin_info &&
|
||||
!plugin.latest_plugin_info?.is_plugin_supported
|
||||
) {
|
||||
showNotification_Error(t("plugin_notifications.disabled_out_of_support"));
|
||||
plugin.is_enabled = false;
|
||||
setTargetSavedPluginsStatus_Init(plugin.plugin_id, false);
|
||||
}
|
||||
|
||||
if (
|
||||
!plugin.downloaded_plugin_info.is_plugin_supported &&
|
||||
plugin.is_outdated
|
||||
) {
|
||||
showNotification_Error(t("plugin_notifications.disabled_out_of_support"));
|
||||
plugin.is_enabled = false;
|
||||
setTargetSavedPluginsStatus_Init(plugin.plugin_id, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
return prev.data;
|
||||
});
|
||||
}
|
||||
}, [store.is_initialized_fetched_plugin_info]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -24,7 +24,8 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
font-family: var(--font_family); /* If not found the font family where 'root:' that is selected by user*/
|
||||
border-radius: 1.8rem;
|
||||
border-radius: 10px; /* fixed by px */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
--primary_600_color_44: #36877744;
|
||||
|
||||
--sent_400_color: #6197b4;
|
||||
--sent_300_color: #6197b4;
|
||||
--received_300_color: #a861b4;
|
||||
--error_bc_color: #bb4448;
|
||||
--error_bc_active_color: #9c3938;
|
||||
@@ -65,4 +66,5 @@
|
||||
--config_page_topbar_height: 8rem;
|
||||
|
||||
--font_family: "Yu Gothic UI";
|
||||
}
|
||||
}
|
||||
/* https://m2.material.io/design/color/the-color-system.html#tools-for-picking-colors */
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
AdvancedSettings,
|
||||
Vr,
|
||||
Hotkeys,
|
||||
// Plugins,
|
||||
Supporters,
|
||||
AboutVrct,
|
||||
} from "@setting_box";
|
||||
@@ -32,6 +33,8 @@ export const SettingBox = () => {
|
||||
return <Hotkeys />;
|
||||
case "advanced_settings":
|
||||
return <AdvancedSettings />;
|
||||
// case "plugins":
|
||||
// return <Plugins />;
|
||||
case "supporters":
|
||||
return <Supporters />;
|
||||
case "about_vrct":
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
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>
|
||||
);
|
||||
case option.update_button:
|
||||
return (
|
||||
<button
|
||||
className={styles.update_button}
|
||||
onClick={() => props.downloadStartFunction(option.id)}
|
||||
>
|
||||
<p className={styles.download_button_label}>Update</p>
|
||||
</button>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return <div className={styles.download_container}>{renderContent()}</div>;
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
@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;
|
||||
border-radius: 0.2rem;
|
||||
&: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;
|
||||
}
|
||||
|
||||
.update_button {
|
||||
pointer-events: auto;
|
||||
background-color: var(--primary_400_color);
|
||||
padding: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
border-radius: 0.2rem;
|
||||
&:hover {
|
||||
background-color: var(--primary_450_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--primary_500_color);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import styles from "./HotkeysEntry.module.scss";
|
||||
import { _Entry } from "../_atoms/_entry/_Entry";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import DeleteSvg from "@images/cancel.svg?react";
|
||||
import { clsx } from "clsx";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const HotkeysEntry = (props) => {
|
||||
const [isAcceptingInput, setIsAcceptingInput] = useState(false);
|
||||
|
||||
@@ -12,4 +12,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 { PluginsControlComponent } from "./plugins_control_component/PluginsControlComponent";
|
||||
@@ -0,0 +1,156 @@
|
||||
import React from "react";
|
||||
import { SwitchBox } from "../index";
|
||||
import { _DownloadButton } from "../_atoms/_download_button/_DownloadButton";
|
||||
import styles from "./PluginsControlComponent.module.scss";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const PluginsControlComponent = ({
|
||||
variable_state,
|
||||
plugin_status,
|
||||
toggleFunction,
|
||||
downloadStartFunction,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const option = {
|
||||
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 downloaded_version = plugin_status.downloaded_plugin_info?.plugin_version;
|
||||
const latest_version = plugin_status.latest_plugin_info?.plugin_version;
|
||||
|
||||
const downloaded_version_label = t("config_page.plugins.downloaded_version",
|
||||
{ downloaded_version: downloaded_version }
|
||||
);
|
||||
const latest_version_label = t("config_page.plugins.latest_version",
|
||||
{ latest_version: latest_version }
|
||||
);
|
||||
|
||||
if (plugin_status.is_downloaded) {
|
||||
return (
|
||||
<DownloadedPluginControl
|
||||
option={option}
|
||||
plugin_status={plugin_status}
|
||||
toggleFunction={toggleFunction}
|
||||
downloadStartFunction={downloadStartFunction}
|
||||
downloaded_version_label={downloaded_version_label}
|
||||
latest_version_label={latest_version_label}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<NotDownloadedPluginControl
|
||||
option={option}
|
||||
plugin_status={plugin_status}
|
||||
downloadStartFunction={downloadStartFunction}
|
||||
downloaded_version_label={downloaded_version_label}
|
||||
latest_version_label={latest_version_label}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const DownloadedPluginControl = ({
|
||||
option,
|
||||
plugin_status,
|
||||
toggleFunction,
|
||||
downloadStartFunction,
|
||||
downloaded_version_label,
|
||||
latest_version_label,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const togglePlugin = () => {
|
||||
toggleFunction(plugin_status.plugin_id);
|
||||
};
|
||||
|
||||
if (!plugin_status.downloaded_plugin_info.is_plugin_supported) {
|
||||
if (plugin_status.is_latest_version_available) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{downloaded_version_label}</p>
|
||||
<p>{latest_version_label}</p>
|
||||
<p>{t("config_page.plugins.available_after_updating")}</p>
|
||||
<_DownloadButton option={option} downloadStartFunction={downloadStartFunction} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{t("config_page.plugins.unavailable_downloaded")}</p>
|
||||
</div>
|
||||
);
|
||||
} else if (plugin_status.is_outdated) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{t("config_page.plugins.no_latest_info")}</p>
|
||||
<SwitchBox variable={option} toggleFunction={togglePlugin} />
|
||||
</div>
|
||||
);
|
||||
} else if (plugin_status.is_latest_version_already) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{latest_version_label}</p>
|
||||
<p>{t("config_page.plugins.using_latest_version")}</p>
|
||||
<SwitchBox variable={option} toggleFunction={togglePlugin} />
|
||||
</div>
|
||||
);
|
||||
} else if (plugin_status.is_latest_version_available) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{latest_version_label}</p>
|
||||
<p>{t("config_page.plugins.available_latest_version")}</p>
|
||||
<_DownloadButton option={option} downloadStartFunction={downloadStartFunction} />
|
||||
<SwitchBox variable={option} toggleFunction={togglePlugin} />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{t("config_page.plugins.available_latest_version")}</p>
|
||||
<SwitchBox variable={option} toggleFunction={togglePlugin} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const NotDownloadedPluginControl = ({
|
||||
option,
|
||||
plugin_status,
|
||||
downloadStartFunction,
|
||||
downloaded_version_label,
|
||||
latest_version_label,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (plugin_status.is_latest_version_available) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{latest_version_label}</p>
|
||||
<_DownloadButton option={option} downloadStartFunction={downloadStartFunction} />
|
||||
</div>
|
||||
);
|
||||
} else if (plugin_status.latest_plugin_info?.is_plugin_supported_latest_vrct) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{latest_version_label}</p>
|
||||
<p>{t("config_page.plugins.available_in_latest_vrct_version")}</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p>{latest_version_label}</p>
|
||||
<p>{t("config_page.plugins.unavailable_not_downloaded")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.unavailable_text {
|
||||
padding: 1rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import styles from "./Slider.module.scss";
|
||||
import MUI_Slider from "@mui/material/Slider";
|
||||
import { clsx } from "clsx";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const Slider = (props) => {
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
@import "@scss_mixins";
|
||||
|
||||
.switchbox_container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
@@ -22,6 +21,7 @@
|
||||
}
|
||||
|
||||
.toggle_control {
|
||||
position: relative;
|
||||
@include toggle_control_styles;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -2,6 +2,8 @@ import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styles from "./AdvancedSettings.module.scss";
|
||||
|
||||
import { Plugins } from "./plugins/Plugins";
|
||||
|
||||
import { useOpenFolder } from "@logics_common";
|
||||
import {
|
||||
useOscIpAddress,
|
||||
@@ -10,22 +12,35 @@ import {
|
||||
|
||||
import {
|
||||
ActionButtonContainer,
|
||||
EntryContainer,
|
||||
EntryWithSaveButtonContainer,
|
||||
} from "../_templates/Templates";
|
||||
|
||||
import {
|
||||
SectionLabelComponent,
|
||||
} from "../_components/";
|
||||
|
||||
|
||||
import OpenFolderSvg from "@images/open_folder.svg?react";
|
||||
import HelpSvg from "@images/help.svg?react";
|
||||
|
||||
export const AdvancedSettings = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<OscIpAddressContainer />
|
||||
<OscPortContainer />
|
||||
<OpenConfigFolderContainer />
|
||||
<OpenSwitchComputeDeviceModalContainer />
|
||||
</>
|
||||
<div className={styles.container}>
|
||||
<div>
|
||||
|
||||
<OscIpAddressContainer />
|
||||
<OscPortContainer />
|
||||
<OpenConfigFolderContainer />
|
||||
<OpenSwitchComputeDeviceModalContainer />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SectionLabelComponent label={t("config_page.advanced_settings.section_label_plugins")} />
|
||||
<Plugins />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,22 +1,5 @@
|
||||
.container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
&.flex_column {
|
||||
flex-direction: column;
|
||||
}
|
||||
border-bottom: solid 0.1rem var(--dark_800_color);
|
||||
}
|
||||
|
||||
.switch_section_container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
gap: 6.4rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { usePlugins } from "@logics_configs";
|
||||
import styles from "./Plugins.module.scss";
|
||||
import { PluginsControlComponent } from "../../_components/plugins_control_component/PluginsControlComponent";
|
||||
import { useNotificationStatus } from "@logics_common";
|
||||
import ExternalLink from "@images/external_link.svg?react";
|
||||
|
||||
export const Plugins = () => {
|
||||
const {
|
||||
asyncFetchPluginsInfo,
|
||||
} = usePlugins();
|
||||
const hasRunRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasRunRef.current) {
|
||||
asyncFetchPluginsInfo();
|
||||
}
|
||||
return () => hasRunRef.current = true;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<PluginDownloadContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PluginDownloadContainer = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const {
|
||||
downloadAndExtractPlugin,
|
||||
currentPluginsData,
|
||||
currentSavedPluginsStatus,
|
||||
toggleSavedPluginsStatus,
|
||||
handlePendingPlugin,
|
||||
currentFetchedPluginsInfo,
|
||||
} = usePlugins();
|
||||
const { showNotification_Success, showNotification_Error } = useNotificationStatus();
|
||||
|
||||
// ダウンロード開始時の状態更新処理
|
||||
const downloadStartFunction = async (target_plugin_id) => {
|
||||
handlePendingPlugin(target_plugin_id, true);
|
||||
showNotification_Success(t("plugin_notifications.downloading"));
|
||||
|
||||
const target_plugin_info = currentPluginsData.data.find(
|
||||
(d) => d.plugin_id === target_plugin_id
|
||||
);
|
||||
downloadAndExtractPlugin(target_plugin_info).then(() => {
|
||||
handlePendingPlugin(target_plugin_id, false);
|
||||
showNotification_Success(t("plugin_notifications.downloaded_success"));
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
showNotification_Error(t("plugin_notifications.downloaded_error"));
|
||||
});
|
||||
};
|
||||
|
||||
// プラグインのオンオフ切り替え処理
|
||||
const toggleFunction = (target_plugin_id) => {
|
||||
toggleSavedPluginsStatus(target_plugin_id);
|
||||
};
|
||||
|
||||
const variable_state = currentSavedPluginsStatus.state;
|
||||
|
||||
const filtered_plugins_data = currentPluginsData.data.filter(plugin => !plugin.is_outdated)
|
||||
|
||||
// plugin_id で ABC 順にソート
|
||||
const sorted_plugins_data = filtered_plugins_data.sort((a, b) =>
|
||||
a.plugin_id.localeCompare(b.plugin_id)
|
||||
);
|
||||
|
||||
// Duplicate
|
||||
const is_failed_to_fetch = currentFetchedPluginsInfo.state === "error";
|
||||
const is_fetching = currentFetchedPluginsInfo.state === "pending";
|
||||
|
||||
return (
|
||||
<div className={styles.plugins_list_container}>
|
||||
{is_failed_to_fetch && <p>Failed to fetch plugins data</p>}
|
||||
{is_fetching && <p>Fetching plugins data...</p>}
|
||||
{sorted_plugins_data.map((plugin) => {
|
||||
const target_info = plugin.is_downloaded
|
||||
? plugin.downloaded_plugin_info
|
||||
: plugin.latest_plugin_info;
|
||||
|
||||
const target_locale = target_info.locales && target_info.locales[i18n.language]
|
||||
? target_info.locales[i18n.language]
|
||||
: {
|
||||
title: target_info.title,
|
||||
desc: target_info.desc || null,
|
||||
};
|
||||
const homepage_link = plugin.latest_plugin_info?.homepage_link;
|
||||
|
||||
return (
|
||||
<div key={plugin.plugin_id} className={styles.plugin_wrapper}>
|
||||
<div className={styles.labels_wrapper}>
|
||||
<p className={styles.title}>
|
||||
{target_locale.title}
|
||||
</p>
|
||||
<p className={styles.desc}>
|
||||
{target_locale.desc}
|
||||
</p>
|
||||
{/* <p className={styles.plugin_id}>{plugin.plugin_id}</p> */}
|
||||
{homepage_link && <HomepageLinkButton homepage_link={homepage_link}/>
|
||||
}
|
||||
</div>
|
||||
<div className={styles.plugin_info_wrapper}>
|
||||
{plugin.is_error ? (
|
||||
<p style={{ color: "red" }}>Error: {plugin.error_message}</p>
|
||||
) : (
|
||||
<PluginsControlComponent
|
||||
variable_state={variable_state}
|
||||
toggleFunction={toggleFunction}
|
||||
downloadStartFunction={downloadStartFunction}
|
||||
plugin_status={plugin}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const HomepageLinkButton = ({ homepage_link, speed = 40 /* px/s */ }) => {
|
||||
const containerRef = useRef(null);
|
||||
const textRef = useRef(null);
|
||||
const [inlineStyle, setInlineStyle] = useState({});
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
const container = containerRef.current;
|
||||
const text = textRef.current;
|
||||
if (!container || !text) return;
|
||||
const overflow = text.scrollWidth - container.clientWidth;
|
||||
if (overflow > 0) {
|
||||
const duration = overflow / speed;
|
||||
setInlineStyle({
|
||||
transform: `translateX(-${overflow}px)`,
|
||||
transition: `transform ${duration}s linear`,
|
||||
});
|
||||
}
|
||||
}, [speed]);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setInlineStyle({
|
||||
transform: 'translateX(0)',
|
||||
transition: 'transform 0.3s ease-out',
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.open_homepage_button_wrapper}>
|
||||
<a
|
||||
className={styles.open_homepage_button}
|
||||
href={homepage_link}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<div className={styles.text_container} ref={containerRef}>
|
||||
<p
|
||||
className={styles.open_homepage_text}
|
||||
ref={textRef}
|
||||
style={inlineStyle}
|
||||
>
|
||||
{homepage_link}
|
||||
</p>
|
||||
</div>
|
||||
<ExternalLink className={styles.external_link_svg} />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
.container {
|
||||
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;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
gap: 2rem;
|
||||
&:not(:last-child) {
|
||||
border-bottom: 0.1rem solid var(--dark_750_color);
|
||||
}
|
||||
}
|
||||
|
||||
.labels_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.plugin_info_wrapper {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.6rem;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.desc {
|
||||
font-size: 1.4rem;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
// .plugin_id {
|
||||
// font-size: 1rem;
|
||||
// color: var(--dark_600_color);
|
||||
// width: 100%;
|
||||
// overflow: hidden;
|
||||
// white-space: nowrap;
|
||||
// text-overflow: ellipsis;
|
||||
// }
|
||||
|
||||
.open_homepage_button_wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.open_homepage_button {
|
||||
padding: 0.6rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
&:hover .external_link_svg {
|
||||
color: var(--dark_200_color);
|
||||
}
|
||||
&:hover .open_homepage_text {
|
||||
border-bottom: 0.1rem solid var(--dark_500_color);
|
||||
}
|
||||
}
|
||||
|
||||
.text_container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex: 1; /* テキスト部分をアイコンまで伸ばす */
|
||||
}
|
||||
|
||||
.open_homepage_text {
|
||||
margin: 0; /* pタグのデフォルトマージン除去 */
|
||||
font-size: 1.4rem;
|
||||
color: var(--sent_400_color);
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
transform: translateX(0);
|
||||
border-bottom: 0.1rem solid var(--sent_400_color);
|
||||
}
|
||||
|
||||
.external_link_svg {
|
||||
flex-shrink: 0;
|
||||
width: 1.6rem;
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
@@ -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";
|
||||
@@ -3,7 +3,7 @@ import fanbox_logo from "@images/supporters/fanbox_logo.png";
|
||||
import kofi_logo from "@images/supporters/kofi_logo.png";
|
||||
import patreon_logo from "@images/supporters/patreon_logo.png";
|
||||
import styles from "./SupportUsContainer.module.scss";
|
||||
import { clsx } from "clsx";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const SupportUsContainer = () => {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
$progress_ease: cubic-bezier(0, 1, 0.75, 1);
|
||||
// Duplicated
|
||||
|
||||
@keyframes revealTopImg {
|
||||
0% {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useCallback, useEffect } from "react";
|
||||
import clsx from "clsx";
|
||||
import Tooltip, { tooltipClasses } from '@mui/material/Tooltip';
|
||||
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||
import styles from "./SupportersWrapper.module.scss";
|
||||
import { shuffleArray, randomIntMinMax, randomMinMax } from "@utils";
|
||||
@@ -261,18 +262,39 @@ const SupporterLabelComponent = ({ item, target_plan, chato_src }) => {
|
||||
|
||||
const SupporterPeriodContainer = ({ settings, calc_support_period }) => {
|
||||
const period_data = extractKeys(settings, calc_support_period);
|
||||
const offset = {
|
||||
popper: {
|
||||
sx: {
|
||||
[`&.${tooltipClasses.popper}[data-popper-placement*="top"] .${tooltipClasses.tooltip}`]: { marginBottom: "0.2em" },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.supporter_period_container}>
|
||||
{Object.entries(period_data).map(([key, item], index) => {
|
||||
if (item === "") return null;
|
||||
const class_name = clsx(styles.period_box, {
|
||||
const period_box_class_name = clsx(styles.period_box, {
|
||||
[styles.mogu_bar]: item === "mogu_2000",
|
||||
[styles.mochi_bar]: item === "mochi_1000",
|
||||
[styles.fuwa_bar]: item === "fuwa_500",
|
||||
[styles.basic_bar]: item === "basic_300",
|
||||
});
|
||||
|
||||
return <div key={index} className={class_name}></div>;
|
||||
return (
|
||||
<Tooltip
|
||||
key={index}
|
||||
title={
|
||||
<p className={styles.tooltip_period_label}>{key}</p>
|
||||
}
|
||||
placement="top"
|
||||
slotProps={offset}
|
||||
>
|
||||
<div className={styles.period_box_wrapper}>
|
||||
<div className={period_box_class_name}></div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
align-content: start;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 1.8rem;
|
||||
row-gap: 1rem;
|
||||
row-gap: 0.4rem;
|
||||
}
|
||||
|
||||
.supporter_image_container {
|
||||
@@ -21,12 +21,14 @@
|
||||
width: 18rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.supporter_image_wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&:hover .supporter_icon_wrapper{
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.supporter_image {
|
||||
@@ -54,7 +56,8 @@
|
||||
height: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
$progress_ease: cubic-bezier(0, 1, 0.75, 1);
|
||||
// Duplicated
|
||||
.supporter_icon_wrapper {
|
||||
height: 4rem;
|
||||
aspect-ratio: 1 /1;
|
||||
@@ -62,6 +65,7 @@
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: transform 0.6s $progress_ease;
|
||||
}
|
||||
|
||||
.supporter_icon {
|
||||
@@ -227,6 +231,12 @@
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
}
|
||||
.period_box_wrapper {
|
||||
padding: 0.3rem 0 0.4rem 0;
|
||||
}
|
||||
.tooltip_period_label {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
|
||||
.progress_bar {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { clsx } from "clsx";
|
||||
import clsx from "clsx";
|
||||
import styles from "./Vr.module.scss";
|
||||
import { ui_configs } from "@ui_configs";
|
||||
import { Slider } from "../_components/";
|
||||
|
||||
@@ -12,6 +12,7 @@ export const SidebarSection = () => {
|
||||
<Tab tab_id="vr" />
|
||||
<Tab tab_id="others" />
|
||||
<Tab tab_id="hotkeys" />
|
||||
{/* <Tab tab_id="plugins" /> */}
|
||||
<Tab tab_id="advanced_settings" />
|
||||
</div>
|
||||
<div className={styles.separated_tabs_wrapper}>
|
||||
@@ -44,7 +45,9 @@ const Tab = (props) => {
|
||||
|
||||
const getLabel = () => {
|
||||
if (props.tab_id === "vr") return "VR";
|
||||
if (props.tab_id === "supporters") return "Supporters";
|
||||
if (props.tab_id === "supporters") return (
|
||||
<>Supporters<span className={styles.crown_emoji}>👑</span></>
|
||||
);
|
||||
if (props.tab_id === "about_vrct") return "About VRCT";
|
||||
return t(`config_page.side_menu_labels.${props.tab_id}`);
|
||||
};
|
||||
|
||||
@@ -60,9 +60,18 @@
|
||||
}
|
||||
|
||||
.tab_text {
|
||||
overflow: hidden;
|
||||
// overflow: hidden;
|
||||
font-size: 1.6rem;
|
||||
text-overflow: ellipsis;
|
||||
// text-overflow: ellipsis;
|
||||
position: relative;
|
||||
}
|
||||
.crown_emoji {
|
||||
font-size: 1.6rem;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 100%;
|
||||
transform: translateY(-50%);
|
||||
padding-left: 0.4rem;
|
||||
}
|
||||
|
||||
.separated_tabs_wrapper {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import { clsx } from "clsx";
|
||||
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";
|
||||
|
||||
|
||||
91
src-ui/app/error_boundary/AppErrorBoundary.jsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { useState } from "react";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import XMarkSvg from "@images/cancel.svg?react";
|
||||
import CopySvg from "@images/copy.svg?react";
|
||||
import CheckMarkSvg from "@images/check_mark.svg?react";
|
||||
|
||||
import { ContactsContainer } from "./contacts_container/ContactsContainer";
|
||||
|
||||
import styles from "./AppErrorBoundary.module.scss";
|
||||
|
||||
export const AppErrorBoundary = ({children}) => {
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallbackRender={({ error }) => (
|
||||
<ErrorContainer error={error} />
|
||||
)
|
||||
}>
|
||||
{children}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorContainer = ({error}) => {
|
||||
const [is_copied, setIsCopied] = useState(false);
|
||||
|
||||
const formatted_stack = error ? formatStackTrace(error.stack) : "Unknown error";
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
if (is_copied) return;
|
||||
|
||||
await navigator.clipboard.writeText(formatted_stack);
|
||||
setIsCopied(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<CloseButtonContainer />
|
||||
<div className={styles.wrapper}>
|
||||
<p className={styles.error_message}>An error occurred. Please restart VRCT or contact the developers.</p>
|
||||
{error ?
|
||||
<div className={styles.error_detail_container}>
|
||||
<div className={styles.error_stack_container}>
|
||||
<p className={styles.error_stack}>
|
||||
{formatted_stack}
|
||||
</p>
|
||||
</div>
|
||||
<button className={styles.copy_error_message_button} onClick={copyToClipboard}>
|
||||
<p className={styles.copy_text}>Copy</p>
|
||||
{is_copied
|
||||
? <CheckMarkSvg className={styles.check_mark_svg}/>
|
||||
: <CopySvg className={styles.copy_svg}/>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
: null}
|
||||
<ContactsContainer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CloseButtonContainer = () => {
|
||||
|
||||
const asyncClose = async () => {
|
||||
const appWindow = await getCurrentWindow();
|
||||
appWindow.close();
|
||||
};
|
||||
|
||||
return (
|
||||
<button className={styles.close_button_wrapper} onClick={asyncClose}>
|
||||
<div className={styles.close_button}>
|
||||
<XMarkSvg className={styles.x_mark_svg}/>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const formatStackTrace = (stack) => {
|
||||
if (!stack) return "";
|
||||
// フルパスの除去(例として window.location.origin や絶対パス部分を削除)
|
||||
// ※必要に応じて正規表現を調整してください
|
||||
const formatted = stack.replace(new RegExp(window.location.origin, "g"), "");
|
||||
|
||||
return formatted;
|
||||
};
|
||||
110
src-ui/app/error_boundary/AppErrorBoundary.module.scss
Normal file
@@ -0,0 +1,110 @@
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: safe center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 2rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.error_message {
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
user-select: text;
|
||||
margin-bottom: 3.2rem;
|
||||
}
|
||||
|
||||
|
||||
.error_detail_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: end;
|
||||
gap: 1rem;
|
||||
}
|
||||
.error_stack_container {
|
||||
max-height: 10rem;
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
padding: 1rem;
|
||||
background-color: var(--dark_950_color);
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
.error_stack {
|
||||
font-size: 1rem;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.copy_error_message_button {
|
||||
// background-color: var(--dark_800_color);
|
||||
padding: 0.8rem 1rem;
|
||||
font-size: 1.4rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 0.4rem;
|
||||
background-color: var(--dark_825_color);
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_850_color);
|
||||
}
|
||||
}
|
||||
|
||||
.copy_svg {
|
||||
width: 1.4rem;
|
||||
color: var(--dark_500_color);
|
||||
}
|
||||
|
||||
.check_mark_svg {
|
||||
width: 1.4rem;
|
||||
color: var(--primary_300_color);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.close_button_wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 100%;
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: end;
|
||||
width: 68px;
|
||||
aspect-ratio: 1 / 1;
|
||||
background-color: var(--error_bc_color);
|
||||
& .x_mark_svg {
|
||||
color: var(--dark_200_color);
|
||||
}
|
||||
&:hover {
|
||||
& .x_mark_svg {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--error_bc_active_color);
|
||||
}
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
.close_button {
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
}
|
||||
|
||||
.x_mark_svg {
|
||||
width: 24px;
|
||||
transform: rotate(-45deg);
|
||||
color: var(--dark_700_color);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import styles from "./ContactsContainer.module.scss";
|
||||
|
||||
export const ContactsContainer = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<OpenLinkContainer className={styles.github_issues} href_id="github_issues" text="Github Issues"/>
|
||||
<OpenLinkContainer className={styles.google_forms} href_id="google_forms" text="Google Forms"/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import dev_github_icon from "@images/about_vrct/dev_github_icon.png";
|
||||
import document from "@images/document.png";
|
||||
|
||||
const contacts_links = {
|
||||
github_issues: { img: dev_github_icon, href: "https://github.com/misyaguziya/VRCT/issues" },
|
||||
google_forms: { img: document, href: "https://docs.google.com/forms/d/e/1FAIpQLSei-xoydOY60ivXqhOjaTzNN8PiBQIDcNhzfy6cw2sjYkcg_g/viewform" },
|
||||
};
|
||||
|
||||
const OpenLinkContainer = ({className, href_id, text}) => {
|
||||
const href = contacts_links[href_id].href;
|
||||
const img = contacts_links[href_id].img;
|
||||
return (
|
||||
<a className={className} href={href} target="_blank" rel="noreferrer" >
|
||||
<img className={styles.contact_button_icon} src={img} />
|
||||
<p className={styles.contact_button_label}>{text}</p>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 3.2rem;
|
||||
}
|
||||
|
||||
.github_issues, .google_forms {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 1rem;
|
||||
border-radius: 0.4rem;
|
||||
gap: 1rem;
|
||||
&:hover {
|
||||
background-color: var(--dark_800_color);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--dark_900_color)
|
||||
}
|
||||
}
|
||||
.contact_button_icon {
|
||||
width: 5.2rem;
|
||||
}
|
||||
.contact_button_label {
|
||||
font-size: 1.4rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -9,12 +9,27 @@ import { useStore_IsOpenedLanguageSelector } from "@store";
|
||||
import { useLanguageSettings } from "@logics_main";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { PluginHost } from "./PluginHost";
|
||||
|
||||
import { usePlugins } from "@logics_configs";
|
||||
|
||||
export const MainSection = () => {
|
||||
const { currentPluginsData } = usePlugins();
|
||||
|
||||
const render_plugins = currentPluginsData.data.filter((plugin) => (
|
||||
plugin.is_downloaded &&
|
||||
plugin.is_enabled &&
|
||||
plugin.downloaded_plugin_info.is_plugin_supported &&
|
||||
plugin.downloaded_plugin_info.location === "main_section"
|
||||
));
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<TopBar />
|
||||
<MessageContainer />
|
||||
{render_plugins.length
|
||||
? <PluginHost render_components={render_plugins}/>
|
||||
: <MessageContainer />
|
||||
}
|
||||
<HandleLanguageSelector />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
// justify-content: space-between;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.language_selector_container {
|
||||
|
||||
14
src-ui/app/main_page/main_section/PluginHost.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
export const PluginHost = ({render_components}) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{render_components
|
||||
.map((plugin, index) => {
|
||||
const PluginComponent = plugin.component;
|
||||
return PluginComponent ? <PluginComponent key={index} /> : null;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -5,7 +5,7 @@ import { useMessage } from "@logics_common";
|
||||
import { useSendMessageButtonType, useEnableAutoClearMessageInputBox } from "@logics_configs";
|
||||
import { useMessageLogScroll } from "@logics_main";
|
||||
import { store } from "@store";
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
|
||||
export const MessageInputBox = () => {
|
||||
const [message_history, setMessageHistory] = useState([]);
|
||||
@@ -41,6 +41,7 @@ export const MessageInputBox = () => {
|
||||
|
||||
const onSubmitFunction = (e) => {
|
||||
e.preventDefault();
|
||||
// const appWindow = getCurrentWindow();
|
||||
// appWindow.minimize();
|
||||
|
||||
if (!currentMessageInputValue.data.trim()) return updateMessageInputValue("");
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import clsx from "clsx";
|
||||
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 clsx from "clsx";
|
||||
import { usePlugins } from "@logics_configs";
|
||||
import {
|
||||
useComputeMode,
|
||||
useUpdateSoftware,
|
||||
useIsSoftwareUpdating,
|
||||
useSoftwareVersion,
|
||||
} from "@logics_common";
|
||||
|
||||
import { PluginCompatibilityList } from "./plugins_compatibility_list/PluginCompatibilityList";
|
||||
|
||||
export const UpdateModal = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -11,9 +18,10 @@ 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;
|
||||
const is_latest_version_already = currentLatestSoftwareVersionInfo.data.is_update_available === false;
|
||||
const is_cpu_version = currentComputeMode.data === "cpu";
|
||||
|
||||
const onClickUpdateSoftware = () => {
|
||||
@@ -37,30 +45,34 @@ export const UpdateModal = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.update_section}>
|
||||
<div className={styles.cpu_section}>
|
||||
<div className={styles.button_wrapper}>
|
||||
<button className={cpu_accept_button_class_name} onClick={onClickUpdateSoftware}>CPU</button>
|
||||
{is_cpu_version ? <CurrentVersionLabel is_latest_version_already={is_latest_version_already} /> : null}
|
||||
<div className={styles.update_section_wrapper}>
|
||||
{isAnyPluginEnabled() && <PluginCompatibilityList />}
|
||||
<div className={styles.update_section}>
|
||||
<div className={styles.cpu_section}>
|
||||
<div className={styles.button_wrapper}>
|
||||
<button className={cpu_accept_button_class_name} onClick={onClickUpdateSoftware}>CPU</button>
|
||||
{is_cpu_version ? <CurrentVersionLabel is_latest_version_already={is_latest_version_already} /> : null}
|
||||
</div>
|
||||
<div className={styles.version_desc_container}>
|
||||
<VersionDescComponent desc={t("update_modal.cpu_desc")} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.version_desc_container}>
|
||||
<VersionDescComponent desc={t("update_modal.cpu_desc")} />
|
||||
<div className={styles.cuda_section}>
|
||||
<div className={styles.button_wrapper}>
|
||||
<button className={cuda_accept_button_class_name} onClick={onClickUpdateSoftware_CUDA}>CUDA (GPU)</button>
|
||||
{!is_cpu_version ? <CurrentVersionLabel is_latest_version_already={is_latest_version_already} is_cuda={true}/> : null}
|
||||
</div>
|
||||
<div className={styles.version_desc_container}>
|
||||
<VersionDescComponent desc={t("update_modal.cuda_desc")} />
|
||||
<VersionDescComponent desc={t("update_modal.cuda_compare_cpu_desc")} />
|
||||
<VersionDescComponent desc={t("update_modal.cuda_disk_space_desc", {size: "5GB"})} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.cuda_section}>
|
||||
<div className={styles.button_wrapper}>
|
||||
<button className={cuda_accept_button_class_name} onClick={onClickUpdateSoftware_CUDA}>CUDA (GPU)</button>
|
||||
{!is_cpu_version ? <CurrentVersionLabel is_latest_version_already={is_latest_version_already} is_cuda={true}/> : null}
|
||||
</div>
|
||||
<div className={styles.version_desc_container}>
|
||||
<VersionDescComponent desc={t("update_modal.cuda_desc")} />
|
||||
<VersionDescComponent desc={t("update_modal.cuda_compare_cpu_desc")} />
|
||||
<VersionDescComponent desc={t("update_modal.cuda_disk_space_desc", {size: "5GB"})} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className={styles.update_desc}>{t("update_modal.download_latest_and_restart")}</p>
|
||||
<p className={styles.update_desc}>{t("update_modal.download_latest_and_restart")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.button_wrapper}>
|
||||
<button className={styles.deny_button} onClick={() => updateOpenedQuickSetting("")} >{t("update_modal.close_modal")}</button>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
justify-content: safe center;
|
||||
align-items: center;
|
||||
gap: 2.4rem;
|
||||
}
|
||||
@@ -16,6 +16,14 @@
|
||||
gap: 8rem;
|
||||
}
|
||||
|
||||
.update_section_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4rem;
|
||||
}
|
||||
|
||||
.update_section {
|
||||
border: 0.1rem solid var(--dark_600_color);
|
||||
border-radius: 0.4rem;
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { useEffect } from "react";
|
||||
import styles from "./PluginCompatibilityList.module.scss";
|
||||
import { usePlugins } from "@logics_configs";
|
||||
import CheckMarkSvg from "@images/check_mark.svg?react";
|
||||
import XSvg from "@images/x_mark.svg?react";
|
||||
import WarningSvg from "@images/warning.svg?react";
|
||||
|
||||
export const PluginCompatibilityList = () => {
|
||||
const {
|
||||
enabledPluginsList,
|
||||
asyncFetchPluginsInfo,
|
||||
currentFetchedPluginsInfo,
|
||||
} = usePlugins();
|
||||
|
||||
useEffect(() => {
|
||||
asyncFetchPluginsInfo();
|
||||
}, []);
|
||||
|
||||
// ダウンロード済みのもの
|
||||
const downloaded_plugin = enabledPluginsList().filter(p => p.is_downloaded);
|
||||
|
||||
const compatible_plugins_list = [];
|
||||
const incompatible_plugins_list = [];
|
||||
for (const p of downloaded_plugin) {
|
||||
if (!p.downloaded_plugin_info?.is_plugin_supported_latest_vrct && !p.latest_plugin_info?.is_plugin_supported_latest_vrct) {
|
||||
// プラグイン最新版でも、VRCT最新版(VRCTアプデ後)に非対応のもの
|
||||
incompatible_plugins_list.push(p);
|
||||
} else {
|
||||
// 現プラグイン or 最新版が、VRCT最新版(VRCTアプデ後)に対応しているもの
|
||||
compatible_plugins_list.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
const is_any_incompatible_plugin = incompatible_plugins_list.length > 0;
|
||||
const is_any_compatible_plugin = compatible_plugins_list.length > 0;
|
||||
|
||||
if (!is_any_incompatible_plugin && !is_any_compatible_plugin) return null; // This is just for safety.
|
||||
|
||||
// Duplicate
|
||||
const is_failed_to_fetch = currentFetchedPluginsInfo.state === "error";
|
||||
const is_fetching = currentFetchedPluginsInfo.state === "pending";
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<p className={styles.title}>使用中プラグインの互換性チェック</p>
|
||||
{is_failed_to_fetch && <p>Failed to fetch plugins data</p>}
|
||||
{is_fetching && <p>Fetching plugins data...</p>}
|
||||
<div className={styles.plugins_compatibility_container}>
|
||||
{incompatible_plugins_list.map(plugin => {
|
||||
const target_data = plugin.downloaded_plugin_info;
|
||||
return <PluginContainer key={target_data.plugin_id} target_data={target_data} is_compatible={false}/>;
|
||||
})}
|
||||
{compatible_plugins_list.map(plugin => {
|
||||
const target_data = plugin.downloaded_plugin_info;
|
||||
return <PluginContainer key={target_data.plugin_id} target_data={target_data} is_compatible={true} />;
|
||||
})}
|
||||
</div>
|
||||
{is_any_incompatible_plugin &&
|
||||
<div className={styles.warning_container}>
|
||||
<WarningSvg className={styles.warning_svg}/>
|
||||
<p className={styles.warning_text}>VRCT最新バージョンで互換性のないプラグインはアップデート後に無効化されます。引き続き使用したい場合は、各プラグインの更新を待ってください。</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PluginContainer = ({ target_data, is_compatible }) => {
|
||||
return (
|
||||
<div className={styles.plugin_box}>
|
||||
<p className={clsx(styles.plugin_label, {[styles.is_compatible]: is_compatible})} >{target_data.title}</p>
|
||||
{is_compatible
|
||||
? <CheckMarkSvg className={styles.check_mark_svg}/>
|
||||
: <XSvg className={styles.x_svg}/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.plugins_compatibility_container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.2rem 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.plugin_box {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.4rem 0.6rem;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.plugin_label {
|
||||
font-size: 1.4rem;
|
||||
color: var(--error_bc_color);
|
||||
&.is_compatible {
|
||||
color: var(--primary_300_color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.check_mark_svg {
|
||||
width: 1.8rem;
|
||||
color: var(--primary_300_color);
|
||||
}
|
||||
.x_svg {
|
||||
width: 1.8rem;
|
||||
color: var(--error_bc_color);
|
||||
}
|
||||
|
||||
.warning_container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.warning_svg {
|
||||
padding-bottom: 0.4rem;
|
||||
width: 2.4rem;
|
||||
color: var(--waring_color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.warning_text {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { clsx } from "clsx";
|
||||
import clsx from "clsx";
|
||||
import Snackbar from "@mui/material/Snackbar";
|
||||
import Slide from "@mui/material/Slide";
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { StartUpProgressContainer } from "./start_up_progress_container/StartUpP
|
||||
import { DownloadModelsContainer } from "./download_models_container/DownloadModelsContainer/";
|
||||
import MegaphoneSvg from "@images/megaphone.svg?react";
|
||||
import XMarkSvg from "@images/cancel.svg?react";
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import clsx from "clsx";
|
||||
|
||||
export const SplashComponent = () => {
|
||||
@@ -73,12 +73,13 @@ const AnnouncementsContainer = () => {
|
||||
|
||||
|
||||
const CloseButtonContainer = () => {
|
||||
const close = () => {
|
||||
const asyncClose = async () => {
|
||||
const appWindow = await getCurrentWindow();
|
||||
appWindow.close();
|
||||
};
|
||||
|
||||
return (
|
||||
<button className={styles.close_button_wrapper} onClick={close}>
|
||||
<button className={styles.close_button_wrapper} onClick={asyncClose}>
|
||||
<div className={styles.close_button}>
|
||||
<XMarkSvg className={styles.x_mark_svg}/>
|
||||
</div>
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
}
|
||||
|
||||
$progress_ease: cubic-bezier(0, 1, 0.75, 1);
|
||||
// Duplicated
|
||||
.progress_bar {
|
||||
height: 8px;
|
||||
transition: width 0.3s $progress_ease;
|
||||
|
||||
@@ -5,15 +5,17 @@ import SquareSvg from "@images/square.svg?react";
|
||||
import LineSvg from "@images/line.svg?react";
|
||||
import VrctSvg from "@images/vrct.svg?react";
|
||||
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
|
||||
export const WindowTitleBar = () => {
|
||||
|
||||
const minimize = () => {
|
||||
const asyncMinimize = async () => {
|
||||
const appWindow = await getCurrentWindow();
|
||||
appWindow.minimize();
|
||||
};
|
||||
|
||||
const maximize = async () => {
|
||||
const asyncMaximize = async () => {
|
||||
const appWindow = await getCurrentWindow();
|
||||
const maximizeState = await appWindow.isMaximized();
|
||||
if (!maximizeState) {
|
||||
appWindow.maximize();
|
||||
@@ -22,7 +24,8 @@ export const WindowTitleBar = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
const asyncClose = async () => {
|
||||
const appWindow = await getCurrentWindow();
|
||||
appWindow.close();
|
||||
};
|
||||
|
||||
@@ -34,13 +37,13 @@ export const WindowTitleBar = () => {
|
||||
</div>
|
||||
|
||||
<div className={styles.window_control_wrapper}>
|
||||
<div className={styles.minimize_button} onClick={minimize}>
|
||||
<div className={styles.minimize_button} onClick={asyncMinimize}>
|
||||
<LineSvg className={styles.line_svg}/>
|
||||
</div>
|
||||
<div className={styles.maximize_button} onClick={maximize}>
|
||||
<div className={styles.maximize_button} onClick={asyncMaximize}>
|
||||
<SquareSvg className={styles.square_svg}/>
|
||||
</div>
|
||||
<div className={styles.close_button} onClick={close}>
|
||||
<div className={styles.close_button} onClick={asyncClose}>
|
||||
<XMarkSvg className={styles.x_mark_svg}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 41 KiB |
BIN
src-ui/assets/document.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
1
src-ui/assets/x_mark.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M23.954 21.03l-9.184-9.095 9.092-9.174-2.832-2.807-9.09 9.179-9.176-9.088-2.81 2.81 9.186 9.105-9.095 9.184 2.81 2.81 9.112-9.192 9.18 9.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 217 B |
@@ -1,4 +1,4 @@
|
||||
import { clsx } from "clsx";
|
||||
import clsx from "clsx";
|
||||
import styles from "./Checkbox.module.scss";
|
||||
export const Checkbox = ({
|
||||
checkboxId,
|
||||
|
||||
@@ -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";
|
||||
@@ -11,4 +11,5 @@ export { useMessage } from "./useMessage";
|
||||
export { useUpdateSoftware } from "./useUpdateSoftware";
|
||||
export { useVolume } from "./useVolume";
|
||||
export { useHandleNetworkConnection } from "./useHandleNetworkConnection";
|
||||
export { useIsVrctAvailable } from "./useIsVrctAvailable";
|
||||
export { useIsVrctAvailable } from "./useIsVrctAvailable";
|
||||
export { useFetch } from "./useFetch";
|
||||
27
src-ui/logics/common/useFetch.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
|
||||
|
||||
export const useFetch = () => {
|
||||
const asyncTauriFetchGithub = async (url, {return_row = false} = {}) => {
|
||||
console.log("tauriFetch", url);
|
||||
|
||||
const response = await tauriFetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"User-Agent": "VRCTPluginApp"
|
||||
}
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(url, "Failed to fetch, response: " + response);
|
||||
}
|
||||
|
||||
if (return_row === true) return await response;
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
return {
|
||||
asyncTauriFetchGithub,
|
||||
};
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
import { useStore_IsSoftwareUpdateAvailable } from "@store";
|
||||
|
||||
export const useIsSoftwareUpdateAvailable = () => {
|
||||
const { currentIsSoftwareUpdateAvailable, updateIsSoftwareUpdateAvailable } = useStore_IsSoftwareUpdateAvailable();
|
||||
|
||||
return {
|
||||
currentIsSoftwareUpdateAvailable,
|
||||
updateIsSoftwareUpdateAvailable,
|
||||
};
|
||||
};
|
||||
40
src-ui/logics/common/useSoftwareVersion.js
Normal file
@@ -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,
|
||||
};
|
||||
};
|
||||