add tsf availability check

This commit is contained in:
mii
2025-03-05 02:52:00 +09:00
parent 723445786e
commit 89b688176b
7 changed files with 244 additions and 10 deletions

BIN
public/windows_settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -11,6 +11,7 @@ mod transform_rule;
mod tsf;
mod tsf_conversion;
mod tauri_emit_subscriber;
mod tsf_availability;
use std::sync::Mutex;
@ -24,6 +25,8 @@ use config::Config;
use handler::ConversionHandler;
use tauri_emit_subscriber::TauriEmitSubscriber;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use tsf_availability::check_tsf_availability;
use tracing::debug;
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Log {
@ -56,6 +59,27 @@ fn save_settings(config: Config, state: State<AppState>) -> Result<(), String> {
config.save(state)
}
#[tauri::command]
fn check_tsf_availability_command() -> Result<bool, String> {
debug!("Checking TSF availability");
match check_tsf_availability() {
Ok(result) => {
debug!("TSF availability check result: {}", result);
Ok(result)
},
Err(e) => Err(format!("Failed to check TSF availability: {}", e)),
}
}
#[tauri::command]
fn open_ms_settings_regionlanguage_jpnime() -> Result<(), String> {
let _ = std::process::Command::new("cmd")
.args(&["/C", "start", "ms-settings:regionlanguage-jpnime"])
.output()
.map_err(|e| format!("Failed to open MS Settings: {}", e))?;
Ok(())
}
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
@ -66,7 +90,7 @@ fn main() {
Config::load().expect("Failed to load default config")
})),
})
.invoke_handler(tauri::generate_handler![load_settings, save_settings])
.invoke_handler(tauri::generate_handler![load_settings, save_settings, check_tsf_availability_command, open_ms_settings_regionlanguage_jpnime])
.setup(|app| {
let _span = tracing::span!(tracing::Level::INFO, "main");
app.manage(STATE.lock().unwrap().clone());

View File

@ -52,6 +52,7 @@ where
timestamp: format!("{}-{}-{} {}:{}:{}", now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second()),
};
println!("[{} {}] {} {}", event.timestamp, event.level, event.module_path, event.message);
if self
.app_handle
.emit(

View File

@ -0,0 +1,24 @@
use anyhow::Result;
use tracing::{error, info};
use crate::tsf::{search_candidate_provider::SearchCandidateProvider, set_thread_local_input_settings};
pub fn check_tsf_availability() -> Result<bool> {
info!("Checking TSF availability");
if let Err(e) = set_thread_local_input_settings(true) {
error!("Failed to set thread local input settings: {:?}", e);
return Ok(false);
}
match SearchCandidateProvider::create() {
Ok(_) => {
info!("TSF is available");
Ok(true)
}
Err(e) => {
error!("Failed to create SearchCandidateProvider: {:?}", e);
Ok(false)
}
}
}

View File

@ -1,6 +1,7 @@
import { useState, useEffect } from "react";
import { List, Settings, Terminal, Bug, Info } from 'lucide-react';
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import TitleBar from "./TitleBar";
import SettingsComponent from "./SettingsComponent";
@ -8,6 +9,8 @@ import { ThemeProvider } from "./ThemeContext";
import { LogProvider } from "./LogContext";
import TerminalComponent from "./TerminalComponent";
import AboutComponent from "./AboutComponent";
import TsfSettingsModal from "./TsfSettingsModal";
import { Config } from "./SettingsComponent";
interface Log {
time: string;
@ -18,6 +21,9 @@ interface Log {
const AppContent = () => {
const [logs, setLogs] = useState<Log[]>([]);
const [activeMenuItem, setActiveMenuItem] = useState('home');
const [showTsfModal, setShowTsfModal] = useState(false);
const [currentSettings, setCurrentSettings] = useState<Config | null>(null);
const [isTsfAvailable, setIsTsfAvailable] = useState<boolean | null>(null);
useEffect(() => {
const unlisten = listen<Log>('addLog', (event) => {
@ -29,6 +35,40 @@ const AppContent = () => {
}
}, []);
// TSF設定のチェック
useEffect(() => {
const checkTsfSettings = async () => {
try {
const settings: Config = await invoke('load_settings');
setCurrentSettings(settings);
if (settings.use_tsf_reconvert) {
// TSF設定が有効な場合、TSFが利用可能か確認
const available: boolean = await invoke('check_tsf_availability_command');
setIsTsfAvailable(available);
if (!available) {
setShowTsfModal(true);
}
}
} catch (error) {
console.error('TSF設定確認エラー:', error);
}
};
checkTsfSettings();
}, []);
// 設定保存関数
const saveSettings = async (config: Config) => {
try {
await invoke('save_settings', { config });
setCurrentSettings(config);
} catch (error) {
console.error('設定保存エラー:', error);
}
};
const renderLogEntry = (log: { time: string; original: string; converted: string }, index: number) => (
<div key={index} className="mb-2 p-2 bg-white/90 dark:bg-gray-800 rounded border border-gray-100 dark:border-gray-700 text-sm transition-colors">
<div className="text-xs text-gray-500 dark:text-gray-400 mb-1">{log.time}</div>
@ -79,7 +119,7 @@ const AppContent = () => {
</div>
);
case 'settings':
return <SettingsComponent />;
return <SettingsComponent setShowTsfModal={setShowTsfModal} />;
case 'terminal':
return <TerminalComponent />;
case 'about':
@ -111,6 +151,16 @@ const AppContent = () => {
{renderContent()}
</div>
</div>
{/* TSF設定モーダル */}
{currentSettings && (
<TsfSettingsModal
isOpen={showTsfModal}
onClose={() => setShowTsfModal(false)}
onSaveSettings={saveSettings}
currentSettings={currentSettings}
/>
)}
</div>
);
};

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { ChevronDown, Settings, Save, AlertCircle, Check } from 'lucide-react';
interface Config {
export interface Config {
prefix: string;
split: string;
command: string;
@ -11,9 +11,10 @@ interface Config {
skip_url: boolean;
use_tsf_reconvert: boolean;
skip_on_out_of_vrc: boolean;
tsf_announce?: boolean;
}
enum OnCopyMode {
export enum OnCopyMode {
ReturnToClipboard = 'ReturnToClipboard',
ReturnToChatbox = 'ReturnToChatbox',
SendDirectly = 'SendDirectly'
@ -57,9 +58,10 @@ interface CheckboxFieldProps {
label: React.ReactNode;
checked: boolean;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
disabled?: boolean;
}
const CheckboxField: React.FC<CheckboxFieldProps> = ({ id, name, label, checked, onChange }) => (
const CheckboxField: React.FC<CheckboxFieldProps> = ({ id, name, label, checked, onChange, disabled }) => (
<div className="mb-2">
<div className="flex items-start">
<div className="flex items-center h-4">
@ -69,11 +71,16 @@ const CheckboxField: React.FC<CheckboxFieldProps> = ({ id, name, label, checked,
name={name}
checked={checked}
onChange={onChange}
className="h-3.5 w-3.5 text-indigo-500 dark:text-indigo-400 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700 transition-colors"
disabled={disabled}
className={`h-3.5 w-3.5 text-indigo-500 dark:text-indigo-400 border-gray-300 dark:border-gray-600 rounded dark:bg-gray-700 transition-colors ${
disabled ? 'opacity-50 cursor-not-allowed' : ''
}`}
/>
</div>
<div className="ml-2 text-xs">
<label htmlFor={id} className="text-gray-700 dark:text-gray-300 transition-colors">
<label htmlFor={id} className={`text-gray-700 dark:text-gray-300 transition-colors ${
disabled ? 'opacity-50 cursor-not-allowed' : ''
}`}>
{label}
</label>
</div>
@ -81,7 +88,11 @@ const CheckboxField: React.FC<CheckboxFieldProps> = ({ id, name, label, checked,
</div>
);
const SettingsComponent: React.FC = () => {
interface SettingsComponentProps {
setShowTsfModal: (show: boolean) => void;
}
const SettingsComponent: React.FC<SettingsComponentProps> = ({ setShowTsfModal }) => {
const [settings, setSettings] = useState<Config>({
prefix: ';',
split: '/',
@ -136,6 +147,30 @@ const SettingsComponent: React.FC = () => {
saveSettings(newSettings);
};
const handleTsfChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const isChecked = e.target.checked;
if (isChecked) {
try {
const available: boolean = await invoke('check_tsf_availability_command');
if (!available && setShowTsfModal) {
setShowTsfModal(true);
return;
}
} catch (error) {
return;
}
}
const { name, value, type } = e.target;
const newSettings = {
...settings,
[name]: type === 'checkbox' ? isChecked : value
};
setSettings(newSettings);
saveSettings(newSettings);
};
const handleSelectChange = (value: OnCopyMode) => {
const newSettings = { ...settings, on_copy_mode: value };
setSettings(newSettings);
@ -193,6 +228,7 @@ const SettingsComponent: React.FC = () => {
label="区切り文字"
value={settings.split}
onChange={handleChange}
disabled={settings.use_tsf_reconvert}
description="複数の変換モードを使いたい場合の区切り文字"
/>
@ -201,6 +237,7 @@ const SettingsComponent: React.FC = () => {
label="モード変更文字"
value={settings.command}
onChange={handleChange}
disabled={settings.use_tsf_reconvert}
description="変換モードを変更するための文字"
/>
@ -210,6 +247,7 @@ const SettingsComponent: React.FC = () => {
label="無条件で変換"
checked={settings.ignore_prefix}
onChange={handleChange}
disabled={settings.use_tsf_reconvert}
/>
<InputField
@ -217,7 +255,7 @@ const SettingsComponent: React.FC = () => {
label="開始文字"
value={settings.prefix}
onChange={handleChange}
disabled={settings.ignore_prefix}
disabled={settings.ignore_prefix || settings.use_tsf_reconvert}
description="変換を開始する文字(無条件で変換がオンの場合は無効)"
/>
@ -282,7 +320,7 @@ const SettingsComponent: React.FC = () => {
</span>
}
checked={settings.use_tsf_reconvert}
onChange={handleChange}
onChange={handleTsfChange}
/>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-0.5 ml-5 transition-colors">
Windows10/11 Microsoft IME 使

97
src/TsfSettingsModal.tsx Normal file
View File

@ -0,0 +1,97 @@
import React from 'react';
import { open } from '@tauri-apps/plugin-shell';
import { AlertCircle, Settings, Check, X, ExternalLink } from 'lucide-react';
import { Config } from './SettingsComponent';
import { invoke } from '@tauri-apps/api/core';
interface TsfSettingsModalProps {
isOpen: boolean;
onClose: () => void;
onSaveSettings: (config: Config) => Promise<void>;
currentSettings: Config;
}
const TsfSettingsModal: React.FC<TsfSettingsModalProps> = ({
isOpen,
onClose,
onSaveSettings,
currentSettings
}) => {
if (!isOpen) return null;
const openWindowsSettings = async () => {
invoke('open_ms_settings_regionlanguage_jpnime');
};
const useLegacyConverter = async () => {
try {
const newSettings = { ...currentSettings, use_tsf_reconvert: false };
await onSaveSettings(newSettings);
onClose();
} catch (error) {
console.error('設定保存に失敗しました:', error);
}
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-2xl w-full max-h-[90vh] overflow-y-auto transition-colors">
<div className="flex justify-between items-start mb-4">
<h2 className="text-lg font-medium text-gray-800 dark:text-gray-200 flex items-center">
<AlertCircle size={18} className="mr-2 text-yellow-500" />
Windowsの設定が必要です
</h2>
<button onClick={onClose} className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<X size={20} />
</button>
</div>
<div className="flex flex-col sm:flex-row gap-3 justify-start mb-4">
<button
onClick={openWindowsSettings}
className="flex items-center justify-center bg-indigo-500 hover:bg-indigo-600 text-white px-4 py-2 rounded"
>
<Settings size={16} className="mr-2" />
Windows設定を開く
</button>
</div>
<div className="text-gray-700 dark:text-gray-300 mb-4">
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded mb-4">
<h3 className="text-sm font-medium mb-2 flex items-center">
<Settings size={14} className="mr-1.5" />
</h3>
<ol className="list-decimal list-inside text-sm space-y-1.5 ml-1">
<li>Windows設定を開く</li>
<li></li>
<li> Microsoft IME 使</li>
</ol>
</div>
</div>
<div className="bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg mb-6 overflow-hidden">
<div className="p-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-600 text-sm font-medium text-gray-700 dark:text-gray-300">
Windows設定画面
</div>
<div className="p-4 flex justify-center">
<img src="./windows_settings.png"/>
</div>
</div>
<div className="flex flex-col sm:flex-row gap-3 justify-end">
<button
onClick={useLegacyConverter}
className="flex items-center justify-center bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 px-4 py-2 rounded"
>
<Check size={16} className="mr-2" />
使
<span className="text-xs text-red-500 ml-1.5">()</span>
</button>
</div>
</div>
</div>
);
};
export default TsfSettingsModal;