diff --git a/package-lock.json b/package-lock.json
index b56c2b2..de89143 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,16 +1,16 @@
{
"name": "vrclipboard-ime-gui",
- "version": "1.9.0",
+ "version": "1.11.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vrclipboard-ime-gui",
- "version": "1.9.0",
+ "version": "1.11.0",
"dependencies": {
"@tauri-apps/api": "^2.0.0-rc.0",
"@tauri-apps/plugin-shell": "^2.0.0",
- "@tauri-apps/plugin-updater": "^2.0.0",
+ "@tauri-apps/plugin-updater": "^2.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
@@ -1276,9 +1276,10 @@
}
},
"node_modules/@tauri-apps/plugin-updater": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.0.0.tgz",
- "integrity": "sha512-N0cl71g7RPr7zK2Fe5aoIwzw14NcdLcz7XMGFWZVjprsqgDRWoxbnUkknyCQMZthjhGkppCd/wN2MIsUz+eAhQ==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.6.0.tgz",
+ "integrity": "sha512-j74RUolLIhQDQwrff6R28xIewYVXME1gFU+d+4LYN1dLRzLD+ySa7VHqzyWYxWEvm+TPZ7lkUxa5a9uH9Ist3A==",
+ "license": "MIT OR Apache-2.0",
"dependencies": {
"@tauri-apps/api": "^2.0.0"
}
diff --git a/package.json b/package.json
index ea1553b..26fa903 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vrclipboard-ime-gui",
"private": true,
- "version": "1.10.0",
+ "version": "1.11.0",
"type": "module",
"scripts": {
"dev": "vite",
@@ -12,7 +12,7 @@
"dependencies": {
"@tauri-apps/api": "^2.0.0-rc.0",
"@tauri-apps/plugin-shell": "^2.0.0",
- "@tauri-apps/plugin-updater": "^2.0.0",
+ "@tauri-apps/plugin-updater": "^2.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
diff --git a/release.json b/release.json
index 023c03c..c2da0cf 100644
--- a/release.json
+++ b/release.json
@@ -1,7 +1,7 @@
{
- "url": "https://r2-vrime.mii.dev/releases/vrclipboard-ime-gui_1.9.0_x64_ja-JP.msi.zip",
- "version": "1.9.0",
- "notes": "VRChat外でコピーした文字列の変換をスキップする機能の実装",
- "pub_date": "2024-09-27T12:18:27+00:00",
- "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVTTStkVlpUR0NpcVdBalVrbUpDcDdac3NhSFhMZFZGbW90STNOYnJNVVhzZkdFQ0t3TzVTMkRoUkhQQVI1RnVHR2xSdUNYL0ttS0NTWW1OcFMyS2NtaGoybmZmdjU5TUFJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzI3NDM5Mjk5CWZpbGU6dnJjbGlwYm9hcmQtaW1lLWd1aV8xLjkuMF94NjRfamEtSlAubXNpLnppcAp6QTNXc2hGbXNaRU1PdVA3L1FHOVBrU2wvOE1HZGFpL3h6K2lSa3Buby9Jbnc0VTVnaGMrR2tQOHVOUHVHa1A3b1FmZmVUQlJZYk9WY3hmL3dteVZEZz09Cg=="
+ "url": "https://r2-vrime.mii.dev/releases/vrclipboard-ime-gui_1.11.0_x64_ja-JP.msi.zip",
+ "version": "1.11.0",
+ "notes": "UIの変更, TSF再変換機能の正式リリース",
+ "pub_date": "2025-03-11T19:14:02+09:00",
+ "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVTTStkVlpUR0NpcVcvYitzcmhYakFkOTBWZVo5RHFwVk9xbjdzNlg3VHMrZ3NBaHpxS2VacEg4ckE3V3ZZSGZETzFyZE1Qc0JoRTltUGhvREdsUzF2Ry91c2RtWnlqOXdrPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzQxNjg4MzA3CWZpbGU6dnJjbGlwYm9hcmQtaW1lLWd1aV8xLjExLjBfeDY0X2phLUpQLm1zaS56aXAKVWt0b0t5YkR5MlczM1FRYXV2THRHYjQwWVFxRGR2OW1KTExJNGFpeGcyN2k2Q1hrU3Yrb3dxU3NvY2FLNTk4bDd1Q1lFanpaQktIM0UxdTBzNmd3Q3c9PQo="
}
diff --git a/src-tauri/2.0.0-rc b/src-tauri/2.0.0-rc
index e3187fb..ee72c95 100644
--- a/src-tauri/2.0.0-rc
+++ b/src-tauri/2.0.0-rc
@@ -1,7 +1,12 @@
-added 1 package, and audited 194 packages in 4s
+changed 1 package, and audited 194 packages in 3s
38 packages are looking for funding
run `npm fund` for details
-found 0 vulnerabilities
+4 vulnerabilities (3 moderate, 1 high)
+
+To address all issues, run:
+ npm audit fix
+
+Run `npm audit` for details.
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 19e13ad..0c4a785 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -4399,7 +4399,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vrclipboard-ime-gui"
-version = "1.10.0"
+version = "1.11.0"
dependencies = [
"anyhow",
"calc",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index d0358d9..1878793 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "vrclipboard-ime-gui"
-version = "1.10.0"
+version = "1.11.0"
description = "VRClipboard IME"
authors = ["mii"]
edition = "2021"
diff --git a/src-tauri/capabilities/desktop.json b/src-tauri/capabilities/desktop.json
index 00ff1a6..9874144 100644
--- a/src-tauri/capabilities/desktop.json
+++ b/src-tauri/capabilities/desktop.json
@@ -6,6 +6,7 @@
"linux"
],
"permissions": [
+ "updater:default",
"updater:default"
]
}
\ No newline at end of file
diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs
index 669a5fd..845d976 100644
--- a/src-tauri/src/config.rs
+++ b/src-tauri/src/config.rs
@@ -27,7 +27,7 @@ pub struct Config {
pub on_copy_mode: OnCopyMode,
#[serde(default = "bool_true")]
pub skip_url: bool,
- #[serde(default = "bool_false")]
+ #[serde(default = "bool_true")]
pub use_tsf_reconvert: bool,
#[serde(default = "bool_true")]
pub skip_on_out_of_vrc: bool,
@@ -44,7 +44,7 @@ impl Default for Config {
ignore_prefix: true,
on_copy_mode: OnCopyMode::ReturnToChatbox,
skip_url: true,
- use_tsf_reconvert: false,
+ use_tsf_reconvert: true,
skip_on_out_of_vrc: true,
tsf_announce: false
}
@@ -95,7 +95,11 @@ impl Config {
let mut contents = String::new();
file.read_to_string(&mut contents)?;
trace!("Raw config contents: {}", contents);
- let config: Config = serde_yaml::from_str(&contents)?;
+ let mut config: Config = serde_yaml::from_str(&contents)?;
+ if !config.tsf_announce {
+ config.use_tsf_reconvert = true;
+ config.tsf_announce = true;
+ }
debug!("Config loaded successfully");
Ok(config)
}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 668f9b5..fceb903 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -24,14 +24,17 @@
},
"productName": "vrclipboard-ime-gui",
"mainBinaryName": "vrclipboard-ime-gui",
- "version": "1.10.0",
+ "version": "1.11.0",
"identifier": "dev.mii.vrclipboard-ime",
"plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEE5QTI2MDRDNTlENUY5OEMKUldTTStkVlpUR0NpcVIrMXZqOHFpNzNXMFVKT0d3aHJIWFlOUVJubGN5VTAzUkVwYW95bVlMYUQK",
"endpoints": [
"https://r2-vrime.mii.dev/release.json"
- ]
+ ],
+ "windows": {
+ "installMode": "passive"
+ }
}
},
"app": {
diff --git a/src/AboutComponent.tsx b/src/AboutComponent.tsx
index 972b3a7..652c622 100644
--- a/src/AboutComponent.tsx
+++ b/src/AboutComponent.tsx
@@ -30,7 +30,7 @@ const AboutComponent: React.FC = () => {
バージョン:
- 1.10.0
+ 1.11.0
ライセンス:
@@ -38,7 +38,7 @@ const AboutComponent: React.FC = () => {
最終更新:
- 2025年2月
+ 2025年3月11日
技術:
diff --git a/src/App.tsx b/src/App.tsx
index 9d7d36c..9329d6f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
-import { List, Settings, Terminal, Bug, Info, Check, Book } from 'lucide-react';
+import { List, Settings, Terminal, Bug, Info, Check } from 'lucide-react';
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
@@ -24,7 +24,7 @@ const AppContent = () => {
const [activeMenuItem, setActiveMenuItem] = useState('home');
const [showTsfModal, setShowTsfModal] = useState(false);
const [currentSettings, setCurrentSettings] = useState
(null);
- const [isTsfAvailable, setIsTsfAvailable] = useState(null);
+ const [_isTsfAvailable, setIsTsfAvailable] = useState(null);
const [showTsfSuccessMessage, setShowTsfSuccessMessage] = useState(false);
useEffect(() => {
@@ -146,7 +146,7 @@ const AppContent = () => {
} label="ログ" id="home" />
} label="設定" id="settings" />
- } label="辞書" id="dictionary" />
+ {/*} label="辞書" id="dictionary" />*/}
{/* 下側にデバッグタブを配置 */}
diff --git a/src/DictionaryComponent.tsx b/src/DictionaryComponent.tsx
index e935acb..c47e0c2 100644
--- a/src/DictionaryComponent.tsx
+++ b/src/DictionaryComponent.tsx
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react';
import { invoke } from '@tauri-apps/api/core';
-import { Book, Save, Plus, Trash, ChevronUp, ChevronDown, AlertCircle, Check, Edit, AlignLeft, Info, X } from 'lucide-react';
+import { Book, Save, Plus, Trash, ChevronUp, ChevronDown, AlertCircle, Check, Edit, AlignLeft, X } from 'lucide-react';
import {
Dictionary,
DictionaryEntry,
@@ -12,16 +12,136 @@ import {
convertFromRustEntry
} from './types/dictionary';
+// 入力フィールド用の内部コンポーネント(フォーカスを保持するため)
+const InputField: React.FC<{
+ label: string;
+ value: string;
+ placeholder?: string;
+ onChange: (value: string) => void;
+ inputRef?: React.RefObject
;
+}> = ({ label, value, placeholder, onChange, inputRef }) => {
+ // ローカルステートで値を管理
+ const [localValue, setLocalValue] = useState(value);
+
+ // 親から渡された値が変わったらローカルステートを更新
+ useEffect(() => {
+ setLocalValue(value);
+ }, [value]);
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const newValue = e.target.value;
+ setLocalValue(newValue);
+ onChange(newValue);
+ };
+
+ return (
+
+
+
+
+ );
+};
+
+// 数値入力フィールド用のコンポーネント
+const NumberField: React.FC<{
+ label: string;
+ value: number;
+ onChange: (value: number) => void;
+ description?: string;
+}> = ({ label, value, onChange, description }) => {
+ // ローカルステートで値を管理
+ const [localValue, setLocalValue] = useState(value.toString());
+
+ useEffect(() => {
+ setLocalValue(value.toString());
+ }, [value]);
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const newValue = e.target.value;
+ setLocalValue(newValue);
+ onChange(parseInt(newValue) || 0);
+ };
+
+ return (
+
+
+
+ {description && (
+
+ {description}
+
+ )}
+
+ );
+};
+
+// チェックボックス用のコンポーネント
+const CheckboxField: React.FC<{
+ id: string;
+ label: string;
+ checked: boolean;
+ onChange: (checked: boolean) => void;
+}> = ({ id, label, checked, onChange }) => {
+ // ローカルステートで値を管理
+ const [isChecked, setIsChecked] = useState(checked);
+
+ useEffect(() => {
+ setIsChecked(checked);
+ }, [checked]);
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const newValue = e.target.checked;
+ setIsChecked(newValue);
+ onChange(newValue);
+ };
+
+ return (
+
+
+
+
+ );
+};
+
const DictionaryComponent: React.FC = () => {
const [dictionary, setDictionary] = useState({ entries: [] });
- const [selectedEntry, setSelectedEntry] = useState(null);
const [editIndex, setEditIndex] = useState(null);
- const [isEditing, setIsEditing] = useState(false);
const [showDialog, setShowDialog] = useState(false);
const [isMethodDropdownOpen, setIsMethodDropdownOpen] = useState(false);
const [isConverterDropdownOpen, setIsConverterDropdownOpen] = useState(false);
const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'success' | 'error'>('idle');
-
+
+ // 編集中のエントリを更新するためのフォースレンダリング用state
+ const [, forceUpdate] = useState({});
+
+ // Refを使って値を保持
+ const entryRef = useRef(null);
+
const methodDropdownRef = useRef(null);
const converterDropdownRef = useRef(null);
@@ -86,17 +206,19 @@ const DictionaryComponent: React.FC = () => {
};
const handleAddEntry = () => {
- setSelectedEntry(getDefaultDictionaryEntry());
+ entryRef.current = getDefaultDictionaryEntry();
setEditIndex(null);
- setIsEditing(true);
setShowDialog(true);
+ // 強制的に再レンダリング
+ forceUpdate({});
};
const handleEditEntry = (entry: DictionaryEntry, index: number) => {
- setSelectedEntry({...entry});
+ entryRef.current = {...entry};
setEditIndex(index);
- setIsEditing(true);
setShowDialog(true);
+ // 強制的に再レンダリング
+ forceUpdate({});
};
const handleDeleteEntry = (index: number) => {
@@ -108,34 +230,41 @@ const DictionaryComponent: React.FC = () => {
};
const handleSaveEntry = () => {
- if (!selectedEntry) return;
+ if (!entryRef.current) return;
const newEntries = [...dictionary.entries];
// 新規追加の場合
if (editIndex === null) {
- newEntries.push(selectedEntry);
+ newEntries.push(entryRef.current);
} else {
// 編集の場合
- newEntries[editIndex] = selectedEntry;
+ newEntries[editIndex] = entryRef.current;
}
const newDict = { ...dictionary, entries: newEntries };
setDictionary(newDict);
saveDictionary(newDict);
+
+ // ダイアログを閉じて状態をリセット
+ closeDialog();
+ };
+
+ const closeDialog = () => {
setShowDialog(false);
- setIsEditing(false);
- setSelectedEntry(null);
+ setIsMethodDropdownOpen(false);
+ setIsConverterDropdownOpen(false);
+ entryRef.current = null;
};
const handleChangePriority = (index: number, direction: 'up' | 'down') => {
if (dictionary.entries.length <= 1) return;
const newEntries = [...dictionary.entries];
- const entry = newEntries[index];
+ const entry = {...newEntries[index]};
if (direction === 'up' && index > 0) {
- const prevEntry = newEntries[index - 1];
+ const prevEntry = {...newEntries[index - 1]};
const tempPriority = prevEntry.priority;
prevEntry.priority = entry.priority;
entry.priority = tempPriority;
@@ -144,7 +273,7 @@ const DictionaryComponent: React.FC = () => {
newEntries[index] = prevEntry;
newEntries[index - 1] = entry;
} else if (direction === 'down' && index < newEntries.length - 1) {
- const nextEntry = newEntries[index + 1];
+ const nextEntry = {...newEntries[index + 1]};
const tempPriority = nextEntry.priority;
nextEntry.priority = entry.priority;
entry.priority = tempPriority;
@@ -159,10 +288,15 @@ const DictionaryComponent: React.FC = () => {
saveDictionary(newDict);
};
- const handleChangeEntryField = (field: keyof DictionaryEntry, value: any) => {
- if (!selectedEntry) return;
+ // 入力フィールドの値を更新(フォーカスを失わないようRef経由で更新)
+ const handleChangeEntryField = (field: K, value: DictionaryEntry[K]) => {
+ if (!entryRef.current) return;
- const updatedEntry = { ...selectedEntry, [field]: value };
+ // RefのDictionaryEntryをディープコピー
+ const updatedEntry = { ...entryRef.current };
+
+ // フィールドの値を更新
+ updatedEntry[field] = value as DictionaryEntry[K];
// 変換方法がReplace以外の場合はoutputをnullに
if (field === 'method' && value !== ConversionMethod.Replace) {
@@ -174,7 +308,13 @@ const DictionaryComponent: React.FC = () => {
updatedEntry.converter_char = 'r';
}
- setSelectedEntry(updatedEntry);
+ // 更新したEntryをRefに保存
+ entryRef.current = updatedEntry;
+
+ // UIを強制的に更新(ドロップダウンメニュー表示/非表示の対応のため)
+ if (field === 'method' || field === 'converter_char') {
+ forceUpdate({});
+ }
};
const handleSelectMethod = (method: ConversionMethod) => {
@@ -219,7 +359,9 @@ const DictionaryComponent: React.FC = () => {
// 辞書エントリダイアログ
const EntryDialog = () => {
- if (!showDialog || !selectedEntry) return null;
+ if (!showDialog || !entryRef.current) return null;
+
+ const entry = entryRef.current;
return (
@@ -229,13 +371,7 @@ const DictionaryComponent: React.FC = () => {
{editIndex !== null ? '辞書エントリの編集' : '新しい辞書エントリ'}
-
+
handleChangeEntryField('input', value)}
+ />
-
+
handleChangeEntryField('use_regex', checked)}
+ />
+
+
setIsMethodDropdownOpen(!isMethodDropdownOpen)}
>
- {getMethodLabel(selectedEntry.method as ConversionMethod, selectedEntry.converter_char)}
+ {getMethodLabel(entry.method as ConversionMethod, entry.converter_char)}
{isMethodDropdownOpen && (
@@ -286,9 +410,9 @@ const DictionaryComponent: React.FC = () => {
className="p-1.5 hover:bg-indigo-50 dark:hover:bg-indigo-900/50 cursor-pointer"
onClick={() => handleSelectMethod(ConversionMethod.Replace)}
>
-
- {selectedEntry.method === ConversionMethod.Replace &&
}
-
+
+ {entry.method === ConversionMethod.Replace && }
+
置き換え
@@ -297,9 +421,9 @@ const DictionaryComponent: React.FC = () => {
className="p-1.5 hover:bg-indigo-50 dark:hover:bg-indigo-900/50 cursor-pointer"
onClick={() => handleSelectMethod(ConversionMethod.None)}
>
-
- {selectedEntry.method === ConversionMethod.None &&
}
-
+
+ {entry.method === ConversionMethod.None && }
+
無変換
@@ -308,9 +432,9 @@ const DictionaryComponent: React.FC = () => {
className="p-1.5 hover:bg-indigo-50 dark:hover:bg-indigo-900/50 cursor-pointer"
onClick={() => handleSelectMethod(ConversionMethod.Converter)}
>
-
- {selectedEntry.method === ConversionMethod.Converter &&
}
-
+
+ {entry.method === ConversionMethod.Converter && }
+
変換
@@ -319,19 +443,18 @@ const DictionaryComponent: React.FC = () => {
)}
- {selectedEntry.method === ConversionMethod.Converter && (
-
+ {entry.method === ConversionMethod.Converter && (
+
setIsConverterDropdownOpen(!isConverterDropdownOpen)}
>
- {selectedEntry.converter_char
- ? getConverterInfo(selectedEntry.converter_char)?.name || 'ローマ字→漢字'
+ {entry.converter_char
+ ? getConverterInfo(entry.converter_char)?.name || 'ローマ字→漢字'
: 'ローマ字→漢字'}
@@ -344,9 +467,9 @@ const DictionaryComponent: React.FC = () => {
className="p-1.5 hover:bg-indigo-50 dark:hover:bg-indigo-900/50 cursor-pointer"
onClick={() => handleSelectConverter(converter.id)}
>
-
- {selectedEntry.converter_char === converter.id &&
}
-
+
+ {entry.converter_char === converter.id && }
+
{converter.name} - {converter.description}
@@ -357,47 +480,26 @@ const DictionaryComponent: React.FC = () => {
)}
- {selectedEntry.method === ConversionMethod.Replace && (
-
-
- handleChangeEntryField('output', e.target.value)}
- className="w-full p-1.5 text-sm border rounded bg-white dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200 focus:border-indigo-400 outline-none"
- placeholder="例: Hello"
- />
-
+ {entry.method === ConversionMethod.Replace && (
+
handleChangeEntryField('output', value)}
+ />
)}
-
-
-
handleChangeEntryField('priority', parseInt(e.target.value) || 0)}
- className="w-full p-1.5 text-sm border rounded bg-white dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200 focus:border-indigo-400 outline-none"
- min="0"
- />
-
- 数値が大きいほど優先度が高くなります。
-
-
+ handleChangeEntryField('priority', value)}
+ description="数値が大きいほど優先度が高くなります。"
+ />