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",
|
"name": "vrclipboard-ime-gui",
|
||||||
"version": "1.9.0",
|
"version": "1.11.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "vrclipboard-ime-gui",
|
"name": "vrclipboard-ime-gui",
|
||||||
"version": "1.9.0",
|
"version": "1.11.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.0.0-rc.0",
|
"@tauri-apps/api": "^2.0.0-rc.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.0.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": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
@ -1276,9 +1276,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tauri-apps/plugin-updater": {
|
"node_modules/@tauri-apps/plugin-updater": {
|
||||||
"version": "2.0.0",
|
"version": "2.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.6.0.tgz",
|
||||||
"integrity": "sha512-N0cl71g7RPr7zK2Fe5aoIwzw14NcdLcz7XMGFWZVjprsqgDRWoxbnUkknyCQMZthjhGkppCd/wN2MIsUz+eAhQ==",
|
"integrity": "sha512-j74RUolLIhQDQwrff6R28xIewYVXME1gFU+d+4LYN1dLRzLD+ySa7VHqzyWYxWEvm+TPZ7lkUxa5a9uH9Ist3A==",
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.0.0"
|
"@tauri-apps/api": "^2.0.0"
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vrclipboard-ime-gui",
|
"name": "vrclipboard-ime-gui",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.10.0",
|
"version": "1.11.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.0.0-rc.0",
|
"@tauri-apps/api": "^2.0.0-rc.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.0.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": "^18.2.0",
|
||||||
"react-dom": "^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",
|
"url": "https://r2-vrime.mii.dev/releases/vrclipboard-ime-gui_1.11.0_x64_ja-JP.msi.zip",
|
||||||
"version": "1.9.0",
|
"version": "1.11.0",
|
||||||
"notes": "VRChat外でコピーした文字列の変換をスキップする機能の実装",
|
"notes": "UIの変更, TSF再変換機能の正式リリース",
|
||||||
"pub_date": "2024-09-27T12:18:27+00:00",
|
"pub_date": "2025-03-11T19:14:02+09:00",
|
||||||
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVTTStkVlpUR0NpcVdBalVrbUpDcDdac3NhSFhMZFZGbW90STNOYnJNVVhzZkdFQ0t3TzVTMkRoUkhQQVI1RnVHR2xSdUNYL0ttS0NTWW1OcFMyS2NtaGoybmZmdjU5TUFJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzI3NDM5Mjk5CWZpbGU6dnJjbGlwYm9hcmQtaW1lLWd1aV8xLjkuMF94NjRfamEtSlAubXNpLnppcAp6QTNXc2hGbXNaRU1PdVA3L1FHOVBrU2wvOE1HZGFpL3h6K2lSa3Buby9Jbnc0VTVnaGMrR2tQOHVOUHVHa1A3b1FmZmVUQlJZYk9WY3hmL3dteVZEZz09Cg=="
|
"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
|
38 packages are looking for funding
|
||||||
run `npm fund` for details
|
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]]
|
[[package]]
|
||||||
name = "vrclipboard-ime-gui"
|
name = "vrclipboard-ime-gui"
|
||||||
version = "1.10.0"
|
version = "1.11.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"calc",
|
"calc",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "vrclipboard-ime-gui"
|
name = "vrclipboard-ime-gui"
|
||||||
version = "1.10.0"
|
version = "1.11.0"
|
||||||
description = "VRClipboard IME"
|
description = "VRClipboard IME"
|
||||||
authors = ["mii"]
|
authors = ["mii"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"linux"
|
"linux"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
"updater:default",
|
||||||
"updater:default"
|
"updater:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -27,7 +27,7 @@ pub struct Config {
|
|||||||
pub on_copy_mode: OnCopyMode,
|
pub on_copy_mode: OnCopyMode,
|
||||||
#[serde(default = "bool_true")]
|
#[serde(default = "bool_true")]
|
||||||
pub skip_url: bool,
|
pub skip_url: bool,
|
||||||
#[serde(default = "bool_false")]
|
#[serde(default = "bool_true")]
|
||||||
pub use_tsf_reconvert: bool,
|
pub use_tsf_reconvert: bool,
|
||||||
#[serde(default = "bool_true")]
|
#[serde(default = "bool_true")]
|
||||||
pub skip_on_out_of_vrc: bool,
|
pub skip_on_out_of_vrc: bool,
|
||||||
@ -44,7 +44,7 @@ impl Default for Config {
|
|||||||
ignore_prefix: true,
|
ignore_prefix: true,
|
||||||
on_copy_mode: OnCopyMode::ReturnToChatbox,
|
on_copy_mode: OnCopyMode::ReturnToChatbox,
|
||||||
skip_url: true,
|
skip_url: true,
|
||||||
use_tsf_reconvert: false,
|
use_tsf_reconvert: true,
|
||||||
skip_on_out_of_vrc: true,
|
skip_on_out_of_vrc: true,
|
||||||
tsf_announce: false
|
tsf_announce: false
|
||||||
}
|
}
|
||||||
@ -95,7 +95,11 @@ impl Config {
|
|||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
file.read_to_string(&mut contents)?;
|
file.read_to_string(&mut contents)?;
|
||||||
trace!("Raw config contents: {}", 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");
|
debug!("Config loaded successfully");
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
@ -24,14 +24,17 @@
|
|||||||
},
|
},
|
||||||
"productName": "vrclipboard-ime-gui",
|
"productName": "vrclipboard-ime-gui",
|
||||||
"mainBinaryName": "vrclipboard-ime-gui",
|
"mainBinaryName": "vrclipboard-ime-gui",
|
||||||
"version": "1.10.0",
|
"version": "1.11.0",
|
||||||
"identifier": "dev.mii.vrclipboard-ime",
|
"identifier": "dev.mii.vrclipboard-ime",
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"updater": {
|
"updater": {
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEE5QTI2MDRDNTlENUY5OEMKUldTTStkVlpUR0NpcVIrMXZqOHFpNzNXMFVKT0d3aHJIWFlOUVJubGN5VTAzUkVwYW95bVlMYUQK",
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEE5QTI2MDRDNTlENUY5OEMKUldTTStkVlpUR0NpcVIrMXZqOHFpNzNXMFVKT0d3aHJIWFlOUVJubGN5VTAzUkVwYW95bVlMYUQK",
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
"https://r2-vrime.mii.dev/release.json"
|
"https://r2-vrime.mii.dev/release.json"
|
||||||
]
|
],
|
||||||
|
"windows": {
|
||||||
|
"installMode": "passive"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
|
@ -30,7 +30,7 @@ const AboutComponent: React.FC = () => {
|
|||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<div className="flex items-center mb-1">
|
<div className="flex items-center mb-1">
|
||||||
<span className="text-gray-700 dark:text-gray-300 w-20">バージョン:</span>
|
<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>
|
||||||
<div className="flex items-center mb-1">
|
<div className="flex items-center mb-1">
|
||||||
<span className="text-gray-700 dark:text-gray-300 w-20">ライセンス:</span>
|
<span className="text-gray-700 dark:text-gray-300 w-20">ライセンス:</span>
|
||||||
@ -38,7 +38,7 @@ const AboutComponent: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center mb-1">
|
<div className="flex items-center mb-1">
|
||||||
<span className="text-gray-700 dark:text-gray-300 w-20">最終更新:</span>
|
<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>
|
||||||
<div className="flex items-center mb-1">
|
<div className="flex items-center mb-1">
|
||||||
<span className="text-gray-700 dark:text-gray-300 w-20">技術:</span>
|
<span className="text-gray-700 dark:text-gray-300 w-20">技術:</span>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect } from "react";
|
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 { listen } from "@tauri-apps/api/event";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
@ -24,7 +24,7 @@ const AppContent = () => {
|
|||||||
const [activeMenuItem, setActiveMenuItem] = useState('home');
|
const [activeMenuItem, setActiveMenuItem] = useState('home');
|
||||||
const [showTsfModal, setShowTsfModal] = useState(false);
|
const [showTsfModal, setShowTsfModal] = useState(false);
|
||||||
const [currentSettings, setCurrentSettings] = useState<Config | null>(null);
|
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);
|
const [showTsfSuccessMessage, setShowTsfSuccessMessage] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -146,7 +146,7 @@ const AppContent = () => {
|
|||||||
<div className="space-y-1 flex-grow">
|
<div className="space-y-1 flex-grow">
|
||||||
<MenuItem icon={<List size={16} />} label="ログ" id="home" />
|
<MenuItem icon={<List size={16} />} label="ログ" id="home" />
|
||||||
<MenuItem icon={<Settings size={16} />} label="設定" id="settings" />
|
<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>
|
||||||
{/* 下側にデバッグタブを配置 */}
|
{/* 下側にデバッグタブを配置 */}
|
||||||
<div className="pt-2 border-t border-gray-100 dark:border-gray-700">
|
<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 React, { useState, useEffect, useRef } from 'react';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
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 {
|
import {
|
||||||
Dictionary,
|
Dictionary,
|
||||||
DictionaryEntry,
|
DictionaryEntry,
|
||||||
@ -12,16 +12,136 @@ import {
|
|||||||
convertFromRustEntry
|
convertFromRustEntry
|
||||||
} from './types/dictionary';
|
} 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 DictionaryComponent: React.FC = () => {
|
||||||
const [dictionary, setDictionary] = useState<Dictionary>({ entries: [] });
|
const [dictionary, setDictionary] = useState<Dictionary>({ entries: [] });
|
||||||
const [selectedEntry, setSelectedEntry] = useState<DictionaryEntry | null>(null);
|
|
||||||
const [editIndex, setEditIndex] = useState<number | null>(null);
|
const [editIndex, setEditIndex] = useState<number | null>(null);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
|
||||||
const [showDialog, setShowDialog] = useState(false);
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
const [isMethodDropdownOpen, setIsMethodDropdownOpen] = useState(false);
|
const [isMethodDropdownOpen, setIsMethodDropdownOpen] = useState(false);
|
||||||
const [isConverterDropdownOpen, setIsConverterDropdownOpen] = useState(false);
|
const [isConverterDropdownOpen, setIsConverterDropdownOpen] = useState(false);
|
||||||
const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'success' | 'error'>('idle');
|
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 methodDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
const converterDropdownRef = useRef<HTMLDivElement>(null);
|
const converterDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@ -86,17 +206,19 @@ const DictionaryComponent: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAddEntry = () => {
|
const handleAddEntry = () => {
|
||||||
setSelectedEntry(getDefaultDictionaryEntry());
|
entryRef.current = getDefaultDictionaryEntry();
|
||||||
setEditIndex(null);
|
setEditIndex(null);
|
||||||
setIsEditing(true);
|
|
||||||
setShowDialog(true);
|
setShowDialog(true);
|
||||||
|
// 強制的に再レンダリング
|
||||||
|
forceUpdate({});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditEntry = (entry: DictionaryEntry, index: number) => {
|
const handleEditEntry = (entry: DictionaryEntry, index: number) => {
|
||||||
setSelectedEntry({...entry});
|
entryRef.current = {...entry};
|
||||||
setEditIndex(index);
|
setEditIndex(index);
|
||||||
setIsEditing(true);
|
|
||||||
setShowDialog(true);
|
setShowDialog(true);
|
||||||
|
// 強制的に再レンダリング
|
||||||
|
forceUpdate({});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteEntry = (index: number) => {
|
const handleDeleteEntry = (index: number) => {
|
||||||
@ -108,34 +230,41 @@ const DictionaryComponent: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveEntry = () => {
|
const handleSaveEntry = () => {
|
||||||
if (!selectedEntry) return;
|
if (!entryRef.current) return;
|
||||||
|
|
||||||
const newEntries = [...dictionary.entries];
|
const newEntries = [...dictionary.entries];
|
||||||
|
|
||||||
// 新規追加の場合
|
// 新規追加の場合
|
||||||
if (editIndex === null) {
|
if (editIndex === null) {
|
||||||
newEntries.push(selectedEntry);
|
newEntries.push(entryRef.current);
|
||||||
} else {
|
} else {
|
||||||
// 編集の場合
|
// 編集の場合
|
||||||
newEntries[editIndex] = selectedEntry;
|
newEntries[editIndex] = entryRef.current;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newDict = { ...dictionary, entries: newEntries };
|
const newDict = { ...dictionary, entries: newEntries };
|
||||||
setDictionary(newDict);
|
setDictionary(newDict);
|
||||||
saveDictionary(newDict);
|
saveDictionary(newDict);
|
||||||
|
|
||||||
|
// ダイアログを閉じて状態をリセット
|
||||||
|
closeDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
setShowDialog(false);
|
setShowDialog(false);
|
||||||
setIsEditing(false);
|
setIsMethodDropdownOpen(false);
|
||||||
setSelectedEntry(null);
|
setIsConverterDropdownOpen(false);
|
||||||
|
entryRef.current = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangePriority = (index: number, direction: 'up' | 'down') => {
|
const handleChangePriority = (index: number, direction: 'up' | 'down') => {
|
||||||
if (dictionary.entries.length <= 1) return;
|
if (dictionary.entries.length <= 1) return;
|
||||||
|
|
||||||
const newEntries = [...dictionary.entries];
|
const newEntries = [...dictionary.entries];
|
||||||
const entry = newEntries[index];
|
const entry = {...newEntries[index]};
|
||||||
|
|
||||||
if (direction === 'up' && index > 0) {
|
if (direction === 'up' && index > 0) {
|
||||||
const prevEntry = newEntries[index - 1];
|
const prevEntry = {...newEntries[index - 1]};
|
||||||
const tempPriority = prevEntry.priority;
|
const tempPriority = prevEntry.priority;
|
||||||
prevEntry.priority = entry.priority;
|
prevEntry.priority = entry.priority;
|
||||||
entry.priority = tempPriority;
|
entry.priority = tempPriority;
|
||||||
@ -144,7 +273,7 @@ const DictionaryComponent: React.FC = () => {
|
|||||||
newEntries[index] = prevEntry;
|
newEntries[index] = prevEntry;
|
||||||
newEntries[index - 1] = entry;
|
newEntries[index - 1] = entry;
|
||||||
} else if (direction === 'down' && index < newEntries.length - 1) {
|
} else if (direction === 'down' && index < newEntries.length - 1) {
|
||||||
const nextEntry = newEntries[index + 1];
|
const nextEntry = {...newEntries[index + 1]};
|
||||||
const tempPriority = nextEntry.priority;
|
const tempPriority = nextEntry.priority;
|
||||||
nextEntry.priority = entry.priority;
|
nextEntry.priority = entry.priority;
|
||||||
entry.priority = tempPriority;
|
entry.priority = tempPriority;
|
||||||
@ -159,10 +288,15 @@ const DictionaryComponent: React.FC = () => {
|
|||||||
saveDictionary(newDict);
|
saveDictionary(newDict);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeEntryField = (field: keyof DictionaryEntry, value: any) => {
|
// 入力フィールドの値を更新(フォーカスを失わないようRef経由で更新)
|
||||||
if (!selectedEntry) return;
|
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に
|
// 変換方法がReplace以外の場合はoutputをnullに
|
||||||
if (field === 'method' && value !== ConversionMethod.Replace) {
|
if (field === 'method' && value !== ConversionMethod.Replace) {
|
||||||
@ -174,7 +308,13 @@ const DictionaryComponent: React.FC = () => {
|
|||||||
updatedEntry.converter_char = 'r';
|
updatedEntry.converter_char = 'r';
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedEntry(updatedEntry);
|
// 更新したEntryをRefに保存
|
||||||
|
entryRef.current = updatedEntry;
|
||||||
|
|
||||||
|
// UIを強制的に更新(ドロップダウンメニュー表示/非表示の対応のため)
|
||||||
|
if (field === 'method' || field === 'converter_char') {
|
||||||
|
forceUpdate({});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectMethod = (method: ConversionMethod) => {
|
const handleSelectMethod = (method: ConversionMethod) => {
|
||||||
@ -219,7 +359,9 @@ const DictionaryComponent: React.FC = () => {
|
|||||||
|
|
||||||
// 辞書エントリダイアログ
|
// 辞書エントリダイアログ
|
||||||
const EntryDialog = () => {
|
const EntryDialog = () => {
|
||||||
if (!showDialog || !selectedEntry) return null;
|
if (!showDialog || !entryRef.current) return null;
|
||||||
|
|
||||||
|
const entry = entryRef.current;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
|
<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 ? '辞書エントリの編集' : '新しい辞書エントリ'}
|
{editIndex !== null ? '辞書エントリの編集' : '新しい辞書エントリ'}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={closeDialog}
|
||||||
setShowDialog(false);
|
|
||||||
setIsEditing(false);
|
|
||||||
setSelectedEntry(null);
|
|
||||||
setIsMethodDropdownOpen(false);
|
|
||||||
setIsConverterDropdownOpen(false);
|
|
||||||
}}
|
|
||||||
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||||
>
|
>
|
||||||
<X size={20} />
|
<X size={20} />
|
||||||
@ -243,41 +379,29 @@ const DictionaryComponent: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<InputField
|
||||||
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
|
label="変換対象文字列"
|
||||||
変換対象文字列
|
value={entry.input}
|
||||||
</label>
|
placeholder="例: こんにちは"
|
||||||
<input
|
onChange={(value) => handleChangeEntryField('input', value)}
|
||||||
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>
|
|
||||||
|
|
||||||
<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 className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||||
変換方法
|
変換方法
|
||||||
</label>
|
</label>
|
||||||
<div
|
<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"
|
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)}
|
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' : ''}`} />
|
<ChevronDown size={14} className={`transition-transform ${isMethodDropdownOpen ? 'transform rotate-180' : ''}`} />
|
||||||
</div>
|
</div>
|
||||||
{isMethodDropdownOpen && (
|
{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"
|
className="p-1.5 hover:bg-indigo-50 dark:hover:bg-indigo-900/50 cursor-pointer"
|
||||||
onClick={() => handleSelectMethod(ConversionMethod.Replace)}
|
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'}`}>
|
<div className={`flex items-center ${entry.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" />}
|
{entry.method === ConversionMethod.Replace && <Check size={12} className="mr-1.5" />}
|
||||||
<span className={selectedEntry.method === ConversionMethod.Replace ? 'ml-0' : 'ml-4'}>
|
<span className={entry.method === ConversionMethod.Replace ? 'ml-0' : 'ml-4'}>
|
||||||
置き換え
|
置き換え
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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"
|
className="p-1.5 hover:bg-indigo-50 dark:hover:bg-indigo-900/50 cursor-pointer"
|
||||||
onClick={() => handleSelectMethod(ConversionMethod.None)}
|
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'}`}>
|
<div className={`flex items-center ${entry.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" />}
|
{entry.method === ConversionMethod.None && <Check size={12} className="mr-1.5" />}
|
||||||
<span className={selectedEntry.method === ConversionMethod.None ? 'ml-0' : 'ml-4'}>
|
<span className={entry.method === ConversionMethod.None ? 'ml-0' : 'ml-4'}>
|
||||||
無変換
|
無変換
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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"
|
className="p-1.5 hover:bg-indigo-50 dark:hover:bg-indigo-900/50 cursor-pointer"
|
||||||
onClick={() => handleSelectMethod(ConversionMethod.Converter)}
|
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'}`}>
|
<div className={`flex items-center ${entry.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" />}
|
{entry.method === ConversionMethod.Converter && <Check size={12} className="mr-1.5" />}
|
||||||
<span className={selectedEntry.method === ConversionMethod.Converter ? 'ml-0' : 'ml-4'}>
|
<span className={entry.method === ConversionMethod.Converter ? 'ml-0' : 'ml-4'}>
|
||||||
変換
|
変換
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -319,19 +443,18 @@ const DictionaryComponent: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedEntry.method === ConversionMethod.Converter && (
|
{entry.method === ConversionMethod.Converter && (
|
||||||
<div className="relative mb-3">
|
<div className="relative mb-3" ref={converterDropdownRef}>
|
||||||
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
|
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||||
変換器
|
変換器
|
||||||
</label>
|
</label>
|
||||||
<div
|
<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"
|
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)}
|
onClick={() => setIsConverterDropdownOpen(!isConverterDropdownOpen)}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
{selectedEntry.converter_char
|
{entry.converter_char
|
||||||
? getConverterInfo(selectedEntry.converter_char)?.name || 'ローマ字→漢字'
|
? getConverterInfo(entry.converter_char)?.name || 'ローマ字→漢字'
|
||||||
: 'ローマ字→漢字'}
|
: 'ローマ字→漢字'}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown size={14} className={`transition-transform ${isConverterDropdownOpen ? 'transform rotate-180' : ''}`} />
|
<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"
|
className="p-1.5 hover:bg-indigo-50 dark:hover:bg-indigo-900/50 cursor-pointer"
|
||||||
onClick={() => handleSelectConverter(converter.id)}
|
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'}`}>
|
<div className={`flex items-center ${entry.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" />}
|
{entry.converter_char === converter.id && <Check size={12} className="mr-1.5" />}
|
||||||
<span className={selectedEntry.converter_char === converter.id ? 'ml-0' : 'ml-4'}>
|
<span className={entry.converter_char === converter.id ? 'ml-0' : 'ml-4'}>
|
||||||
{converter.name} - {converter.description}
|
{converter.name} - {converter.description}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -357,47 +480,26 @@ const DictionaryComponent: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedEntry.method === ConversionMethod.Replace && (
|
{entry.method === ConversionMethod.Replace && (
|
||||||
<div>
|
<InputField
|
||||||
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
|
label="置き換え後の文字列"
|
||||||
置き換え後の文字列
|
value={entry.output || ''}
|
||||||
</label>
|
placeholder="例: Hello"
|
||||||
<input
|
onChange={(value) => handleChangeEntryField('output', value)}
|
||||||
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>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<NumberField
|
||||||
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
|
label="優先順位"
|
||||||
優先順位
|
value={entry.priority}
|
||||||
</label>
|
onChange={(value) => handleChangeEntryField('priority', value)}
|
||||||
<input
|
description="数値が大きいほど優先度が高くなります。"
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 flex justify-end">
|
<div className="mt-6 flex justify-end">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={closeDialog}
|
||||||
setShowDialog(false);
|
|
||||||
setIsEditing(false);
|
|
||||||
setSelectedEntry(null);
|
|
||||||
setIsMethodDropdownOpen(false);
|
|
||||||
setIsConverterDropdownOpen(false);
|
|
||||||
}}
|
|
||||||
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"
|
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 (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<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">
|
<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" />
|
<Book size={16} className="mr-1.5" />
|
||||||
辞書
|
辞書
|
||||||
@ -433,6 +536,7 @@ const DictionaryComponent: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="bg-white dark:bg-gray-800 rounded border border-gray-100 dark:border-gray-700 p-3 transition-colors">
|
<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 ? (
|
{dictionary.entries.length > 0 ? (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
|
@ -334,7 +334,7 @@ const SettingsComponent: React.FC<SettingsComponentProps> = ({
|
|||||||
name="use_tsf_reconvert"
|
name="use_tsf_reconvert"
|
||||||
label={
|
label={
|
||||||
<span>
|
<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>
|
</span>
|
||||||
}
|
}
|
||||||
checked={settings.use_tsf_reconvert}
|
checked={settings.use_tsf_reconvert}
|
||||||
|
@ -79,7 +79,7 @@ const TitleBar = () => {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center" data-tauri-drag-region>
|
<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 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>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<TitleButton
|
<TitleButton
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { open } from '@tauri-apps/plugin-shell';
|
import { AlertCircle, Settings, Check, X } from 'lucide-react';
|
||||||
import { AlertCircle, Settings, Check, X, ExternalLink } from 'lucide-react';
|
|
||||||
import { Config } from './SettingsComponent';
|
import { Config } from './SettingsComponent';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
|
||||||
@ -19,7 +18,7 @@ const TsfSettingsModal: React.FC<TsfSettingsModalProps> = ({
|
|||||||
currentSettings,
|
currentSettings,
|
||||||
onTsfEnabled
|
onTsfEnabled
|
||||||
}) => {
|
}) => {
|
||||||
const [checkingStatus, setCheckingStatus] = useState<'idle' | 'checking'>('idle');
|
const [_checkingStatus, _setCheckingStatus] = useState<'idle' | 'checking'>('idle');
|
||||||
|
|
||||||
// TSFが利用可能かどうかを定期的にチェックする
|
// TSFが利用可能かどうかを定期的にチェックする
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
Reference in New Issue
Block a user