Skip to content

Commit ba86d87

Browse files
committed
Refactor: Split tauri-commands/file-viewer
- file-viewer.ts was a grab bag of unrelated commands — now viewer-only - New file-actions.ts, icons.ts, app-state.ts for the displaced functions - Moved getSyncStatus and font metrics to file-listing.ts - Added "where to put new commands" guide to CLAUDE.md
1 parent c9dc4e5 commit ba86d87

7 files changed

Lines changed: 342 additions & 296 deletions

File tree

apps/desktop/src/lib/tauri-commands/CLAUDE.md

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,36 @@ import { listDirectoryStart } from '$lib/tauri-commands/file-listing'
1313

1414
## Files
1515

16-
| File | Contents |
17-
| --------------------- | ----------------------------------------------------------------------------------------------------------- |
18-
| `index.ts` | Barrel re-export of everything below |
19-
| `file-listing.ts` | Virtual-scroll listing API, drag-and-drop, `pathExists`, `createDirectory` |
20-
| `file-viewer.ts` | Viewer session, icons, context menu, macOS integrations, MCP pane state, font metrics |
21-
| `write-operations.ts` | Copy/move/delete, conflict resolution, scan preview, `formatBytes`/`formatDuration` |
22-
| `rename.ts` | `checkRenamePermission`, `checkRenameValidity`, `renameFile`, `moveToTrash` |
23-
| `storage.ts` | `listVolumes`, `getVolumeSpace`, `checkFullDiskAccess`, `openPrivacySettings` |
24-
| `networking.ts` | SMB host discovery, share listing, Keychain credential ops, mounting |
25-
| `mtp.ts` | Android MTP: device listing, connect/disconnect, file ops, transfer progress, volume copy |
26-
| `licensing.ts` | License status, activation, expiry, server validation |
27-
| `settings.ts` | Port checking, file watcher debounce, indexing toggle, AI subsystem commands, `updateServiceResolveTimeout` |
16+
| File | Contents |
17+
| --------------------- | ----------------------------------------------------------------------------------------------------- |
18+
| `index.ts` | Barrel re-export of everything below |
19+
| `file-listing.ts` | Virtual-scroll listing API, drag-and-drop, `pathExists`, `createDirectory`, sync status, font metrics |
20+
| `file-viewer.ts` | Viewer session only: open, seek, search, close, word wrap menu |
21+
| `file-actions.ts` | Open file/URL, Finder reveal, Quick Look, Get Info, context menu, clipboard, open in editor |
22+
| `icons.ts` | Icon fetching (`getIcons`, `refreshDirectoryIcons`) and cache invalidation |
23+
| `app-state.ts` | MCP pane state, dialog open/close tracking, menu context, view settings, `showMainWindow` |
24+
| `write-operations.ts` | Copy/move/delete, conflict resolution, scan preview, `formatBytes`/`formatDuration` |
25+
| `rename.ts` | `checkRenamePermission`, `checkRenameValidity`, `renameFile`, `moveToTrash` |
26+
| `storage.ts` | `listVolumes`, `getVolumeSpace`, `checkFullDiskAccess`, `openPrivacySettings` |
27+
| `networking.ts` | SMB host discovery, share listing, Keychain credential ops, mounting |
28+
| `mtp.ts` | Android MTP: device listing, connect/disconnect, file ops, transfer progress, volume copy |
29+
| `licensing.ts` | License status, activation, expiry, server validation |
30+
| `settings.ts` | Port checking, file watcher debounce, indexing toggle, AI subsystem commands |
31+
32+
## Where to put new commands
33+
34+
- **Viewer session** (anything prefixed `viewer_*`) → `file-viewer.ts`
35+
- **File listing display** (listing API, sync status, font metrics) → `file-listing.ts`
36+
- **Single-file actions** (open, reveal, preview, context menu) → `file-actions.ts`
37+
- **Icons** (fetch, refresh, cache clear) → `icons.ts`
38+
- **MCP pane/dialog state, menu sync, window lifecycle**`app-state.ts`
39+
- **Copy/move/delete operations**`write-operations.ts`
40+
- **Rename/trash**`rename.ts`
41+
- **Volumes/disk access**`storage.ts`
42+
- **Network/SMB**`networking.ts`
43+
- **MTP/Android**`mtp.ts`
44+
- **Licensing**`licensing.ts`
45+
- **Settings/AI**`settings.ts`
2846

2947
## Key patterns
3048

@@ -43,13 +61,14 @@ returning safe empty/null fallbacks so the same code runs on other platforms.
4361

4462
## Notable non-obvious placements
4563

