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