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]]
|
[[package]]
|
||||||
name = "iccery"
|
name = "iccery"
|
||||||
version = "0.2.1"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"image",
|
"image",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "iccery"
|
name = "iccery"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
description = "Modern Printer Profiling UI frontend for ArgyllCMS"
|
description = "Modern Printer Profiling UI frontend for ArgyllCMS"
|
||||||
authors = ["Gordon"]
|
authors = ["Gordon"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -121,6 +121,63 @@ pub fn get_default_working_dir(app: AppHandle) -> Result<String, String> {
|
|||||||
resolve_safe_cwd(&app, "")
|
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]
|
#[tauri::command]
|
||||||
pub async fn detect_instruments(app: AppHandle, state: State<'_, ProcessManager>) -> Result<(), String> {
|
pub async fn detect_instruments(app: AppHandle, state: State<'_, ProcessManager>) -> Result<(), String> {
|
||||||
let binary = resolve_binary(app.clone(), "instlist".to_string()).await?;
|
let binary = resolve_binary(app.clone(), "instlist".to_string()).await?;
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ pub fn run() {
|
|||||||
commands::spawn_process,
|
commands::spawn_process,
|
||||||
commands::get_app_info,
|
commands::get_app_info,
|
||||||
commands::get_default_working_dir,
|
commands::get_default_working_dir,
|
||||||
|
commands::select_target_file,
|
||||||
|
commands::select_directory,
|
||||||
commands::send_stdin,
|
commands::send_stdin,
|
||||||
commands::kill_process,
|
commands::kill_process,
|
||||||
commands::resolve_binary,
|
commands::resolve_binary,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "ICCery",
|
"productName": "ICCery",
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"identifier": "com.gronod.iccery",
|
"identifier": "com.gronod.iccery",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../src"
|
"frontendDist": "../src"
|
||||||
|
|||||||
+1
-1
@@ -460,7 +460,7 @@
|
|||||||
<img src="./assets/ICCery-logo.svg" alt="ICCery Logo" class="about-logo" />
|
<img src="./assets/ICCery-logo.svg" alt="ICCery Logo" class="about-logo" />
|
||||||
</div>
|
</div>
|
||||||
<h2>About ICCery</h2>
|
<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>
|
<p><strong>Copyright © 2026 Gordon Bolton. All rights reserved.</strong></p>
|
||||||
<h3>Licences & EULA</h3>
|
<h3>Licences & EULA</h3>
|
||||||
<div class="license-text-container">
|
<div class="license-text-container">
|
||||||
|
|||||||
+159
-133
@@ -1,6 +1,5 @@
|
|||||||
const { invoke } = window.__TAURI__.core;
|
const { invoke } = window.__TAURI__.core;
|
||||||
const { listen } = window.__TAURI__.event;
|
const { listen } = window.__TAURI__.event;
|
||||||
const { save } = window.__TAURI__.dialog;
|
|
||||||
import { setStage1Result } from './printtarg.js';
|
import { setStage1Result } from './printtarg.js';
|
||||||
import { wizardState } from './state.js';
|
import { wizardState } from './state.js';
|
||||||
|
|
||||||
@@ -18,20 +17,23 @@ export function initTargen() {
|
|||||||
const logPre = document.getElementById("targenLog");
|
const logPre = document.getElementById("targenLog");
|
||||||
|
|
||||||
let currentWorkingDir = "";
|
let currentWorkingDir = "";
|
||||||
let currentBasename = "";
|
|
||||||
|
|
||||||
function updateGenerateButton() {
|
function updateGenerateButton() {
|
||||||
const hasBasename = targetBasename.value.trim().length > 0;
|
const hasBasename = targetBasename && targetBasename.value.trim().length > 0;
|
||||||
const hasCwd = currentWorkingDir.trim().length > 0;
|
const hasCwd = currentWorkingDir && currentWorkingDir.trim().length > 0;
|
||||||
btnGenerate.disabled = !(hasBasename && hasCwd);
|
if (btnGenerate) {
|
||||||
|
btnGenerate.disabled = !(hasBasename && hasCwd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load sensible default working directory on startup
|
// Load sensible default working directory on startup
|
||||||
invoke("get_default_working_dir")
|
invoke("get_default_working_dir")
|
||||||
.then((defaultDir) => {
|
.then((defaultDir) => {
|
||||||
if (defaultDir && !currentWorkingDir) {
|
if (defaultDir) {
|
||||||
currentWorkingDir = defaultDir;
|
currentWorkingDir = defaultDir;
|
||||||
selectedPathDisplay.textContent = `Directory: ${currentWorkingDir}`;
|
if (selectedPathDisplay) {
|
||||||
|
selectedPathDisplay.textContent = `Directory: ${currentWorkingDir}`;
|
||||||
|
}
|
||||||
updateGenerateButton();
|
updateGenerateButton();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -40,143 +42,167 @@ export function initTargen() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Handle patch count preset changes
|
// Handle patch count preset changes
|
||||||
patchCountPreset.addEventListener("change", (e) => {
|
if (patchCountPreset && patchCountCustom) {
|
||||||
if (e.target.value === "custom") {
|
patchCountPreset.addEventListener("change", (e) => {
|
||||||
patchCountCustom.classList.remove("hidden");
|
if (e.target.value === "custom") {
|
||||||
} else {
|
patchCountCustom.classList.remove("hidden");
|
||||||
patchCountCustom.classList.add("hidden");
|
} else {
|
||||||
}
|
patchCountCustom.classList.add("hidden");
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Enable generate button only when both basename and directory are valid
|
// Enable generate button when basename is input
|
||||||
targetBasename.addEventListener("input", () => {
|
if (targetBasename) {
|
||||||
updateGenerateButton();
|
targetBasename.addEventListener("input", () => {
|
||||||
});
|
if (!currentWorkingDir) {
|
||||||
|
invoke("get_default_working_dir")
|
||||||
// Browse button opens save dialog
|
.then((defaultDir) => {
|
||||||
btnBrowse.addEventListener("click", async () => {
|
if (defaultDir) {
|
||||||
try {
|
currentWorkingDir = defaultDir;
|
||||||
let filePath = await save({
|
if (selectedPathDisplay) {
|
||||||
title: "Save Target File As...",
|
selectedPathDisplay.textContent = `Directory: ${currentWorkingDir}`;
|
||||||
filters: [{
|
}
|
||||||
name: "ArgyllCMS Target",
|
}
|
||||||
extensions: ["ti1"]
|
updateGenerateButton();
|
||||||
}]
|
})
|
||||||
});
|
.catch(() => updateGenerateButton());
|
||||||
|
} else {
|
||||||
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}`;
|
|
||||||
updateGenerateButton();
|
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
|
// Generate button clicks
|
||||||
btnGenerate.addEventListener("click", async () => {
|
if (btnGenerate) {
|
||||||
const basename = targetBasename.value.trim();
|
btnGenerate.addEventListener("click", async () => {
|
||||||
if (!currentWorkingDir || !basename) {
|
const basename = targetBasename ? targetBasename.value.trim() : "";
|
||||||
logPre.textContent = "[ERROR] Please specify both a target basename and a working directory.\n";
|
if (!currentWorkingDir || !basename) {
|
||||||
logContainer.open = true;
|
if (logPre) {
|
||||||
logContainer.classList.remove("hidden");
|
logPre.textContent = "[ERROR] Please specify both a target basename and a working directory.\n";
|
||||||
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 (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) => {
|
const config = {
|
||||||
if (event.payload.id === processId && event.payload.line) {
|
colour_space: colourSpace,
|
||||||
logPre.textContent += "ERR: " + event.payload.line + "\n";
|
patch_count: patchCount,
|
||||||
logPre.scrollTop = logPre.scrollHeight;
|
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) => {
|
try {
|
||||||
if (event.payload.id === processId) {
|
const processId = `targen_${basename}`;
|
||||||
unlistenStdout();
|
|
||||||
unlistenStderr();
|
|
||||||
unlistenExit();
|
|
||||||
|
|
||||||
if (event.payload.code === 0) {
|
const unlistenStdout = await listen("process:stdout", (event) => {
|
||||||
logPre.textContent += "\n[SUCCESS] Targen completed successfully.\n";
|
if (event.payload.id === processId && event.payload.line && logPre) {
|
||||||
btnGenerate.disabled = false;
|
logPre.textContent += event.payload.line + "\n";
|
||||||
wizardState.setTarget(basename, currentWorkingDir);
|
logPre.scrollTop = logPre.scrollHeight;
|
||||||
setStage1Result(basename, currentWorkingDir);
|
|
||||||
advanceToStage2();
|
|
||||||
} else {
|
|
||||||
logPre.textContent += `\n[ERROR] Targen exited with code ${event.payload.code}.\n`;
|
|
||||||
btnGenerate.disabled = false;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
logPre.textContent = "Starting targen...\n";
|
const unlistenStderr = await listen("process:stderr", (event) => {
|
||||||
await invoke("run_targen", { config });
|
if (event.payload.id === processId && event.payload.line && logPre) {
|
||||||
|
logPre.textContent += "ERR: " + event.payload.line + "\n";
|
||||||
|
logPre.scrollTop = logPre.scrollHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
} catch (err) {
|
const unlistenExit = await listen("process:exit", (event) => {
|
||||||
logPre.textContent += `\n[INVOKE ERROR] ${err}\n`;
|
if (event.payload.id === processId) {
|
||||||
btnGenerate.disabled = false;
|
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() {
|
function advanceToStage2() {
|
||||||
@@ -184,11 +210,11 @@ function advanceToStage2() {
|
|||||||
const stages = document.querySelectorAll('.stage');
|
const stages = document.querySelectorAll('.stage');
|
||||||
|
|
||||||
// Update stepper
|
// Update stepper
|
||||||
steps.forEach(s => s.classList.remove('active'));
|
steps.forEach((s) => s.classList.remove('active'));
|
||||||
if (steps[1]) steps[1].classList.add('active');
|
if (steps[1]) steps[1].classList.add('active');
|
||||||
|
|
||||||
// Update sections
|
// Update sections
|
||||||
stages.forEach(s => {
|
stages.forEach((s) => {
|
||||||
s.classList.remove('active');
|
s.classList.remove('active');
|
||||||
s.classList.add('hidden');
|
s.classList.add('hidden');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user