Merge branch 'develop'
# Conflicts: # locales/en.yml # locales/ja.yml
6
.github/FUNDING.yml
vendored
@@ -1,13 +1,13 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: [misyaguziya]
|
github: [misyaguziya]
|
||||||
patreon: # Replace with a single Patreon username
|
patreon: vrct_dev
|
||||||
open_collective: # Replace with a single Open Collective username
|
open_collective: # Replace with a single Open Collective username
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
ko_fi: vrct_dev
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
liberapay: # Replace with a single Liberapay username
|
liberapay: # Replace with a single Liberapay username
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||||
polar: # Replace with a single Polar username
|
polar: # Replace with a single Polar username
|
||||||
custom: ["https://misyaguziya.booth.pm/", "https://vrct-dev.fanbox.cc/"]
|
custom: ["https://misyaguziya.booth.pm", "https://vrct-dev.fanbox.cc"]
|
||||||
|
|||||||
48
README.ja.md
@@ -1,12 +1,56 @@
|
|||||||
<div align="center">
|
<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/releases)
|
[](https://github.com/misyaguziya/VRCT/releases)
|
||||||
[](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
|
[](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
|
||||||
[](https://misyaguziya.booth.pm/items/5155325)
|
[](https://misyaguziya.booth.pm/items/5155325)
|
||||||
[](https://github.com/sponsors/misyaguziya)
|
[](https://github.com/sponsors/misyaguziya)
|
||||||
[](https://vrct-dev.fanbox.cc/)
|
|
||||||
|
<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.md) | **日本語** | [한국어](./README.ko.md) | [繁體中文](./README.zh-Hant.md) |
|
| [English](./README.md) | **日本語** | [한국어](./README.ko.md) | [繁體中文](./README.zh-Hant.md) |
|
||||||
|
|
||||||
|
|||||||
50
README.ko.md
@@ -1,14 +1,58 @@
|
|||||||
<div align="center">
|
<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/releases)
|
[](https://github.com/misyaguziya/VRCT/releases)
|
||||||
[](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
|
[](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
|
||||||
[](https://misyaguziya.booth.pm/items/5155325)
|
[](https://misyaguziya.booth.pm/items/5155325)
|
||||||
[](https://github.com/sponsors/misyaguziya)
|
[](https://github.com/sponsors/misyaguziya)
|
||||||
[](https://vrct-dev.fanbox.cc/)
|
|
||||||
|
|
||||||
| [English](./README.md) | [日本語](./README.ja.md) | **한국어** |
|
<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.md) | [日本語](./README.ja.md) | **한국어** | [繁體中文](./README.zh-Hant.md) |
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
VRCT는 음성인식 및 번역 기능을 통해 VRChat의 대화를 지원하는 소프트웨어입니다.
|
VRCT는 음성인식 및 번역 기능을 통해 VRChat의 대화를 지원하는 소프트웨어입니다.
|
||||||
|
|||||||
48
README.md
@@ -1,12 +1,56 @@
|
|||||||
<div align="center">
|
<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/releases)
|
[](https://github.com/misyaguziya/VRCT/releases)
|
||||||
[](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
|
[](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
|
||||||
[](https://misyaguziya.booth.pm/items/5155325)
|
[](https://misyaguziya.booth.pm/items/5155325)
|
||||||
[](https://github.com/sponsors/misyaguziya)
|
[](https://github.com/sponsors/misyaguziya)
|
||||||
[](https://vrct-dev.fanbox.cc/)
|
|
||||||
|
<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) |
|
| **English** | [日本語](./README.ja.md) | [한국어](./README.ko.md) | [繁體中文](./README.zh-Hant.md) |
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,56 @@
|
|||||||
<div align="center">
|
<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/releases)
|
[](https://github.com/misyaguziya/VRCT/releases)
|
||||||
[](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
|
[](https://github.com/misyaguziya/VRCT/blob/master/LICENSE)
|
||||||
[](https://misyaguziya.booth.pm/items/5155325)
|
[](https://misyaguziya.booth.pm/items/5155325)
|
||||||
[](https://github.com/sponsors/misyaguziya)
|
[](https://github.com/sponsors/misyaguziya)
|
||||||
[](https://vrct-dev.fanbox.cc/)
|
|
||||||
|
<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.md) | [日本語](./README.ja.md) | [한국어](./README.ko.md) | **繁體中文** |
|
| [English](./README.md) | [日本語](./README.ja.md) | [한국어](./README.ko.md) | **繁體中文** |
|
||||||
|
|
||||||
|
|||||||
BIN
docs/kofi_logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/patreon_logo_black.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
docs/patreon_logo_white.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
docs/pixiv_fanbox_black.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
docs/pixiv_fanbox_white.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/supporter_section_border_d.png
Normal file
|
After Width: | Height: | Size: 182 B |
BIN
docs/supporter_section_border_l.png
Normal file
|
After Width: | Height: | Size: 186 B |
BIN
docs/vrct_logo_black.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
docs/vrct_logo_white.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
@@ -94,6 +94,7 @@ config_page:
|
|||||||
transcription: Transcription
|
transcription: Transcription
|
||||||
vr: VR
|
vr: VR
|
||||||
others: Others
|
others: Others
|
||||||
|
hotkeys: Hotkeys
|
||||||
advanced_settings: Advanced Settings
|
advanced_settings: Advanced Settings
|
||||||
supporters: Supporters
|
supporters: Supporters
|
||||||
about_vrct: About VRCT
|
about_vrct: About VRCT
|
||||||
@@ -257,6 +258,16 @@ config_page:
|
|||||||
label: Send Received Message To VRChat
|
label: Send Received Message To VRChat
|
||||||
desc: Send the message you received from the speaker's sound to VRChat's chatbox.
|
desc: Send the message you received from the speaker's sound to VRChat's chatbox.
|
||||||
|
|
||||||
|
hotkeys:
|
||||||
|
toggle_vrct_visibility:
|
||||||
|
label: Toggle VRCT Visibility
|
||||||
|
toggle_translation:
|
||||||
|
label: Toggle {{translation}}
|
||||||
|
toggle_transcription_send:
|
||||||
|
label: Toggle {{transcription_send}}
|
||||||
|
toggle_transcription_receive:
|
||||||
|
label: Toggle {{transcription_receive}}
|
||||||
|
|
||||||
advanced_settings:
|
advanced_settings:
|
||||||
osc_ip_address:
|
osc_ip_address:
|
||||||
label: OSC IP Address
|
label: OSC IP Address
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ config_page:
|
|||||||
translation: 翻訳
|
translation: 翻訳
|
||||||
transcription: 音声認識
|
transcription: 音声認識
|
||||||
others: その他
|
others: その他
|
||||||
|
hotkeys: ホットキー
|
||||||
advanced_settings: 高度な設定
|
advanced_settings: 高度な設定
|
||||||
|
|
||||||
device:
|
device:
|
||||||
@@ -255,6 +256,16 @@ config_page:
|
|||||||
label: 受信したメッセージをVRChatに送信する
|
label: 受信したメッセージをVRChatに送信する
|
||||||
desc: スピーカーから聞き取り、文字起こしされたメッセージをVRChatに送信します。
|
desc: スピーカーから聞き取り、文字起こしされたメッセージをVRChatに送信します。
|
||||||
|
|
||||||
|
hotkeys:
|
||||||
|
toggle_vrct_visibility:
|
||||||
|
label: VRCTの最小化/アクティブ化の切り替え
|
||||||
|
toggle_translation:
|
||||||
|
label: '{{translation}}機能切り替え'
|
||||||
|
toggle_transcription_send:
|
||||||
|
label: '{{transcription_send}}機能切り替え'
|
||||||
|
toggle_transcription_receive:
|
||||||
|
label: '{{transcription_receive}}機能切り替え'
|
||||||
|
|
||||||
advanced_settings:
|
advanced_settings:
|
||||||
osc_ip_address:
|
osc_ip_address:
|
||||||
label: OSC IP Address
|
label: OSC IP Address
|
||||||
|
|||||||
@@ -17,10 +17,9 @@
|
|||||||
"dev-ui": "npm-run-all --parallel vite tauri-dev",
|
"dev-ui": "npm-run-all --parallel vite tauri-dev",
|
||||||
"build": "npm run build-python && npm run vite-build && npm run tauri build",
|
"build": "npm run build-python && npm run vite-build && npm run tauri build",
|
||||||
"build-cuda": "npm run build-python-cuda && npm run vite-build && npm run tauri build",
|
"build-cuda": "npm run build-python-cuda && npm run vite-build && npm run tauri build",
|
||||||
"build-ui": "npm run vite-build && npm run tauri build",
|
"release": "npm run build && python zip.py --zip_name VRCT.zip",
|
||||||
"release": "python zip.py --zip_name VRCT.zip",
|
"release-cuda": "npm run build-cuda && python zip.py --zip_name VRCT_cuda.zip",
|
||||||
"release-cuda": "python zip.py --zip_name VRCT_cuda.zip",
|
"release-all": "npm run release && npm run release-cuda"
|
||||||
"release-all": "npm run build && npm run release && npm run build-cuda && npm run release-cuda"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "11.14.0",
|
"@emotion/react": "11.14.0",
|
||||||
|
|||||||
@@ -237,6 +237,15 @@ class Config:
|
|||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
self._SELECTABLE_TRANSLATION_ENGINE_STATUS = value
|
self._SELECTABLE_TRANSLATION_ENGINE_STATUS = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def SELECTABLE_TRANSCRIPTION_ENGINE_STATUS(self):
|
||||||
|
return self._SELECTABLE_TRANSCRIPTION_ENGINE_STATUS
|
||||||
|
|
||||||
|
@SELECTABLE_TRANSCRIPTION_ENGINE_STATUS.setter
|
||||||
|
def SELECTABLE_TRANSCRIPTION_ENGINE_STATUS(self, value):
|
||||||
|
if isinstance(value, dict):
|
||||||
|
self._SELECTABLE_TRANSCRIPTION_ENGINE_STATUS = value
|
||||||
|
|
||||||
# Save Json Data
|
# Save Json Data
|
||||||
## Main Window
|
## Main Window
|
||||||
@property
|
@property
|
||||||
@@ -533,6 +542,19 @@ class Config:
|
|||||||
self._MIC_WORD_FILTER = sorted(set(value), key=value.index)
|
self._MIC_WORD_FILTER = sorted(set(value), key=value.index)
|
||||||
self.saveConfig(inspect.currentframe().f_code.co_name, value)
|
self.saveConfig(inspect.currentframe().f_code.co_name, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
@json_serializable('HOTKEYS')
|
||||||
|
def HOTKEYS(self):
|
||||||
|
return self._HOTKEYS
|
||||||
|
|
||||||
|
@HOTKEYS.setter
|
||||||
|
def HOTKEYS(self, value):
|
||||||
|
if isinstance(value, dict) and set(value.keys()) == set(self.HOTKEYS.keys()):
|
||||||
|
for key, value in value.items():
|
||||||
|
if isinstance(value, list) or value is None:
|
||||||
|
self._HOTKEYS[key] = value
|
||||||
|
self.saveConfig(inspect.currentframe().f_code.co_name, self.HOTKEYS, immediate_save=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@json_serializable('MIC_AVG_LOGPROB')
|
@json_serializable('MIC_AVG_LOGPROB')
|
||||||
def MIC_AVG_LOGPROB(self):
|
def MIC_AVG_LOGPROB(self):
|
||||||
@@ -911,7 +933,7 @@ class Config:
|
|||||||
|
|
||||||
def init_config(self):
|
def init_config(self):
|
||||||
# Read Only
|
# Read Only
|
||||||
self._VERSION = "3.0.1"
|
self._VERSION = "3.0.2"
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
self._PATH_LOCAL = os_path.dirname(sys.executable)
|
self._PATH_LOCAL = os_path.dirname(sys.executable)
|
||||||
else:
|
else:
|
||||||
@@ -964,6 +986,9 @@ class Config:
|
|||||||
self._SELECTABLE_TRANSLATION_ENGINE_STATUS = {}
|
self._SELECTABLE_TRANSLATION_ENGINE_STATUS = {}
|
||||||
for engine in self.SELECTABLE_TRANSLATION_ENGINE_LIST:
|
for engine in self.SELECTABLE_TRANSLATION_ENGINE_LIST:
|
||||||
self._SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
|
self._SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
|
||||||
|
self._SELECTABLE_TRANSCRIPTION_ENGINE_STATUS = {}
|
||||||
|
for engine in self.SELECTABLE_TRANSCRIPTION_ENGINE_LIST:
|
||||||
|
self._SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
|
||||||
|
|
||||||
# Save Json Data
|
# Save Json Data
|
||||||
## Main Window
|
## Main Window
|
||||||
@@ -1026,6 +1051,12 @@ class Config:
|
|||||||
self._MIC_PHRASE_TIMEOUT = 3
|
self._MIC_PHRASE_TIMEOUT = 3
|
||||||
self._MIC_MAX_PHRASES = 10
|
self._MIC_MAX_PHRASES = 10
|
||||||
self._MIC_WORD_FILTER = []
|
self._MIC_WORD_FILTER = []
|
||||||
|
self._HOTKEYS = {
|
||||||
|
"toggle_vrct_visibility": None,
|
||||||
|
"toggle_translation": None,
|
||||||
|
"toggle_transcription_send": None,
|
||||||
|
"toggle_transcription_receive": None,
|
||||||
|
}
|
||||||
self._MIC_AVG_LOGPROB = -0.8
|
self._MIC_AVG_LOGPROB = -0.8
|
||||||
self._MIC_NO_SPEECH_PROB = 0.6
|
self._MIC_NO_SPEECH_PROB = 0.6
|
||||||
self._AUTO_SPEAKER_SELECT = True
|
self._AUTO_SPEAKER_SELECT = True
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import re
|
|||||||
from device_manager import device_manager
|
from device_manager import device_manager
|
||||||
from config import config
|
from config import config
|
||||||
from model import model
|
from model import model
|
||||||
from utils import removeLog, printLog, errorLogging
|
from utils import removeLog, printLog, errorLogging, isConnectedNetwork
|
||||||
|
|
||||||
class Controller:
|
class Controller:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -25,6 +25,34 @@ class Controller:
|
|||||||
self.run = run
|
self.run = run
|
||||||
|
|
||||||
# response functions
|
# response functions
|
||||||
|
def connectedNetwork(self) -> None:
|
||||||
|
self.run(
|
||||||
|
200,
|
||||||
|
self.run_mapping["connected_network"],
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def disconnectedNetwork(self) -> None:
|
||||||
|
self.run(
|
||||||
|
200,
|
||||||
|
self.run_mapping["connected_network"],
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def enableAiModels(self) -> None:
|
||||||
|
self.run(
|
||||||
|
200,
|
||||||
|
self.run_mapping["enable_ai_models"],
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def disableAiModels(self) -> None:
|
||||||
|
self.run(
|
||||||
|
200,
|
||||||
|
self.run_mapping["enable_ai_models"],
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
|
||||||
def updateMicHostList(self) -> None:
|
def updateMicHostList(self) -> None:
|
||||||
self.run(
|
self.run(
|
||||||
200,
|
200,
|
||||||
@@ -148,6 +176,7 @@ class Controller:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def downloaded(self) -> None:
|
def downloaded(self) -> None:
|
||||||
|
if model.checkTranslatorCTranslate2ModelWeight(self.weight_type) is True:
|
||||||
weight_type_dict = config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT
|
weight_type_dict = config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT
|
||||||
weight_type_dict[self.weight_type] = True
|
weight_type_dict[self.weight_type] = True
|
||||||
config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT = weight_type_dict
|
config.SELECTABLE_CTRANSLATE2_WEIGHT_TYPE_DICT = weight_type_dict
|
||||||
@@ -157,6 +186,15 @@ class Controller:
|
|||||||
self.run_mapping["downloaded_ctranslate2_weight"],
|
self.run_mapping["downloaded_ctranslate2_weight"],
|
||||||
self.weight_type,
|
self.weight_type,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
self.run(
|
||||||
|
400,
|
||||||
|
self.run_mapping["error_ctranslate2_weight"],
|
||||||
|
{
|
||||||
|
"message":"CTranslate2 weight download error",
|
||||||
|
"data": None
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
class DownloadWhisper:
|
class DownloadWhisper:
|
||||||
def __init__(self, run_mapping:dict, weight_type:str, run:Callable[[int, str, Any], None]) -> None:
|
def __init__(self, run_mapping:dict, weight_type:str, run:Callable[[int, str, Any], None]) -> None:
|
||||||
@@ -173,6 +211,7 @@ class Controller:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def downloaded(self) -> None:
|
def downloaded(self) -> None:
|
||||||
|
if model.checkTranscriptionWhisperModelWeight(self.weight_type) is True:
|
||||||
weight_type_dict = config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT
|
weight_type_dict = config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT
|
||||||
weight_type_dict[self.weight_type] = True
|
weight_type_dict[self.weight_type] = True
|
||||||
config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = weight_type_dict
|
config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = weight_type_dict
|
||||||
@@ -182,6 +221,15 @@ class Controller:
|
|||||||
self.run_mapping["downloaded_whisper_weight"],
|
self.run_mapping["downloaded_whisper_weight"],
|
||||||
self.weight_type,
|
self.weight_type,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
self.run(
|
||||||
|
400,
|
||||||
|
self.run_mapping["error_whisper_weight"],
|
||||||
|
{
|
||||||
|
"message":"Whisper weight download error",
|
||||||
|
"data": None
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def micMessage(self, result: dict) -> None:
|
def micMessage(self, result: dict) -> None:
|
||||||
message = result["text"]
|
message = result["text"]
|
||||||
@@ -494,7 +542,10 @@ class Controller:
|
|||||||
your_language = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]
|
your_language = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]["1"]
|
||||||
for target_language in config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO].values():
|
for target_language in config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO].values():
|
||||||
if your_language["language"] == target_language["language"] and target_language["enable"] is True:
|
if your_language["language"] == target_language["language"] and target_language["enable"] is True:
|
||||||
|
if config.SELECTABLE_TRANSLATION_ENGINE_STATUS["CTranslate2"] is True:
|
||||||
engines = ["CTranslate2"]
|
engines = ["CTranslate2"]
|
||||||
|
else:
|
||||||
|
engines = []
|
||||||
|
|
||||||
return {"status":200, "result":engines}
|
return {"status":200, "result":engines}
|
||||||
|
|
||||||
@@ -541,6 +592,11 @@ class Controller:
|
|||||||
self.updateTranslationEngineAndEngineList()
|
self.updateTranslationEngineAndEngineList()
|
||||||
return {"status":200, "result":config.SELECTED_TARGET_LANGUAGES}
|
return {"status":200, "result":config.SELECTED_TARGET_LANGUAGES}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getTranscriptionEngines(*args, **kwargs) -> dict:
|
||||||
|
engines = [key for key, value in config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS.items() if value is True]
|
||||||
|
return {"status":200, "result":engines}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getSelectedTranscriptionEngine(*args, **kwargs) -> dict:
|
def getSelectedTranscriptionEngine(*args, **kwargs) -> dict:
|
||||||
return {"status":200, "result":config.SELECTED_TRANSCRIPTION_ENGINE}
|
return {"status":200, "result":config.SELECTED_TRANSCRIPTION_ENGINE}
|
||||||
@@ -996,6 +1052,15 @@ class Controller:
|
|||||||
response = {"status":200, "result":config.SPEAKER_MAX_PHRASES}
|
response = {"status":200, "result":config.SPEAKER_MAX_PHRASES}
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getHotkeys(*args, **kwargs) -> dict:
|
||||||
|
return {"status":200, "result":config.HOTKEYS}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def setHotkeys(data, *args, **kwargs) -> dict:
|
||||||
|
config.HOTKEYS = data
|
||||||
|
return {"status":200, "result":config.HOTKEYS}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getSpeakerAvgLogprob(*args, **kwargs) -> dict:
|
def getSpeakerAvgLogprob(*args, **kwargs) -> dict:
|
||||||
return {"status":200, "result":config.SPEAKER_AVG_LOGPROB}
|
return {"status":200, "result":config.SPEAKER_AVG_LOGPROB}
|
||||||
@@ -1411,6 +1476,7 @@ class Controller:
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
model.downloadCTranslate2ModelWeight(weight_type, download_ctranslate2.progressBar, download_ctranslate2.downloaded)
|
model.downloadCTranslate2ModelWeight(weight_type, download_ctranslate2.progressBar, download_ctranslate2.downloaded)
|
||||||
|
model.downloadCTranslate2ModelTokenizer(weight_type)
|
||||||
return {"status":200, "result":True}
|
return {"status":200, "result":True}
|
||||||
|
|
||||||
def downloadWhisperWeight(self, data:str, asynchronous:bool=True, *args, **kwargs) -> dict:
|
def downloadWhisperWeight(self, data:str, asynchronous:bool=True, *args, **kwargs) -> dict:
|
||||||
@@ -1572,10 +1638,22 @@ class Controller:
|
|||||||
config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = weight_type_dict
|
config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT = weight_type_dict
|
||||||
|
|
||||||
def updateTranscriptionEngine(self):
|
def updateTranscriptionEngine(self):
|
||||||
weight_type_dict = config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT
|
|
||||||
weight_type = config.WHISPER_WEIGHT_TYPE
|
weight_type = config.WHISPER_WEIGHT_TYPE
|
||||||
if config.SELECTED_TRANSCRIPTION_ENGINE == "Whisper" and weight_type_dict[weight_type] is False:
|
weight_type_dict = config.SELECTABLE_WHISPER_WEIGHT_TYPE_DICT
|
||||||
config.SELECTED_TRANSCRIPTION_ENGINE = "Google"
|
weight_available = bool(weight_type_dict.get(weight_type))
|
||||||
|
current_engine = config.SELECTED_TRANSCRIPTION_ENGINE
|
||||||
|
selected_engines = [key for key, value in config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS.items() if value is True]
|
||||||
|
|
||||||
|
# 選択可能なエンジンがなければ、Whisper に変更
|
||||||
|
if current_engine in {"Whisper", "Google"}:
|
||||||
|
if current_engine not in selected_engines:
|
||||||
|
if weight_available:
|
||||||
|
alternate = "Google" if current_engine == "Whisper" else "Whisper"
|
||||||
|
config.SELECTED_TRANSCRIPTION_ENGINE = alternate if alternate in selected_engines else None
|
||||||
|
else:
|
||||||
|
config.SELECTED_TRANSCRIPTION_ENGINE = "Whisper"
|
||||||
|
else:
|
||||||
|
config.SELECTED_TRANSCRIPTION_ENGINE = "Whisper"
|
||||||
|
|
||||||
def startCheckMicEnergy(self) -> None:
|
def startCheckMicEnergy(self) -> None:
|
||||||
while self.device_access_status is False:
|
while self.device_access_status is False:
|
||||||
@@ -1654,28 +1732,18 @@ class Controller:
|
|||||||
self.run(200, self.run_mapping["initialization_progress"], progress)
|
self.run(200, self.run_mapping["initialization_progress"], progress)
|
||||||
|
|
||||||
def init(self, *args, **kwargs) -> None:
|
def init(self, *args, **kwargs) -> None:
|
||||||
printLog("Start Initialization")
|
|
||||||
removeLog()
|
removeLog()
|
||||||
|
printLog("Start Initialization")
|
||||||
printLog("Init Translation Engine Status")
|
connected_network = isConnectedNetwork()
|
||||||
for engine in config.SELECTABLE_TRANSLATION_ENGINE_LIST:
|
if connected_network is True:
|
||||||
match engine:
|
self.connectedNetwork()
|
||||||
case "DeepL_API":
|
|
||||||
printLog("Start check DeepL API Key")
|
|
||||||
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
|
|
||||||
if config.AUTH_KEYS[engine] is not None:
|
|
||||||
if model.authenticationTranslatorDeepLAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
|
|
||||||
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
|
|
||||||
else:
|
else:
|
||||||
# error update Auth key
|
self.disconnectedNetwork()
|
||||||
auth_keys = config.AUTH_KEYS
|
printLog(f"Connected Network: {connected_network}")
|
||||||
auth_keys[engine] = None
|
|
||||||
config.AUTH_KEYS = auth_keys
|
|
||||||
case _:
|
|
||||||
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
|
|
||||||
|
|
||||||
self.initializationProgress(1)
|
self.initializationProgress(1)
|
||||||
|
|
||||||
|
if connected_network is True:
|
||||||
# download CTranslate2 Model Weight
|
# download CTranslate2 Model Weight
|
||||||
printLog("Download CTranslate2 Model Weight")
|
printLog("Download CTranslate2 Model Weight")
|
||||||
weight_type = config.CTRANSLATE2_WEIGHT_TYPE
|
weight_type = config.CTRANSLATE2_WEIGHT_TYPE
|
||||||
@@ -1699,6 +1767,49 @@ class Controller:
|
|||||||
if isinstance(th_download_whisper, Thread):
|
if isinstance(th_download_whisper, Thread):
|
||||||
th_download_whisper.join()
|
th_download_whisper.join()
|
||||||
|
|
||||||
|
if (model.checkTranslatorCTranslate2ModelWeight(config.CTRANSLATE2_WEIGHT_TYPE) is False or
|
||||||
|
model.checkTranscriptionWhisperModelWeight(config.WHISPER_WEIGHT_TYPE) is False):
|
||||||
|
self.disableAiModels()
|
||||||
|
else:
|
||||||
|
self.enableAiModels()
|
||||||
|
|
||||||
|
printLog("Init Translation Engine Status")
|
||||||
|
for engine in config.SELECTABLE_TRANSLATION_ENGINE_LIST:
|
||||||
|
match engine:
|
||||||
|
case "CTranslate2":
|
||||||
|
if model.checkTranslatorCTranslate2ModelWeight(config.CTRANSLATE2_WEIGHT_TYPE) is True:
|
||||||
|
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
|
||||||
|
else:
|
||||||
|
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
|
||||||
|
case "DeepL_API":
|
||||||
|
printLog("Start check DeepL API Key")
|
||||||
|
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
|
||||||
|
if config.AUTH_KEYS[engine] is not None:
|
||||||
|
if model.authenticationTranslatorDeepLAuthKey(auth_key=config.AUTH_KEYS[engine]) is True:
|
||||||
|
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
|
||||||
|
else:
|
||||||
|
# error update Auth key
|
||||||
|
auth_keys = config.AUTH_KEYS
|
||||||
|
auth_keys[engine] = None
|
||||||
|
config.AUTH_KEYS = auth_keys
|
||||||
|
case _:
|
||||||
|
if connected_network is True:
|
||||||
|
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = True
|
||||||
|
else:
|
||||||
|
config.SELECTABLE_TRANSLATION_ENGINE_STATUS[engine] = False
|
||||||
|
|
||||||
|
for engine in config.SELECTABLE_TRANSCRIPTION_ENGINE_LIST:
|
||||||
|
match engine:
|
||||||
|
case "Whisper":
|
||||||
|
if model.checkTranscriptionWhisperModelWeight(config.WHISPER_WEIGHT_TYPE) is True:
|
||||||
|
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = True
|
||||||
|
else:
|
||||||
|
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
|
||||||
|
case _:
|
||||||
|
if connected_network is True:
|
||||||
|
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = True
|
||||||
|
else:
|
||||||
|
config.SELECTABLE_TRANSCRIPTION_ENGINE_STATUS[engine] = False
|
||||||
self.initializationProgress(2)
|
self.initializationProgress(2)
|
||||||
|
|
||||||
# set Translation Engine
|
# set Translation Engine
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ from queue import Queue
|
|||||||
from controller import Controller
|
from controller import Controller
|
||||||
from utils import printLog, printResponse, errorLogging, encodeBase64
|
from utils import printLog, printResponse, errorLogging, encodeBase64
|
||||||
|
|
||||||
controller = Controller()
|
|
||||||
|
|
||||||
run_mapping = {
|
run_mapping = {
|
||||||
|
"connected_network":"/run/connected_network",
|
||||||
|
"enable_ai_models":"/run/enable_ai_models",
|
||||||
|
|
||||||
"transcription_mic":"/run/transcription_send_mic_message",
|
"transcription_mic":"/run/transcription_send_mic_message",
|
||||||
"transcription_speaker":"/run/transcription_receive_speaker_message",
|
"transcription_speaker":"/run/transcription_receive_speaker_message",
|
||||||
|
|
||||||
@@ -22,8 +23,10 @@ run_mapping = {
|
|||||||
|
|
||||||
"download_progress_ctranslate2_weight":"/run/download_progress_ctranslate2_weight",
|
"download_progress_ctranslate2_weight":"/run/download_progress_ctranslate2_weight",
|
||||||
"downloaded_ctranslate2_weight":"/run/downloaded_ctranslate2_weight",
|
"downloaded_ctranslate2_weight":"/run/downloaded_ctranslate2_weight",
|
||||||
|
"error_ctranslate2_weight":"/run/error_ctranslate2_weight",
|
||||||
"download_progress_whisper_weight":"/run/download_progress_whisper_weight",
|
"download_progress_whisper_weight":"/run/download_progress_whisper_weight",
|
||||||
"downloaded_whisper_weight":"/run/downloaded_whisper_weight",
|
"downloaded_whisper_weight":"/run/downloaded_whisper_weight",
|
||||||
|
"error_whisper_weight":"/run/error_whisper_weight",
|
||||||
|
|
||||||
"selected_mic_device":"/run/selected_mic_device",
|
"selected_mic_device":"/run/selected_mic_device",
|
||||||
"selected_speaker_device":"/run/selected_speaker_device",
|
"selected_speaker_device":"/run/selected_speaker_device",
|
||||||
@@ -41,11 +44,11 @@ run_mapping = {
|
|||||||
"initialization_complete":"/run/initialization_complete",
|
"initialization_complete":"/run/initialization_complete",
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.setRunMapping(run_mapping)
|
|
||||||
|
|
||||||
def run(status:int, endpoint:str, result:Any) -> None:
|
def run(status:int, endpoint:str, result:Any) -> None:
|
||||||
printResponse(status, endpoint, result)
|
printResponse(status, endpoint, result)
|
||||||
|
|
||||||
|
controller = Controller()
|
||||||
|
controller.setRunMapping(run_mapping)
|
||||||
controller.setRun(run)
|
controller.setRun(run)
|
||||||
|
|
||||||
mapping = {
|
mapping = {
|
||||||
@@ -81,6 +84,7 @@ mapping = {
|
|||||||
"/get/data/selected_target_languages": {"status": True, "variable":controller.getSelectedTargetLanguages},
|
"/get/data/selected_target_languages": {"status": True, "variable":controller.getSelectedTargetLanguages},
|
||||||
"/set/data/selected_target_languages": {"status": True, "variable":controller.setSelectedTargetLanguages},
|
"/set/data/selected_target_languages": {"status": True, "variable":controller.setSelectedTargetLanguages},
|
||||||
|
|
||||||
|
"/get/data/transcription_engines": {"status": False, "variable":controller.getTranscriptionEngines},
|
||||||
"/get/data/selected_transcription_engine": {"status": False, "variable":controller.getSelectedTranscriptionEngine},
|
"/get/data/selected_transcription_engine": {"status": False, "variable":controller.getSelectedTranscriptionEngine},
|
||||||
"/set/data/selected_transcription_engine": {"status": False, "variable":controller.setSelectedTranscriptionEngine},
|
"/set/data/selected_transcription_engine": {"status": False, "variable":controller.setSelectedTranscriptionEngine},
|
||||||
|
|
||||||
@@ -183,6 +187,9 @@ mapping = {
|
|||||||
"/get/data/mic_max_phrases": {"status": True, "variable":controller.getMicMaxPhrases},
|
"/get/data/mic_max_phrases": {"status": True, "variable":controller.getMicMaxPhrases},
|
||||||
"/set/data/mic_max_phrases": {"status": True, "variable":controller.setMicMaxPhrases},
|
"/set/data/mic_max_phrases": {"status": True, "variable":controller.setMicMaxPhrases},
|
||||||
|
|
||||||
|
"/get/data/hotkeys": {"status": True, "variable":controller.getHotkeys},
|
||||||
|
"/set/data/hotkeys": {"status": True, "variable":controller.setHotkeys},
|
||||||
|
|
||||||
"/get/data/mic_avg_logprob": {"status": True, "variable":controller.getMicAvgLogprob},
|
"/get/data/mic_avg_logprob": {"status": True, "variable":controller.getMicAvgLogprob},
|
||||||
"/set/data/mic_avg_logprob": {"status": True, "variable":controller.setMicAvgLogprob},
|
"/set/data/mic_avg_logprob": {"status": True, "variable":controller.setMicAvgLogprob},
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from models.transcription.transcription_recorder import SelectedMicEnergyRecorde
|
|||||||
from models.transcription.transcription_transcriber import AudioTranscriber
|
from models.transcription.transcription_transcriber import AudioTranscriber
|
||||||
from models.translation.translation_languages import translation_lang
|
from models.translation.translation_languages import translation_lang
|
||||||
from models.transcription.transcription_languages import transcription_lang
|
from models.transcription.transcription_languages import transcription_lang
|
||||||
from models.translation.translation_utils import checkCTranslate2Weight, downloadCTranslate2Weight
|
from models.translation.translation_utils import checkCTranslate2Weight, downloadCTranslate2Weight, downloadCTranslate2Tokenizer
|
||||||
from models.transcription.transcription_whisper import checkWhisperWeight, downloadWhisperWeight
|
from models.transcription.transcription_whisper import checkWhisperWeight, downloadWhisperWeight
|
||||||
from models.overlay.overlay import Overlay
|
from models.overlay.overlay import Overlay
|
||||||
from models.overlay.overlay_image import OverlayImage
|
from models.overlay.overlay_image import OverlayImage
|
||||||
@@ -72,12 +72,15 @@ class Model:
|
|||||||
self.th_check_device = None
|
self.th_check_device = None
|
||||||
self.mic_print_transcript = None
|
self.mic_print_transcript = None
|
||||||
self.mic_audio_recorder = None
|
self.mic_audio_recorder = None
|
||||||
|
self.mic_transcriber = None
|
||||||
self.mic_energy_recorder = None
|
self.mic_energy_recorder = None
|
||||||
self.mic_energy_plot_progressbar = None
|
self.mic_energy_plot_progressbar = None
|
||||||
self.speaker_print_transcript = None
|
self.speaker_print_transcript = None
|
||||||
self.speaker_audio_recorder = None
|
self.speaker_audio_recorder = None
|
||||||
|
self.speaker_transcriber = None
|
||||||
self.speaker_energy_recorder = None
|
self.speaker_energy_recorder = None
|
||||||
self.speaker_energy_plot_progressbar = None
|
self.speaker_energy_plot_progressbar = None
|
||||||
|
|
||||||
self.previous_send_message = ""
|
self.previous_send_message = ""
|
||||||
self.previous_receive_message = ""
|
self.previous_receive_message = ""
|
||||||
self.translator = Translator()
|
self.translator = Translator()
|
||||||
@@ -110,6 +113,9 @@ class Model:
|
|||||||
def downloadCTranslate2ModelWeight(self, weight_type, callback=None, end_callback=None):
|
def downloadCTranslate2ModelWeight(self, weight_type, callback=None, end_callback=None):
|
||||||
return downloadCTranslate2Weight(config.PATH_LOCAL, weight_type, callback, end_callback)
|
return downloadCTranslate2Weight(config.PATH_LOCAL, weight_type, callback, end_callback)
|
||||||
|
|
||||||
|
def downloadCTranslate2ModelTokenizer(self, weight_type):
|
||||||
|
return downloadCTranslate2Tokenizer(config.PATH_LOCAL, weight_type)
|
||||||
|
|
||||||
def isLoadedCTranslate2Model(self):
|
def isLoadedCTranslate2Model(self):
|
||||||
return self.translator.isLoadedCTranslate2Model()
|
return self.translator.isLoadedCTranslate2Model()
|
||||||
|
|
||||||
@@ -426,6 +432,7 @@ class Model:
|
|||||||
selected_your_languages = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]
|
selected_your_languages = config.SELECTED_YOUR_LANGUAGES[config.SELECTED_TAB_NO]
|
||||||
languages = [data["language"] for data in selected_your_languages.values() if data["enable"] is True]
|
languages = [data["language"] for data in selected_your_languages.values() if data["enable"] is True]
|
||||||
countries = [data["country"] for data in selected_your_languages.values() if data["enable"] is True]
|
countries = [data["country"] for data in selected_your_languages.values() if data["enable"] is True]
|
||||||
|
if isinstance(self.mic_transcriber, AudioTranscriber) is True:
|
||||||
res = self.mic_transcriber.transcribeAudioQueue(
|
res = self.mic_transcriber.transcribeAudioQueue(
|
||||||
self.mic_audio_queue,
|
self.mic_audio_queue,
|
||||||
languages,
|
languages,
|
||||||
@@ -444,7 +451,7 @@ class Model:
|
|||||||
self.mic_audio_queue.get()
|
self.mic_audio_queue.get()
|
||||||
# while not self.mic_energy_queue.empty():
|
# while not self.mic_energy_queue.empty():
|
||||||
# self.mic_energy_queue.get()
|
# self.mic_energy_queue.get()
|
||||||
del self.mic_transcriber
|
self.mic_transcriber = None
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
# def sendMicEnergy():
|
# def sendMicEnergy():
|
||||||
@@ -593,6 +600,7 @@ class Model:
|
|||||||
selected_target_languages = config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]
|
selected_target_languages = config.SELECTED_TARGET_LANGUAGES[config.SELECTED_TAB_NO]
|
||||||
languages = [data["language"] for data in selected_target_languages.values() if data["enable"] is True]
|
languages = [data["language"] for data in selected_target_languages.values() if data["enable"] is True]
|
||||||
countries = [data["country"] for data in selected_target_languages.values() if data["enable"] is True]
|
countries = [data["country"] for data in selected_target_languages.values() if data["enable"] is True]
|
||||||
|
if isinstance(self.speaker_transcriber, AudioTranscriber) is True:
|
||||||
res = self.speaker_transcriber.transcribeAudioQueue(
|
res = self.speaker_transcriber.transcribeAudioQueue(
|
||||||
speaker_audio_queue,
|
speaker_audio_queue,
|
||||||
languages,
|
languages,
|
||||||
@@ -611,7 +619,7 @@ class Model:
|
|||||||
speaker_audio_queue.get()
|
speaker_audio_queue.get()
|
||||||
# while not speaker_energy_queue.empty():
|
# while not speaker_energy_queue.empty():
|
||||||
# speaker_energy_queue.get()
|
# speaker_energy_queue.get()
|
||||||
del self.speaker_transcriber
|
self.speaker_transcriber = None
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
# def sendSpeakerEnergy():
|
# def sendSpeakerEnergy():
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class OverlayImage:
|
|||||||
|
|
||||||
text_width = draw.textlength(text, font)
|
text_width = draw.textlength(text, font)
|
||||||
character_width = text_width // len(text)
|
character_width = text_width // len(text)
|
||||||
character_line_num = (base_width // character_width) - 12
|
character_line_num = int((base_width // character_width) - 12)
|
||||||
if len(text) > character_line_num:
|
if len(text) > character_line_num:
|
||||||
text = "\n".join([text[i:i + character_line_num] for i in range(0, len(text), character_line_num)])
|
text = "\n".join([text[i:i + character_line_num] for i in range(0, len(text), character_line_num)])
|
||||||
text_height = font_size * (len(text.split("\n")) + 1) + 20
|
text_height = font_size * (len(text.split("\n")) + 1) + 20
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from typing import Callable
|
|||||||
import huggingface_hub
|
import huggingface_hub
|
||||||
from faster_whisper import WhisperModel
|
from faster_whisper import WhisperModel
|
||||||
import logging
|
import logging
|
||||||
|
from utils import getBestComputeType
|
||||||
|
|
||||||
logger = logging.getLogger('faster_whisper')
|
logger = logging.getLogger('faster_whisper')
|
||||||
logger.setLevel(logging.CRITICAL)
|
logger.setLevel(logging.CRITICAL)
|
||||||
@@ -73,7 +74,7 @@ def downloadWhisperWeight(root, weight_type, callback=None, end_callback=None):
|
|||||||
|
|
||||||
def getWhisperModel(root, weight_type, device="cpu", device_index=0):
|
def getWhisperModel(root, weight_type, device="cpu", device_index=0):
|
||||||
path = os_path.join(root, "weights", "whisper", weight_type)
|
path = os_path.join(root, "weights", "whisper", weight_type)
|
||||||
compute_type = "int8" if device == "cpu" else "float16"
|
compute_type = getBestComputeType(device, device_index)
|
||||||
return WhisperModel(
|
return WhisperModel(
|
||||||
path,
|
path,
|
||||||
device=device,
|
device=device,
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
from os import path as os_path
|
from os import path as os_path
|
||||||
from deepl import Translator as deepl_Translator
|
from deepl import Translator as deepl_Translator
|
||||||
|
try:
|
||||||
from translators import translate_text as other_web_Translator
|
from translators import translate_text as other_web_Translator
|
||||||
|
ENABLE_TRANSLATORS = True
|
||||||
|
except Exception:
|
||||||
|
ENABLE_TRANSLATORS = False
|
||||||
|
|
||||||
from .translation_languages import translation_lang
|
from .translation_languages import translation_lang
|
||||||
from .translation_utils import ctranslate2_weights
|
from .translation_utils import ctranslate2_weights
|
||||||
|
|
||||||
import ctranslate2
|
import ctranslate2
|
||||||
import transformers
|
import transformers
|
||||||
from utils import errorLogging
|
from utils import errorLogging, getBestComputeType
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
warnings.filterwarnings("ignore")
|
warnings.filterwarnings("ignore")
|
||||||
@@ -18,6 +23,7 @@ class Translator():
|
|||||||
self.ctranslate2_translator = None
|
self.ctranslate2_translator = None
|
||||||
self.ctranslate2_tokenizer = None
|
self.ctranslate2_tokenizer = None
|
||||||
self.is_loaded_ctranslate2_model = False
|
self.is_loaded_ctranslate2_model = False
|
||||||
|
self.is_enable_translators = ENABLE_TRANSLATORS
|
||||||
|
|
||||||
def authenticationDeepLAuthKey(self, authkey):
|
def authenticationDeepLAuthKey(self, authkey):
|
||||||
result = True
|
result = True
|
||||||
@@ -37,7 +43,7 @@ class Translator():
|
|||||||
weight_path = os_path.join(path, "weights", "ctranslate2", directory_name)
|
weight_path = os_path.join(path, "weights", "ctranslate2", directory_name)
|
||||||
tokenizer_path = os_path.join(path, "weights", "ctranslate2", directory_name, "tokenizer")
|
tokenizer_path = os_path.join(path, "weights", "ctranslate2", directory_name, "tokenizer")
|
||||||
|
|
||||||
compute_type = "int8" if device == "cpu" else "float16"
|
compute_type = getBestComputeType(device, device_index)
|
||||||
self.ctranslate2_translator = ctranslate2.Translator(
|
self.ctranslate2_translator = ctranslate2.Translator(
|
||||||
weight_path,
|
weight_path,
|
||||||
device=device,
|
device=device,
|
||||||
@@ -97,6 +103,7 @@ class Translator():
|
|||||||
source_language, target_language = self.getLanguageCode(translator_name, target_country, source_language, target_language)
|
source_language, target_language = self.getLanguageCode(translator_name, target_country, source_language, target_language)
|
||||||
match translator_name:
|
match translator_name:
|
||||||
case "DeepL":
|
case "DeepL":
|
||||||
|
if self.is_enable_translators is True:
|
||||||
result = other_web_Translator(
|
result = other_web_Translator(
|
||||||
query_text=message,
|
query_text=message,
|
||||||
translator="deepl",
|
translator="deepl",
|
||||||
@@ -104,6 +111,7 @@ class Translator():
|
|||||||
to_language=target_language,
|
to_language=target_language,
|
||||||
)
|
)
|
||||||
case "DeepL_API":
|
case "DeepL_API":
|
||||||
|
if self.is_enable_translators is True:
|
||||||
if self.deepl_client is None:
|
if self.deepl_client is None:
|
||||||
result = False
|
result = False
|
||||||
else:
|
else:
|
||||||
@@ -113,6 +121,7 @@ class Translator():
|
|||||||
target_lang=target_language,
|
target_lang=target_language,
|
||||||
).text
|
).text
|
||||||
case "Google":
|
case "Google":
|
||||||
|
if self.is_enable_translators is True:
|
||||||
result = other_web_Translator(
|
result = other_web_Translator(
|
||||||
query_text=message,
|
query_text=message,
|
||||||
translator="google",
|
translator="google",
|
||||||
@@ -120,6 +129,7 @@ class Translator():
|
|||||||
to_language=target_language,
|
to_language=target_language,
|
||||||
)
|
)
|
||||||
case "Bing":
|
case "Bing":
|
||||||
|
if self.is_enable_translators is True:
|
||||||
result = other_web_Translator(
|
result = other_web_Translator(
|
||||||
query_text=message,
|
query_text=message,
|
||||||
translator="bing",
|
translator="bing",
|
||||||
@@ -127,6 +137,7 @@ class Translator():
|
|||||||
to_language=target_language,
|
to_language=target_language,
|
||||||
)
|
)
|
||||||
case "Papago":
|
case "Papago":
|
||||||
|
if self.is_enable_translators is True:
|
||||||
result = other_web_Translator(
|
result = other_web_Translator(
|
||||||
query_text=message,
|
query_text=message,
|
||||||
translator="papago",
|
translator="papago",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from os import makedirs as os_makedirs
|
|||||||
from requests import get as requests_get
|
from requests import get as requests_get
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import transformers
|
||||||
from utils import errorLogging
|
from utils import errorLogging
|
||||||
|
|
||||||
ctranslate2_weights = {
|
ctranslate2_weights = {
|
||||||
@@ -87,3 +88,16 @@ def downloadCTranslate2Weight(root, weight_type="small", callback=None, end_call
|
|||||||
|
|
||||||
if isinstance(end_callback, Callable):
|
if isinstance(end_callback, Callable):
|
||||||
end_callback()
|
end_callback()
|
||||||
|
|
||||||
|
def downloadCTranslate2Tokenizer(path, weight_type="small"):
|
||||||
|
directory_name = ctranslate2_weights[weight_type]["directory_name"]
|
||||||
|
tokenizer = ctranslate2_weights[weight_type]["tokenizer"]
|
||||||
|
tokenizer_path = os_path.join(path, "weights", "ctranslate2", directory_name, "tokenizer")
|
||||||
|
|
||||||
|
try:
|
||||||
|
os_makedirs(tokenizer_path, exist_ok=True)
|
||||||
|
transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path)
|
||||||
|
except Exception:
|
||||||
|
errorLogging()
|
||||||
|
tokenizer_path = os_path.join("./weights", "ctranslate2", directory_name, "tokenizer")
|
||||||
|
transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path)
|
||||||
@@ -1,68 +1,28 @@
|
|||||||
import base64
|
import base64
|
||||||
from typing import Any
|
from typing import Any
|
||||||
import json
|
import json
|
||||||
import random
|
|
||||||
from typing import Union
|
|
||||||
from os import path as os_path, rename as os_rename
|
|
||||||
import traceback
|
import traceback
|
||||||
import logging
|
import logging
|
||||||
from PIL.Image import open as Image_open
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
def getImageFile(file_name):
|
from ctranslate2 import get_supported_compute_types
|
||||||
img = Image_open(os_path.join(os_path.dirname(__file__), "img", file_name))
|
import requests
|
||||||
return img
|
|
||||||
|
|
||||||
def callFunctionIfCallable(function, *args):
|
def isConnectedNetwork(url="http://www.google.com", timeout=3):
|
||||||
if callable(function) is True:
|
try:
|
||||||
function(*args)
|
response = requests.get(url, timeout=timeout)
|
||||||
|
return response.status_code == 200
|
||||||
|
except requests.RequestException:
|
||||||
|
return False
|
||||||
|
|
||||||
def isEven(number):
|
def getBestComputeType(device, device_index) -> str:
|
||||||
return number % 2 == 0
|
compute_types = get_supported_compute_types(device, device_index)
|
||||||
|
compute_types = set(compute_types)
|
||||||
|
preferred_types = ["int8_bfloat16", "int8_float16", "int8", "bfloat16", "float16", "int8_float32", "float32"]
|
||||||
|
|
||||||
def makeEven(number, minus:bool=False):
|
for preferred_type in preferred_types:
|
||||||
if minus is True:
|
if preferred_type in compute_types:
|
||||||
return number if isEven(number) else number - 1
|
return preferred_type
|
||||||
return number if isEven(number) else number + 1
|
|
||||||
|
|
||||||
def intToPctStr(value:int):
|
|
||||||
return f"{value}%"
|
|
||||||
|
|
||||||
def floatToPctStr(value:float):
|
|
||||||
return f"{int(value*100)}%"
|
|
||||||
|
|
||||||
def strPctToInt(value:str):
|
|
||||||
return int(value.replace("%", ""))
|
|
||||||
|
|
||||||
def isUniqueStrings(unique_strings:Union[str, list], input_string:str, require=False):
|
|
||||||
import re
|
|
||||||
if isinstance(unique_strings, str):
|
|
||||||
unique_strings = [unique_strings]
|
|
||||||
patterns = [re.escape(s) for s in unique_strings]
|
|
||||||
|
|
||||||
counts = [len(re.findall(pattern, input_string)) for pattern in patterns]
|
|
||||||
|
|
||||||
if require is True:
|
|
||||||
# If require is True, unique_strings must appear once
|
|
||||||
return all(count == 1 for count in counts) and counts.count(1) == 2
|
|
||||||
else:
|
|
||||||
# If require is False, check if unique strings are used exactly once
|
|
||||||
return all(count == 1 for count in counts)
|
|
||||||
|
|
||||||
# path先のweightフォルダがある場合にはそのフォルダ名をweightsに変更する
|
|
||||||
def renameWeightFolder(path):
|
|
||||||
weight_path = os_path.join(path, "weight")
|
|
||||||
if os_path.exists(weight_path):
|
|
||||||
os_rename(weight_path, os_path.join(path, "weights"))
|
|
||||||
|
|
||||||
def splitList(lst:list, split_count:int, to_shuffle:bool=False):
|
|
||||||
if to_shuffle is True:
|
|
||||||
random.shuffle(lst)
|
|
||||||
|
|
||||||
split_lists = []
|
|
||||||
for i in range(0, len(lst), split_count):
|
|
||||||
sub_list = lst[i:i+split_count]
|
|
||||||
split_lists.append(sub_list)
|
|
||||||
return split_lists
|
|
||||||
|
|
||||||
def encodeBase64(data:str) -> dict:
|
def encodeBase64(data:str) -> dict:
|
||||||
return json.loads(base64.b64decode(data).decode('utf-8'))
|
return json.loads(base64.b64decode(data).decode('utf-8'))
|
||||||
@@ -80,8 +40,17 @@ def setupLogger(name, log_file, level=logging.INFO):
|
|||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
logger.propagate = False # 親ロガーへの伝播を防ぐ
|
logger.propagate = False # 親ロガーへの伝播を防ぐ
|
||||||
|
|
||||||
|
# filled with 10MB logs
|
||||||
|
max_log_size = 10 * 1024 * 1024 # 10MB
|
||||||
|
|
||||||
# ハンドラーを作成
|
# ハンドラーを作成
|
||||||
file_handler = logging.FileHandler(log_file, encoding="utf-8", delay=True)
|
file_handler = RotatingFileHandler(
|
||||||
|
log_file,
|
||||||
|
maxBytes=max_log_size,
|
||||||
|
backupCount=1,
|
||||||
|
encoding="utf-8",
|
||||||
|
delay=True
|
||||||
|
)
|
||||||
file_handler.setLevel(level)
|
file_handler.setLevel(level)
|
||||||
|
|
||||||
# フォーマッターを設定
|
# フォーマッターを設定
|
||||||
|
|||||||
1070
src-tauri/Cargo.lock
generated
@@ -11,12 +11,13 @@ edition = "2021"
|
|||||||
tauri-build = { version = "1", features = [] }
|
tauri-build = { version = "1", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "1", features = [ "window-set-size", "window-set-position", "window-unmaximize", "window-close", "window-maximize", "window-minimize", "window-unminimize", "window-start-dragging", "window-set-decorations", "window-set-always-on-top", "shell-sidecar", "shell-open", "devtools"] }
|
tauri = { version = "1", features = [ "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 = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
font-kit = "0.14.2"
|
font-kit = "0.14.2"
|
||||||
window-shadows = { git = "https://github.com/tauri-apps/window-shadows.git" }
|
window-shadows = { git = "https://github.com/tauri-apps/window-shadows.git" }
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
// use tauri::command;
|
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
use window_shadows::set_shadow;
|
use window_shadows::set_shadow;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
@@ -17,6 +17,11 @@ fn main() {
|
|||||||
|
|
||||||
Ok(())
|
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])
|
.invoke_handler(tauri::generate_handler![get_font_list])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -15,8 +15,10 @@
|
|||||||
"window": {
|
"window": {
|
||||||
"all": false,
|
"all": false,
|
||||||
"setAlwaysOnTop": true,
|
"setAlwaysOnTop": true,
|
||||||
|
"setFocus": true,
|
||||||
"setDecorations": true,
|
"setDecorations": true,
|
||||||
"close": true,
|
"close": true,
|
||||||
|
"hide": true,
|
||||||
"setPosition": true,
|
"setPosition": true,
|
||||||
"setSize": true,
|
"setSize": true,
|
||||||
"maximize": true,
|
"maximize": true,
|
||||||
@@ -25,6 +27,9 @@
|
|||||||
"unminimize": true,
|
"unminimize": true,
|
||||||
"startDragging": true
|
"startDragging": true
|
||||||
},
|
},
|
||||||
|
"globalShortcut": {
|
||||||
|
"all": true
|
||||||
|
},
|
||||||
"shell": {
|
"shell": {
|
||||||
"all": false,
|
"all": false,
|
||||||
"open": true,
|
"open": true,
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import {
|
|
||||||
useWindow,
|
|
||||||
} from "@logics_common";
|
|
||||||
|
|
||||||
// import React from "react";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
KeyEventController,
|
KeyEventController,
|
||||||
StartPythonController,
|
StartPythonController,
|
||||||
|
GlobalHotKeyController,
|
||||||
UiLanguageController,
|
UiLanguageController,
|
||||||
ConfigPageCloseTriggerController,
|
ConfigPageCloseTriggerController,
|
||||||
UiSizeController,
|
UiSizeController,
|
||||||
@@ -24,9 +19,10 @@ import { UpdatingComponent } from "./updating_component/UpdatingComponent";
|
|||||||
import { ModalController } from "./modal_controller/ModalController";
|
import { ModalController } from "./modal_controller/ModalController";
|
||||||
import { SnackbarController } from "./snackbar_controller/SnackbarController";
|
import { SnackbarController } from "./snackbar_controller/SnackbarController";
|
||||||
import styles from "./App.module.scss";
|
import styles from "./App.module.scss";
|
||||||
import { useIsBackendReady, useIsSoftwareUpdating } from "@logics_common";
|
import { useIsBackendReady, useIsSoftwareUpdating, useIsVrctAvailable, useWindow } from "@logics_common";
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
|
const { currentIsVrctAvailable } = useIsVrctAvailable();
|
||||||
const { currentIsBackendReady } = useIsBackendReady();
|
const { currentIsBackendReady } = useIsBackendReady();
|
||||||
const { WindowGeometryController } = useWindow();
|
const { WindowGeometryController } = useWindow();
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
@@ -35,6 +31,7 @@ export const App = () => {
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<KeyEventController />
|
<KeyEventController />
|
||||||
<StartPythonController />
|
<StartPythonController />
|
||||||
|
<GlobalHotKeyController />
|
||||||
<UiLanguageController />
|
<UiLanguageController />
|
||||||
<ConfigPageCloseTriggerController />
|
<ConfigPageCloseTriggerController />
|
||||||
<UiSizeController />
|
<UiSizeController />
|
||||||
@@ -42,10 +39,12 @@ export const App = () => {
|
|||||||
<TransparencyController />
|
<TransparencyController />
|
||||||
<WindowGeometryController />
|
<WindowGeometryController />
|
||||||
|
|
||||||
{currentIsBackendReady.data === false
|
{(currentIsBackendReady.data === false || currentIsVrctAvailable.data === false)
|
||||||
? <SplashComponent />
|
? <SplashComponent />
|
||||||
: <Contents key={i18n.language}/>
|
: <Contents key={i18n.language}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<SnackbarController />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -61,7 +60,6 @@ const Contents = () => {
|
|||||||
<ConfigPage />
|
<ConfigPage />
|
||||||
<MainPage />
|
<MainPage />
|
||||||
<ModalController />
|
<ModalController />
|
||||||
<SnackbarController />
|
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
<UpdatingComponent />
|
<UpdatingComponent />
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
useMainFunction,
|
useMainFunction,
|
||||||
} from "@logics_main";
|
} from "@logics_main";
|
||||||
|
|
||||||
|
import { useHotkeys } from "@logics_configs";
|
||||||
|
|
||||||
import { useStore_MainFunctionsStateMemory } from "@store";
|
import { useStore_MainFunctionsStateMemory } from "@store";
|
||||||
|
|
||||||
export const ConfigPageCloseTriggerController = () => {
|
export const ConfigPageCloseTriggerController = () => {
|
||||||
@@ -27,6 +29,8 @@ export const ConfigPageCloseTriggerController = () => {
|
|||||||
volumeCheckStop_Speaker,
|
volumeCheckStop_Speaker,
|
||||||
} = useVolume();
|
} = useVolume();
|
||||||
|
|
||||||
|
const { registerShortcuts, unregisterAll } = useHotkeys();
|
||||||
|
|
||||||
|
|
||||||
const memorizeLatestMainFunctionsState = () => {
|
const memorizeLatestMainFunctionsState = () => {
|
||||||
updateMainFunctionsStateMemory({
|
updateMainFunctionsStateMemory({
|
||||||
@@ -43,9 +47,11 @@ export const ConfigPageCloseTriggerController = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentIsOpenedConfigPage.data === true) { // When config page is opened.
|
if (currentIsOpenedConfigPage.data === true) { // When config page is opened.
|
||||||
memorizeLatestMainFunctionsState();
|
memorizeLatestMainFunctionsState();
|
||||||
|
unregisterAll();
|
||||||
if (currentTranscriptionSendStatus.data === true) setTranscriptionSend(false);
|
if (currentTranscriptionSendStatus.data === true) setTranscriptionSend(false);
|
||||||
if (currentTranscriptionReceiveStatus.data === true) setTranscriptionReceive(false);
|
if (currentTranscriptionReceiveStatus.data === true) setTranscriptionReceive(false);
|
||||||
} else if (currentIsOpenedConfigPage.data === false) { // When config page is closed.
|
} else if (currentIsOpenedConfigPage.data === false) { // When config page is closed.
|
||||||
|
registerShortcuts();
|
||||||
if (currentMicThresholdCheckStatus.data === true) volumeCheckStop_Mic();
|
if (currentMicThresholdCheckStatus.data === true) volumeCheckStop_Mic();
|
||||||
if (currentSpeakerThresholdCheckStatus.data === true) volumeCheckStop_Speaker();
|
if (currentSpeakerThresholdCheckStatus.data === true) volumeCheckStop_Speaker();
|
||||||
restoreMainFunctionState();
|
restoreMainFunctionState();
|
||||||
|
|||||||
25
src-ui/app/_app_controllers/GlobalHotKeyController.jsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useHotkeys } from "@logics_configs";
|
||||||
|
import { useIsBackendReady, useIsSoftwareUpdating, useIsVrctAvailable } from "@logics_common";
|
||||||
|
|
||||||
|
export const GlobalHotKeyController = () => {
|
||||||
|
const { currentIsBackendReady } = useIsBackendReady();
|
||||||
|
const { currentIsSoftwareUpdating } = useIsSoftwareUpdating();
|
||||||
|
const { registerShortcuts, unregisterAll } = useHotkeys();
|
||||||
|
|
||||||
|
const { currentIsVrctAvailable } = useIsVrctAvailable();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const is_backend_ready = currentIsBackendReady.data;
|
||||||
|
const is_software_updating = currentIsSoftwareUpdating.data;
|
||||||
|
const is_vrct_available = currentIsVrctAvailable.data;
|
||||||
|
|
||||||
|
if (is_vrct_available && is_backend_ready && !is_software_updating) {
|
||||||
|
registerShortcuts();
|
||||||
|
} else {
|
||||||
|
unregisterAll();
|
||||||
|
}
|
||||||
|
}, [currentIsBackendReady.data, currentIsSoftwareUpdating.data, currentIsVrctAvailable.data]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -3,8 +3,13 @@ import { useEffect } from "react";
|
|||||||
export const KeyEventController = () => {
|
export const KeyEventController = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeydown = (event) => {
|
const handleKeydown = (event) => {
|
||||||
if (event.key === "F5" || (event.ctrlKey && event.key === "r") ||
|
if (
|
||||||
(event.metaKey && event.key === "r")) {
|
event.key === "F5" || // Page reload
|
||||||
|
event.key === "F10" || // Focus thw window menu (maybe)
|
||||||
|
event.key === "F12" || // Open dev tool
|
||||||
|
(event.ctrlKey && event.key === "r") ||
|
||||||
|
(event.metaKey && event.key === "r")
|
||||||
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export { KeyEventController } from "./KeyEventController";
|
export { KeyEventController } from "./KeyEventController";
|
||||||
export { StartPythonController } from "./StartPythonController";
|
export { StartPythonController } from "./StartPythonController";
|
||||||
|
export { GlobalHotKeyController } from "./GlobalHotKeyController";
|
||||||
export { UiLanguageController } from "./UiLanguageController";
|
export { UiLanguageController } from "./UiLanguageController";
|
||||||
export { ConfigPageCloseTriggerController } from "./ConfigPageCloseTriggerController";
|
export { ConfigPageCloseTriggerController } from "./ConfigPageCloseTriggerController";
|
||||||
export { UiSizeController } from "./UiSizeController";
|
export { UiSizeController } from "./UiSizeController";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-size: 62.5%;
|
font-size: 62.5%;
|
||||||
color: #F2F2F2;
|
color: var(--dark_basic_text_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
|
|
||||||
--sent_400_color: #6197b4;
|
--sent_400_color: #6197b4;
|
||||||
--received_300_color: #a861b4;
|
--received_300_color: #a861b4;
|
||||||
|
--error_bc_color: #bb4448;
|
||||||
|
--error_bc_active_color: #9c3938;
|
||||||
|
--success_bc_color: #368777;
|
||||||
|
--waring_color: #cb944f;
|
||||||
|
|
||||||
--dark_basic_text_color: #f2f2f2;
|
--dark_basic_text_color: #f2f2f2;
|
||||||
--dark_100_color: #f5f7fb;
|
--dark_100_color: #f5f7fb;
|
||||||
@@ -48,8 +52,11 @@
|
|||||||
--dark_975_color: #1a1b1d;
|
--dark_975_color: #1a1b1d;
|
||||||
--dark_1000_color: #151517;
|
--dark_1000_color: #151517;
|
||||||
|
|
||||||
--dark_825_color_cc: #434447cc;
|
|
||||||
--dark_550_color_22: #94959922;
|
--dark_550_color_22: #94959922;
|
||||||
|
--dark_825_color_cc: #434447cc;
|
||||||
|
--dark_1000_color_66: #15151766;
|
||||||
|
--dark_1000_color_aa: #151517aa;
|
||||||
|
--dark_1000_color_dd: #151517dd;
|
||||||
|
|
||||||
|
|
||||||
--title_bar_height: 2rem;
|
--title_bar_height: 2rem;
|
||||||
|
|||||||
@@ -3,22 +3,9 @@ import styles from "./ConfigPage.module.scss";
|
|||||||
import { Topbar } from "./topbar/Topbar.jsx";
|
import { Topbar } from "./topbar/Topbar.jsx";
|
||||||
import { SidebarSection } from "./sidebar_section/SidebarSection.jsx";
|
import { SidebarSection } from "./sidebar_section/SidebarSection.jsx";
|
||||||
import { SettingSection } from "./setting_section/SettingSection.jsx";
|
import { SettingSection } from "./setting_section/SettingSection.jsx";
|
||||||
|
import { VersionLabel } from "./version_label/VersionLabel.jsx";
|
||||||
import { useSoftwareVersion } from "@logics_configs";
|
|
||||||
import { useComputeMode } from "@logics_common";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
export const ConfigPage = () => {
|
export const ConfigPage = () => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const { currentSoftwareVersion } = useSoftwareVersion();
|
|
||||||
const { currentComputeMode } = useComputeMode();
|
|
||||||
|
|
||||||
const version_label = currentComputeMode.data === "cpu"
|
|
||||||
? t("config_page.version", { version: currentSoftwareVersion.data })
|
|
||||||
: currentComputeMode.data === "cuda"
|
|
||||||
? t("config_page.version", { version: currentSoftwareVersion.data }) + " CUDA"
|
|
||||||
: t("config_page.version", { version: currentSoftwareVersion.data });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.page}>
|
<div className={styles.page}>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
@@ -27,7 +14,7 @@ export const ConfigPage = () => {
|
|||||||
<SidebarSection />
|
<SidebarSection />
|
||||||
<SettingSection />
|
<SettingSection />
|
||||||
</div>
|
</div>
|
||||||
<p className={styles.software_version}>{version_label}</p>
|
<VersionLabel />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,11 +24,3 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
padding-top: var(--config_page_topbar_height);
|
padding-top: var(--config_page_topbar_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
.software_version {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0.8rem;
|
|
||||||
left: 1.2rem;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: var(--dark_400_color);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
.scroll_container {
|
.scroll_container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: scroll;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Others,
|
Others,
|
||||||
AdvancedSettings,
|
AdvancedSettings,
|
||||||
Vr,
|
Vr,
|
||||||
|
Hotkeys,
|
||||||
Supporters,
|
Supporters,
|
||||||
AboutVrct,
|
AboutVrct,
|
||||||
} from "@setting_box";
|
} from "@setting_box";
|
||||||
@@ -27,6 +28,8 @@ export const SettingBox = () => {
|
|||||||
return <Others />;
|
return <Others />;
|
||||||
case "vr":
|
case "vr":
|
||||||
return <Vr />;
|
return <Vr />;
|
||||||
|
case "hotkeys":
|
||||||
|
return <Hotkeys />;
|
||||||
case "advanced_settings":
|
case "advanced_settings":
|
||||||
return <AdvancedSettings />;
|
return <AdvancedSettings />;
|
||||||
case "supporters":
|
case "supporters":
|
||||||
|
|||||||
@@ -8,23 +8,36 @@ const _Entry = forwardRef((props, ref) => {
|
|||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
focus: () => {
|
focus: () => {
|
||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
|
},
|
||||||
|
blur: () => {
|
||||||
|
inputRef.current.blur();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
const input_class_names = clsx(styles.entry_input_area, {
|
const input_class_names = clsx(styles.entry_input_area, {
|
||||||
[styles.is_disabled]: props.is_disabled
|
[styles.is_disabled]: props.is_disabled,
|
||||||
|
});
|
||||||
|
const input_wrapper_class_names = clsx(styles.entry_wrapper, {
|
||||||
|
[styles.is_activated]: props.is_activated,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.entry_container}>
|
<div className={styles.entry_container}>
|
||||||
<div
|
<div
|
||||||
className={styles.entry_wrapper}
|
className={input_wrapper_class_names}
|
||||||
style={{width: props.width }}
|
style={{width: props.width }}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
text={props.text ? props.text : "text"}
|
||||||
|
placeholder={props.placeholder ? props.placeholder : ""}
|
||||||
className={input_class_names}
|
className={input_class_names}
|
||||||
value={props.ui_variable === null ? "" : props.ui_variable}
|
value={props.ui_variable === null ? "" : props.ui_variable}
|
||||||
onChange={(e) => props.onChange(e)}
|
onChange={(e) => props.onChange?.(e)}
|
||||||
|
onFocus={(e) => props.onFocus?.(e)}
|
||||||
|
onBlur={(e) => props.onBlur?.(e)}
|
||||||
|
onKeyDown={(e) => props.onKeyDown?.(e)}
|
||||||
|
onKeyUp={(e) => props.onKeyUp?.(e)}
|
||||||
|
readOnly={props.readOnly === true ? true : false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
background-color: var(--dark_875_color);
|
background-color: var(--dark_875_color);
|
||||||
border: 0.1rem solid var(--dark_750_color);
|
border: 0.1rem solid var(--dark_750_color);
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
|
&.is_activated {
|
||||||
|
border: 0.1rem solid var(--primary_400_color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.entry_input_area {
|
.entry_input_area {
|
||||||
@@ -16,7 +19,6 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
resize: none;
|
resize: none;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
&.is_disabled {
|
&.is_disabled {
|
||||||
color: var(--dark_500_color);
|
color: var(--dark_500_color);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@@ -22,11 +22,11 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
background-color: (#00000044);
|
background-color: var(--dark_1000_color_66);
|
||||||
backdrop-filter: blur(4rem);
|
backdrop-filter: blur(4rem);
|
||||||
border: solid 0.1rem var(--dark_700_color);
|
border: solid 0.1rem var(--dark_700_color);
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: (#00000088);
|
background-color: var(--dark_1000_color_aa);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
backdrop-filter: blur(1.4rem);
|
backdrop-filter: blur(1.4rem);
|
||||||
@@ -35,7 +35,6 @@
|
|||||||
|
|
||||||
.edit_button {
|
.edit_button {
|
||||||
padding: 0.8rem 1.2rem;
|
padding: 0.8rem 1.2rem;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
@@ -62,7 +61,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.save_button_label {
|
.save_button_label {
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +89,6 @@
|
|||||||
|
|
||||||
.open_webpage_text {
|
.open_webpage_text {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.external_link_svg {
|
.external_link_svg {
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { useState, useEffect } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import clsx from "clsx";
|
|
||||||
import CircularProgress from "@mui/material/CircularProgress";
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
import styles from "./DownloadModels.module.scss";
|
import styles from "./DownloadModels.module.scss";
|
||||||
import {
|
import {
|
||||||
RadioButton,
|
RadioButton,
|
||||||
// DownloadModels,
|
|
||||||
} from "../index";
|
} from "../index";
|
||||||
|
|
||||||
export const DownloadModels = (props) => {
|
export const DownloadModels = (props) => {
|
||||||
const options = props.options.map(item => ({
|
const options = props.options.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
@@ -25,25 +23,11 @@ export const DownloadModels = (props) => {
|
|||||||
downloadStartFunction={props.downloadStartFunction}
|
downloadStartFunction={props.downloadStartFunction}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
// <div className={styles.container}>
|
|
||||||
// {props.models.map((option) => (
|
|
||||||
// <ModelSelector key={option.model_id} option={option} {...props}/>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ModelSelector = ({option, ...props}) => {
|
const ModelSelector = ({option, ...props}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [circular_color, setCircularColor] = useState("");
|
|
||||||
const [circular_color_2, setCircularColor2] = useState("");
|
|
||||||
useEffect(() => {
|
|
||||||
const circular_color = getComputedStyle(document.documentElement).getPropertyValue("--dark_600_color");
|
|
||||||
setCircularColor(circular_color.trim());
|
|
||||||
const circular_color_2 = getComputedStyle(document.documentElement).getPropertyValue("--primary_300_color");
|
|
||||||
setCircularColor2(circular_color_2.trim());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
const circular_progress = Math.floor(option.progress / 10) * 10;
|
const circular_progress = Math.floor(option.progress / 10) * 10;
|
||||||
@@ -56,13 +40,13 @@ const ModelSelector = ({option, ...props}) => {
|
|||||||
variant={(option.progress === 100) ? "indeterminate" : "determinate"}
|
variant={(option.progress === 100) ? "indeterminate" : "determinate"}
|
||||||
value={circular_progress}
|
value={circular_progress}
|
||||||
size="3rem"
|
size="3rem"
|
||||||
sx={{ color: circular_color_2 }}
|
sx={{ color: "var(--primary_300_color)" }}
|
||||||
/>
|
/>
|
||||||
<p className={styles.progress_label}>{`${Math.round(option.progress)}%`}</p>
|
<p className={styles.progress_label}>{`${Math.round(option.progress)}%`}</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
case option.is_pending:
|
case option.is_pending:
|
||||||
return <CircularProgress size="3rem" sx={{ color: circular_color }}/>;
|
return <CircularProgress size="3rem" sx={{ color: "var(--dark_600_color)" }}/>;
|
||||||
case !option.is_downloaded:
|
case !option.is_downloaded:
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -22,11 +22,9 @@
|
|||||||
}
|
}
|
||||||
.download_button_label {
|
.download_button_label {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress_label {
|
.progress_label {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
export const HotkeysEntry = (props) => {
|
||||||
|
const [isAcceptingInput, setIsAcceptingInput] = useState(false);
|
||||||
|
const [displayValue, setDisplayValue] = useState("");
|
||||||
|
const lastKeyRef = useRef(null);
|
||||||
|
const isModifierOnlyRef = useRef(false);
|
||||||
|
const entryRef = useRef(null);
|
||||||
|
const pressedKeys = useRef(new Set());
|
||||||
|
const keysRef = useRef([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const init_display_value = props.value[props.hotkey_id] ? props.value[props.hotkey_id].join(" + ") : "";
|
||||||
|
setDisplayValue(init_display_value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const updateHotkeys = (keys) => {
|
||||||
|
entryRef.current.blur();
|
||||||
|
const result = props.setHotkeys({ [props.hotkey_id]: keys });
|
||||||
|
if (result === false) setDisplayValue("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const processKey = (key) => {
|
||||||
|
if (/^[a-zA-Z]$/.test(key)) return key.toUpperCase();
|
||||||
|
if (key === "Meta") return "Super";
|
||||||
|
return key;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyInput = (event) => {
|
||||||
|
const keys = [];
|
||||||
|
const nonModifierKeys = [];
|
||||||
|
|
||||||
|
["Ctrl", "Shift", "Alt", "Meta"].forEach((modKey) => {
|
||||||
|
if (event[`${modKey.toLowerCase()}Key`] && !keys.includes(modKey)) {
|
||||||
|
let register_mod_key = (modKey === "Meta") ? "Super" : modKey;
|
||||||
|
keys.push(register_mod_key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = processKey(event.key);
|
||||||
|
if (!["Control", "Shift", "Alt", "Meta"].includes(event.key)) {
|
||||||
|
keys.push(key);
|
||||||
|
nonModifierKeys.push(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pressedKeys.current.has(key)) {
|
||||||
|
pressedKeys.current.add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
keysRef.current = keys;
|
||||||
|
setDisplayValue(keys.join(" + "));
|
||||||
|
isModifierOnlyRef.current = nonModifierKeys.length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (lastKeyRef.current === event.key) return;
|
||||||
|
|
||||||
|
lastKeyRef.current = event.key;
|
||||||
|
handleKeyInput(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyUp = (event) => {
|
||||||
|
lastKeyRef.current = null;
|
||||||
|
|
||||||
|
const key = processKey(event.key);
|
||||||
|
pressedKeys.current.delete(key);
|
||||||
|
|
||||||
|
if (isModifierOnlyRef.current) {
|
||||||
|
setDisplayValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pressedKeys.current.size === 0) {
|
||||||
|
const hasNonModifierKeys = keysRef.current.some(
|
||||||
|
(key) => !["Ctrl", "Shift", "Alt", "Super"].includes(key)
|
||||||
|
);
|
||||||
|
if (hasNonModifierKeys) {
|
||||||
|
updateHotkeys(keysRef.current);
|
||||||
|
} else {
|
||||||
|
const display_value = props.value[props.hotkey_id] ? props.value[props.hotkey_id].join(" + ") : "";
|
||||||
|
setDisplayValue(display_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
setIsAcceptingInput(false);
|
||||||
|
pressedKeys.current.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
updateHotkeys(null);
|
||||||
|
setDisplayValue("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const is_pending = props.state === "pending";
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
{is_pending && <span className={styles.loader}></span>}
|
||||||
|
<_Entry
|
||||||
|
ref={entryRef}
|
||||||
|
type="text"
|
||||||
|
onFocus={() => setIsAcceptingInput(true)}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onKeyUp={handleKeyUp}
|
||||||
|
ui_variable={displayValue}
|
||||||
|
width="20rem"
|
||||||
|
is_activated={isAcceptingInput}
|
||||||
|
is_disabled={is_pending}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<button className={clsx(styles.delete_button, { [styles.is_pending]: is_pending })} onClick={handleDelete}>
|
||||||
|
<DeleteSvg className={styles.delete_svg}/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
@import "@scss_mixins";
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete_button {
|
||||||
|
padding: 0.4rem;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
// background-color: var(--dark_800_color);
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--dark_850_color);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background-color: var(--dark_900_color);
|
||||||
|
}
|
||||||
|
&.is_pending {
|
||||||
|
pointer-events: none;
|
||||||
|
& .delete_svg {
|
||||||
|
color: var(--dark_600_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete_svg {
|
||||||
|
width: 2.2rem;
|
||||||
|
color: var(--error_bc_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
@include loader(2rem, 0.2rem, left, -2.2rem);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ export { ComputeDevice } from "./compute_device/ComputeDevice";
|
|||||||
export { DeeplAuthKey, OpenWebpage_DeeplAuthKey } from "./deepl_auth_key/DeeplAuthKey";
|
export { DeeplAuthKey, OpenWebpage_DeeplAuthKey } from "./deepl_auth_key/DeeplAuthKey";
|
||||||
export { DropdownMenu } from "./dropdown_menu/DropdownMenu";
|
export { DropdownMenu } from "./dropdown_menu/DropdownMenu";
|
||||||
export { Entry } from "./entry/Entry";
|
export { Entry } from "./entry/Entry";
|
||||||
|
export { HotkeysEntry } from "./hotkeys_entry/HotkeysEntry";
|
||||||
export { LabelComponent } from "./label_component/LabelComponent";
|
export { LabelComponent } from "./label_component/LabelComponent";
|
||||||
export { RadioButton } from "./radio_button/RadioButton";
|
export { RadioButton } from "./radio_button/RadioButton";
|
||||||
export { SectionLabelComponent } from "./section_label_component/SectionLabelComponent";
|
export { SectionLabelComponent } from "./section_label_component/SectionLabelComponent";
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
.label {
|
.label {
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,13 +31,6 @@ export const Slider = (props) => {
|
|||||||
backgroundColor: "var(--dark_800_color)",
|
backgroundColor: "var(--dark_800_color)",
|
||||||
padding: "0.6rem 1rem",
|
padding: "0.6rem 1rem",
|
||||||
lineHeight: "1.15",
|
lineHeight: "1.15",
|
||||||
// top: "-1.4rem",
|
|
||||||
// "&::before": {
|
|
||||||
// left: "30%",
|
|
||||||
// width: "1rem",
|
|
||||||
// height: "1rem",
|
|
||||||
// clipPath: "polygon(50% 0, 100% 100%, 0 100%)",
|
|
||||||
// },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"& .MuiSlider-markLabel": {
|
"& .MuiSlider-markLabel": {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
resize: none;
|
resize: none;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
&.is_disable {
|
&.is_disable {
|
||||||
color: var(--dark_500_color);
|
color: var(--dark_500_color);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@@ -41,5 +41,4 @@
|
|||||||
|
|
||||||
.button_text {
|
.button_text {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,6 @@
|
|||||||
.item_text {
|
.item_text {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
&.is_redoable {
|
&.is_redoable {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
@@ -56,7 +55,7 @@
|
|||||||
|
|
||||||
.delete_svg {
|
.delete_svg {
|
||||||
width: 2.4rem;
|
width: 2.4rem;
|
||||||
color: #bb4448;
|
color: var(--error_bc_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.redo_svg {
|
.redo_svg {
|
||||||
@@ -77,7 +76,6 @@
|
|||||||
.add_button {
|
.add_button {
|
||||||
padding: 0.8rem 1.2rem;
|
padding: 0.8rem 1.2rem;
|
||||||
background-color: var(--primary_600_color);
|
background-color: var(--primary_600_color);
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Slider,
|
Slider,
|
||||||
SwitchBox,
|
SwitchBox,
|
||||||
Entry,
|
Entry,
|
||||||
|
HotkeysEntry,
|
||||||
RadioButton,
|
RadioButton,
|
||||||
OpenWebpage_DeeplAuthKey,
|
OpenWebpage_DeeplAuthKey,
|
||||||
DeeplAuthKey,
|
DeeplAuthKey,
|
||||||
@@ -75,6 +76,10 @@ export const EntryContainer = (props) => (
|
|||||||
<CommonContainer Component={Entry} {...props} add_break_point={false} />
|
<CommonContainer Component={Entry} {...props} add_break_point={false} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const HotkeysEntryContainer = (props) => (
|
||||||
|
<CommonContainer Component={HotkeysEntry} {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
export const RadioButtonContainer = (props) => (
|
export const RadioButtonContainer = (props) => (
|
||||||
<CommonContainer Component={RadioButton} {...props} />
|
<CommonContainer Component={RadioButton} {...props} />
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,10 +3,20 @@ import dev_section_title from "@images/about_vrct/dev_section_title.png";
|
|||||||
import dev_misya from "@images/about_vrct/dev_misya.png";
|
import dev_misya from "@images/about_vrct/dev_misya.png";
|
||||||
import dev_shiina from "@images/about_vrct/dev_shiina.png";
|
import dev_shiina from "@images/about_vrct/dev_shiina.png";
|
||||||
import vrct_logo_for_about_vrct from "@images/about_vrct/vrct_logo_for_about_vrct.png";
|
import vrct_logo_for_about_vrct from "@images/about_vrct/vrct_logo_for_about_vrct.png";
|
||||||
|
|
||||||
import contributors_section_title from "@images/about_vrct/contributors_section_title.png";
|
import contributors_section_title from "@images/about_vrct/contributors_section_title.png";
|
||||||
import contributors_members from "@images/about_vrct/contributors_members.png";
|
import contributor_done from "@images/about_vrct/contributor_done.png";
|
||||||
|
import contributor_iya from "@images/about_vrct/contributor_iya.png";
|
||||||
|
import contributor_rera from "@images/about_vrct/contributor_rera.png";
|
||||||
|
import contributor_poposuke from "@images/about_vrct/contributor_poposuke.png";
|
||||||
|
import contributor_kumaguma from "@images/about_vrct/contributor_kumaguma.png";
|
||||||
|
|
||||||
import localization_section_title from "@images/about_vrct/localization_section_title.png";
|
import localization_section_title from "@images/about_vrct/localization_section_title.png";
|
||||||
import localization_members from "@images/about_vrct/localization_members.png";
|
import localization_1 from "@images/about_vrct/localization_1.png";
|
||||||
|
import localization_2 from "@images/about_vrct/localization_2.png";
|
||||||
|
import localization_3 from "@images/about_vrct/localization_3.png";
|
||||||
|
import localization_4 from "@images/about_vrct/localization_4.png";
|
||||||
|
import localization_5 from "@images/about_vrct/localization_5.png";
|
||||||
|
|
||||||
import special_thanks_section_title from "@images/about_vrct/special_thanks_section_title.png";
|
import special_thanks_section_title from "@images/about_vrct/special_thanks_section_title.png";
|
||||||
import special_thanks_members from "@images/about_vrct/special_thanks_members.png";
|
import special_thanks_members from "@images/about_vrct/special_thanks_members.png";
|
||||||
@@ -15,8 +25,6 @@ import special_thanks_message_ja from "@images/about_vrct/special_thanks_message
|
|||||||
|
|
||||||
import poster_showcase_section_title from "@images/about_vrct/poster_showcase_section_title.png";
|
import poster_showcase_section_title from "@images/about_vrct/poster_showcase_section_title.png";
|
||||||
|
|
||||||
import vrchat_disclaimer from "@images/about_vrct/vrchat_disclaimer.png";
|
|
||||||
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useStore_UiLanguage } from "@store";
|
import { useStore_UiLanguage } from "@store";
|
||||||
@@ -43,7 +51,9 @@ export const AboutVrct = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.project_links_and_logo_section}>
|
<div className={styles.project_links_and_logo_section}>
|
||||||
|
<div className={styles.about_vrct_logo_wrapper}>
|
||||||
<img src={vrct_logo_for_about_vrct} className={styles.about_vrct_logo} />
|
<img src={vrct_logo_for_about_vrct} className={styles.about_vrct_logo} />
|
||||||
|
</div>
|
||||||
<div className={styles.project_links_wrapper}>
|
<div className={styles.project_links_wrapper}>
|
||||||
<OpenLinkContainer className={styles.project_link} href_id="project_link_booth" />
|
<OpenLinkContainer className={styles.project_link} href_id="project_link_booth" />
|
||||||
<OpenLinkContainer className={styles.project_link} href_id="project_link_documents" />
|
<OpenLinkContainer className={styles.project_link} href_id="project_link_documents" />
|
||||||
@@ -55,19 +65,46 @@ export const AboutVrct = () => {
|
|||||||
<div className={styles.contributors_section}>
|
<div className={styles.contributors_section}>
|
||||||
<img src={contributors_section_title} className={clsx(styles.section_title, styles.contributors)} />
|
<img src={contributors_section_title} className={clsx(styles.section_title, styles.contributors)} />
|
||||||
<div className={styles.contributors_img_wrapper}>
|
<div className={styles.contributors_img_wrapper}>
|
||||||
<img src={contributors_members} className={clsx(styles.contributors_img, styles.contributors)} />
|
<div className={styles.contributor_card_wrapper}>
|
||||||
|
<img src={contributor_done} className={clsx(styles.contributors_img, styles.contributors)} />
|
||||||
<OpenLinkContainer className={styles.contributors_done_san_x} href_id="contributors_done_san_x" />
|
<OpenLinkContainer className={styles.contributors_done_san_x} href_id="contributors_done_san_x" />
|
||||||
|
</div>
|
||||||
|
<div className={styles.contributor_card_wrapper}>
|
||||||
|
<img src={contributor_iya} className={clsx(styles.contributors_img, styles.contributors)} />
|
||||||
<OpenLinkContainer className={styles.contributors_iya_x} href_id="contributors_iya_x" />
|
<OpenLinkContainer className={styles.contributors_iya_x} href_id="contributors_iya_x" />
|
||||||
|
</div>
|
||||||
|
<div className={styles.contributor_card_wrapper}>
|
||||||
|
<img src={contributor_rera} className={clsx(styles.contributors_img, styles.contributors)} />
|
||||||
<OpenLinkContainer className={styles.contributors_rera_x} href_id="contributors_rera_x" />
|
<OpenLinkContainer className={styles.contributors_rera_x} href_id="contributors_rera_x" />
|
||||||
<OpenLinkContainer className={styles.contributors_rera_github} href_id="contributors_rera_github" />
|
<OpenLinkContainer className={styles.contributors_rera_github} href_id="contributors_rera_github" />
|
||||||
|
</div>
|
||||||
|
<div className={styles.contributor_card_wrapper}>
|
||||||
|
<img src={contributor_poposuke} className={clsx(styles.contributors_img, styles.contributors)} />
|
||||||
<OpenLinkContainer className={styles.contributors_poposuke_x} href_id="contributors_poposuke_x" />
|
<OpenLinkContainer className={styles.contributors_poposuke_x} href_id="contributors_poposuke_x" />
|
||||||
|
</div>
|
||||||
|
<div className={styles.contributor_card_wrapper}>
|
||||||
|
<img src={contributor_kumaguma} className={clsx(styles.contributors_img, styles.contributors)} />
|
||||||
<OpenLinkContainer className={styles.contributors_kumaguma_x} href_id="contributors_kumaguma_x" />
|
<OpenLinkContainer className={styles.contributors_kumaguma_x} href_id="contributors_kumaguma_x" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={styles.localization_section}>
|
<div className={styles.localization_section}>
|
||||||
<img src={localization_section_title} className={clsx(styles.section_title, styles.localization)} />
|
<img src={localization_section_title} className={clsx(styles.section_title, styles.localization)} />
|
||||||
<img src={localization_members} className={clsx(styles.localization_members_img, styles.localization)} />
|
<div className={styles.localization_members_wrapper}>
|
||||||
|
<div className={styles.localization_members_row_wrapper}>
|
||||||
|
<img src={localization_1} className={styles.localization_members_img} />
|
||||||
|
<img src={localization_2} className={styles.localization_members_img} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.localization_members_row_wrapper}>
|
||||||
|
<img src={localization_3} className={styles.localization_members_img} />
|
||||||
|
<img src={localization_4} className={styles.localization_members_img} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.localization_members_row_wrapper}>
|
||||||
|
<img src={localization_5} className={styles.localization_members_img} />
|
||||||
|
{/* <img src={localization_6} className={styles.localization_members_img} /> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.special_thanks_section}>
|
<div className={styles.special_thanks_section}>
|
||||||
@@ -87,7 +124,7 @@ export const AboutVrct = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.vrchat_disclaimer_section}>
|
<div className={styles.vrchat_disclaimer_section}>
|
||||||
<img src={vrchat_disclaimer} className={styles.vrchat_disclaimer} />
|
<p className={styles.vrchat_disclaimer}>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.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 2.2rem;
|
gap: 2.2rem;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 72rem;
|
max-width: 72rem;
|
||||||
// background-color: gray;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section_title {
|
.section_title {
|
||||||
@@ -30,20 +32,23 @@
|
|||||||
}
|
}
|
||||||
.dev_section_wrapper {
|
.dev_section_wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
gap: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.dev_card_wrapper {
|
.dev_card_wrapper {
|
||||||
width: 34.6rem;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
// width: 100%;
|
||||||
}
|
}
|
||||||
.dev_card_img {
|
.dev_card_img {
|
||||||
width: 100%;
|
width: 34.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin dev_sns_styles($right) {
|
@mixin dev_sns_styles($right) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: $right;
|
right: $right;
|
||||||
bottom: 1.2rem;
|
bottom: 0.6rem;
|
||||||
width: 2.8rem;
|
width: 2.8rem;
|
||||||
padding: 0.4rem;
|
padding: 0.4rem;
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
@@ -69,8 +74,16 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
text-align: center;
|
|
||||||
padding: 0 5.5rem;
|
padding: 0 5.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.about_vrct_logo_wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 2rem 0;
|
||||||
}
|
}
|
||||||
.about_vrct_logo {
|
.about_vrct_logo {
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
@@ -78,7 +91,7 @@
|
|||||||
}
|
}
|
||||||
.project_links_wrapper {
|
.project_links_wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-wrap: wrap;
|
||||||
gap: 0.2rem;
|
gap: 0.2rem;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
@@ -87,6 +100,7 @@
|
|||||||
height: 2.6rem;
|
height: 2.6rem;
|
||||||
padding: 0.4rem 1rem;
|
padding: 0.4rem 1rem;
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
|
flex-shrink: 0;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--dark_850_color);
|
background-color: var(--dark_850_color);
|
||||||
}
|
}
|
||||||
@@ -96,19 +110,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.contributors_img_wrapper {
|
.contributors_img_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.contributor_card_wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.contributors_img {
|
.contributors_img {
|
||||||
width: 100%;
|
width: 22rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin contributors_sns_styles($top, $left) {
|
@mixin contributors_sns_styles($bottom, $left) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: $left;
|
left: $left;
|
||||||
top: $top;
|
bottom: $bottom;
|
||||||
width: 2.4rem;
|
width: 2.4rem;
|
||||||
padding: 0.4rem;
|
padding: 0.4rem;
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
|
transform: translate(50%, 50%);
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--dark_825_color);
|
background-color: var(--dark_825_color);
|
||||||
}
|
}
|
||||||
@@ -117,34 +139,51 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$first_line_top: 6.2rem;
|
$bottom_pos: 16%;
|
||||||
|
$sns_left_pos: 0.8rem;
|
||||||
.contributors_done_san_x {
|
.contributors_done_san_x {
|
||||||
@include contributors_sns_styles($first_line_top, 2rem);
|
@include contributors_sns_styles($bottom_pos, $sns_left_pos);
|
||||||
}
|
}
|
||||||
.contributors_iya_x {
|
.contributors_iya_x {
|
||||||
@include contributors_sns_styles($first_line_top, 27.6rem);
|
@include contributors_sns_styles($bottom_pos, $sns_left_pos);
|
||||||
}
|
}
|
||||||
.contributors_rera_x {
|
.contributors_rera_x {
|
||||||
@include contributors_sns_styles($first_line_top, 52.2rem);
|
@include contributors_sns_styles($bottom_pos, calc($sns_left_pos - 1.4rem));
|
||||||
}
|
}
|
||||||
.contributors_rera_github {
|
.contributors_rera_github {
|
||||||
@include contributors_sns_styles($first_line_top, 55rem);
|
@include contributors_sns_styles($bottom_pos, calc($sns_left_pos + 1.4rem));
|
||||||
}
|
}
|
||||||
|
|
||||||
$second_line_top: 16.6rem;
|
|
||||||
.contributors_poposuke_x {
|
.contributors_poposuke_x {
|
||||||
@include contributors_sns_styles($second_line_top, 14.8rem);
|
@include contributors_sns_styles($bottom_pos, $sns_left_pos);
|
||||||
}
|
}
|
||||||
.contributors_kumaguma_x {
|
.contributors_kumaguma_x {
|
||||||
@include contributors_sns_styles($second_line_top, 40.8rem);
|
@include contributors_sns_styles($bottom_pos, $sns_left_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
.localization_section {
|
.localization_section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
gap: 0.6rem;
|
||||||
|
}
|
||||||
|
.localization_members_wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: start;
|
||||||
|
column-gap: 6rem;
|
||||||
|
row-gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.localization_members_row_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: start;
|
||||||
|
gap: 0.2rem;
|
||||||
}
|
}
|
||||||
.localization_members_img {
|
.localization_members_img {
|
||||||
width: 100%;
|
height: 2.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.special_thanks_section {
|
.special_thanks_section {
|
||||||
@@ -163,9 +202,11 @@ $second_line_top: 16.6rem;
|
|||||||
.poster_showcase_section {
|
.poster_showcase_section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vrchat_disclaimer {
|
.vrchat_disclaimer {
|
||||||
width: 100%;
|
font-size: 1.2rem;
|
||||||
margin-top: 8rem;
|
margin-top: 8rem;
|
||||||
|
color: var(--dark_600_color);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
// flex: 1;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
width: 42rem;
|
||||||
}
|
}
|
||||||
$image_height: 2.8rem;
|
$image_height: 2.8rem;
|
||||||
$y_padding: 0.4rem;
|
$y_padding: 0.4rem;
|
||||||
@@ -32,6 +33,7 @@ $image_height_gap: 0.4rem;
|
|||||||
}
|
}
|
||||||
.poster_showcase_world_img {
|
.poster_showcase_world_img {
|
||||||
height: $image_height;
|
height: $image_height;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination_container {
|
.pagination_container {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.poster_pagination_wrapper {
|
.poster_pagination_wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -61,7 +61,6 @@
|
|||||||
|
|
||||||
.device_label {
|
.device_label {
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.device_contents {
|
.device_contents {
|
||||||
@@ -76,6 +75,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1.2rem;
|
gap: 1.2rem;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device_dropdown_wrapper {
|
.device_dropdown_wrapper {
|
||||||
@@ -97,6 +98,7 @@
|
|||||||
|
|
||||||
.device_secondary_label {
|
.device_secondary_label {
|
||||||
padding-left: 0.2rem;
|
padding-left: 0.2rem;
|
||||||
|
padding-right: 0.4rem;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
color: var(--dark_500_color);
|
color: var(--dark_500_color);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { useHotkeys } from "@logics_configs";
|
||||||
|
import styles from "./Hotkeys.module.scss";
|
||||||
|
import { HotkeysEntryContainer } from "../_templates/Templates";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
export const Hotkeys = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<HotkeysBoxContainer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const HotkeysBoxContainer = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { currentHotkeys, setHotkeys } = useHotkeys();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<HotkeysEntryContainer
|
||||||
|
label={t("config_page.hotkeys.toggle_vrct_visibility.label")}
|
||||||
|
hotkey_id="toggle_vrct_visibility"
|
||||||
|
value={currentHotkeys.data}
|
||||||
|
state={currentHotkeys.state}
|
||||||
|
setHotkeys={setHotkeys}
|
||||||
|
/>
|
||||||
|
<HotkeysEntryContainer
|
||||||
|
label={t("config_page.hotkeys.toggle_translation.label", {translation: t("main_page.translation")})}
|
||||||
|
hotkey_id="toggle_translation"
|
||||||
|
value={currentHotkeys.data}
|
||||||
|
state={currentHotkeys.state}
|
||||||
|
setHotkeys={setHotkeys}
|
||||||
|
/>
|
||||||
|
<HotkeysEntryContainer
|
||||||
|
label={t("config_page.hotkeys.toggle_transcription_send.label", {transcription_send: t("main_page.transcription_send")})}
|
||||||
|
hotkey_id="toggle_transcription_send"
|
||||||
|
value={currentHotkeys.data}
|
||||||
|
state={currentHotkeys.state}
|
||||||
|
setHotkeys={setHotkeys}
|
||||||
|
/>
|
||||||
|
<HotkeysEntryContainer
|
||||||
|
label={t("config_page.hotkeys.toggle_transcription_receive.label", {transcription_receive: t("main_page.transcription_receive")})}
|
||||||
|
hotkey_id="toggle_transcription_receive"
|
||||||
|
value={currentHotkeys.data}
|
||||||
|
state={currentHotkeys.state}
|
||||||
|
setHotkeys={setHotkeys}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
// gap: 6.4rem;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
@@ -5,5 +5,6 @@ export { Transcription } from "./transcription/Transcription";
|
|||||||
export { Others, VrcMicMuteSyncContainer } from "./others/Others";
|
export { Others, VrcMicMuteSyncContainer } from "./others/Others";
|
||||||
export { AdvancedSettings } from "./advanced_settings/AdvancedSettings";
|
export { AdvancedSettings } from "./advanced_settings/AdvancedSettings";
|
||||||
export { Vr } from "./vr/Vr";
|
export { Vr } from "./vr/Vr";
|
||||||
|
export { Hotkeys } from "./hotkeys/Hotkeys";
|
||||||
export { AboutVrct } from "./about_vrct/AboutVrct";
|
export { AboutVrct } from "./about_vrct/AboutVrct";
|
||||||
export { Supporters } from "./supporters/Supporters";
|
export { Supporters } from "./supporters/Supporters";
|
||||||
@@ -1,224 +1,22 @@
|
|||||||
import { useState, useEffect } from "react";
|
|
||||||
import styles from "./Supporters.module.scss";
|
import styles from "./Supporters.module.scss";
|
||||||
import clsx from "clsx";
|
import { SupportUsContainer } from "./support_us_container/SupportUsContainer";
|
||||||
import { useTranslation } from "react-i18next";
|
import { SupportersContainer } from "./supporters_container/SupportersContainer";
|
||||||
|
import { useSupporters } from "@logics_configs";
|
||||||
import {
|
import { useEffect } from "react";
|
||||||
useSettingBoxScrollPosition,
|
|
||||||
} from "@logics_configs"
|
|
||||||
|
|
||||||
const supporter_images = import.meta.glob("@images/supporters/supporters_images/*.{png,jpg,jpeg,svg}", { eager: true });
|
|
||||||
const chato_expression_images = import.meta.glob("@images/supporters/chato_expressions/*.{png,jpg,jpeg,svg}", { eager: true });
|
|
||||||
import fanbox_img from "@images/supporters/c_fanbox_1620x580.png";
|
|
||||||
import vrct_supporters_title from "@images/supporters/vrct_supporters_title.png";
|
|
||||||
import fanbox_button from "@images/supporters/fanbox_button.png";
|
|
||||||
import kofi_preparing from "@images/supporters/kofi_preparing.png";
|
|
||||||
|
|
||||||
import ExternalLink from "@images/external_link.svg?react";
|
|
||||||
|
|
||||||
const mogu_count = 8;
|
|
||||||
const mochi_count = 3;
|
|
||||||
const fuwa_count = 4;
|
|
||||||
const basic_count = 5;
|
|
||||||
const former_count = 2;
|
|
||||||
const and_you_count = 1;
|
|
||||||
const default_icon_numbers = ["05", "06", "07", "11"];
|
|
||||||
|
|
||||||
const supporters_filenames = Array.from({ length: 23 }, (_, index) => `supporter_${String(index + 1).padStart(2, "0")}`);
|
|
||||||
const chato_expressions_filenames = Array.from({ length: 7 }, (_, index) => `chato_expression_${String(index + 1).padStart(2, "0")}`);
|
|
||||||
|
|
||||||
const SHUFFLE_INTERVAL_TIME = 20000;
|
|
||||||
const shuffleArray = (array) => {
|
|
||||||
return array
|
|
||||||
.map((value) => ({ value, sort: Math.random() }))
|
|
||||||
.sort((a, b) => a.sort - b.sort)
|
|
||||||
.map(({ value }) => value);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Supporters = () => {
|
export const Supporters = () => {
|
||||||
|
const { asyncFetchSupportersData } = useSupporters();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
asyncFetchSupportersData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<SupportUsContainer />
|
<SupportUsContainer />
|
||||||
|
<div className={styles.supportersWrapper}>
|
||||||
<SupportersContainer />
|
<SupportersContainer />
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SupportUsContainer = () => {
|
|
||||||
return (
|
|
||||||
<div className={styles.support_us_container}>
|
|
||||||
<img className={styles.fanbox_img} src={fanbox_img} />
|
|
||||||
<div className={styles.support_us_button_wrapper}>
|
|
||||||
<div className={styles.fanbox_wrapper}>
|
|
||||||
<a className={styles.fanbox_button} href="https://vrct-dev.fanbox.cc/" target="_blank" rel="noreferrer">
|
|
||||||
<img style={{ height: "100%", width: "100%", objectFit: "contain" }} src={fanbox_button} />
|
|
||||||
</a>
|
|
||||||
<p className={styles.mainly_japanese}>日本語 / Mainly Japanese</p>
|
|
||||||
</div>
|
|
||||||
<div className={styles.kofi_wrapper}>
|
|
||||||
<img className={styles.kofi_preparing} src={kofi_preparing} />
|
|
||||||
<p className={styles.mainly_english}>Mainly English</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRandomImage = (images) => {
|
|
||||||
const random_index = Math.floor(Math.random() * images.length);
|
|
||||||
return images[random_index];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SupportersContainer = () => {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.supporters_container}>
|
|
||||||
<img className={styles.vrct_supporters_title} src={vrct_supporters_title} />
|
|
||||||
<p className={styles.vrct_supporters_desc}>{`VRCT3.0のアップデートに向けて、めちゃ大変な開発を支えてくれた "Early Supporters" です。\nThey are the 'Early Supporters' who supported us through the incredibly challenging development toward the VRCT3.0 update.`}</p>
|
|
||||||
<ProgressBar />
|
|
||||||
<SupportsWrapper />
|
|
||||||
<ProgressBar />
|
|
||||||
<p className={styles.vrct_supporters_desc_end}>{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(in開発室) しいなは喜び庭駆け回っています!!!ふわもちもぐもぐです!ありがとうございます。これからもまだまだ進化するVRCTをどうかよろしくお願いします!\nThanks to everyone, Misha has been granted the privilege of sleeping in a proper bed (in the development room), and Shiina is so happy, running around the yard! Fuwa-mochi-mogu-mogu! Thank you so much! We hope you'll continue to support the ever-evolving VRCT!`}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ProgressBar = () => {
|
|
||||||
const [is_active, setIsActive] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsActive(true);
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
setIsActive(false);
|
|
||||||
setTimeout(() => setIsActive(true), 50);
|
|
||||||
}, SHUFFLE_INTERVAL_TIME);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={clsx(styles.progress_bar, {
|
|
||||||
[styles.progress_bar_active]: is_active,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SupportsWrapper = () => {
|
|
||||||
const { saveScrollPosition, restoreScrollPosition } = useSettingBoxScrollPosition();
|
|
||||||
const [imagesState, setImagesState] = useState({
|
|
||||||
mogu_images: [],
|
|
||||||
mochi_images: [],
|
|
||||||
fuwa_images: [],
|
|
||||||
basic_images: [],
|
|
||||||
former_images: [],
|
|
||||||
and_you_images: [],
|
|
||||||
chato_images: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const shuffleImages = () => {
|
|
||||||
saveScrollPosition();
|
|
||||||
const getCategoryImages = (start, count) => {
|
|
||||||
const category_images = supporters_filenames.slice(start, start + count);
|
|
||||||
return shuffleArray(category_images);
|
|
||||||
};
|
|
||||||
|
|
||||||
const randomChatoImages = shuffleArray(
|
|
||||||
Array.from({ length: mogu_count + mochi_count + fuwa_count + basic_count + former_count }, () =>
|
|
||||||
getRandomImage(chato_expressions_filenames)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
setImagesState({
|
|
||||||
mogu_images: getCategoryImages(0, mogu_count),
|
|
||||||
mochi_images: getCategoryImages(mogu_count, mochi_count),
|
|
||||||
fuwa_images: getCategoryImages(mogu_count + mochi_count, fuwa_count),
|
|
||||||
basic_images: getCategoryImages(mogu_count + mochi_count + fuwa_count, basic_count),
|
|
||||||
former_images: getCategoryImages(mogu_count + mochi_count + fuwa_count + basic_count, former_count),
|
|
||||||
and_you_images: getCategoryImages(mogu_count + mochi_count + fuwa_count + basic_count + former_count, and_you_count),
|
|
||||||
chato_images: randomChatoImages,
|
|
||||||
});
|
|
||||||
setTimeout(() => restoreScrollPosition(), 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
shuffleImages();
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
shuffleImages();
|
|
||||||
}, SHUFFLE_INTERVAL_TIME);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
const getSupportersImageByFileName = (file_name) => {
|
|
||||||
const image_path = Object.keys(supporter_images).find((path) => path.endsWith(file_name + ".png"));
|
|
||||||
return image_path ? supporter_images[image_path]?.default : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getChatoImageByFileName = (file_name) => {
|
|
||||||
const image_path = Object.keys(chato_expression_images).find((path) => path.endsWith(file_name + ".png"));
|
|
||||||
return image_path ? chato_expression_images[image_path]?.default : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRandomDelay = (min, max) => {
|
|
||||||
const random_value = Math.random() * (max - min) + min;
|
|
||||||
return `${random_value.toFixed(1)}s`;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const renderImages = (image_list, chato_list, options = {}) => {
|
|
||||||
return image_list.map((file_name, index) => {
|
|
||||||
const img_src = getSupportersImageByFileName(file_name);
|
|
||||||
const is_default_icon = default_icon_numbers.some((element) => file_name.endsWith(element));
|
|
||||||
const chato_expression_src = is_default_icon ? getChatoImageByFileName(chato_list[index]) : null;
|
|
||||||
const random_delay = getRandomDelay(0.1, 6);
|
|
||||||
|
|
||||||
return img_src ? (
|
|
||||||
<div
|
|
||||||
key={file_name}
|
|
||||||
className={clsx(styles.supporter_image_wrapper, options.class_name)}
|
|
||||||
style={{ "--delay": random_delay }}
|
|
||||||
>
|
|
||||||
<img className={styles.supporter_image} src={img_src} />
|
|
||||||
{chato_expression_src && (
|
|
||||||
<img
|
|
||||||
className={styles.default_chato_expression_image}
|
|
||||||
src={chato_expression_src}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{options.is_and_you_icon ? <AndYouIcon /> : null}
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.supporters_wrapper}>
|
|
||||||
{renderImages(imagesState.mogu_images, imagesState.chato_images, { class_name: styles.mogu_image })}
|
|
||||||
{renderImages(imagesState.mochi_images, imagesState.chato_images)}
|
|
||||||
{renderImages(imagesState.fuwa_images, imagesState.chato_images)}
|
|
||||||
{renderImages(imagesState.basic_images, imagesState.chato_images)}
|
|
||||||
{renderImages(imagesState.former_images, imagesState.chato_images)}
|
|
||||||
<a href="https://vrct-dev.fanbox.cc/" target="_blank" rel="noreferrer">
|
|
||||||
{renderImages(imagesState.and_you_images, imagesState.chato_images, { is_and_you_icon: true, class_name: styles.and_you_image })}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AndYouIcon = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={styles.and_you_container}>
|
|
||||||
<div className={styles.and_you_1}></div>
|
|
||||||
<div className={styles.and_you_2}></div>
|
|
||||||
</div>
|
|
||||||
<p className={styles.and_you_fanbox_link_text}>
|
|
||||||
FANBOX
|
|
||||||
<ExternalLink className={styles.external_link_svg} />
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,205 +1,21 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1.2rem;
|
gap: 3.2rem;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
// background-color: gray;
|
}
|
||||||
|
.supportersWrapper {
|
||||||
|
opacity: 0;
|
||||||
|
animation: fadeIn 0.8s ease-in-out 1.6s forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.support_us_container {
|
@keyframes fadeIn {
|
||||||
display: flex;
|
from {
|
||||||
flex-direction: column;
|
opacity: 0;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2rem;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.fanbox_img {
|
to {
|
||||||
width: 60vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.support_us_button_wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
gap: 4.8rem;
|
|
||||||
}
|
|
||||||
.fanbox_wrapper, .kofi_wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
.fanbox_button {
|
|
||||||
width: 14rem;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
&:hover {
|
|
||||||
width: 16rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.kofi_preparing {
|
|
||||||
width: 6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainly_japanese, .mainly_english {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: var(--dark_400_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.supporters_container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
.vrct_supporters_title {
|
|
||||||
height: 6rem;
|
|
||||||
}
|
|
||||||
.vrct_supporters_desc {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
text-align: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.supporters_wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
align-content: start;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
column-gap: 1.4rem;
|
|
||||||
row-gap: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.supporter_image_wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 18rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.supporter_image {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mogu_image {
|
|
||||||
position: relative;
|
|
||||||
&::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: -200%;
|
|
||||||
left: -200%;
|
|
||||||
width: 300%;
|
|
||||||
height: 300%;
|
|
||||||
background: linear-gradient(45deg, rgba(255, 255, 255, 0) 30%, rgba(255, 255, 255, 0.7) 50%, rgba(255, 255, 255, 0) 70%);
|
|
||||||
transform: rotate(90deg);
|
|
||||||
animation: shine 2.5s infinite;
|
|
||||||
filter: blur(0.4rem);
|
|
||||||
animation-delay: var(--delay, 0s);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes shine {
|
|
||||||
0% {
|
|
||||||
top: -200%;
|
|
||||||
left: -200%;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
top: 200%;
|
|
||||||
left: 200%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.default_chato_expression_image {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 2.9rem;
|
|
||||||
width: 2.8rem;
|
|
||||||
transform: translate(-50%, -50%) rotate(10deg);
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.and_you_container {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.and_you_1, .and_you_2 {
|
|
||||||
width: 2.2rem;
|
|
||||||
height: 0.2rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--dark_400_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.and_you_2 {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%) rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.supporter_image_wrapper {
|
|
||||||
&:hover .and_you_container {
|
|
||||||
top: 40%;
|
|
||||||
transform: translate(-50%, -50%) rotate(180deg);
|
|
||||||
}
|
|
||||||
&:hover .and_you_fanbox_link_text {
|
|
||||||
top: 70%;
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.supporter_image_wrapper.and_you_image {
|
|
||||||
cursor: pointer;
|
|
||||||
&:active {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.and_you_fanbox_link_text {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: var(--dark_400_color);
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.external_link_svg {
|
|
||||||
color: var(--dark_200_color);
|
|
||||||
width: 1.2rem;
|
|
||||||
margin-left: 0.6rem;
|
|
||||||
padding-bottom: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vrct_supporters_desc_end {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
margin-top: 2rem;
|
|
||||||
color: var(--dark_300_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress_bar {
|
|
||||||
height: 0.2rem;
|
|
||||||
width: 0%;
|
|
||||||
&.progress_bar_active {
|
|
||||||
transition: width 20000ms linear;
|
|
||||||
background-color: var(--primary_400_color);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import top_img from "@images/supporters/patreon_1600x400px.png";
|
||||||
|
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";
|
||||||
|
|
||||||
|
export const SupportUsContainer = () => {
|
||||||
|
return (
|
||||||
|
<div id="support_us_container" className={styles.support_us_container}>
|
||||||
|
<img className={styles.top_img} src={top_img} />
|
||||||
|
<div className={styles.support_buttons_wrapper}>
|
||||||
|
<div className={styles.support_us_button_wrapper}>
|
||||||
|
<a className={styles.support_button} href="https://vrct-dev.fanbox.cc" target="_blank" rel="noreferrer">
|
||||||
|
<img
|
||||||
|
src={fanbox_logo}
|
||||||
|
className={clsx(styles.support_img, styles.fanbox_logo)}
|
||||||
|
/>
|
||||||
|
<div className={styles.spiral_top}></div>
|
||||||
|
<div className={styles.spiral_bottom}></div>
|
||||||
|
</a>
|
||||||
|
<a className={styles.support_button} href="https://ko-fi.com/vrct_dev" target="_blank" rel="noreferrer">
|
||||||
|
<img
|
||||||
|
src={kofi_logo}
|
||||||
|
className={clsx(styles.support_img, styles.kofi_logo)}
|
||||||
|
/>
|
||||||
|
<div className={styles.spiral_top}></div>
|
||||||
|
<div className={styles.spiral_bottom}></div>
|
||||||
|
</a>
|
||||||
|
<a className={styles.support_button} href="https://www.patreon.com/vrct_dev" target="_blank" rel="noreferrer">
|
||||||
|
<img
|
||||||
|
src={patreon_logo}
|
||||||
|
className={clsx(styles.support_img, styles.patreon_logo)}
|
||||||
|
/>
|
||||||
|
<div className={styles.spiral_top}></div>
|
||||||
|
<div className={styles.spiral_bottom}></div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className={styles.lines_container}>
|
||||||
|
<div className={styles.line_basic}></div>
|
||||||
|
<div className={styles.line_fuwa}></div>
|
||||||
|
<div className={styles.line_mochi}></div>
|
||||||
|
<div className={styles.line_mogu}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
$progress_ease: cubic-bezier(0, 1, 0.75, 1);
|
||||||
|
|
||||||
|
@keyframes revealTopImg {
|
||||||
|
0% {
|
||||||
|
clip-path: inset(0 50% 0 50%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
clip-path: inset(0 0 0 0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounceIn {
|
||||||
|
0% {
|
||||||
|
transform: translateY(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateY(-10%);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: translateY(10%);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes expandWidth {
|
||||||
|
0% {
|
||||||
|
transform: scaleX(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.support_us_container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.4rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.support_buttons_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top_img {
|
||||||
|
width: 100%;
|
||||||
|
animation: revealTopImg 0.8s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lines_container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
gap: 3.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line_basic,
|
||||||
|
.line_fuwa,
|
||||||
|
.line_mochi,
|
||||||
|
.line_mogu {
|
||||||
|
width: 8.6rem;
|
||||||
|
height: 0.2rem;
|
||||||
|
transform: scaleX(0);
|
||||||
|
transform-origin: left center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line_basic {
|
||||||
|
background-color: var(--dark_800_color);
|
||||||
|
animation: expandWidth 1s $progress_ease 0.4s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line_fuwa {
|
||||||
|
background-color: #5788a2;
|
||||||
|
animation: expandWidth 1s $progress_ease 0.6s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line_mochi {
|
||||||
|
background-color: var(--received_300_color);
|
||||||
|
animation: expandWidth 1s $progress_ease 0.8s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line_mogu {
|
||||||
|
background-color: var(--dark_basic_text_color);
|
||||||
|
animation: expandWidth 1s $progress_ease 1s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.support_us_button_wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 3.6rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.support_button {
|
||||||
|
position: relative;
|
||||||
|
padding: 1.2rem 1.6rem;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.support_button:nth-child(1) {
|
||||||
|
animation: bounceIn 0.4s ease-out 1.2s forwards;
|
||||||
|
}
|
||||||
|
.support_button:nth-child(2) {
|
||||||
|
animation: bounceIn 0.4s ease-out 1.4s forwards;
|
||||||
|
}
|
||||||
|
.support_button:nth-child(3) {
|
||||||
|
animation: bounceIn 0.4s ease-out 1.6s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.support_img {
|
||||||
|
&.fanbox_logo {
|
||||||
|
height: 1.8rem;
|
||||||
|
}
|
||||||
|
&.kofi_logo, &.patreon_logo {
|
||||||
|
height: 2.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.spiral_top::before,
|
||||||
|
.spiral_top::after,
|
||||||
|
.spiral_bottom::before,
|
||||||
|
.spiral_bottom::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spiral_top::before {
|
||||||
|
background: var(--dark_800_color);
|
||||||
|
box-shadow: 0 0 0.4rem 0 var(--dark_800_color);
|
||||||
|
}
|
||||||
|
.spiral_top::after {
|
||||||
|
background: #5788a2;
|
||||||
|
box-shadow: 0 0 0.4rem 0 #5788a2;
|
||||||
|
}
|
||||||
|
.spiral_bottom::before {
|
||||||
|
background: var(--received_300_color);
|
||||||
|
box-shadow: 0 0 0.4rem 0 var(--received_300_color);
|
||||||
|
}
|
||||||
|
.spiral_bottom::after {
|
||||||
|
background: var(--dark_basic_text_color);
|
||||||
|
box-shadow: 0 0 0.4rem 0 var(--dark_basic_text_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spiral_top::before {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0.1rem;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.spiral_top::after {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0.1rem;
|
||||||
|
}
|
||||||
|
.spiral_bottom::before {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0.1rem;
|
||||||
|
}
|
||||||
|
.spiral_bottom::after {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 0.1rem;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.support_button:hover .spiral_top::before {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.support_button:hover .spiral_top::after {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.support_button:hover .spiral_bottom::before {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.support_button:hover .spiral_bottom::after {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.supporters_container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vrct_supporters_title {
|
||||||
|
height: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vrct_supporters_desc {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import styles from "./SupportersContainer.module.scss";
|
||||||
|
import { SupportersWrapper } from "./supporters_wrapper/SupportersWrapper";
|
||||||
|
import { useSupporters } from "@logics_configs";
|
||||||
|
import { supporters_images_url } from "@ui_configs";
|
||||||
|
import vrct_supporters_title from "@images/supporters/vrct_supporters_title.png";
|
||||||
|
|
||||||
|
export const SupportersContainer = () => {
|
||||||
|
const { currentSupportersData } = useSupporters();
|
||||||
|
|
||||||
|
if (currentSupportersData.state === "error")
|
||||||
|
return <div>Failed to retrieve data.</div>;
|
||||||
|
|
||||||
|
if (currentSupportersData.state === "pending" || currentSupportersData.data === null)
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
|
||||||
|
const supporters_settings = currentSupportersData.data.supporters_settings;
|
||||||
|
const last_updated_local_date = new Date(supporters_settings.last_updated_utc_date).toLocaleString();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.supporters_container}>
|
||||||
|
<div className={styles.vrct_supporters_title_wrapper}>
|
||||||
|
<img className={styles.vrct_supporters_title} src={vrct_supporters_title}/>
|
||||||
|
<img className={styles.calc_period} src={`${supporters_images_url}/calc_period_label.png`}/>
|
||||||
|
<p className={styles.last_updated_local_date}>{`Last updated date: ${last_updated_local_date}`}</p>
|
||||||
|
</div>
|
||||||
|
<SupportersWrapper />
|
||||||
|
<p className={styles.vrct_supporters_desc_end}>{`みなさんのおかげで、みしゃ社長は布団で寝ることを許され(in開発室) しいなは喜び庭駆け回っています!!!ふわもちもぐもぐです!ありがとうございます。これからもまだまだ進化するVRCTをどうかよろしくお願いします!\nThanks to everyone, Misha has been granted the privilege of sleeping in a proper bed (in the development room), and Shiina is so happy, running around the yard! Fuwa-mochi-mogu-mogu! Thank you so much! We hope you'll continue to support the ever-evolving VRCT!`}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
.supporters_container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vrct_supporters_title_wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.2rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.vrct_supporters_title {
|
||||||
|
height: 4.2rem;
|
||||||
|
}
|
||||||
|
.calc_period {
|
||||||
|
height: 1.6rem;
|
||||||
|
}
|
||||||
|
.last_updated_local_date {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--dark_600_color);
|
||||||
|
width: 100%;
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vrct_supporters_desc_end {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: var(--dark_300_color);
|
||||||
|
}
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
import React, { useState, useCallback, useEffect } from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import ArrowLeftSvg from "@images/arrow_left.svg?react";
|
||||||
|
import styles from "./SupportersWrapper.module.scss";
|
||||||
|
import { shuffleArray, randomIntMinMax, randomMinMax } from "@utils";
|
||||||
|
|
||||||
|
import {
|
||||||
|
useSettingBoxScrollPosition,
|
||||||
|
useSupporters,
|
||||||
|
} from "@logics_configs";
|
||||||
|
|
||||||
|
import { supporters_images_url } from "@ui_configs";
|
||||||
|
|
||||||
|
const SHUFFLE_INTERVAL_TIME = 20000;
|
||||||
|
|
||||||
|
const and_you_data = {
|
||||||
|
supporter_id: "and_you",
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const image_sets = {
|
||||||
|
supporter_cards: `${supporters_images_url}/supporter_cards/`,
|
||||||
|
chato_expressions: `${supporters_images_url}/chato_expressions/`,
|
||||||
|
supporters_labels: `${supporters_images_url}/supporters_labels/`,
|
||||||
|
supporters_icons: `${supporters_images_url}/supporters_icons/`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSupporterCard = (plan_name) => {
|
||||||
|
const card_map = {
|
||||||
|
"mogu_2000": "mogu_card",
|
||||||
|
"mochi_1000": "mochi_card",
|
||||||
|
"fuwa_500": "fuwa_card",
|
||||||
|
"basic_300": "basic_card",
|
||||||
|
};
|
||||||
|
if (!card_map[plan_name]) return `${image_sets.supporter_cards}basic_card.png`;
|
||||||
|
|
||||||
|
return `${image_sets.supporter_cards}${card_map[plan_name]}.png`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getChatoExpressionsPath = (file_name) => `${image_sets.chato_expressions}${file_name}.png`;
|
||||||
|
const getSupportersLabelsPath = (file_name) => `${image_sets.supporters_labels}${file_name}.png`;
|
||||||
|
const getSupportersIconsPath = (file_name) => `${image_sets.supporters_icons}${file_name}.png`;
|
||||||
|
|
||||||
|
|
||||||
|
export const SupportersWrapper = () => {
|
||||||
|
const { saveScrollPosition, restoreScrollPosition } = useSettingBoxScrollPosition();
|
||||||
|
const { currentSupportersData } = useSupporters();
|
||||||
|
|
||||||
|
const [json_data, setJsonData] = useState();
|
||||||
|
const [supportersData, setSupportersData] = useState([]);
|
||||||
|
const [chatoExpressions, setChatoExpressions] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setJsonData(currentSupportersData.data);
|
||||||
|
}, [currentSupportersData.data]);
|
||||||
|
|
||||||
|
const supporters_settings = currentSupportersData.data.supporters_settings;
|
||||||
|
const calc_support_period = supporters_settings.calc_support_period;
|
||||||
|
const chato_ex_count = supporters_settings.chato_ex_count;
|
||||||
|
|
||||||
|
const recalcAndUpdateSupporters = useCallback(() => {
|
||||||
|
if (!json_data) return;
|
||||||
|
|
||||||
|
const grouped_data = {
|
||||||
|
mogu_2000: [],
|
||||||
|
mochi_1000: [],
|
||||||
|
fuwa_500: [],
|
||||||
|
basic_300: [],
|
||||||
|
former_supporter: [],
|
||||||
|
and_you: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
json_data.supporters_data.forEach((supporter) => {
|
||||||
|
const value = supporter.highest_plan_during_the_period || "former_supporter";
|
||||||
|
if (grouped_data[value]) {
|
||||||
|
grouped_data[value].push(supporter);
|
||||||
|
} else {
|
||||||
|
grouped_data["former_supporter"].push(supporter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const newSupportersData = [
|
||||||
|
...shuffleArray(grouped_data["mogu_2000"]),
|
||||||
|
...shuffleArray(grouped_data["mochi_1000"]),
|
||||||
|
...shuffleArray(grouped_data["fuwa_500"]),
|
||||||
|
...shuffleArray(grouped_data["basic_300"]),
|
||||||
|
...shuffleArray(grouped_data["former_supporter"]),
|
||||||
|
and_you_data,
|
||||||
|
];
|
||||||
|
|
||||||
|
setSupportersData(newSupportersData);
|
||||||
|
|
||||||
|
setChatoExpressions(
|
||||||
|
newSupportersData.map(() =>
|
||||||
|
getChatoExpressionsPath(
|
||||||
|
`chato_expression_${randomIntMinMax(1, chato_ex_count)}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [json_data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
recalcAndUpdateSupporters();
|
||||||
|
}, [json_data, recalcAndUpdateSupporters]);
|
||||||
|
|
||||||
|
const shuffleSupporters = useCallback(() => {
|
||||||
|
if (!json_data) return;
|
||||||
|
saveScrollPosition();
|
||||||
|
recalcAndUpdateSupporters();
|
||||||
|
setTimeout(() => restoreScrollPosition(), 0);
|
||||||
|
}, [json_data, recalcAndUpdateSupporters, saveScrollPosition, restoreScrollPosition]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
shuffleSupporters();
|
||||||
|
}, SHUFFLE_INTERVAL_TIME);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [shuffleSupporters]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<ProgressBar />
|
||||||
|
<div className={styles.supporters_wrapper}>
|
||||||
|
<SupporterCardsComponent
|
||||||
|
supportersData={supportersData}
|
||||||
|
chatoExpressions={chatoExpressions}
|
||||||
|
calc_support_period={calc_support_period}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ProgressBar />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AndYouIcon = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.and_you_container}>
|
||||||
|
<div className={styles.and_you_1}></div>
|
||||||
|
<div className={styles.and_you_2}></div>
|
||||||
|
</div>
|
||||||
|
<p className={styles.and_you_fanbox_link_text}>
|
||||||
|
FANBOX Ko-fi Patreon
|
||||||
|
</p>
|
||||||
|
<ArrowLeftSvg className={styles.arrow_left_svg} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SupporterCardsComponent = ({ supportersData, chatoExpressions, calc_support_period }) => {
|
||||||
|
return supportersData.map((item, index) => {
|
||||||
|
const target_plan = item.highest_plan_during_the_period;
|
||||||
|
|
||||||
|
const img_src = getSupporterCard(target_plan);
|
||||||
|
|
||||||
|
const is_and_you = item.supporter_id === "and_you";
|
||||||
|
|
||||||
|
const random_delay = `${randomMinMax(0.1, 6).toFixed(1)}s`;
|
||||||
|
|
||||||
|
const supporter_image_wrapper_classname = clsx(
|
||||||
|
styles.supporter_image_wrapper,
|
||||||
|
{
|
||||||
|
[styles.mogu_image]: target_plan === "mogu_2000",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return is_and_you ? (
|
||||||
|
<a href="#support_us_container" key={item.supporter_id}>
|
||||||
|
<div className={styles.supporter_image_container}>
|
||||||
|
<div
|
||||||
|
className={supporter_image_wrapper_classname}
|
||||||
|
style={{ "--delay": random_delay }}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className={styles.supporter_image}
|
||||||
|
src={img_src}
|
||||||
|
alt="supporter"
|
||||||
|
/>
|
||||||
|
<SupporterLabelComponent
|
||||||
|
target_plan={target_plan}
|
||||||
|
item={item}
|
||||||
|
chatoExpressions={chatoExpressions}
|
||||||
|
/>
|
||||||
|
<AndYouIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
) : img_src ? (
|
||||||
|
<div key={item.supporter_id} className={styles.supporter_image_container}>
|
||||||
|
<div
|
||||||
|
className={supporter_image_wrapper_classname}
|
||||||
|
style={{ "--delay": random_delay }}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className={styles.supporter_image}
|
||||||
|
src={img_src}
|
||||||
|
alt="supporter"
|
||||||
|
/>
|
||||||
|
<SupporterLabelComponent
|
||||||
|
target_plan={target_plan}
|
||||||
|
item={item}
|
||||||
|
chato_src={chatoExpressions[index]}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<SupporterPeriodContainer settings={item} calc_support_period={calc_support_period}/>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const SupporterLabelComponent = ({ item, target_plan, chato_src }) => {
|
||||||
|
const is_icon_plan = ["mogu_2000", "mochi_1000"].includes(
|
||||||
|
target_plan
|
||||||
|
);
|
||||||
|
|
||||||
|
const supporter_label_component_classname = clsx(
|
||||||
|
styles.supporter_label_component,
|
||||||
|
{
|
||||||
|
[styles.is_icon_plan]: is_icon_plan,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const is_and_you = item.supporter_id === "and_you";
|
||||||
|
const is_default_icon = item.supporter_icon_id === "";
|
||||||
|
|
||||||
|
const file_name = is_and_you ? "and_you" : `supporter_${item.supporter_id}`;
|
||||||
|
const label_img_src = getSupportersLabelsPath(file_name);
|
||||||
|
const icon_img_src = getSupportersIconsPath(
|
||||||
|
`supporter_icon_${item.supporter_icon_id}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={supporter_label_component_classname}>
|
||||||
|
{is_icon_plan && (
|
||||||
|
<div className={styles.supporter_icon_wrapper}>
|
||||||
|
{is_default_icon ? (
|
||||||
|
<img
|
||||||
|
className={styles.default_chato_expression_image}
|
||||||
|
src={chato_src}
|
||||||
|
alt="chato expression"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
className={styles.supporter_icon}
|
||||||
|
src={icon_img_src}
|
||||||
|
alt="supporter icon"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<img
|
||||||
|
className={styles.supporter_label_image}
|
||||||
|
src={label_img_src}
|
||||||
|
alt="supporter label"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SupporterPeriodContainer = ({ settings, calc_support_period }) => {
|
||||||
|
const period_data = extractKeys(settings, calc_support_period);
|
||||||
|
return (
|
||||||
|
<div className={styles.supporter_period_container}>
|
||||||
|
{Object.entries(period_data).map(([key, item], index) => {
|
||||||
|
if (item === "") return null;
|
||||||
|
const class_name = clsx(styles.period_box, {
|
||||||
|
[styles.mogu_bar]: item === "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>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractKeys = (data, keys_to_extract) => {
|
||||||
|
const result = {};
|
||||||
|
for (const key of keys_to_extract) {
|
||||||
|
if (key in data) {
|
||||||
|
result[key] = data[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const ProgressBar = () => {
|
||||||
|
const [is_active, setIsActive] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setIsActive(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setIsActive(false);
|
||||||
|
setTimeout(() => setIsActive(true), 50);
|
||||||
|
}, SHUFFLE_INTERVAL_TIME);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(styles.progress_bar, {
|
||||||
|
[styles.progress_bar_active]: is_active,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporters_wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-content: start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
column-gap: 1.8rem;
|
||||||
|
row-gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image_container {
|
||||||
|
position: relative;
|
||||||
|
width: 18rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image_wrapper {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_label_component {
|
||||||
|
position: absolute;
|
||||||
|
left: 0.6rem;
|
||||||
|
&.is_icon_plan {
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
top: 0.4rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_label_image {
|
||||||
|
height: 2rem;
|
||||||
|
// margin-top: 0.2rem;
|
||||||
|
&.small {
|
||||||
|
height: 1.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_icon_wrapper {
|
||||||
|
height: 4rem;
|
||||||
|
aspect-ratio: 1 /1;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_icon {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default_chato_expression_image {
|
||||||
|
position: absolute;
|
||||||
|
top: 52%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) rotate(10deg);
|
||||||
|
width: 2.8rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mogu_image {
|
||||||
|
position: relative;
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -200%;
|
||||||
|
left: -200%;
|
||||||
|
width: 300%;
|
||||||
|
height: 300%;
|
||||||
|
background: linear-gradient(45deg, rgba(255, 255, 255, 0) 30%, rgba(255, 255, 255, 0.7) 50%, rgba(255, 255, 255, 0) 70%);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
animation: shine 2.5s infinite;
|
||||||
|
filter: blur(0.4rem);
|
||||||
|
animation-delay: var(--delay, 0s);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shine {
|
||||||
|
0% {
|
||||||
|
top: -200%;
|
||||||
|
left: -200%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
top: 200%;
|
||||||
|
left: 200%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.and_you_container {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.and_you_1, .and_you_2 {
|
||||||
|
width: 2.2rem;
|
||||||
|
height: 0.2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--dark_400_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.and_you_2 {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image_container {
|
||||||
|
&:hover .and_you_container {
|
||||||
|
top: 36%;
|
||||||
|
transform: translate(-50%, -50%) rotate(180deg);
|
||||||
|
animation: disappear 0.3s forwards;
|
||||||
|
}
|
||||||
|
&:hover .and_you_fanbox_link_text {
|
||||||
|
top: 74%;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
&:hover .arrow_left_svg {
|
||||||
|
opacity: 0;
|
||||||
|
animation: arrow_up_down 0.3s forwards;
|
||||||
|
animation-delay: 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.supporter_image_container.and_you_image {
|
||||||
|
cursor: pointer;
|
||||||
|
&:active {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.and_you_fanbox_link_text {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--dark_400_color);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow_left_svg {
|
||||||
|
position: absolute;
|
||||||
|
top: 60%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) rotate(90deg);
|
||||||
|
color: var(--dark_400_color);
|
||||||
|
width: 2rem;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes arrow_up_down {
|
||||||
|
0% {
|
||||||
|
top: 60%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
top: 36%;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes disappear {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.supporter_period_container {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.2rem;
|
||||||
|
padding-left: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period_box {
|
||||||
|
width: 1.8rem;
|
||||||
|
height: 0.3rem;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
&.mogu_bar {
|
||||||
|
background-color: var(--dark_basic_text_color);
|
||||||
|
}
|
||||||
|
&.mochi_bar {
|
||||||
|
background-color: var(--received_300_color);
|
||||||
|
}
|
||||||
|
&.fuwa_bar {
|
||||||
|
background-color: #5788a2;
|
||||||
|
}
|
||||||
|
&.basic_bar {
|
||||||
|
background-color: var(--dark_800_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.progress_bar {
|
||||||
|
height: 0.2rem;
|
||||||
|
width: 0%;
|
||||||
|
&.progress_bar_active {
|
||||||
|
transition: width 20000ms linear;
|
||||||
|
background-color: var(--primary_400_color);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
background-color: var(--dark_850_color);
|
background-color: var(--dark_850_color);
|
||||||
}
|
}
|
||||||
&.is_selected .controller_switcher_label {
|
&.is_selected .controller_switcher_label {
|
||||||
color: var(--dark_basic_text_color);
|
color: var(--dark_200_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.controller_switcher_label {
|
.controller_switcher_label {
|
||||||
@@ -92,7 +92,6 @@
|
|||||||
transform: translateX(10%) rotate(90deg);
|
transform: translateX(10%) rotate(90deg);
|
||||||
}
|
}
|
||||||
.sample_text_button_label {
|
.sample_text_button_label {
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 110%;
|
top: 110%;
|
||||||
@@ -281,7 +280,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.restore_default_settings_button {
|
.restore_default_settings_button {
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
margin-top: 6rem;
|
margin-top: 6rem;
|
||||||
padding: 0.8rem;
|
padding: 0.8rem;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import styles from "./SidebarSection.module.scss";
|
|||||||
export const SidebarSection = () => {
|
export const SidebarSection = () => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
|
<div className={styles.scroll_container}>
|
||||||
<div className={styles.tabs_wrapper}>
|
<div className={styles.tabs_wrapper}>
|
||||||
<Tab tab_id="device" />
|
<Tab tab_id="device" />
|
||||||
<Tab tab_id="appearance" />
|
<Tab tab_id="appearance" />
|
||||||
@@ -10,6 +11,7 @@ export const SidebarSection = () => {
|
|||||||
<Tab tab_id="transcription" />
|
<Tab tab_id="transcription" />
|
||||||
<Tab tab_id="vr" />
|
<Tab tab_id="vr" />
|
||||||
<Tab tab_id="others" />
|
<Tab tab_id="others" />
|
||||||
|
<Tab tab_id="hotkeys" />
|
||||||
<Tab tab_id="advanced_settings" />
|
<Tab tab_id="advanced_settings" />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.separated_tabs_wrapper}>
|
<div className={styles.separated_tabs_wrapper}>
|
||||||
@@ -17,6 +19,7 @@ export const SidebarSection = () => {
|
|||||||
<Tab tab_id="about_vrct" />
|
<Tab tab_id="about_vrct" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
.container {
|
.container {
|
||||||
width: var(--config_page_sidebar_width);
|
width: var(--config_page_sidebar_width);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
padding: 0rem 0rem 5.8rem 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll_container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0rem 0rem 5.8rem 1.2rem;
|
overflow-y: auto;
|
||||||
|
height: 100%;
|
||||||
max-height: 60rem;
|
max-height: 60rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +27,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
padding: 0.8rem 0 0.8rem 1rem;
|
padding: 0.8rem 0 0.8rem 1rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
49
src-ui/app/config_page/version_label/VersionLabel.jsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { clsx } from "clsx";
|
||||||
|
import styles from "./VersionLabel.module.scss";
|
||||||
|
|
||||||
|
import { useSoftwareVersion } from "@logics_configs";
|
||||||
|
import { useComputeMode } from "@logics_common";
|
||||||
|
import CopySvg from "@images/copy.svg?react";
|
||||||
|
import CheckMarkSvg from "@images/check_mark.svg?react";
|
||||||
|
|
||||||
|
export const VersionLabel = () => {
|
||||||
|
const [is_copied, setIsCopied] = useState(false);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { currentSoftwareVersion } = useSoftwareVersion();
|
||||||
|
const { currentComputeMode } = useComputeMode();
|
||||||
|
|
||||||
|
const version_label = currentComputeMode.data === "cpu"
|
||||||
|
? t("config_page.version", { version: currentSoftwareVersion.data })
|
||||||
|
: currentComputeMode.data === "cuda"
|
||||||
|
? t("config_page.version", { version: currentSoftwareVersion.data }) + " CUDA"
|
||||||
|
: t("config_page.version", { version: currentSoftwareVersion.data });
|
||||||
|
|
||||||
|
const is_cpu = currentComputeMode.data === "cpu";
|
||||||
|
|
||||||
|
const copyToClipboard = async () => {
|
||||||
|
if (is_copied) return;
|
||||||
|
const copy_text = is_cpu ? `${currentSoftwareVersion.data}` : `${currentSoftwareVersion.data} CUDA`;
|
||||||
|
await navigator.clipboard.writeText(copy_text);
|
||||||
|
setIsCopied(true);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsCopied(false);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={clsx(styles.wrapper, {[styles.is_copied]: is_copied})} onClick={copyToClipboard}>
|
||||||
|
<p className={styles.version_label}>{version_label}</p>
|
||||||
|
{is_copied
|
||||||
|
? <CheckMarkSvg className={styles.check_mark_svg}/>
|
||||||
|
: <CopySvg className={styles.copy_svg}/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
.container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1.2rem;
|
||||||
|
left: 1.4rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--dark_400_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.6rem;
|
||||||
|
cursor: pointer;
|
||||||
|
&.is_copied {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.version_label {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--dark_400_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy_svg {
|
||||||
|
width: 1.4rem;
|
||||||
|
color: var(--dark_500_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.check_mark_svg {
|
||||||
|
width: 1.4rem;
|
||||||
|
color: var(--primary_300_color);
|
||||||
|
}
|
||||||
@@ -26,39 +26,3 @@
|
|||||||
background-color: var(--dark_888_color);
|
background-color: var(--dark_888_color);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main_page_cover {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
background-color: (#000000cc);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2rem;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
backdrop-filter: blur(0.8rem);
|
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover_message {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close_settings_window_button {
|
|
||||||
font-size: 1.6rem;
|
|
||||||
font-weight: 300;
|
|
||||||
padding: 1rem 1.4rem;
|
|
||||||
border-radius: 0.4rem;
|
|
||||||
background-color: var(--dark_888_color);
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--dark_825_color);
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
background-color: var(--dark_925_color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -46,5 +46,4 @@
|
|||||||
|
|
||||||
.language_label {
|
.language_label {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
}
|
}
|
||||||
@@ -1,99 +1,67 @@
|
|||||||
import { useResizable } from "react-resizable-layout";
|
import { useResizable } from "react-resizable-layout";
|
||||||
import { useRef, useEffect, useState } from "react";
|
import { useRef, useEffect, useState, forwardRef } from "react";
|
||||||
import styles from "./MessageContainer.module.scss";
|
import styles from "./MessageContainer.module.scss";
|
||||||
import { appWindow } from "@tauri-apps/api/window";
|
|
||||||
import { LogBox } from "./log_box/LogBox";
|
import { LogBox } from "./log_box/LogBox";
|
||||||
import { MessageLogSettingsContainer } from "./message_log_settings_container/MessageLogSettingsContainer";
|
import { MessageLogSettingsContainer } from "./message_log_settings_container/MessageLogSettingsContainer";
|
||||||
import { MessageInputBox } from "./message_input_box/MessageInputBox";
|
import { MessageInputBox } from "./message_input_box/MessageInputBox";
|
||||||
import { useMessageInputBoxRatio } from "@logics_main";
|
import { useMessageInputBoxRatio } from "@logics_main";
|
||||||
import { useUiScaling } from "@logics_configs";
|
|
||||||
import { useStore_IsAppliedInitMessageBoxHeight } from "@store";
|
|
||||||
|
|
||||||
export const MessageContainer = () => {
|
export const MessageContainer = () => {
|
||||||
const { currentMessageInputBoxRatio, asyncSetMessageInputBoxRatio } = useMessageInputBoxRatio();
|
const { currentMessageInputBoxRatio, asyncSetMessageInputBoxRatio } = useMessageInputBoxRatio();
|
||||||
const { currentUiScaling } = useUiScaling();
|
const [ui_message_box_ratio, setUiMessageBoxRatio] = useState(false);
|
||||||
const [is_hovered, setIsHovered] = useState(false);
|
const [is_hovered, setIsHovered] = useState(false);
|
||||||
const [message_box_height_in_rem, setMessageBoxHeightInRem] = useState(10);
|
|
||||||
const FONT_SIZE_STANDARD = 10 * currentUiScaling.data / 100; // 10px = 1rem
|
|
||||||
const { currentIsAppliedInitMessageBoxHeight, updateIsAppliedInitMessageBoxHeight } = useStore_IsAppliedInitMessageBoxHeight();
|
|
||||||
|
|
||||||
const container_ref = useRef(null);
|
const container_ref = useRef(null);
|
||||||
|
const separator_ref = useRef(null);
|
||||||
const log_box_ref = useRef(null);
|
const log_box_ref = useRef(null);
|
||||||
const message_box_wrapper_ref = useRef(null);
|
const message_input_box_wrapper_ref = useRef(null);
|
||||||
|
|
||||||
const asyncSetMessageBoxHeightInRem = async (data) => {
|
const calculateMessageInputBoxRatio = (position) => {
|
||||||
const minimized = await appWindow.isMinimized();
|
if (log_box_ref.current && message_input_box_wrapper_ref.current && separator_ref.current && container_ref.current) {
|
||||||
if (minimized) return; // don't save while the window is minimized.
|
const container_padding_bottom = parseFloat(
|
||||||
setMessageBoxHeightInRem(data);
|
window.getComputedStyle(container_ref.current).paddingBottom
|
||||||
|
);
|
||||||
|
const total_height =
|
||||||
|
log_box_ref.current.offsetHeight +
|
||||||
|
separator_ref.current.offsetHeight * 2 +
|
||||||
|
message_input_box_wrapper_ref.current.offsetHeight;
|
||||||
|
const adjusted_position = position - container_padding_bottom;
|
||||||
|
const message_box_ratio = (adjusted_position / total_height) * 100;
|
||||||
|
return message_box_ratio;
|
||||||
|
}
|
||||||
|
console.warn("References not ready for calculation");
|
||||||
|
return 10; // Default initial height percentage
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculateMessageBoxRatioAndHeight = () => {
|
const asyncSaveRatio = (position) => {
|
||||||
if (!currentIsAppliedInitMessageBoxHeight.data) {
|
if (position > 0) {
|
||||||
asyncSetMessageInputBoxRatio(currentMessageInputBoxRatio.data);
|
asyncSetMessageInputBoxRatio(calculateMessageInputBoxRatio(position));
|
||||||
asyncSetMessageBoxHeightInRem(convertRatioToRem(currentMessageInputBoxRatio.data));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log_box_ref.current && message_box_wrapper_ref.current && container_ref.current) {
|
|
||||||
const container_height = container_ref.current.offsetHeight;
|
|
||||||
const container_padding_bottom = parseFloat(window.getComputedStyle(container_ref.current).paddingBottom);
|
|
||||||
const total_height = container_height - container_padding_bottom;
|
|
||||||
|
|
||||||
const message_box_height = message_box_wrapper_ref.current.offsetHeight;
|
|
||||||
const message_box_ratio = (message_box_height / total_height) * 100;
|
|
||||||
|
|
||||||
asyncSetMessageInputBoxRatio(message_box_ratio);
|
|
||||||
asyncSetMessageBoxHeightInRem(convertRatioToRem(message_box_ratio));
|
|
||||||
} else {
|
|
||||||
console.warn("References not ready for calculation");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const { position, separatorProps } = useResizable({
|
const { position, separatorProps } = useResizable({
|
||||||
axis: "y",
|
axis: "y",
|
||||||
reverse: true,
|
reverse: true,
|
||||||
onResizeEnd: calculateMessageBoxRatioAndHeight,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
asyncSetMessageBoxHeightInRem((position / FONT_SIZE_STANDARD) - 1.4);
|
if (position > 0) {
|
||||||
|
setUiMessageBoxRatio(calculateMessageInputBoxRatio(position));
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
asyncSaveRatio(position);
|
||||||
|
}, 200);
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}
|
||||||
}, [position]);
|
}, [position]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
asyncSetMessageBoxHeightInRem(convertRatioToRem(currentMessageInputBoxRatio.data));
|
setUiMessageBoxRatio(currentMessageInputBoxRatio.data);
|
||||||
}, [currentMessageInputBoxRatio.data]);
|
}, [currentMessageInputBoxRatio]);
|
||||||
|
|
||||||
const convertRatioToRem = (ratio) => {
|
|
||||||
if (!container_ref.current) return 0;
|
|
||||||
const container_height = container_ref.current.offsetHeight;
|
|
||||||
const container_padding_bottom = parseFloat(window.getComputedStyle(container_ref.current).paddingBottom);
|
|
||||||
const total_height = container_height - container_padding_bottom;
|
|
||||||
return total_height === 0 ? 0 : ((ratio / 100) * total_height / FONT_SIZE_STANDARD);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
calculateMessageBoxRatioAndHeight();
|
|
||||||
updateIsAppliedInitMessageBoxHeight(true); // Ensure this happens after initial calculation
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let resizeTimeout;
|
|
||||||
|
|
||||||
const unlisten = appWindow.onResized(() => {
|
|
||||||
clearTimeout(resizeTimeout);
|
|
||||||
resizeTimeout = setTimeout(() => {
|
|
||||||
calculateMessageBoxRatioAndHeight();
|
|
||||||
}, 200);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unlisten.then((dispose) => dispose());
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container} ref={container_ref}>
|
<div className={styles.container} ref={container_ref}>
|
||||||
<div className={styles.log_box_resize_wrapper}
|
<div
|
||||||
|
className={styles.log_box_resize_wrapper}
|
||||||
ref={log_box_ref}
|
ref={log_box_ref}
|
||||||
onMouseOver={() => setIsHovered(true)}
|
onMouseOver={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
@@ -101,11 +69,11 @@ export const MessageContainer = () => {
|
|||||||
<LogBox />
|
<LogBox />
|
||||||
<MessageLogSettingsContainer to_visible_toggle_bar={is_hovered} />
|
<MessageLogSettingsContainer to_visible_toggle_bar={is_hovered} />
|
||||||
</div>
|
</div>
|
||||||
<Separator {...separatorProps} onDragStart={calculateMessageBoxRatioAndHeight} />
|
<Separator {...separatorProps} ref={separator_ref} />
|
||||||
<div
|
<div
|
||||||
className={styles.message_box_resize_wrapper}
|
className={styles.message_box_resize_wrapper}
|
||||||
ref={message_box_wrapper_ref}
|
ref={message_input_box_wrapper_ref}
|
||||||
style={{ height: `${message_box_height_in_rem}rem` }}
|
style={{ height: `${ui_message_box_ratio}%` }}
|
||||||
>
|
>
|
||||||
<MessageInputBox />
|
<MessageInputBox />
|
||||||
</div>
|
</div>
|
||||||
@@ -113,8 +81,8 @@ export const MessageContainer = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Separator = ({ onDragStart, ...props }) => (
|
const Separator = forwardRef((props, ref) => (
|
||||||
<div tabIndex={0} className={styles.separator} {...props} onDragStart={onDragStart}>
|
<div tabIndex={0} className={styles.separator} ref={ref} {...props}>
|
||||||
<span className={styles.separator_line}></span>
|
<span className={styles.separator_line}></span>
|
||||||
</div>
|
</div>
|
||||||
);
|
));
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.message_box_resize_wrapper {
|
.message_box_resize_wrapper {
|
||||||
height: 10rem;
|
height: 10%;
|
||||||
min-height: 3.8rem;
|
min-height: 3.8rem;
|
||||||
max-height: 90%;
|
max-height: 90%;
|
||||||
}
|
}
|
||||||
@@ -1,50 +1,26 @@
|
|||||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
import React, { useRef, useLayoutEffect, useEffect } from "react";
|
||||||
import styles from "./LogBox.module.scss";
|
import styles from "./LogBox.module.scss";
|
||||||
import { store } from "@store";
|
|
||||||
import { MessageContainer } from "./message_container/MessageContainer";
|
import { MessageContainer } from "./message_container/MessageContainer";
|
||||||
import { scrollToBottom } from "@utils";
|
|
||||||
import { useMessage } from "@logics_common";
|
import { useMessage } from "@logics_common";
|
||||||
|
import { useMessageLogScroll } from "@logics_main";
|
||||||
|
import { store } from "@store";
|
||||||
|
|
||||||
export const LogBox = () => {
|
export const LogBox = () => {
|
||||||
const { currentMessageLogs } = useMessage();
|
const { currentMessageLogs } = useMessage();
|
||||||
const log_container_ref = useRef(null);
|
const { scrollToBottom, isScrolling } = useMessageLogScroll();
|
||||||
const [is_scrolling, setIsScrolling] = useState(false);
|
const logContainerRef = useRef(null);
|
||||||
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
store.log_box_ref = log_container_ref;
|
store.log_box_ref = logContainerRef;
|
||||||
if (!is_scrolling) {
|
if (!isScrolling) {
|
||||||
scrollToBottom(store.log_box_ref);
|
scrollToBottom();
|
||||||
// scrollToBottom(store.log_box_ref, true); [Fix me]
|
|
||||||
}
|
}
|
||||||
}, [currentMessageLogs.data]);
|
}, [currentMessageLogs.data, isScrolling]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleScroll = () => {
|
|
||||||
const element = log_container_ref.current;
|
|
||||||
if (!element) return;
|
|
||||||
const currentScrollTop = element.scrollTop;
|
|
||||||
const at_bottom = element.scrollHeight - currentScrollTop === element.clientHeight;
|
|
||||||
|
|
||||||
if (at_bottom) {
|
|
||||||
setIsScrolling(false);
|
|
||||||
} else {
|
|
||||||
setIsScrolling(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const element = log_container_ref.current;
|
|
||||||
element.addEventListener("scroll", handleScroll);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
element.removeEventListener("scroll", handleScroll);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="log_container" className={styles.container} ref={log_container_ref}>
|
<div id="log_container" className={styles.container} ref={logContainerRef}>
|
||||||
<MessageLogUiSizeController />
|
<MessageLogUiSizeController />
|
||||||
{currentMessageLogs.data.map(message_data => (
|
{currentMessageLogs.data.map((message_data) => (
|
||||||
<MessageContainer key={message_data.id} {...message_data} />
|
<MessageContainer key={message_data.id} {...message_data} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -72,18 +72,21 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
&.sent_message {
|
&.sent_message {
|
||||||
|
width: 100%;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
text-align: end;
|
text-align: end;
|
||||||
}
|
}
|
||||||
&.received_message {
|
&.received_message {
|
||||||
|
width: 100%;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message_main {
|
.message_main {
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
user-select: text;
|
user-select: text;
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message_second {
|
.message_second {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
.tooltip_title {
|
.tooltip_title {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: var(--dark_basic_text_color);
|
color: var(--dark_200_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hold_progress_bar {
|
.hold_progress_bar {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useLayoutEffect, useRef } from "react";
|
||||||
import styles from "./MessageInputBox.module.scss";
|
import styles from "./MessageInputBox.module.scss";
|
||||||
import SendMessageSvg from "@images/send_message.svg?react";
|
import SendMessageSvg from "@images/send_message.svg?react";
|
||||||
import { useMessage } from "@logics_common";
|
import { useMessage } from "@logics_common";
|
||||||
import { useSendMessageButtonType, useEnableAutoClearMessageInputBox } from "@logics_configs";
|
import { useSendMessageButtonType, useEnableAutoClearMessageInputBox } from "@logics_configs";
|
||||||
|
import { useMessageLogScroll } from "@logics_main";
|
||||||
import { store } from "@store";
|
import { store } from "@store";
|
||||||
import { scrollToBottom } from "@utils";
|
import { appWindow } from "@tauri-apps/api/window";
|
||||||
|
|
||||||
export const MessageInputBox = () => {
|
export const MessageInputBox = () => {
|
||||||
const [message_history, setMessageHistory] = useState([]);
|
const [message_history, setMessageHistory] = useState([]);
|
||||||
@@ -21,6 +22,14 @@ export const MessageInputBox = () => {
|
|||||||
const { currentEnableAutoClearMessageInputBox } = useEnableAutoClearMessageInputBox();
|
const { currentEnableAutoClearMessageInputBox } = useEnableAutoClearMessageInputBox();
|
||||||
const { currentSendMessageButtonType } = useSendMessageButtonType();
|
const { currentSendMessageButtonType } = useSendMessageButtonType();
|
||||||
|
|
||||||
|
const { scrollToBottom } = useMessageLogScroll();
|
||||||
|
|
||||||
|
const log_box_ref = useRef(null);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
store.text_area_ref = log_box_ref;
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentMessageLogs.data) {
|
if (currentMessageLogs.data) {
|
||||||
const sentMessages = currentMessageLogs.data
|
const sentMessages = currentMessageLogs.data
|
||||||
@@ -32,6 +41,7 @@ export const MessageInputBox = () => {
|
|||||||
|
|
||||||
const onSubmitFunction = (e) => {
|
const onSubmitFunction = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
// appWindow.minimize();
|
||||||
|
|
||||||
if (!currentMessageInputValue.data.trim()) return updateMessageInputValue("");
|
if (!currentMessageInputValue.data.trim()) return updateMessageInputValue("");
|
||||||
|
|
||||||
@@ -40,7 +50,7 @@ export const MessageInputBox = () => {
|
|||||||
if (currentEnableAutoClearMessageInputBox.data) updateMessageInputValue("");
|
if (currentEnableAutoClearMessageInputBox.data) updateMessageInputValue("");
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
scrollToBottom(store.log_box_ref);
|
scrollToBottom();
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
setHistoryIndex(-1);
|
setHistoryIndex(-1);
|
||||||
@@ -69,7 +79,7 @@ export const MessageInputBox = () => {
|
|||||||
if (history_index > -1) {
|
if (history_index > -1) {
|
||||||
const new_index = history_index - 1;
|
const new_index = history_index - 1;
|
||||||
setHistoryIndex(new_index);
|
setHistoryIndex(new_index);
|
||||||
setInputValue(
|
updateMessageInputValue(
|
||||||
new_index >= 0
|
new_index >= 0
|
||||||
? message_history[message_history.length - 1 - new_index]
|
? message_history[message_history.length - 1 - new_index]
|
||||||
: ""
|
: ""
|
||||||
@@ -89,6 +99,7 @@ export const MessageInputBox = () => {
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.message_box_wrapper}>
|
<div className={styles.message_box_wrapper}>
|
||||||
<textarea
|
<textarea
|
||||||
|
ref={log_box_ref}
|
||||||
className={styles.message_box_input_area}
|
className={styles.message_box_input_area}
|
||||||
onChange={onChangeFunction}
|
onChange={onChangeFunction}
|
||||||
onBlur={stopTyping}
|
onBlur={stopTyping}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
.container {
|
.container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message_box_wrapper {
|
.message_box_wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-right: 1rem;
|
|
||||||
padding: 0.8rem;
|
padding: 0.8rem;
|
||||||
background-color: var(--dark_875_color);
|
background-color: var(--dark_875_color);
|
||||||
border: 0.1rem solid var(--dark_750_color);
|
border: 0.1rem solid var(--dark_750_color);
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
&.is_compact_mode {
|
&.is_compact_mode {
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
.scroll_container {
|
.scroll_container {
|
||||||
overflow-y: hidden;
|
// overflow-y: hidden;
|
||||||
width: auto;
|
// width: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,6 @@
|
|||||||
|
|
||||||
.selected_language {
|
.selected_language {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow_left_svg {
|
.arrow_left_svg {
|
||||||
@@ -66,7 +65,6 @@
|
|||||||
margin: 0 0.2rem;
|
margin: 0 0.2rem;
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
width: 1.6rem;
|
width: 1.6rem;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
&.reverse {
|
&.reverse {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,6 @@
|
|||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: var(--dark_500_color);
|
color: var(--dark_500_color);
|
||||||
&.is_hovered {
|
&.is_hovered {
|
||||||
color: var(--dark_basic_text_color);
|
color: var(--dark_200_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
&.is_selected {
|
&.is_selected {
|
||||||
background-color: var(--dark_800_color);
|
background-color: var(--dark_800_color);
|
||||||
color: var(--dark_basic_text_color);
|
color: var(--dark_200_color);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,5 +31,5 @@
|
|||||||
margin-left: 0.2rem;
|
margin-left: 0.2rem;
|
||||||
padding-bottom: 0.2rem;
|
padding-bottom: 0.2rem;
|
||||||
width: 1.8rem;
|
width: 1.8rem;
|
||||||
color: #cb944f;
|
color: var(--waring_color);
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,7 @@
|
|||||||
bottom: 100%;
|
bottom: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 26rem;
|
height: 26rem;
|
||||||
background-color: (#000000dd);
|
background-color: var(--dark_1000_color_dd);
|
||||||
// background-color: (var(--dark_875_color) + 80);
|
|
||||||
backdrop-filter: blur(0.1rem);
|
backdrop-filter: blur(0.1rem);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -75,7 +74,7 @@ $box_size: 6.8rem;
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: (#22222233);
|
background-color: var(--dark_1000_color_66);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
@@ -87,5 +86,4 @@ $box_size: 6.8rem;
|
|||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
}
|
}
|
||||||
&.is_compact_mode {
|
&.is_compact_mode {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
&.is_pending {
|
&.is_pending {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -35,11 +36,9 @@
|
|||||||
gap: 0.8rem;
|
gap: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
$basic_label_color: var(--dark_basic_text_color);
|
|
||||||
$pending_label_color: var(--dark_500_color);
|
$pending_label_color: var(--dark_500_color);
|
||||||
.switch_label {
|
.switch_label {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
color: $basic_label_color;
|
|
||||||
&.is_compact_mode {
|
&.is_compact_mode {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -50,7 +49,6 @@ $pending_label_color: var(--dark_500_color);
|
|||||||
|
|
||||||
.switch_svg {
|
.switch_svg {
|
||||||
width: 1.8rem;
|
width: 1.8rem;
|
||||||
color: $basic_label_color;
|
|
||||||
&.is_pending {
|
&.is_pending {
|
||||||
color: $pending_label_color;
|
color: $pending_label_color;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,6 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.accept_button {
|
.accept_button {
|
||||||
@@ -91,19 +90,19 @@
|
|||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
}
|
}
|
||||||
&.is_latest_version_already {
|
&.is_latest_version_already {
|
||||||
background-color: var(--dark_800_color);
|
background-color: var(--dark_825_color);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.deny_button {
|
.deny_button {
|
||||||
background-color: var(--dark_800_color);
|
background-color: var(--dark_825_color);
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--dark_700_color);
|
background-color: var(--dark_800_color);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
background-color: var(--dark_800_color);
|
background-color: var(--dark_850_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +136,6 @@
|
|||||||
gap: 0.6rem;
|
gap: 0.6rem;
|
||||||
}
|
}
|
||||||
.version_desc_point {
|
.version_desc_point {
|
||||||
background-color: var(--dark_basic_text_color);
|
|
||||||
width: 0.3rem;
|
width: 0.3rem;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
@@ -147,5 +145,4 @@
|
|||||||
max-width: 48rem;
|
max-width: 48rem;
|
||||||
// text-align: center;
|
// text-align: center;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
color: var(--dark_basic_text_color);
|
|
||||||
}
|
}
|
||||||
@@ -17,17 +17,23 @@ export const SnackbarController = () => {
|
|||||||
[styles.is_error]: currentNotificationStatus.data.status === "error",
|
[styles.is_error]: currentNotificationStatus.data.status === "error",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const settings = currentNotificationStatus.data;
|
||||||
|
|
||||||
|
let hide_duration = 5000;
|
||||||
|
if (settings.options?.hide_duration === null) hide_duration = null;
|
||||||
|
if (Number(settings.options?.hide_duration)) hide_duration = settings.options.hide_duration;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Snackbar
|
<Snackbar
|
||||||
open={currentNotificationStatus.data.is_open}
|
open={settings.is_open}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
TransitionComponent={SlideTransition}
|
TransitionComponent={SlideTransition}
|
||||||
key={currentNotificationStatus.data.key}
|
key={settings.key}
|
||||||
autoHideDuration={5000}
|
autoHideDuration={hide_duration}
|
||||||
>
|
>
|
||||||
<div className={snackbar_classname}>
|
<div className={snackbar_classname}>
|
||||||
<p className={styles.snackbar_message}>{currentNotificationStatus.data.message}</p>
|
<p className={styles.snackbar_message}>{settings.message}</p>
|
||||||
</div>
|
</div>
|
||||||
</Snackbar>
|
</Snackbar>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
&.is_success {
|
&.is_success {
|
||||||
background-color: #368777;
|
background-color: var(--success_bc_color);
|
||||||
}
|
}
|
||||||
&.is_error {
|
&.is_error {
|
||||||
background-color: #bb4448;
|
background-color: var(--error_bc_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,13 +22,13 @@
|
|||||||
animation: appear .3s ease;
|
animation: appear .3s ease;
|
||||||
}
|
}
|
||||||
& .announcements_link_svg {
|
& .announcements_link_svg {
|
||||||
color: var(--dark_basic_text_color);
|
color: var(--dark_200_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--dark_825_color);
|
background-color: var(--dark_825_color);
|
||||||
& .announcements_label {
|
& .announcements_label {
|
||||||
color: var(--dark_basic_text_color);
|
color: var(--dark_200_color);
|
||||||
}
|
}
|
||||||
& .announcements_link_svg {
|
& .announcements_link_svg {
|
||||||
color: var(--primary_300_color);
|
color: var(--primary_300_color);
|
||||||
@@ -79,14 +79,14 @@
|
|||||||
width: 68px;
|
width: 68px;
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #bb4448;
|
background-color: var(--error_bc_color);
|
||||||
& .x_mark_svg {
|
& .x_mark_svg {
|
||||||
color: var(--dark_200_color);
|
color: var(--dark_200_color);
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
background-color: #9c3938;
|
background-color: var(--error_bc_active_color);
|
||||||
}
|
}
|
||||||
transition: all 0.1s ease;
|
transition: all 0.1s ease;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
$progress_ease: cubic-bezier(0, 1, 0.75, 1);
|
$progress_ease: cubic-bezier(0, 1, 0.75, 1);
|
||||||
|
// Duplicated
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -61,10 +61,10 @@
|
|||||||
}
|
}
|
||||||
.close_button {
|
.close_button {
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #bb4448;
|
background-color: var(--error_bc_color);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
background-color: #9c3938;
|
background-color: var(--error_bc_active_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
src-ui/assets/about_vrct/contributor_done.png
Normal file
|
After Width: | Height: | Size: 60 KiB |