feat(v3): add native directory picker for CWD fields via tauri-plugin-dialog
This commit is contained in:
parent
99282e833a
commit
a64ab2e55f
6 changed files with 147 additions and 21 deletions
65
v2/Cargo.lock
generated
65
v2/Cargo.lock
generated
|
|
@ -302,6 +302,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-log",
|
"tauri-plugin-log",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
|
@ -3365,6 +3366,30 @@ dependencies = [
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rfd"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672"
|
||||||
|
dependencies = [
|
||||||
|
"block2",
|
||||||
|
"dispatch2",
|
||||||
|
"glib-sys",
|
||||||
|
"gobject-sys",
|
||||||
|
"gtk-sys",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"objc2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-foundation",
|
||||||
|
"raw-window-handle",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.17.14"
|
version = "0.17.14"
|
||||||
|
|
@ -4331,6 +4356,46 @@ dependencies = [
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-dialog"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"raw-window-handle",
|
||||||
|
"rfd",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"tauri-plugin-fs",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-fs"
|
||||||
|
version = "2.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"dunce",
|
||||||
|
"glob",
|
||||||
|
"percent-encoding",
|
||||||
|
"schemars 0.8.22",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_repr",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"tauri-utils",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"toml 0.9.12+spec-1.1.0",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-log"
|
name = "tauri-plugin-log"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
|
|
|
||||||
10
v2/package-lock.json
generated
10
v2/package-lock.json
generated
|
|
@ -10,6 +10,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.70",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.70",
|
||||||
"@tauri-apps/api": "^2.10.1",
|
"@tauri-apps/api": "^2.10.1",
|
||||||
|
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||||
"@tauri-apps/plugin-updater": "^2.10.0",
|
"@tauri-apps/plugin-updater": "^2.10.0",
|
||||||
"@xterm/addon-canvas": "^0.7.0",
|
"@xterm/addon-canvas": "^0.7.0",
|
||||||
"@xterm/addon-fit": "^0.11.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
|
|
@ -1363,6 +1364,15 @@
|
||||||
"url": "https://opencollective.com/tauri"
|
"url": "https://opencollective.com/tauri"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tauri-apps/plugin-dialog": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==",
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tauri-apps/plugin-updater": {
|
"node_modules/@tauri-apps/plugin-updater": {
|
||||||
"version": "2.10.0",
|
"version": "2.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-updater/-/plugin-updater-2.10.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.70",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.70",
|
||||||
"@tauri-apps/api": "^2.10.1",
|
"@tauri-apps/api": "^2.10.1",
|
||||||
|
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||||
"@tauri-apps/plugin-updater": "^2.10.0",
|
"@tauri-apps/plugin-updater": "^2.10.0",
|
||||||
"@xterm/addon-canvas": "^0.7.0",
|
"@xterm/addon-canvas": "^0.7.0",
|
||||||
"@xterm/addon-fit": "^0.11.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ rusqlite = { version = "0.31", features = ["bundled"] }
|
||||||
dirs = "5"
|
dirs = "5"
|
||||||
notify = { version = "6", features = ["macos_fsevent"] }
|
notify = { version = "6", features = ["macos_fsevent"] }
|
||||||
tauri-plugin-updater = "2.10.0"
|
tauri-plugin-updater = "2.10.0"
|
||||||
|
tauri-plugin-dialog = "2"
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
tokio-tungstenite = { version = "0.21", features = ["native-tls"] }
|
tokio-tungstenite = { version = "0.21", features = ["native-tls"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
|
||||||
|
|
@ -421,14 +421,7 @@ fn project_agent_state_load(
|
||||||
state.session_db.load_project_agent_state(&project_id)
|
state.session_db.load_project_agent_state(&project_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Directory picker command ---
|
// Directory picker: uses tauri-plugin-dialog (frontend API: @tauri-apps/plugin-dialog)
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn pick_directory() -> Option<String> {
|
|
||||||
// Use native file dialog via rfd
|
|
||||||
// Fallback: return None and let frontend use a text input
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- CLI argument commands ---
|
// --- CLI argument commands ---
|
||||||
|
|
||||||
|
|
@ -554,7 +547,6 @@ pub fn run() {
|
||||||
claude_list_profiles,
|
claude_list_profiles,
|
||||||
claude_list_skills,
|
claude_list_skills,
|
||||||
claude_read_skill,
|
claude_read_skill,
|
||||||
pick_directory,
|
|
||||||
groups_load,
|
groups_load,
|
||||||
groups_save,
|
groups_save,
|
||||||
discover_markdown_files,
|
discover_markdown_files,
|
||||||
|
|
@ -565,6 +557,7 @@ pub fn run() {
|
||||||
cli_get_group,
|
cli_get_group,
|
||||||
])
|
])
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
app.handle().plugin(
|
app.handle().plugin(
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
||||||
import { getCurrentTheme, setTheme } from '../../stores/theme.svelte';
|
import { getCurrentTheme, setTheme } from '../../stores/theme.svelte';
|
||||||
import { THEME_LIST, getPalette, type ThemeId } from '../../styles/themes';
|
import { THEME_LIST, getPalette, type ThemeId } from '../../styles/themes';
|
||||||
|
import { open as openDialog } from '@tauri-apps/plugin-dialog';
|
||||||
|
|
||||||
let activeGroupId = $derived(getActiveGroupId());
|
let activeGroupId = $derived(getActiveGroupId());
|
||||||
let activeGroup = $derived(getActiveGroup());
|
let activeGroup = $derived(getActiveGroup());
|
||||||
|
|
@ -176,6 +177,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function browseDirectory(): Promise<string | null> {
|
||||||
|
const selected = await openDialog({ directory: true, multiple: false });
|
||||||
|
return typeof selected === 'string' ? selected : null;
|
||||||
|
}
|
||||||
|
|
||||||
// New project form
|
// New project form
|
||||||
let newName = $state('');
|
let newName = $state('');
|
||||||
let newCwd = $state('');
|
let newCwd = $state('');
|
||||||
|
|
@ -385,12 +391,17 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-field">
|
<div class="setting-field">
|
||||||
<label for="default-cwd" class="setting-label">Working directory</label>
|
<label for="default-cwd" class="setting-label">Working directory</label>
|
||||||
|
<div class="input-with-browse">
|
||||||
<input
|
<input
|
||||||
id="default-cwd"
|
id="default-cwd"
|
||||||
value={defaultCwd}
|
value={defaultCwd}
|
||||||
placeholder="~"
|
placeholder="~"
|
||||||
onchange={e => { defaultCwd = (e.target as HTMLInputElement).value; saveGlobalSetting('default_cwd', defaultCwd); }}
|
onchange={e => { defaultCwd = (e.target as HTMLInputElement).value; saveGlobalSetting('default_cwd', defaultCwd); }}
|
||||||
/>
|
/>
|
||||||
|
<button class="browse-btn" title="Browse..." onclick={async () => { const d = await browseDirectory(); if (d) { defaultCwd = d; saveGlobalSetting('default_cwd', d); } }}>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"><path d="M3 7v13h18V7H3zm0-2h7l2 2h9v1H3V5z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -434,10 +445,15 @@
|
||||||
</label>
|
</label>
|
||||||
<label class="project-field project-field-grow">
|
<label class="project-field project-field-grow">
|
||||||
<span class="field-label">CWD</span>
|
<span class="field-label">CWD</span>
|
||||||
|
<div class="input-with-browse">
|
||||||
<input
|
<input
|
||||||
value={project.cwd}
|
value={project.cwd}
|
||||||
onchange={e => updateProject(activeGroupId, project.id, { cwd: (e.target as HTMLInputElement).value })}
|
onchange={e => updateProject(activeGroupId, project.id, { cwd: (e.target as HTMLInputElement).value })}
|
||||||
/>
|
/>
|
||||||
|
<button class="browse-btn" title="Browse..." onclick={async () => { const d = await browseDirectory(); if (d) updateProject(activeGroupId, project.id, { cwd: d }); }}>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"><path d="M3 7v13h18V7H3zm0-2h7l2 2h9v1H3V5z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<label class="project-field">
|
<label class="project-field">
|
||||||
<span class="field-label">Icon</span>
|
<span class="field-label">Icon</span>
|
||||||
|
|
@ -464,7 +480,12 @@
|
||||||
{#if activeGroup.projects.length < 5}
|
{#if activeGroup.projects.length < 5}
|
||||||
<div class="add-form">
|
<div class="add-form">
|
||||||
<input bind:value={newName} placeholder="Project name" />
|
<input bind:value={newName} placeholder="Project name" />
|
||||||
|
<div class="input-with-browse add-form-path">
|
||||||
<input bind:value={newCwd} placeholder="/path/to/project" />
|
<input bind:value={newCwd} placeholder="/path/to/project" />
|
||||||
|
<button class="browse-btn" title="Browse..." onclick={async () => { const d = await browseDirectory(); if (d) newCwd = d; }}>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"><path d="M3 7v13h18V7H3zm0-2h7l2 2h9v1H3V5z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button class="btn-primary" onclick={handleAddProject} disabled={!newName.trim() || !newCwd.trim()}>
|
<button class="btn-primary" onclick={handleAddProject} disabled={!newName.trim() || !newCwd.trim()}>
|
||||||
Add Project
|
Add Project
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -516,7 +537,8 @@
|
||||||
letter-spacing: 0.03em;
|
letter-spacing: 0.03em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-field > input {
|
.setting-field > input,
|
||||||
|
.setting-field .input-with-browse input {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
background: var(--ctp-surface0);
|
background: var(--ctp-surface0);
|
||||||
border: 1px solid var(--ctp-surface1);
|
border: 1px solid var(--ctp-surface1);
|
||||||
|
|
@ -835,4 +857,38 @@
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-with-browse {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-with-browse input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-form-path {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browse-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
background: var(--ctp-surface0);
|
||||||
|
border: 1px solid var(--ctp-surface1);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--ctp-subtext0);
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browse-btn:hover {
|
||||||
|
color: var(--ctp-text);
|
||||||
|
background: var(--ctp-surface1);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue