update to 1.11.0

This commit is contained in:
mii443
2025-03-11 19:43:37 +09:00
parent 04123966dc
commit 185e1bcf73
15 changed files with 256 additions and 139 deletions

13
package-lock.json generated
View File

@ -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"
}

View File

@ -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"
},

View File

@ -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="
}

View File

@ -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.

2
src-tauri/Cargo.lock generated
View File

@ -4399,7 +4399,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vrclipboard-ime-gui"
version = "1.10.0"
version = "1.11.0"
dependencies = [
"anyhow",
"calc",

View File

@ -1,6 +1,6 @@
[package]
name = "vrclipboard-ime-gui"
version = "1.10.0"
version = "1.11.0"
description = "VRClipboard IME"
authors = ["mii"]
edition = "2021"

View File

@ -6,6 +6,7 @@
"linux"
],
"permissions": [
"updater:default",
"updater:default"
]
}

View File

@ -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)
}

View File

@ -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": {

View File

@ -30,7 +30,7 @@ const AboutComponent: React.FC = () => {
<div className="text-sm">
<div className="flex items-center mb-1">
<span className="text-gray-700 dark:text-gray-300 w-20">:</span>
<span className="text-gray-600 dark:text-gray-400">1.10.0</span>
<span className="text-gray-600 dark:text-gray-400">1.11.0</span>
</div>
<div className="flex items-center mb-1">
<span className="text-gray-700 dark:text-gray-300 w-20">:</span>
@ -38,7 +38,7 @@ const AboutComponent: React.FC = () => {
</div>
<div className="flex items-center mb-1">
<span className="text-gray-700 dark:text-gray-300 w-20">:</span>
<span className="text-gray-600 dark:text-gray-400">20252</span>
<span className="text-gray-600 dark:text-gray-400">2025311</span>
</div>
<div className="flex items-center mb-1">
<span className="text-gray-700 dark:text-gray-300 w-20">:</span>

View File

@ -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<Config | null>(null);
const [isTsfAvailable, setIsTsfAvailable] = useState<boolean | null>(null);
const [_isTsfAvailable, setIsTsfAvailable] = useState<boolean | null>(null);
const [showTsfSuccessMessage, setShowTsfSuccessMessage] = useState(false);
useEffect(() => {
@ -146,7 +146,7 @@ const AppContent = () => {
<div className="space-y-1 flex-grow">
<MenuItem icon={<List size={16} />} label="ログ" id="home" />
<MenuItem icon={<Settings size={16} />} label="設定" id="settings" />
<MenuItem icon={<Book size={16} />} label="辞書" id="dictionary" />
{/*<MenuItem icon={<Book size={16} />} label="辞書" id="dictionary" />*/}
</div>
{/* 下側にデバッグタブを配置 */}
<div className="pt-2 border-t border-gray-100 dark:border-gray-700">

View File

@ -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<HTMLInputElement>;
}> = ({ label, value, placeholder, onChange, inputRef }) => {
// ローカルステートで値を管理
const [localValue, setLocalValue] = useState(value);
// 親から渡された値が変わったらローカルステートを更新
useEffect(() => {
setLocalValue(value);
}, [value]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
setLocalValue(newValue);
onChange(newValue);
};
return (
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
{label}
</label>
<input
ref={inputRef}
type="text"
value={localValue}
onChange={handleChange}
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={placeholder}
/>
</div>
);
};
// 数値入力フィールド用のコンポーネント
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<HTMLInputElement>) => {
const newValue = e.target.value;
setLocalValue(newValue);
onChange(parseInt(newValue) || 0);
};
return (
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
{label}
</label>
<input
type="number"
value={localValue}
onChange={handleChange}
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"
/>
{description && (
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
{description}
</p>
)}
</div>
);
};
// チェックボックス用のコンポーネント
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<HTMLInputElement>) => {
const newValue = e.target.checked;
setIsChecked(newValue);
onChange(newValue);
};
return (
<div className="flex items-center mt-1.5">
<input
type="checkbox"
id={id}
checked={isChecked}
onChange={handleChange}
className="h-3.5 w-3.5 text-indigo-500 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"
/>
<label htmlFor={id} className="ml-2 text-xs text-gray-700 dark:text-gray-300">
{label}
</label>
</div>
);
};
const DictionaryComponent: React.FC = () => {
const [dictionary, setDictionary] = useState<Dictionary>({ entries: [] });
const [selectedEntry, setSelectedEntry] = useState<DictionaryEntry | null>(null);
const [editIndex, setEditIndex] = useState<number | null>(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<DictionaryEntry | null>(null);
const methodDropdownRef = useRef<HTMLDivElement>(null);
const converterDropdownRef = useRef<HTMLDivElement>(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 = <K extends keyof DictionaryEntry>(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 (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
@ -229,13 +371,7 @@ const DictionaryComponent: React.FC = () => {
{editIndex !== null ? '辞書エントリの編集' : '新しい辞書エントリ'}
</h3>
<button
onClick={() => {
setShowDialog(false);
setIsEditing(false);
setSelectedEntry(null);
setIsMethodDropdownOpen(false);
setIsConverterDropdownOpen(false);
}}
onClick={closeDialog}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
>
<X size={20} />
@ -243,41 +379,29 @@ const DictionaryComponent: React.FC = () => {
</div>
<div className="space-y-4">
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
</label>
<input
type="text"
value={selectedEntry.input}
onChange={(e) => handleChangeEntryField('input', 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"
<InputField
label="変換対象文字列"
value={entry.input}
placeholder="例: こんにちは"
onChange={(value) => handleChangeEntryField('input', value)}
/>
<div className="flex items-center mt-1.5">
<input
type="checkbox"
id="use_regex"
checked={selectedEntry.use_regex}
onChange={(e) => handleChangeEntryField('use_regex', e.target.checked)}
className="h-3.5 w-3.5 text-indigo-500 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700"
/>
<label htmlFor="use_regex" className="ml-2 text-xs text-gray-700 dark:text-gray-300">
使
</label>
</div>
</div>
<div className="relative mb-3">
<CheckboxField
id="use_regex"
label="正規表現を使用"
checked={entry.use_regex}
onChange={(checked) => handleChangeEntryField('use_regex', checked)}
/>
<div className="relative mb-3" ref={methodDropdownRef}>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
</label>
<div
ref={methodDropdownRef}
className="w-full p-1.5 text-sm border rounded bg-white dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200 flex justify-between items-center cursor-pointer hover:border-indigo-300 dark:hover:border-indigo-500 transition-colors"
onClick={() => setIsMethodDropdownOpen(!isMethodDropdownOpen)}
>
<span>{getMethodLabel(selectedEntry.method as ConversionMethod, selectedEntry.converter_char)}</span>
<span>{getMethodLabel(entry.method as ConversionMethod, entry.converter_char)}</span>
<ChevronDown size={14} className={`transition-transform ${isMethodDropdownOpen ? 'transform rotate-180' : ''}`} />
</div>
{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)}
>
<div className={`flex items-center ${selectedEntry.method === ConversionMethod.Replace ? 'text-indigo-600 dark:text-indigo-400 font-medium' : 'dark:text-gray-300'}`}>
{selectedEntry.method === ConversionMethod.Replace && <Check size={12} className="mr-1.5" />}
<span className={selectedEntry.method === ConversionMethod.Replace ? 'ml-0' : 'ml-4'}>
<div className={`flex items-center ${entry.method === ConversionMethod.Replace ? 'text-indigo-600 dark:text-indigo-400 font-medium' : 'dark:text-gray-300'}`}>
{entry.method === ConversionMethod.Replace && <Check size={12} className="mr-1.5" />}
<span className={entry.method === ConversionMethod.Replace ? 'ml-0' : 'ml-4'}>
</span>
</div>
@ -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)}
>
<div className={`flex items-center ${selectedEntry.method === ConversionMethod.None ? 'text-indigo-600 dark:text-indigo-400 font-medium' : 'dark:text-gray-300'}`}>
{selectedEntry.method === ConversionMethod.None && <Check size={12} className="mr-1.5" />}
<span className={selectedEntry.method === ConversionMethod.None ? 'ml-0' : 'ml-4'}>
<div className={`flex items-center ${entry.method === ConversionMethod.None ? 'text-indigo-600 dark:text-indigo-400 font-medium' : 'dark:text-gray-300'}`}>
{entry.method === ConversionMethod.None && <Check size={12} className="mr-1.5" />}
<span className={entry.method === ConversionMethod.None ? 'ml-0' : 'ml-4'}>
</span>
</div>
@ -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)}
>
<div className={`flex items-center ${selectedEntry.method === ConversionMethod.Converter ? 'text-indigo-600 dark:text-indigo-400 font-medium' : 'dark:text-gray-300'}`}>
{selectedEntry.method === ConversionMethod.Converter && <Check size={12} className="mr-1.5" />}
<span className={selectedEntry.method === ConversionMethod.Converter ? 'ml-0' : 'ml-4'}>
<div className={`flex items-center ${entry.method === ConversionMethod.Converter ? 'text-indigo-600 dark:text-indigo-400 font-medium' : 'dark:text-gray-300'}`}>
{entry.method === ConversionMethod.Converter && <Check size={12} className="mr-1.5" />}
<span className={entry.method === ConversionMethod.Converter ? 'ml-0' : 'ml-4'}>
</span>
</div>
@ -319,19 +443,18 @@ const DictionaryComponent: React.FC = () => {
)}
</div>
{selectedEntry.method === ConversionMethod.Converter && (
<div className="relative mb-3">
{entry.method === ConversionMethod.Converter && (
<div className="relative mb-3" ref={converterDropdownRef}>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
</label>
<div
ref={converterDropdownRef}
className="w-full p-1.5 text-sm border rounded bg-white dark:bg-gray-700 dark:border-gray-600 dark:text-gray-200 flex justify-between items-center cursor-pointer hover:border-indigo-300 dark:hover:border-indigo-500 transition-colors"
onClick={() => setIsConverterDropdownOpen(!isConverterDropdownOpen)}
>
<span>
{selectedEntry.converter_char
? getConverterInfo(selectedEntry.converter_char)?.name || 'ローマ字→漢字'
{entry.converter_char
? getConverterInfo(entry.converter_char)?.name || 'ローマ字→漢字'
: 'ローマ字→漢字'}
</span>
<ChevronDown size={14} className={`transition-transform ${isConverterDropdownOpen ? 'transform rotate-180' : ''}`} />
@ -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)}
>
<div className={`flex items-center ${selectedEntry.converter_char === converter.id ? 'text-indigo-600 dark:text-indigo-400 font-medium' : 'dark:text-gray-300'}`}>
{selectedEntry.converter_char === converter.id && <Check size={12} className="mr-1.5" />}
<span className={selectedEntry.converter_char === converter.id ? 'ml-0' : 'ml-4'}>
<div className={`flex items-center ${entry.converter_char === converter.id ? 'text-indigo-600 dark:text-indigo-400 font-medium' : 'dark:text-gray-300'}`}>
{entry.converter_char === converter.id && <Check size={12} className="mr-1.5" />}
<span className={entry.converter_char === converter.id ? 'ml-0' : 'ml-4'}>
{converter.name} - {converter.description}
</span>
</div>
@ -357,47 +480,26 @@ const DictionaryComponent: React.FC = () => {
</div>
)}
{selectedEntry.method === ConversionMethod.Replace && (
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
</label>
<input
type="text"
value={selectedEntry.output || ''}
onChange={(e) => 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"
{entry.method === ConversionMethod.Replace && (
<InputField
label="置き換え後の文字列"
value={entry.output || ''}
placeholder="例: Hello"
onChange={(value) => handleChangeEntryField('output', value)}
/>
</div>
)}
<div>
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
</label>
<input
type="number"
value={selectedEntry.priority}
onChange={(e) => 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"
<NumberField
label="優先順位"
value={entry.priority}
onChange={(value) => handleChangeEntryField('priority', value)}
description="数値が大きいほど優先度が高くなります。"
/>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
</p>
</div>
</div>
<div className="mt-6 flex justify-end">
<button
onClick={() => {
setShowDialog(false);
setIsEditing(false);
setSelectedEntry(null);
setIsMethodDropdownOpen(false);
setIsConverterDropdownOpen(false);
}}
onClick={closeDialog}
className="mr-2 px-3 py-1.5 rounded text-sm bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200"
>
@ -417,6 +519,7 @@ const DictionaryComponent: React.FC = () => {
return (
<div className="h-full">
<div className="flex items-center justify-between mb-2">
<h2 className="text-base font-medium text-gray-700 dark:text-gray-200 flex items-center transition-colors">
<Book size={16} className="mr-1.5" />
@ -433,6 +536,7 @@ const DictionaryComponent: React.FC = () => {
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded border border-gray-100 dark:border-gray-700 p-3 transition-colors">
{dictionary.entries.length > 0 ? (
<div className="overflow-x-auto">

View File

@ -334,7 +334,7 @@ const SettingsComponent: React.FC<SettingsComponentProps> = ({
name="use_tsf_reconvert"
label={
<span>
<span className="text-indigo-600 dark:text-indigo-400 font-medium transition-colors">:</span> TSF再変換を使用
<span className="text-indigo-600 dark:text-indigo-400 font-medium transition-colors">TSF再変換を使用</span>
</span>
}
checked={settings.use_tsf_reconvert}

View File

@ -79,7 +79,7 @@ const TitleBar = () => {
>
<div className="flex items-center" data-tauri-drag-region>
<span className="text-xs font-medium" data-tauri-drag-region>VRClipboard-IME</span>
<span className="text-xs opacity-80 ml-1" data-tauri-drag-region>v1.10.0</span>
<span className="text-xs opacity-80 ml-1" data-tauri-drag-region>v1.11.0</span>
</div>
<div className="flex">
<TitleButton

View File

@ -1,6 +1,5 @@
import React, { useEffect, useState } from 'react';
import { open } from '@tauri-apps/plugin-shell';
import { AlertCircle, Settings, Check, X, ExternalLink } from 'lucide-react';
import { AlertCircle, Settings, Check, X } from 'lucide-react';
import { Config } from './SettingsComponent';
import { invoke } from '@tauri-apps/api/core';
@ -19,7 +18,7 @@ const TsfSettingsModal: React.FC<TsfSettingsModalProps> = ({
currentSettings,
onTsfEnabled
}) => {
const [checkingStatus, setCheckingStatus] = useState<'idle' | 'checking'>('idle');
const [_checkingStatus, _setCheckingStatus] = useState<'idle' | 'checking'>('idle');
// TSFが利用可能かどうかを定期的にチェックする
useEffect(() => {