fix(stage1): Replace broken window.__TAURI__.dialog with backend select_target_file command (fixes #103) #104
Generated
+1
-1
@@ -1387,7 +1387,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "iccery"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"image",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "iccery"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
description = "Modern Printer Profiling UI frontend for ArgyllCMS"
|
||||
authors = ["Gordon"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -121,6 +121,63 @@ pub fn get_default_working_dir(app: AppHandle) -> Result<String, String> {
|
||||
resolve_safe_cwd(&app, "")
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn select_target_file(
|
||||
app: AppHandle,
|
||||
default_dir: Option<String>,
|
||||
default_name: Option<String>,
|
||||
) -> Result<Option<String>, String> {
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
|
||||
let mut builder = app.dialog().file().add_filter("ArgyllCMS Target", &["ti1"]);
|
||||
if let Some(ref dir) = default_dir {
|
||||
if !dir.trim().is_empty() {
|
||||
builder = builder.set_directory(std::path::PathBuf::from(dir));
|
||||
}
|
||||
}
|
||||
if let Some(ref name) = default_name {
|
||||
if !name.trim().is_empty() {
|
||||
let filename = if name.to_lowercase().ends_with(".ti1") {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("{}.ti1", name)
|
||||
};
|
||||
builder = builder.set_file_name(filename);
|
||||
}
|
||||
}
|
||||
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
builder.save_file(move |file_path| {
|
||||
let res = file_path.map(|p| p.to_string());
|
||||
let _ = tx.send(res);
|
||||
});
|
||||
|
||||
rx.await.map_err(|e| format!("Dialog channel error: {}", e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn select_directory(
|
||||
app: AppHandle,
|
||||
default_dir: Option<String>,
|
||||
) -> Result<Option<String>, String> {
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
|
||||
let mut builder = app.dialog().file();
|
||||
if let Some(ref dir) = default_dir {
|
||||
if !dir.trim().is_empty() {
|
||||
builder = builder.set_directory(std::path::PathBuf::from(dir));
|
||||
}
|
||||
}
|
||||
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
builder.pick_folder(move |folder_path| {
|
||||
let res = folder_path.map(|p| p.to_string());
|
||||
let _ = tx.send(res);
|
||||
});
|
||||
|
||||
rx.await.map_err(|e| format!("Dialog channel error: {}", e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn detect_instruments(app: AppHandle, state: State<'_, ProcessManager>) -> Result<(), String> {
|
||||
let binary = resolve_binary(app.clone(), "instlist".to_string()).await?;
|
||||
|
||||
@@ -14,6 +14,8 @@ pub fn run() {
|
||||
commands::spawn_process,
|
||||
commands::get_app_info,
|
||||
commands::get_default_working_dir,
|
||||
commands::select_target_file,
|
||||
commands::select_directory,
|
||||
commands::send_stdin,
|
||||
commands::kill_process,
|
||||
commands::resolve_binary,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "ICCery",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.1",
|
||||
"identifier": "com.gronod.iccery",
|
||||
"build": {
|
||||
"frontendDist": "../src"
|
||||
|
||||
+1
-1
@@ -460,7 +460,7 @@
|
||||
<img src="./assets/ICCery-logo.svg" alt="ICCery Logo" class="about-logo" />
|
||||
</div>
|
||||
<h2>About ICCery</h2>
|
||||
<p><strong>Version:</strong> <span id="aboutVersion">v0.1.0</span> • <strong>Build Date:</strong> <span id="aboutBuildDate">August 2026</span></p>
|
||||
<p><strong>Version:</strong> <span id="aboutVersion">v0.3.1</span> • <strong>Build Date:</strong> <span id="aboutBuildDate">August 2026</span></p>
|
||||
<p><strong>Copyright © 2026 Gordon Bolton. All rights reserved.</strong></p>
|
||||
<h3>Licences & EULA</h3>
|
||||
<div class="license-text-container">
|
||||
|
||||
+159
-133
@@ -1,6 +1,5 @@
|
||||
const { invoke } = window.__TAURI__.core;
|
||||
const { listen } = window.__TAURI__.event;
|
||||
const { save } = window.__TAURI__.dialog;
|
||||
import { setStage1Result } from './printtarg.js';
|
||||
import { wizardState } from './state.js';
|
||||
|
||||
@@ -18,20 +17,23 @@ export function initTargen() {
|
||||
const logPre = document.getElementById("targenLog");
|
||||
|
||||
let currentWorkingDir = "";
|
||||
let currentBasename = "";
|
||||
|
||||
function updateGenerateButton() {
|
||||
const hasBasename = targetBasename.value.trim().length > 0;
|
||||
const hasCwd = currentWorkingDir.trim().length > 0;
|
||||
btnGenerate.disabled = !(hasBasename && hasCwd);
|
||||
const hasBasename = targetBasename && targetBasename.value.trim().length > 0;
|
||||
const hasCwd = currentWorkingDir && currentWorkingDir.trim().length > 0;
|
||||
if (btnGenerate) {
|
||||
btnGenerate.disabled = !(hasBasename && hasCwd);
|
||||
}
|
||||
}
|
||||
|
||||
// Load sensible default working directory on startup
|
||||
invoke("get_default_working_dir")
|
||||
.then((defaultDir) => {
|
||||
if (defaultDir && !currentWorkingDir) {
|
||||
if (defaultDir) {
|
||||
currentWorkingDir = defaultDir;
|
||||
selectedPathDisplay.textContent = `Directory: ${currentWorkingDir}`;
|
||||
if (selectedPathDisplay) {
|
||||
selectedPathDisplay.textContent = `Directory: ${currentWorkingDir}`;
|
||||
}
|
||||
updateGenerateButton();
|
||||
}
|
||||
})
|
||||
@@ -40,143 +42,167 @@ export function initTargen() {
|
||||
});
|
||||
|
||||
// Handle patch count preset changes
|
||||
patchCountPreset.addEventListener("change", (e) => {
|
||||
if (e.target.value === "custom") {
|
||||
patchCountCustom.classList.remove("hidden");
|
||||
} else {
|
||||
patchCountCustom.classList.add("hidden");
|
||||
}
|
||||
});
|
||||
if (patchCountPreset && patchCountCustom) {
|
||||
patchCountPreset.addEventListener("change", (e) => {
|
||||
if (e.target.value === "custom") {
|
||||
patchCountCustom.classList.remove("hidden");
|
||||
} else {
|
||||
patchCountCustom.classList.add("hidden");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Enable generate button only when both basename and directory are valid
|
||||
targetBasename.addEventListener("input", () => {
|
||||
updateGenerateButton();
|
||||
});
|
||||
|
||||
// Browse button opens save dialog
|
||||
btnBrowse.addEventListener("click", async () => {
|
||||
try {
|
||||
let filePath = await save({
|
||||
title: "Save Target File As...",
|
||||
filters: [{
|
||||
name: "ArgyllCMS Target",
|
||||
extensions: ["ti1"]
|
||||
}]
|
||||
});
|
||||
|
||||
if (filePath) {
|
||||
// Ensure .ti1 suffix is present in the display if the OS dialog didn't append it
|
||||
if (!filePath.toLowerCase().endsWith('.ti1')) {
|
||||
filePath += '.ti1';
|
||||
}
|
||||
|
||||
// Simple extraction of directory and basename.
|
||||
const isWindows = filePath.includes('\\');
|
||||
const sep = isWindows ? '\\' : '/';
|
||||
const parts = filePath.split(sep);
|
||||
const fileName = parts.pop();
|
||||
|
||||
currentWorkingDir = parts.join(sep);
|
||||
const basename = fileName.replace(/\.ti1$/i, '');
|
||||
|
||||
targetBasename.value = basename;
|
||||
selectedPathDisplay.textContent = `Directory: ${currentWorkingDir}`;
|
||||
// Enable generate button when basename is input
|
||||
if (targetBasename) {
|
||||
targetBasename.addEventListener("input", () => {
|
||||
if (!currentWorkingDir) {
|
||||
invoke("get_default_working_dir")
|
||||
.then((defaultDir) => {
|
||||
if (defaultDir) {
|
||||
currentWorkingDir = defaultDir;
|
||||
if (selectedPathDisplay) {
|
||||
selectedPathDisplay.textContent = `Directory: ${currentWorkingDir}`;
|
||||
}
|
||||
}
|
||||
updateGenerateButton();
|
||||
})
|
||||
.catch(() => updateGenerateButton());
|
||||
} else {
|
||||
updateGenerateButton();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to open save dialog:", err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Browse button opens save dialog via backend command
|
||||
if (btnBrowse) {
|
||||
btnBrowse.addEventListener("click", async () => {
|
||||
try {
|
||||
const filePath = await invoke("select_target_file", {
|
||||
defaultDir: currentWorkingDir || null,
|
||||
defaultName: targetBasename ? targetBasename.value.trim() || null : null,
|
||||
});
|
||||
|
||||
if (filePath) {
|
||||
// Normalize separators to locate directory and filename
|
||||
const isWindows = filePath.includes('\\');
|
||||
const sep = isWindows ? '\\' : '/';
|
||||
const parts = filePath.split(sep);
|
||||
const fileName = parts.pop();
|
||||
|
||||
currentWorkingDir = parts.join(sep);
|
||||
const basename = fileName.replace(/\.ti1$/i, '');
|
||||
|
||||
if (targetBasename) {
|
||||
targetBasename.value = basename;
|
||||
}
|
||||
if (selectedPathDisplay) {
|
||||
selectedPathDisplay.textContent = `Directory: ${currentWorkingDir}`;
|
||||
}
|
||||
updateGenerateButton();
|
||||
}
|
||||
} catch (err)
|
||||
{
|
||||
console.error("Failed to open file dialog:", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Generate button clicks
|
||||
btnGenerate.addEventListener("click", async () => {
|
||||
const basename = targetBasename.value.trim();
|
||||
if (!currentWorkingDir || !basename) {
|
||||
logPre.textContent = "[ERROR] Please specify both a target basename and a working directory.\n";
|
||||
logContainer.open = true;
|
||||
logContainer.classList.remove("hidden");
|
||||
btnGenerate.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
logPre.textContent = "";
|
||||
logContainer.open = false;
|
||||
logContainer.classList.remove("hidden");
|
||||
btnGenerate.disabled = true;
|
||||
|
||||
// Determine patch count
|
||||
let patchCount = parseInt(patchCountPreset.value, 10);
|
||||
if (patchCountPreset.value === "custom") {
|
||||
const customVal = parseInt(patchCountCustom.value, 10);
|
||||
patchCount = (!isNaN(customVal) && customVal > 0) ? customVal : 800;
|
||||
}
|
||||
if (isNaN(patchCount) || patchCount <= 0) {
|
||||
patchCount = 800;
|
||||
}
|
||||
|
||||
// Determine colour space
|
||||
let colourSpace = "rgb";
|
||||
colourSpaceRadios.forEach(radio => {
|
||||
if (radio.checked) colourSpace = radio.value;
|
||||
});
|
||||
|
||||
const basename = targetBasename.value.trim();
|
||||
|
||||
const config = {
|
||||
colour_space: colourSpace,
|
||||
patch_count: patchCount,
|
||||
total_patches: patchCount,
|
||||
white_patches: whitePatches.value ? parseInt(whitePatches.value, 10) : null,
|
||||
black_patches: blackPatches.value ? parseInt(blackPatches.value, 10) : null,
|
||||
basename: basename,
|
||||
cwd: currentWorkingDir
|
||||
};
|
||||
|
||||
try {
|
||||
// Set up listeners just for this run
|
||||
const processId = `targen_${basename}`;
|
||||
|
||||
const unlistenStdout = await listen("process:stdout", (event) => {
|
||||
if (event.payload.id === processId && event.payload.line) {
|
||||
logPre.textContent += event.payload.line + "\n";
|
||||
logPre.scrollTop = logPre.scrollHeight;
|
||||
if (btnGenerate) {
|
||||
btnGenerate.addEventListener("click", async () => {
|
||||
const basename = targetBasename ? targetBasename.value.trim() : "";
|
||||
if (!currentWorkingDir || !basename) {
|
||||
if (logPre) {
|
||||
logPre.textContent = "[ERROR] Please specify both a target basename and a working directory.\n";
|
||||
}
|
||||
if (logContainer) {
|
||||
logContainer.open = true;
|
||||
logContainer.classList.remove("hidden");
|
||||
}
|
||||
btnGenerate.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (logPre) logPre.textContent = "";
|
||||
if (logContainer) {
|
||||
logContainer.open = false;
|
||||
logContainer.classList.remove("hidden");
|
||||
}
|
||||
btnGenerate.disabled = true;
|
||||
|
||||
// Determine patch count
|
||||
let patchCount = 800;
|
||||
if (patchCountPreset) {
|
||||
patchCount = parseInt(patchCountPreset.value, 10);
|
||||
if (patchCountPreset.value === "custom" && patchCountCustom) {
|
||||
const customVal = parseInt(patchCountCustom.value, 10);
|
||||
patchCount = (!isNaN(customVal) && customVal > 0) ? customVal : 800;
|
||||
}
|
||||
}
|
||||
if (isNaN(patchCount) || patchCount <= 0) {
|
||||
patchCount = 800;
|
||||
}
|
||||
|
||||
// Determine colour space
|
||||
let colourSpace = "rgb";
|
||||
colourSpaceRadios.forEach((radio) => {
|
||||
if (radio.checked) colourSpace = radio.value;
|
||||
});
|
||||
|
||||
const unlistenStderr = await listen("process:stderr", (event) => {
|
||||
if (event.payload.id === processId && event.payload.line) {
|
||||
logPre.textContent += "ERR: " + event.payload.line + "\n";
|
||||
logPre.scrollTop = logPre.scrollHeight;
|
||||
}
|
||||
});
|
||||
const config = {
|
||||
colour_space: colourSpace,
|
||||
patch_count: patchCount,
|
||||
total_patches: patchCount,
|
||||
white_patches: (whitePatches && whitePatches.value) ? parseInt(whitePatches.value, 10) : null,
|
||||
black_patches: (blackPatches && blackPatches.value) ? parseInt(blackPatches.value, 10) : null,
|
||||
basename: basename,
|
||||
cwd: currentWorkingDir,
|
||||
};
|
||||
|
||||
const unlistenExit = await listen("process:exit", (event) => {
|
||||
if (event.payload.id === processId) {
|
||||
unlistenStdout();
|
||||
unlistenStderr();
|
||||
unlistenExit();
|
||||
try {
|
||||
const processId = `targen_${basename}`;
|
||||
|
||||
if (event.payload.code === 0) {
|
||||
logPre.textContent += "\n[SUCCESS] Targen completed successfully.\n";
|
||||
btnGenerate.disabled = false;
|
||||
wizardState.setTarget(basename, currentWorkingDir);
|
||||
setStage1Result(basename, currentWorkingDir);
|
||||
advanceToStage2();
|
||||
} else {
|
||||
logPre.textContent += `\n[ERROR] Targen exited with code ${event.payload.code}.\n`;
|
||||
btnGenerate.disabled = false;
|
||||
const unlistenStdout = await listen("process:stdout", (event) => {
|
||||
if (event.payload.id === processId && event.payload.line && logPre) {
|
||||
logPre.textContent += event.payload.line + "\n";
|
||||
logPre.scrollTop = logPre.scrollHeight;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
logPre.textContent = "Starting targen...\n";
|
||||
await invoke("run_targen", { config });
|
||||
const unlistenStderr = await listen("process:stderr", (event) => {
|
||||
if (event.payload.id === processId && event.payload.line && logPre) {
|
||||
logPre.textContent += "ERR: " + event.payload.line + "\n";
|
||||
logPre.scrollTop = logPre.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
logPre.textContent += `\n[INVOKE ERROR] ${err}\n`;
|
||||
btnGenerate.disabled = false;
|
||||
}
|
||||
});
|
||||
const unlistenExit = await listen("process:exit", (event) => {
|
||||
if (event.payload.id === processId) {
|
||||
unlistenStdout();
|
||||
unlistenStderr();
|
||||
unlistenExit();
|
||||
|
||||
if (event.payload.code === 0) {
|
||||
if (logPre) logPre.textContent += "\n[SUCCESS] Targen completed successfully.\n";
|
||||
btnGenerate.disabled = false;
|
||||
wizardState.setTarget(basename, currentWorkingDir);
|
||||
setStage1Result(basename, currentWorkingDir);
|
||||
advanceToStage2();
|
||||
} else {
|
||||
if (logPre) logPre.textContent += `\n[ERROR] Targen exited with code ${event.payload.code}.\n`;
|
||||
btnGenerate.disabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (logPre) logPre.textContent = "Starting targen...\n";
|
||||
await invoke("run_targen", { config });
|
||||
} catch (err) {
|
||||
if (logPre) logPre.textContent += `\n[INVOKE ERROR] ${err}\n`;
|
||||
btnGenerate.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function advanceToStage2() {
|
||||
@@ -184,11 +210,11 @@ function advanceToStage2() {
|
||||
const stages = document.querySelectorAll('.stage');
|
||||
|
||||
// Update stepper
|
||||
steps.forEach(s => s.classList.remove('active'));
|
||||
steps.forEach((s) => s.classList.remove('active'));
|
||||
if (steps[1]) steps[1].classList.add('active');
|
||||
|
||||
// Update sections
|
||||
stages.forEach(s => {
|
||||
stages.forEach((s) => {
|
||||
s.classList.remove('active');
|
||||
s.classList.add('hidden');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user