46-
- `PaneState` and `PaneFileEntry` types live in `file-viewer.ts` (MCP state sync is viewer-adjacent).
4764
- `formatBytes` and `formatDuration` are co-located in `write-operations.ts` with no IPC calls.
4865
- `listen` and `UnlistenFn` from `@tauri-apps/api/event` are re-exported through `write-operations.ts`.
66+
- `getSyncStatus` and font metrics (`storeFontMetrics`, `hasFontMetrics`) live in `file-listing.ts` because they
67+
directly support file list rendering.
4968

5069
## Dependencies
5170

5271
- `@tauri-apps/api/core``invoke`
5372
- `@tauri-apps/api/event``listen`, `UnlistenFn`
54-
- `@tauri-apps/plugin-opener``openExternalUrl`
73+
- `@tauri-apps/plugin-opener``openFile`, `openExternalUrl`
5574
- Types from `$lib/file-explorer/types`
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// App-level state: MCP pane state, dialog tracking, menu context, window lifecycle
2+
3+
import { invoke } from '@tauri-apps/api/core'
4+
5+
// ============================================================================
6+
// MCP pane state
7+
// ============================================================================
8+
9+
/** File entry for pane state updates. */
10+
export interface PaneFileEntry {
11+
name: string
12+
path: string
13+
isDirectory: boolean
14+
size?: number
15+
modified?: string
16+
}
17+
18+
/** State of a single pane. */
19+
export interface PaneState {
20+
path: string
21+
volumeId?: string
22+
volumeName?: string
23+
files: PaneFileEntry[]
24+
cursorIndex: number
25+
viewMode: string
26+
selectedIndices: number[]
27+
sortField?: string
28+
sortOrder?: string
29+
totalFiles?: number
30+
loadedStart?: number
31+
loadedEnd?: number
32+
showHidden?: boolean
33+
}
34+
35+
/**
36+
* Update left pane state for MCP context tools.
37+
*/
38+
export async function updateLeftPaneState(state: PaneState): Promise<void> {
39+
await invoke('update_left_pane_state', { state })
40+
}
41+
42+
/**
43+
* Update right pane state for MCP context tools.
44+
*/
45+
export async function updateRightPaneState(state: PaneState): Promise<void> {
46+
await invoke('update_right_pane_state', { state })
47+
}
48+
49+
/**
50+
* Update focused pane for MCP context tools.
51+
*/
52+
export async function updateFocusedPane(pane: 'left' | 'right'): Promise<void> {
53+
await invoke('update_focused_pane', { pane })
54+
}
55+
56+
// ============================================================================
57+
// Dialog tracking
58+
// ============================================================================
59+
60+
/** Notify backend that a soft (overlay) dialog opened. */
61+
export async function notifyDialogOpened(dialogType: string): Promise<void> {
62+
await invoke('notify_dialog_opened', { dialogType })
63+
}
64+
65+
/** Notify backend that a soft (overlay) dialog closed. */
66+
export async function notifyDialogClosed(dialogType: string): Promise<void> {
67+
await invoke('notify_dialog_closed', { dialogType })
68+
}
69+
70+
/** Register all known soft dialog types with the backend for the MCP "available dialogs" resource. */
71+
export async function registerKnownDialogs(dialogs: readonly { id: string; description?: string }[]): Promise<void> {
72+
await invoke('register_known_dialogs', { dialogs })
73+
}
74+
75+
// ============================================================================
76+
// Menu context and view settings
77+
// ============================================================================
78+
79+
/**
80+
* Updates the global menu context (used by app-level File menu).
81+
* @param path - Absolute path to the file.
82+
* @param filename - Name of the file.
83+
*/
84+
export async function updateMenuContext(path: string, filename: string): Promise<void> {
85+
await invoke('update_menu_context', { path, filename })
86+
}
87+
88+
/**
89+
* Toggle hidden files visibility and sync menu checkbox state.
90+
* @returns The new state of showHiddenFiles.
91+
*/
92+
export async function toggleHiddenFiles(): Promise<boolean> {
93+
return invoke<boolean>('toggle_hidden_files')
94+
}
95+
96+
/**
97+
* Set view mode and sync menu radio button state.
98+
* @param mode - 'full' or 'brief'
99+
*/
100+
export async function setViewMode(mode: 'full' | 'brief'): Promise<void> {
101+
await invoke('set_view_mode', { mode })
102+
}
103+
104+
// ============================================================================
105+
// Window lifecycle
106+
// ============================================================================
107+
108+
/**
109+
* Shows the main window.
110+
* Should be called when the frontend is ready to avoid white flash.
111+
*/
112+
export async function showMainWindow(): Promise<void> {
113+
await invoke('show_main_window')
114+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// File actions: open, reveal, preview, and context menu commands
2+
3+
import { invoke } from '@tauri-apps/api/core'
4+
import { openPath, openUrl } from '@tauri-apps/plugin-opener'
5+
6+
/**
7+
* Opens a file with the system's default application.
8+
* @param path - Path to the file to open.
9+
*/
10+
export async function openFile(path: string): Promise<void> {
11+
await openPath(path)
12+
}
13+
14+
/**
15+
* Opens a URL in the system's default browser.
16+
* @param url - URL to open (like "https://getcmdr.com/renew")
17+
*/
18+
export async function openExternalUrl(url: string): Promise<void> {
19+
await openUrl(url)
20+
}
21+
22+
/**
23+
* Shows a native context menu for a file.
24+
* @param path - Absolute path to the file.
25+
* @param filename - Name of the file.
26+
* @param isDirectory - Whether the entry is a directory.
27+
*/
28+
export async function showFileContextMenu(path: string, filename: string, isDirectory: boolean): Promise<void> {
29+
await invoke('show_file_context_menu', { path, filename, isDirectory })
30+
}
31+
32+
/**
33+
* Show a file in Finder (reveal in parent folder).
34+
* @param path - Absolute path to the file.
35+
*/
36+
export async function showInFinder(path: string): Promise<void> {
37+
await invoke('show_in_finder', { path })
38+
}
39+
40+
/**
41+
* Copy text to clipboard.
42+
* @param text - Text to copy.
43+
*/
44+
export async function copyToClipboard(text: string): Promise<void> {
45+
await invoke('copy_to_clipboard', { text })
46+
}
47+
48+
/**
49+
* Quick Look preview (macOS only).
50+
* @param path - Absolute path to the file.
51+
*/
52+
export async function quickLook(path: string): Promise<void> {
53+
await invoke('quick_look', { path })
54+
}
55+
56+
/**
57+
* Open Get Info window in Finder (macOS only).
58+
* @param path - Absolute path to the file.
59+
*/
60+
export async function getInfo(path: string): Promise<void> {
61+
await invoke('get_info', { path })
62+
}
63+
64+
/**
65+
* Open file in the system's default text editor (macOS only).
66+
* Uses `open -t` which opens the file in the default text editor.
67+
* @param path - Absolute path to the file.
68+
*/
69+
export async function openInEditor(path: string): Promise<void> {
70+
await invoke('open_in_editor', { path })
71+
}

apps/desktop/src/lib/tauri-commands/file-listing.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// On-demand virtual scrolling API (listing-based)
1+
// On-demand virtual scrolling API (listing-based), sync status, font metrics
22

33
import { invoke } from '@tauri-apps/api/core'
44
import type {
@@ -8,6 +8,7 @@ import type {
88
SortColumn,
99
SortOrder,
1010
StreamingListingStartResult,
11+
SyncStatus,
1112
} from '../file-explorer/types'
1213
import type { DirectorySortMode } from '$lib/settings'
1314

@@ -224,3 +225,41 @@ export async function pathExists(path: string, volumeId?: string): Promise<boole
224225
export async function createDirectory(parentPath: string, name: string, volumeId?: string): Promise<string> {
225226
return invoke<string>('create_directory', { volumeId, parentPath, name })
226227
}
228+
229+
// ============================================================================
230+
// Sync status and font metrics (support file list display)
231+
// ============================================================================
232+
233+
/**
234+
* Gets sync status for multiple file paths.
235+
* Returns a map of path -> sync status.
236+
* Only works on macOS with files in cloud-synced folders (Dropbox, iCloud, etc.)
237+
* @param paths - Array of absolute file paths.
238+
* @returns Map of path -> SyncStatus
239+
*/
240+
export async function getSyncStatus(paths: string[]): Promise<Record<string, SyncStatus>> {
241+
try {
242+
return await invoke<Record<string, SyncStatus>>('get_sync_status', { paths })
243+
} catch {
244+
// Command not available (non-macOS) - return empty map
245+
return {}
246+
}
247+
}
248+
249+
/**
250+
* Stores font metrics for a font configuration.
251+
* @param fontId - Font identifier (like "system-400-12")
252+
* @param widths - Map of code point -> width in pixels
253+
*/
254+
export async function storeFontMetrics(fontId: string, widths: Record<number, number>): Promise<void> {
255+
await invoke('store_font_metrics', { fontId, widths })
256+
}
257+
258+
/**
259+
* Checks if font metrics are available for a font ID.
260+
* @param fontId - Font identifier to check
261+
* @returns True if metrics are cached
262+
*/
263+
export async function hasFontMetrics(fontId: string): Promise<boolean> {
264+
return invoke<boolean>('has_font_metrics', { fontId })
265+
}

0 commit comments

Comments
 (0)