feat(wizard): gate stage navigation on disk artefacts and remove test_target fallbacks (v0.1.15) #75
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "iccery",
|
||||
"private": true,
|
||||
"version": "0.1.14",
|
||||
"version": "0.1.15",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"tauri": "tauri"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "iccery"
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
description = "Modern Printer Profiling UI frontend for ArgyllCMS"
|
||||
authors = ["Gordon"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -122,6 +122,43 @@ pub fn resolve_profile_extension(cwd: &str, basename: &str) -> (String, std::pat
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct StageStatus {
|
||||
pub stage1_complete: bool,
|
||||
pub stage2_complete: bool,
|
||||
pub stage3_complete: bool,
|
||||
pub stage4_complete: bool,
|
||||
pub profile_path: Option<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn verify_stage_artefacts(cwd: String, basename: String) -> StageStatus {
|
||||
if cwd.trim().is_empty() || basename.trim().is_empty() {
|
||||
return StageStatus {
|
||||
stage1_complete: false,
|
||||
stage2_complete: false,
|
||||
stage3_complete: false,
|
||||
stage4_complete: false,
|
||||
profile_path: None,
|
||||
};
|
||||
}
|
||||
|
||||
let base_path = std::path::Path::new(&cwd);
|
||||
let ti1 = base_path.join(format!("{}.ti1", basename)).exists();
|
||||
let ti2 = base_path.join(format!("{}.ti2", basename)).exists();
|
||||
let ti3 = base_path.join(format!("{}.ti3", basename)).exists();
|
||||
let (_, prof_p) = resolve_profile_extension(&cwd, &basename);
|
||||
let prof_exists = prof_p.exists();
|
||||
|
||||
StageStatus {
|
||||
stage1_complete: ti1,
|
||||
stage2_complete: ti2,
|
||||
stage3_complete: ti3,
|
||||
stage4_complete: prof_exists,
|
||||
profile_path: if prof_exists { Some(prof_p.to_string_lossy().to_string()) } else { None },
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_profile_path(cwd: String, basename: String) -> String {
|
||||
let (_, path) = resolve_profile_extension(&cwd, &basename);
|
||||
|
||||
@@ -19,6 +19,7 @@ pub fn run() {
|
||||
commands::resolve_binary,
|
||||
commands::detect_instruments,
|
||||
commands::get_profile_path,
|
||||
commands::verify_stage_artefacts,
|
||||
commands::extract_gamut,
|
||||
commands::run_targen,
|
||||
commands::run_printtarg,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "ICCery",
|
||||
"version": "0.1.14",
|
||||
"version": "0.1.15",
|
||||
"identifier": "com.gronod.iccery",
|
||||
"build": {
|
||||
"frontendDist": "../src"
|
||||
|
||||
@@ -5,6 +5,7 @@ import { initColprof } from './colprof.js';
|
||||
import { initProfcheck } from './profcheck.js';
|
||||
import { initSettings } from './settings.js';
|
||||
import { initGamutViewer } from './gamut_viewer.js';
|
||||
import { wizardState } from './state.js';
|
||||
|
||||
const { invoke } = window.__TAURI__.core;
|
||||
|
||||
@@ -13,8 +14,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const steps = document.querySelectorAll('.step');
|
||||
const stages = document.querySelectorAll('.stage');
|
||||
|
||||
// Initialize gating on load
|
||||
wizardState.updateGating();
|
||||
|
||||
steps.forEach(step => {
|
||||
step.addEventListener('click', () => {
|
||||
if (step.classList.contains('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetStep = step.getAttribute('data-step');
|
||||
|
||||
// Update UI
|
||||
|
||||
+16
-7
@@ -2,6 +2,7 @@ const { invoke } = window.__TAURI__.core;
|
||||
const { listen } = window.__TAURI__.event;
|
||||
import { startSwatchListener, stopSwatchListener } from './swatch_grid.js';
|
||||
import { setStage3Result } from './colprof.js';
|
||||
import { wizardState } from './state.js';
|
||||
|
||||
// Module-level state: set by Stage 2 when it completes
|
||||
let stage2Basename = "";
|
||||
@@ -11,8 +12,9 @@ let stage2Cwd = "";
|
||||
* Called by printtarg.js after Stage 2 completes.
|
||||
*/
|
||||
export function setStage2Result(basename, cwd) {
|
||||
stage2Basename = basename;
|
||||
stage2Cwd = cwd;
|
||||
stage2Basename = basename || wizardState.basename;
|
||||
stage2Cwd = cwd || wizardState.cwd;
|
||||
wizardState.setTarget(stage2Basename, stage2Cwd);
|
||||
}
|
||||
|
||||
// State machine states
|
||||
@@ -84,11 +86,17 @@ export function initChartread() {
|
||||
// Start reading button
|
||||
if (btnStartRead) {
|
||||
btnStartRead.addEventListener("click", async () => {
|
||||
if (!stage2Basename) {
|
||||
const basename = stage2Basename || wizardState.basename;
|
||||
const cwd = stage2Cwd || wizardState.cwd;
|
||||
|
||||
if (!basename || !cwd) {
|
||||
setPrompt("Error: No .ti2 file available. Complete Stage 2 first.");
|
||||
return;
|
||||
}
|
||||
|
||||
stage2Basename = basename;
|
||||
stage2Cwd = cwd;
|
||||
|
||||
logPre.textContent = "";
|
||||
logContainer.open = false;
|
||||
logContainer.classList.remove("hidden");
|
||||
@@ -96,11 +104,11 @@ export function initChartread() {
|
||||
setPrompt("Starting chartread... waiting for instrument calibration prompt.");
|
||||
|
||||
const config = {
|
||||
basename: stage2Basename,
|
||||
cwd: stage2Cwd,
|
||||
basename: basename,
|
||||
cwd: cwd,
|
||||
};
|
||||
|
||||
currentProcessId = `chartread_${stage2Basename}`;
|
||||
currentProcessId = `chartread_${basename}`;
|
||||
|
||||
// Start swatch grid listener
|
||||
await startSwatchListener(currentProcessId);
|
||||
@@ -152,7 +160,8 @@ export function initChartread() {
|
||||
setState(STATE.FINISHED);
|
||||
setPrompt("✅ Measurement complete! .ti3 file has been saved.");
|
||||
logPre.textContent += "\n[SUCCESS] chartread completed. .ti3 file written.\n";
|
||||
setStage3Result(stage2Basename, stage2Cwd);
|
||||
wizardState.setTarget(basename, cwd);
|
||||
setStage3Result(basename, cwd);
|
||||
advanceToStage4();
|
||||
} else {
|
||||
setState(STATE.FINISHED);
|
||||
|
||||
+18
-4
@@ -2,6 +2,7 @@ const { invoke } = window.__TAURI__.core;
|
||||
const { listen } = window.__TAURI__.event;
|
||||
import { setStage4Result } from './profcheck.js';
|
||||
import { loadGamutMesh } from './gamut_viewer.js';
|
||||
import { wizardState } from './state.js';
|
||||
|
||||
let chartreadBasename = "";
|
||||
let chartreadCwd = "";
|
||||
@@ -10,8 +11,9 @@ let chartreadCwd = "";
|
||||
* Called by chartread.js after Stage 3 completes.
|
||||
*/
|
||||
export function setStage3Result(basename, cwd) {
|
||||
chartreadBasename = basename;
|
||||
chartreadCwd = cwd;
|
||||
chartreadBasename = basename || wizardState.basename;
|
||||
chartreadCwd = cwd || wizardState.cwd;
|
||||
wizardState.setTarget(chartreadBasename, chartreadCwd);
|
||||
}
|
||||
|
||||
export function initColprof() {
|
||||
@@ -31,8 +33,19 @@ export function initColprof() {
|
||||
if (!btnCreateProfile) return;
|
||||
|
||||
btnCreateProfile.addEventListener("click", async () => {
|
||||
const basename = chartreadBasename || "test_target";
|
||||
const cwd = chartreadCwd || "";
|
||||
const basename = chartreadBasename || wizardState.basename;
|
||||
const cwd = chartreadCwd || wizardState.cwd;
|
||||
|
||||
if (!basename || !cwd) {
|
||||
logPre.textContent = "[ERROR] No .ti3 measurement file available. Please complete Stage 3 first.\n";
|
||||
logContainer.open = true;
|
||||
logContainer.classList.remove("hidden");
|
||||
btnCreateProfile.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
chartreadBasename = basename;
|
||||
chartreadCwd = cwd;
|
||||
|
||||
const description = descInput.value.trim() || basename;
|
||||
|
||||
@@ -104,6 +117,7 @@ export function initColprof() {
|
||||
successInfo.textContent = `Profile: ${displayFilename} (${description})`;
|
||||
successCard.classList.remove("hidden");
|
||||
|
||||
wizardState.setTarget(basename, cwd);
|
||||
setStage4Result(basename, cwd);
|
||||
|
||||
// Automatically extract gamut mesh for 3D visualization
|
||||
|
||||
+5
-2
@@ -1,6 +1,7 @@
|
||||
const { invoke } = window.__TAURI__.core;
|
||||
const { listen } = window.__TAURI__.event;
|
||||
import { setStage2Result } from './chartread.js';
|
||||
import { wizardState } from './state.js';
|
||||
|
||||
// Module-level state: set by Stage 1 when it completes
|
||||
let stage1Basename = "";
|
||||
@@ -13,8 +14,9 @@ let discoveredPrinters = [];
|
||||
* Passes the basename and working directory forward.
|
||||
*/
|
||||
export function setStage1Result(basename, cwd) {
|
||||
stage1Basename = basename;
|
||||
stage1Cwd = cwd;
|
||||
stage1Basename = basename || wizardState.basename;
|
||||
stage1Cwd = cwd || wizardState.cwd;
|
||||
wizardState.setTarget(stage1Basename, stage1Cwd);
|
||||
}
|
||||
|
||||
export function initPrinttarg() {
|
||||
@@ -347,6 +349,7 @@ export function initPrinttarg() {
|
||||
showNotification("info", "Target pages generated. Select your destination printer below and print with color management strictly bypassed.");
|
||||
}
|
||||
|
||||
wizardState.setTarget(stage1Basename, stage1Cwd);
|
||||
setStage2Result(stage1Basename, stage1Cwd);
|
||||
} else {
|
||||
logPre.textContent += `\n[ERROR] printtarg exited with code ${event.payload.code}.\n`;
|
||||
|
||||
+17
-4
@@ -1,6 +1,7 @@
|
||||
const { invoke } = window.__TAURI__.core;
|
||||
const { listen } = window.__TAURI__.event;
|
||||
import { loadGamutMesh } from './gamut_viewer.js';
|
||||
import { wizardState } from './state.js';
|
||||
|
||||
let profileBasename = "";
|
||||
let profileCwd = "";
|
||||
@@ -9,8 +10,9 @@ let profileCwd = "";
|
||||
* Called by colprof.js after Stage 4 completes.
|
||||
*/
|
||||
export function setStage4Result(basename, cwd) {
|
||||
profileBasename = basename;
|
||||
profileCwd = cwd;
|
||||
profileBasename = basename || wizardState.basename;
|
||||
profileCwd = cwd || wizardState.cwd;
|
||||
wizardState.setTarget(profileBasename, profileCwd);
|
||||
}
|
||||
|
||||
export function initProfcheck() {
|
||||
@@ -26,8 +28,19 @@ export function initProfcheck() {
|
||||
if (!btnVerify) return;
|
||||
|
||||
btnVerify.addEventListener("click", async () => {
|
||||
const basename = profileBasename || "test_target";
|
||||
const cwd = profileCwd || "";
|
||||
const basename = profileBasename || wizardState.basename;
|
||||
const cwd = profileCwd || wizardState.cwd;
|
||||
|
||||
if (!basename || !cwd) {
|
||||
logPre.textContent = "[ERROR] No profile available to verify. Please complete Stage 4 first.\n";
|
||||
logContainer.open = true;
|
||||
logContainer.classList.remove("hidden");
|
||||
btnVerify.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
profileBasename = basename;
|
||||
profileCwd = cwd;
|
||||
|
||||
const sep = cwd.includes('\\') ? '\\' : '/';
|
||||
const ti3Path = cwd ? `${cwd}${sep}${basename}.ti3` : `${basename}.ti3`;
|
||||
|
||||
+54
-11
@@ -1,16 +1,59 @@
|
||||
// Simple state machine for the UI
|
||||
export const state = {
|
||||
const { invoke } = window.__TAURI__.core;
|
||||
|
||||
export const wizardState = {
|
||||
currentStage: 1,
|
||||
profileName: 'default_profile',
|
||||
instrument: null,
|
||||
basename: "",
|
||||
cwd: "",
|
||||
|
||||
update(newState) {
|
||||
Object.assign(this, newState);
|
||||
this.render();
|
||||
setTarget(basename, cwd) {
|
||||
if (basename) this.basename = basename;
|
||||
if (cwd) this.cwd = cwd;
|
||||
this.updateGating();
|
||||
},
|
||||
|
||||
render() {
|
||||
// Reactive UI updates go here
|
||||
console.log('State updated:', this);
|
||||
|
||||
async updateGating() {
|
||||
const steps = document.querySelectorAll('.step');
|
||||
if (!steps || steps.length === 0) return;
|
||||
|
||||
if (!this.basename || !this.cwd) {
|
||||
steps.forEach((step, idx) => {
|
||||
if (idx === 0) {
|
||||
step.classList.remove('disabled');
|
||||
} else {
|
||||
step.classList.add('disabled');
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const status = await invoke('verify_stage_artefacts', {
|
||||
cwd: this.cwd,
|
||||
basename: this.basename,
|
||||
});
|
||||
|
||||
// Step 1: always accessible
|
||||
// Step 2: unlocked if .ti1 exists
|
||||
// Step 3: unlocked if .ti2 exists
|
||||
// Step 4: unlocked if .ti3 exists
|
||||
// Step 5: unlocked if profile exists
|
||||
const unlocked = [
|
||||
true,
|
||||
status.stage1_complete,
|
||||
status.stage2_complete,
|
||||
status.stage3_complete,
|
||||
status.stage4_complete,
|
||||
];
|
||||
|
||||
steps.forEach((step, idx) => {
|
||||
if (unlocked[idx]) {
|
||||
step.classList.remove('disabled');
|
||||
} else {
|
||||
step.classList.add('disabled');
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn("Could not verify stage artefacts:", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+2
-2
@@ -2,6 +2,7 @@ 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';
|
||||
|
||||
export function initTargen() {
|
||||
const colourSpaceRadios = document.querySelectorAll('input[name="colourSpace"]');
|
||||
@@ -157,9 +158,8 @@ export function initTargen() {
|
||||
|
||||
if (event.payload.code === 0) {
|
||||
logPre.textContent += "\n[SUCCESS] Targen completed successfully.\n";
|
||||
// In a real app we'd dispatch an event to advance the stepper here.
|
||||
// For now, we'll manually unlock stage 2 in the state.
|
||||
btnGenerate.disabled = false;
|
||||
wizardState.setTarget(basename, currentWorkingDir);
|
||||
setStage1Result(basename, currentWorkingDir);
|
||||
advanceToStage2();
|
||||
} else {
|
||||
|
||||
@@ -93,6 +93,15 @@ body {
|
||||
border-left: 4px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.step.disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.step.disabled:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 24px 32px;
|
||||
|
||||
Reference in New Issue
Block a user