From e27baf6099078337f381c36207a15525db0660fd Mon Sep 17 00:00:00 2001 From: sakastudio Date: Thu, 26 Jun 2025 18:43:52 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E3=83=8F=E3=83=BC=E3=83=89=E3=82=B3?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=81=A0=E3=81=8C=E3=81=A8=E3=82=8A=E3=81=82?= =?UTF-8?q?=E3=81=88=E3=81=9A=E4=BF=AE=E6=AD=A3=E3=81=A7=E3=81=8D=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src-tauri/src/main.rs | 23 +++++++++++++++++++++++ frontend/src/i18n/translationLoader.ts | 22 ++++++++++++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index f5c5be2..b9e76c9 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -1,8 +1,31 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use tauri::Manager; + +// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command +#[tauri::command] +fn get_commands_path(app_handle: tauri::AppHandle) -> Result { + // ここでcommands.yamlのパスを決定するロジックを実装します。 + // 例: アプリケーションのデータディレクトリから取得、または設定ファイルから読み込む + // 今回は、ユーザーが指定したパスを直接返すようにします。 + // 実際には、プロジェクトのロード/保存時にパスを保持する仕組みが必要です。 + // ここでは仮のパスを返します。 + // TODO: 実際のプロジェクトパスを動的に取得するロジックに置き換える必要があります。 + let commands_path = "/Users/katsumi.sato/moorestech/moorestech_client/Assets/AddressableResources/Skit/commands.yaml"; + Ok(commands_path.to_string()) +} + fn main() { tauri::Builder::default() + .setup(|app| { + #[cfg(debug_assertions)] // only in development + { + app.get_window("main").unwrap().open_devtools(); + } + Ok(()) + }) + .invoke_handler(tauri::generate_handler![get_commands_path]) // ここでコマンドを登録 .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/frontend/src/i18n/translationLoader.ts b/frontend/src/i18n/translationLoader.ts index 399f5e4..4da32ee 100644 --- a/frontend/src/i18n/translationLoader.ts +++ b/frontend/src/i18n/translationLoader.ts @@ -45,15 +45,25 @@ const loadDevelopmentTranslations = async (): Promise => { // Load translations from i18n folder export async function loadTranslations(): Promise { + console.log('loadTranslations called'); + console.log('window.__TAURI__:', window.__TAURI__); + console.log('import.meta.env.MODE:', import.meta.env.MODE); + // Check if we're in Tauri environment - if (!window.__TAURI__ || import.meta.env.MODE === 'development') { + if (!window.__TAURI__) { + console.log('Loading development translations (Tauri not available)'); await loadDevelopmentTranslations(); return; } try { // Get the commands.yaml path from store or use default - const commandsPath = await invoke('get_commands_path').catch(() => null); + const commandsPath = await invoke('get_commands_path').catch((e) => { + console.error('Error invoking get_commands_path:', e); + return null; + }); + console.log('commandsPath from get_commands_path:', commandsPath); + if (!commandsPath) { console.warn('Commands path not found, using development translations'); await loadDevelopmentTranslations(); @@ -61,9 +71,13 @@ export async function loadTranslations(): Promise { } const i18nPath = await join(await dirname(commandsPath), 'i18n'); + console.log('Calculated i18nPath:', i18nPath); // Check if i18n directory exists - if (!(await exists(i18nPath))) { + const i18nDirExists = await exists(i18nPath); + console.log('i18n directory exists:', i18nDirExists); + + if (!i18nDirExists) { console.warn('i18n directory not found, using development translations'); await loadDevelopmentTranslations(); return; @@ -94,7 +108,7 @@ export async function loadTranslations(): Promise { } } } catch (error) { - console.error('Failed to load translations:', error); + console.error('Failed to load translations in Tauri mode:', error); await loadDevelopmentTranslations(); } } From 39f9e02382ea36140b85e51a2ca8bfc8ff8a26f8 Mon Sep 17 00:00:00 2001 From: sakastudio Date: Thu, 26 Jun 2025 20:08:44 +0900 Subject: [PATCH 2/3] Fix project path management to sync between frontend and Tauri backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add proper state management in Tauri backend to store current project path - Update frontend store to sync project path with Tauri when changed - Fix translation loader to use project path from store instead of hardcoded path - Add type declarations for window.__TAURI__ to avoid TypeScript errors - Update tests to reflect new implementation This ensures consistent project path handling across the application and fixes the issue where translations were loaded from a hardcoded path instead of the actual project directory. 🤖 Generated with Claude Code Co-Authored-By: Claude --- .claude/settings.local.json | 3 +- frontend/src-tauri/src/main.rs | 54 ++++++++++++++---- frontend/src/App.tsx | 9 ++- frontend/src/i18n/translationLoader.test.ts | 63 +++++++++++++++------ frontend/src/i18n/translationLoader.ts | 20 +++---- frontend/src/store/skitStore.ts | 20 +++++-- frontend/src/types/window.d.ts | 5 ++ 7 files changed, 125 insertions(+), 49 deletions(-) create mode 100644 frontend/src/types/window.d.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 15db90f..1b5aad1 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -21,7 +21,8 @@ "Bash(npx vitest run:*)", "Bash(npx c8 report:*)", "Bash(rg:*)", - "Bash(npx tsc:*)" + "Bash(npx tsc:*)", + "Bash(git add:*)" ] }, "enableAllProjectMcpServers": false diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index b9e76c9..41cba42 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -1,19 +1,44 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use tauri::Manager; +use tauri::{Manager, State}; +use std::sync::Mutex; +use std::path::PathBuf; + +// State to hold the current project path +struct ProjectState { + path: Mutex>, +} // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command #[tauri::command] -fn get_commands_path(app_handle: tauri::AppHandle) -> Result { - // ここでcommands.yamlのパスを決定するロジックを実装します。 - // 例: アプリケーションのデータディレクトリから取得、または設定ファイルから読み込む - // 今回は、ユーザーが指定したパスを直接返すようにします。 - // 実際には、プロジェクトのロード/保存時にパスを保持する仕組みが必要です。 - // ここでは仮のパスを返します。 - // TODO: 実際のプロジェクトパスを動的に取得するロジックに置き換える必要があります。 - let commands_path = "/Users/katsumi.sato/moorestech/moorestech_client/Assets/AddressableResources/Skit/commands.yaml"; - Ok(commands_path.to_string()) +fn get_commands_path(state: State) -> Result { + let project_path = state.path.lock().unwrap(); + + match &*project_path { + Some(path) => { + // Return the commands.yaml path within the project directory + let commands_path = PathBuf::from(path).join("commands.yaml"); + Ok(commands_path.to_string_lossy().to_string()) + }, + None => { + // No project path set, return error + Err("No project path set".to_string()) + } + } +} + +#[tauri::command] +fn set_project_path(state: State, path: String) -> Result<(), String> { + let mut project_path = state.path.lock().unwrap(); + *project_path = Some(path); + Ok(()) +} + +#[tauri::command] +fn get_project_path(state: State) -> Result, String> { + let project_path = state.path.lock().unwrap(); + Ok(project_path.clone()) } fn main() { @@ -25,7 +50,14 @@ fn main() { } Ok(()) }) - .invoke_handler(tauri::generate_handler![get_commands_path]) // ここでコマンドを登録 + .manage(ProjectState { + path: Mutex::new(None), + }) + .invoke_handler(tauri::generate_handler![ + get_commands_path, + set_project_path, + get_project_path + ]) // Register all commands .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 846138a..c443c55 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -26,12 +26,17 @@ import { I18nextProvider } from 'react-i18next'; import i18n from './i18n/config'; function App() { - const { loadCommandsYaml, loadSkits, projectPath } = useSkitStore(); + const { loadCommandsYaml, loadSkits, projectPath, setProjectPath } = useSkitStore(); useKeyboardShortcuts(); useEffect(() => { const loadInitialData = async () => { try { + // Sync project path with Tauri on startup if it exists + if (projectPath && window.__TAURI__) { + await setProjectPath(projectPath); + } + const commandsYamlContent = await loadCommandsYamlFile(projectPath); loadCommandsYaml(commandsYamlContent); @@ -60,7 +65,7 @@ function App() { }; loadInitialData(); - }, [loadCommandsYaml, loadSkits, projectPath]); + }, [loadCommandsYaml, loadSkits, projectPath, setProjectPath]); return ( diff --git a/frontend/src/i18n/translationLoader.test.ts b/frontend/src/i18n/translationLoader.test.ts index 46f0ca9..201821b 100644 --- a/frontend/src/i18n/translationLoader.test.ts +++ b/frontend/src/i18n/translationLoader.test.ts @@ -36,6 +36,15 @@ vi.mock('@tauri-apps/api/path', () => ({ dirname: vi.fn(), })); +// Mock the store +vi.mock('../store/skitStore', () => ({ + useSkitStore: { + getState: vi.fn(() => ({ + projectPath: null, + })), + }, +})); + // Mock fetch for development mode global.fetch = vi.fn(); @@ -97,8 +106,12 @@ describe('translationLoader', () => { (window as any).__TAURI__ = true; (import.meta as any).env = { MODE: 'production' }; - vi.mocked(tauriApi.invoke).mockResolvedValue('/path/to/commands.yaml'); - vi.mocked(tauriPath.dirname).mockResolvedValue('/path/to'); + // Mock store to return a project path + const { useSkitStore } = await import('../store/skitStore'); + vi.mocked(useSkitStore.getState).mockReturnValue({ + projectPath: '/path/to/project', + } as any); + vi.mocked(tauriPath.join).mockImplementation(async (...args) => args.join('/')); vi.mocked(tauriFs.exists).mockResolvedValue(true); vi.mocked(tauriFs.readTextFile).mockResolvedValue(JSON.stringify({ @@ -109,15 +122,19 @@ describe('translationLoader', () => { await loadTranslations(); - expect(tauriApi.invoke).toHaveBeenCalledWith('get_commands_path'); - expect(tauriFs.exists).toHaveBeenCalledWith('/path/to/i18n'); + expect(tauriFs.exists).toHaveBeenCalledWith('/path/to/project/i18n'); expect(tauriFs.readTextFile).toHaveBeenCalled(); expect(i18n.addResourceBundle).toHaveBeenCalled(); }); - it('should fallback to development mode when commands path not found', async () => { + it('should fallback to development mode when project path not found', async () => { (window as any).__TAURI__ = true; - vi.mocked(tauriApi.invoke).mockRejectedValue(new Error('Not found')); + + // Mock store to return no project path + const { useSkitStore } = await import('../store/skitStore'); + vi.mocked(useSkitStore.getState).mockReturnValue({ + projectPath: null, + } as any); const mockFetch = vi.mocked(global.fetch); mockFetch.mockResolvedValue({ @@ -128,7 +145,7 @@ describe('translationLoader', () => { await loadTranslations(); - expect(consoleWarnSpy).toHaveBeenCalledWith('Commands path not found, using development translations'); + expect(consoleWarnSpy).toHaveBeenCalledWith('No project path set, using development translations'); consoleWarnSpy.mockRestore(); }); @@ -137,8 +154,12 @@ describe('translationLoader', () => { (window as any).__TAURI__ = true; (import.meta as any).env = { MODE: 'production' }; - vi.mocked(tauriApi.invoke).mockResolvedValue('/path/to/commands.yaml'); - vi.mocked(tauriPath.dirname).mockResolvedValue('/path/to'); + // Mock store to return a project path + const { useSkitStore } = await import('../store/skitStore'); + vi.mocked(useSkitStore.getState).mockReturnValue({ + projectPath: '/path/to/project', + } as any); + vi.mocked(tauriPath.join).mockImplementation(async (...args) => args.join('/')); vi.mocked(tauriFs.exists).mockResolvedValue(false); @@ -155,8 +176,12 @@ describe('translationLoader', () => { (window as any).__TAURI__ = true; (import.meta as any).env = { MODE: 'production' }; - vi.mocked(tauriApi.invoke).mockResolvedValue('/path/to/commands.yaml'); - vi.mocked(tauriPath.dirname).mockResolvedValue('/path/to'); + // Mock store to return a project path + const { useSkitStore } = await import('../store/skitStore'); + vi.mocked(useSkitStore.getState).mockReturnValue({ + projectPath: '/path/to/project', + } as any); + vi.mocked(tauriPath.join).mockImplementation(async (...args) => args.join('/')); vi.mocked(tauriFs.exists).mockResolvedValue(true); vi.mocked(tauriFs.readTextFile).mockResolvedValue('invalid json'); @@ -174,8 +199,12 @@ describe('translationLoader', () => { (window as any).__TAURI__ = true; (import.meta as any).env = { MODE: 'production' }; - vi.mocked(tauriApi.invoke).mockResolvedValue('/path/to/commands.yaml'); - vi.mocked(tauriPath.dirname).mockResolvedValue('/path/to'); + // Mock store to return a project path + const { useSkitStore } = await import('../store/skitStore'); + vi.mocked(useSkitStore.getState).mockReturnValue({ + projectPath: '/path/to/project', + } as any); + vi.mocked(tauriPath.join).mockImplementation(async (...args) => args.join('/')); // Only i18n directory exists, but no language files @@ -384,9 +413,9 @@ describe('translationLoader', () => { const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - // Make invoke throw an error - vi.mocked(tauriApi.invoke).mockImplementation(() => { - throw new Error('Tauri error'); + // Make the store import throw an error + vi.doMock('../store/skitStore', () => { + throw new Error('Store import error'); }); const mockFetch = vi.mocked(global.fetch); @@ -397,7 +426,7 @@ describe('translationLoader', () => { await loadTranslations(); - expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to load translations:', expect.any(Error)); + expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to load translations in Tauri mode:', expect.any(Error)); // Should fallback to development translations expect(mockFetch).toHaveBeenCalled(); diff --git a/frontend/src/i18n/translationLoader.ts b/frontend/src/i18n/translationLoader.ts index 4da32ee..c4ff569 100644 --- a/frontend/src/i18n/translationLoader.ts +++ b/frontend/src/i18n/translationLoader.ts @@ -1,7 +1,6 @@ import i18n from 'i18next'; -import { invoke } from '@tauri-apps/api'; import { exists, readTextFile } from '@tauri-apps/api/fs'; -import { join, dirname } from '@tauri-apps/api/path'; +import { join } from '@tauri-apps/api/path'; interface TranslationFile { locale: string; @@ -57,20 +56,17 @@ export async function loadTranslations(): Promise { } try { - // Get the commands.yaml path from store or use default - const commandsPath = await invoke('get_commands_path').catch((e) => { - console.error('Error invoking get_commands_path:', e); - return null; - }); - console.log('commandsPath from get_commands_path:', commandsPath); - - if (!commandsPath) { - console.warn('Commands path not found, using development translations'); + // Get the project path from the store + const { useSkitStore } = await import('../store/skitStore'); + const projectPath = useSkitStore.getState().projectPath; + + if (!projectPath) { + console.warn('No project path set, using development translations'); await loadDevelopmentTranslations(); return; } - const i18nPath = await join(await dirname(commandsPath), 'i18n'); + const i18nPath = await join(projectPath, 'i18n'); console.log('Calculated i18nPath:', i18nPath); // Check if i18n directory exists diff --git a/frontend/src/store/skitStore.ts b/frontend/src/store/skitStore.ts index a82b78a..2af38ec 100644 --- a/frontend/src/store/skitStore.ts +++ b/frontend/src/store/skitStore.ts @@ -39,7 +39,7 @@ interface SkitState { redo: () => void; setValidationErrors: (errors: string[]) => void; loadCommandsYaml: (yaml: string) => void; - setProjectPath: (path: string | null) => void; // Add project path setter + setProjectPath: (path: string | null) => Promise; // Add project path setter createGroup: () => void; ungroupCommands: (groupStartId: number) => void; toggleGroupCollapse: (groupStartId: number) => void; @@ -594,10 +594,20 @@ export const useSkitStore = create()( }); }, - setProjectPath: (path) => { + setProjectPath: async (path) => { set((state) => { state.projectPath = path; }); + + // Sync with Tauri backend if available + if (window.__TAURI__ && path) { + try { + const { invoke } = await import('@tauri-apps/api'); + await invoke('set_project_path', { path }); + } catch (error) { + console.error('Failed to sync project path with Tauri:', error); + } + } }, createGroup: () => { @@ -829,10 +839,8 @@ export const useSkitStore = create()( const selectedPath = await selectProjectFolder(); if (selectedPath) { - // Update project path - set((state) => { - state.projectPath = selectedPath; - }); + // Update project path and sync with Tauri + await get().setProjectPath(selectedPath); // Load commands.yaml from the new path try { diff --git a/frontend/src/types/window.d.ts b/frontend/src/types/window.d.ts new file mode 100644 index 0000000..8208a0b --- /dev/null +++ b/frontend/src/types/window.d.ts @@ -0,0 +1,5 @@ +interface Window { + __TAURI__?: { + [key: string]: unknown; + }; +} \ No newline at end of file From 2a79444e5740b6f3553cd59b94f2e9a81ceb0ec3 Mon Sep 17 00:00:00 2001 From: sakastudio Date: Thu, 26 Jun 2025 20:19:43 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E3=83=AD=E3=82=B0=E5=89=8A=E9=99=A4?= =?UTF-8?q?=E3=81=AA=E3=81=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/i18n/translationLoader.test.ts | 1 - frontend/src/i18n/translationLoader.ts | 7 ------- frontend/src/utils/commandFormatting.ts | 2 +- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/frontend/src/i18n/translationLoader.test.ts b/frontend/src/i18n/translationLoader.test.ts index 201821b..0381cc4 100644 --- a/frontend/src/i18n/translationLoader.test.ts +++ b/frontend/src/i18n/translationLoader.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { loadTranslations, generateCommandTranslationKeys, getAvailableLanguages, getTranslationWithFallback } from './translationLoader'; import i18n from 'i18next'; -import * as tauriApi from '@tauri-apps/api'; import * as tauriFs from '@tauri-apps/api/fs'; import * as tauriPath from '@tauri-apps/api/path'; diff --git a/frontend/src/i18n/translationLoader.ts b/frontend/src/i18n/translationLoader.ts index c4ff569..bc3ada5 100644 --- a/frontend/src/i18n/translationLoader.ts +++ b/frontend/src/i18n/translationLoader.ts @@ -44,13 +44,8 @@ const loadDevelopmentTranslations = async (): Promise => { // Load translations from i18n folder export async function loadTranslations(): Promise { - console.log('loadTranslations called'); - console.log('window.__TAURI__:', window.__TAURI__); - console.log('import.meta.env.MODE:', import.meta.env.MODE); - // Check if we're in Tauri environment if (!window.__TAURI__) { - console.log('Loading development translations (Tauri not available)'); await loadDevelopmentTranslations(); return; } @@ -67,11 +62,9 @@ export async function loadTranslations(): Promise { } const i18nPath = await join(projectPath, 'i18n'); - console.log('Calculated i18nPath:', i18nPath); // Check if i18n directory exists const i18nDirExists = await exists(i18nPath); - console.log('i18n directory exists:', i18nDirExists); if (!i18nDirExists) { console.warn('i18n directory not found, using development translations'); diff --git a/frontend/src/utils/commandFormatting.ts b/frontend/src/utils/commandFormatting.ts index 83a45e4..044f52d 100644 --- a/frontend/src/utils/commandFormatting.ts +++ b/frontend/src/utils/commandFormatting.ts @@ -22,7 +22,7 @@ export function formatCommandPreview(command: SkitCommand, commandsMap: Map