mirror of
https://github.com/mii443/vrclipboard-ime-gui.git
synced 2025-08-22 16:15:32 +00:00
update to 1.11.0
This commit is contained in:
13
package-lock.json
generated
13
package-lock.json
generated
@ -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"
|
||||
}
|
||||
|
@ -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"
|
||||
},
|
||||
|
10
release.json
10
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="
|
||||
}
|
||||
|
@ -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
2
src-tauri/Cargo.lock
generated
@ -4399,7 +4399,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vrclipboard-ime-gui"
|
||||
version = "1.10.0"
|
||||
version = "1.11.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"calc",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "vrclipboard-ime-gui"
|
||||
version = "1.10.0"
|
||||
version = "1.11.0"
|
||||
description = "VRClipboard IME"
|
||||
authors = ["mii"]
|
||||
edition = "2021"
|
||||
|
@ -6,6 +6,7 @@
|
||||
"linux"
|
||||
],
|
||||
"permissions": [
|
||||
"updater:default",
|
||||
"updater:default"
|
||||
]
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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": {
|
||||
|
@ -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">2025年2月</span>
|
||||
<span className="text-gray-600 dark:text-gray-400">2025年3月11日</span>
|
||||
</div>
|
||||
<div className="flex items-center mb-1">
|
||||
<span className="text-gray-700 dark:text-gray-300 w-20">技術:</span>
|
||||
|
@ -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">
|
||||
|
@ -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"
|
||||
placeholder="例: こんにちは"
|
||||
/>
|
||||
<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>
|
||||
<InputField
|
||||
label="変換対象文字列"
|
||||
value={entry.input}
|
||||
placeholder="例: こんにちは"
|
||||
onChange={(value) => handleChangeEntryField('input', value)}
|
||||
/>
|
||||
|
||||
<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"
|
||||
placeholder="例: Hello"
|
||||
/>
|
||||
</div>
|
||||
{entry.method === ConversionMethod.Replace && (
|
||||
<InputField
|
||||
label="置き換え後の文字列"
|
||||
value={entry.output || ''}
|
||||
placeholder="例: Hello"
|
||||
onChange={(value) => handleChangeEntryField('output', value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<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"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
数値が大きいほど優先度が高くなります。
|
||||
</p>
|
||||
</div>
|
||||
<NumberField
|
||||
label="優先順位"
|
||||
value={entry.priority}
|
||||
onChange={(value) => handleChangeEntryField('priority', value)}
|
||||
description="数値が大きいほど優先度が高くなります。"
|
||||
/>
|
||||
</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">
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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(() => {
|
||||
|
Reference in New Issue
Block a user