feat(ui): split setup and running views
Some checks failed
Desktop Release / Bootstrap updater signing key (push) Has been cancelled
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB + AppImage (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled
Some checks failed
Desktop Release / Bootstrap updater signing key (push) Has been cancelled
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB + AppImage (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled
This commit is contained in:
parent
cc7c5df011
commit
481b1c181c
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fieldlens/print",
|
"name": "@fieldlens/print",
|
||||||
"version": "0.1.15",
|
"version": "0.1.16",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fieldlens-print"
|
name = "fieldlens-print"
|
||||||
version = "0.1.15"
|
version = "0.1.16"
|
||||||
description = "FieldLens print-stream barcode capture companion"
|
description = "FieldLens print-stream barcode capture companion"
|
||||||
authors = ["Arrowstack"]
|
authors = ["Arrowstack"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "FieldLens Print",
|
"productName": "FieldLens Print",
|
||||||
"version": "0.1.15",
|
"version": "0.1.16",
|
||||||
"identifier": "dev.arrowstack.fieldlens.print",
|
"identifier": "dev.arrowstack.fieldlens.print",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
|
|||||||
376
src/main.ts
376
src/main.ts
@ -85,7 +85,7 @@ let config: AppConfig
|
|||||||
let jobs: Job[] = []
|
let jobs: Job[] = []
|
||||||
let status: Status
|
let status: Status
|
||||||
let loginTimer: number | undefined
|
let loginTimer: number | undefined
|
||||||
let currentVersion = '0.1.15'
|
let currentVersion = '0.1.16'
|
||||||
let bundleType: BundleType | null = null
|
let bundleType: BundleType | null = null
|
||||||
let availableUpdate: Update | null = null
|
let availableUpdate: Update | null = null
|
||||||
let availableManualUpdate: ManualUpdate | null = null
|
let availableManualUpdate: ManualUpdate | null = null
|
||||||
@ -93,6 +93,7 @@ let updateBusy = false
|
|||||||
let captureError: string | null = null
|
let captureError: string | null = null
|
||||||
let systemPrinters: SystemPrinter[] = []
|
let systemPrinters: SystemPrinter[] = []
|
||||||
let virtualPrinterStatus: VirtualPrinterStatus | null = null
|
let virtualPrinterStatus: VirtualPrinterStatus | null = null
|
||||||
|
let activeView: 'setup' | 'running' = 'setup'
|
||||||
|
|
||||||
app.innerHTML = `
|
app.innerHTML = `
|
||||||
<div class="shell">
|
<div class="shell">
|
||||||
@ -104,155 +105,194 @@ app.innerHTML = `
|
|||||||
<p class="subtitle">Capture and inspect print jobs before the printer driver renders them.</p>
|
<p class="subtitle">Capture and inspect print jobs before the printer driver renders them.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="status-pill" class="status"><span class="dot"></span><span>Loading</span></div>
|
|
||||||
|
<div class="topbar-actions">
|
||||||
|
<nav class="view-tabs" aria-label="FieldLens Print view">
|
||||||
|
<button class="view-tab active" id="setup-tab" type="button">Setup</button>
|
||||||
|
<button class="view-tab" id="running-tab" type="button">Running</button>
|
||||||
|
</nav>
|
||||||
|
<div id="status-pill" class="status"><span class="dot"></span><span>Loading</span></div>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="grid">
|
<main>
|
||||||
<section class="card route-card">
|
<section id="setup-view" class="app-view">
|
||||||
<div class="card-head">
|
<div class="grid">
|
||||||
<div>
|
<section class="card route-card">
|
||||||
<span class="eyebrow">PRINTER ROUTING</span>
|
<div class="card-head">
|
||||||
<h2>Destination</h2>
|
|
||||||
</div>
|
|
||||||
<button class="icon-btn" id="refresh-printers" title="Refresh printers" aria-label="Refresh printers">↻</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
<label class="field-label">Physical printer
|
|
||||||
<select id="forwardPrinter">
|
|
||||||
<option value="">Choose a printer…</option>
|
|
||||||
</select>
|
|
||||||
<span id="forward-printer-help" class="help">Choose the printer that should receive production jobs.</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="route-tiles">
|
|
||||||
<div class="route-tile">
|
|
||||||
<div class="route-icon live-route">P</div>
|
|
||||||
<div>
|
<div>
|
||||||
<strong>FieldLens Print</strong>
|
<span class="eyebrow">PRINTER ROUTING</span>
|
||||||
<span>Capture + physical print</span>
|
<h2>Destination</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="icon-btn" id="refresh-printers" title="Refresh printers" aria-label="Refresh printers">↻</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="route-tile">
|
|
||||||
<div class="route-icon test-route">T</div>
|
|
||||||
<div>
|
|
||||||
<strong>FieldLens Test Capture</strong>
|
|
||||||
<span>Full pipeline, no paper</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="printer-state">
|
<div class="card-body">
|
||||||
<span class="state-dot"></span>
|
<label class="field-label">Physical printer
|
||||||
<div>
|
<select id="forwardPrinter">
|
||||||
<strong>System printer</strong>
|
<option value="">Choose a printer…</option>
|
||||||
<span id="virtual-printer-state">Checking FieldLens queues…</span>
|
</select>
|
||||||
</div>
|
<span id="forward-printer-help" class="help">Choose the printer that should receive production jobs.</span>
|
||||||
</div>
|
</label>
|
||||||
|
|
||||||
<div class="actions printer-actions">
|
<div class="route-tiles">
|
||||||
<button class="btn primary" id="install-printer">Create / repair printers</button>
|
<div class="route-tile">
|
||||||
<button class="btn ghost" id="self-test">Run capture self-test</button>
|
<div class="route-icon live-route">P</div>
|
||||||
<button class="btn ghost danger" id="remove-printer">Remove</button>
|
<div>
|
||||||
</div>
|
<strong>FieldLens Print</strong>
|
||||||
|
<span>Capture + physical print</span>
|
||||||
<details class="advanced">
|
</div>
|
||||||
<summary>Advanced capture settings</summary>
|
</div>
|
||||||
<div class="advanced-body">
|
<div class="route-tile">
|
||||||
<div class="fields">
|
<div class="route-icon test-route">T</div>
|
||||||
<label>Capture mode
|
<div>
|
||||||
<select id="mode">
|
<strong>FieldLens Test Capture</strong>
|
||||||
<option value="raw-proxy">RAW 9100 proxy</option>
|
<span>Full pipeline, no paper</span>
|
||||||
<option value="spool-folder">Spool folder</option>
|
</div>
|
||||||
<option value="both">Both routes</option>
|
</div>
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>Listen address
|
|
||||||
<input id="listenAddr" placeholder="127.0.0.1:9100" />
|
|
||||||
</label>
|
|
||||||
<label>RAW/TCP destination
|
|
||||||
<input id="forwardAddr" placeholder="10.0.0.50:9100" />
|
|
||||||
</label>
|
|
||||||
<label>Spool capture folder
|
|
||||||
<input id="spoolDir" />
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions compact-actions">
|
<div class="printer-state">
|
||||||
<button class="btn ghost" id="save">Save settings</button>
|
<span class="state-dot"></span>
|
||||||
<button class="btn ghost" id="start">Start capture</button>
|
|
||||||
<button class="btn ghost danger" id="stop">Stop capture</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="card activity-card">
|
|
||||||
<div class="card-head">
|
|
||||||
<div>
|
|
||||||
<span class="eyebrow">ACTIVITY</span>
|
|
||||||
<h2>FieldLens</h2>
|
|
||||||
</div>
|
|
||||||
<span id="job-hint" class="muted">Waiting for capture</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="metrics">
|
|
||||||
<div class="metric"><b id="job-count">0</b><span>Jobs</span></div>
|
|
||||||
<div class="metric"><b id="code-count">0</b><span>Codes</span></div>
|
|
||||||
<div class="metric"><b id="pending-count">0</b><span>Pending</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="account-row">
|
|
||||||
<div>
|
|
||||||
<strong>FieldLens account</strong>
|
|
||||||
<span id="login-state" class="muted">Not signed in.</span>
|
|
||||||
<div id="login-code" class="login-code"></div>
|
|
||||||
</div>
|
|
||||||
<div class="inline-actions">
|
|
||||||
<button class="btn ghost" id="sync">Sync</button>
|
|
||||||
<button class="btn blue" id="login">Sign in</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<details class="advanced">
|
|
||||||
<summary>Connection & updates</summary>
|
|
||||||
<div class="advanced-body">
|
|
||||||
<div class="fields">
|
|
||||||
<label class="full">FieldLens API<input id="apiBaseUrl" /></label>
|
|
||||||
<label class="full">Keycloak issuer<input id="issuer" /></label>
|
|
||||||
<label>Desktop client<input id="clientId" /></label>
|
|
||||||
<label>Station ID<input id="stationId" placeholder="optional" /></label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="update-box">
|
|
||||||
<div>
|
<div>
|
||||||
<strong>App updates</strong>
|
<strong>System printer</strong>
|
||||||
<span id="version-label" class="muted">Version …</span>
|
<span id="virtual-printer-state">Checking FieldLens queues…</span>
|
||||||
<small id="update-state" class="muted">Checking for updates shortly after startup.</small>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions printer-actions">
|
||||||
|
<button class="btn primary" id="install-printer">Create / repair printers</button>
|
||||||
|
<button class="btn ghost" id="self-test">Run capture self-test</button>
|
||||||
|
<button class="btn ghost danger" id="remove-printer">Remove</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card settings-card">
|
||||||
|
<div class="card-head">
|
||||||
|
<div>
|
||||||
|
<span class="eyebrow">SETTINGS</span>
|
||||||
|
<h2>FieldLens Print</h2>
|
||||||
|
</div>
|
||||||
|
<span id="settings-state" class="muted">Local configuration</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="account-row settings-account">
|
||||||
|
<div>
|
||||||
|
<strong>FieldLens account</strong>
|
||||||
|
<span id="login-state" class="muted">Not signed in.</span>
|
||||||
|
<div id="login-code" class="login-code"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-actions">
|
<div class="inline-actions">
|
||||||
<button class="btn ghost" id="check-update">Check</button>
|
<button class="btn ghost" id="sync">Sync</button>
|
||||||
<button class="btn blue" id="install-update" hidden>Install</button>
|
<button class="btn blue" id="login">Sign in</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="update-progress" class="update-progress" hidden><span></span></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<details class="advanced" open>
|
||||||
|
<summary>Capture settings</summary>
|
||||||
|
<div class="advanced-body">
|
||||||
|
<div class="fields">
|
||||||
|
<label>Capture mode
|
||||||
|
<select id="mode">
|
||||||
|
<option value="raw-proxy">RAW 9100 proxy</option>
|
||||||
|
<option value="spool-folder">Spool folder</option>
|
||||||
|
<option value="both">Both routes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>Listen address
|
||||||
|
<input id="listenAddr" placeholder="127.0.0.1:9100" />
|
||||||
|
</label>
|
||||||
|
<label>RAW/TCP destination
|
||||||
|
<input id="forwardAddr" placeholder="10.0.0.50:9100" />
|
||||||
|
</label>
|
||||||
|
<label>Spool capture folder
|
||||||
|
<input id="spoolDir" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions compact-actions">
|
||||||
|
<button class="btn ghost" id="save">Save settings</button>
|
||||||
|
<button class="btn blue" id="start">Start capture</button>
|
||||||
|
<button class="btn ghost danger" id="stop">Stop capture</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="advanced">
|
||||||
|
<summary>Connection & updates</summary>
|
||||||
|
<div class="advanced-body">
|
||||||
|
<div class="fields">
|
||||||
|
<label class="full">FieldLens API<input id="apiBaseUrl" /></label>
|
||||||
|
<label class="full">Keycloak issuer<input id="issuer" /></label>
|
||||||
|
<label>Desktop client<input id="clientId" /></label>
|
||||||
|
<label>Station ID<input id="stationId" placeholder="optional" /></label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="update-box">
|
||||||
|
<div>
|
||||||
|
<strong>App updates</strong>
|
||||||
|
<span id="version-label" class="muted">Version …</span>
|
||||||
|
<small id="update-state" class="muted">Checking for updates shortly after startup.</small>
|
||||||
|
</div>
|
||||||
|
<div class="inline-actions">
|
||||||
|
<button class="btn ghost" id="check-update">Check</button>
|
||||||
|
<button class="btn blue" id="install-update" hidden>Install</button>
|
||||||
|
</div>
|
||||||
|
<div id="update-progress" class="update-progress" hidden><span></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card jobs-card">
|
<section id="running-view" class="app-view" hidden>
|
||||||
<div class="card-head">
|
<div class="running-grid">
|
||||||
<div>
|
<section class="card running-summary-card">
|
||||||
<span class="eyebrow">RECENT ACTIVITY</span>
|
<div class="card-head">
|
||||||
<h2>Print jobs</h2>
|
<div>
|
||||||
</div>
|
<span class="eyebrow">RUNNING</span>
|
||||||
<span class="muted">Newest first</span>
|
<h2>Capture activity</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<span id="job-hint" class="muted">Waiting for capture</span>
|
||||||
<div id="jobs" class="jobs"></div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="metrics">
|
||||||
|
<div class="metric"><b id="job-count">0</b><span>Jobs</span></div>
|
||||||
|
<div class="metric"><b id="code-count">0</b><span>Codes</span></div>
|
||||||
|
<div class="metric"><b id="pending-count">0</b><span>Pending</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="running-route">
|
||||||
|
<div>
|
||||||
|
<span class="eyebrow">PHYSICAL DESTINATION</span>
|
||||||
|
<strong id="running-printer">No printer selected</strong>
|
||||||
|
</div>
|
||||||
|
<div class="inline-actions">
|
||||||
|
<button class="btn ghost" id="running-self-test">Test capture</button>
|
||||||
|
<button class="btn ghost danger" id="running-stop">Stop capture</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span id="running-action-state" class="muted running-action-state"></span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card jobs-card">
|
||||||
|
<div class="card-head">
|
||||||
|
<div>
|
||||||
|
<span class="eyebrow">RECENT ACTIVITY</span>
|
||||||
|
<h2>Print jobs</h2>
|
||||||
|
</div>
|
||||||
|
<span class="muted">Newest first</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="jobs" class="jobs"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
@ -263,6 +303,14 @@ function el<T extends HTMLElement>(id: string) {
|
|||||||
return document.getElementById(id) as T
|
return document.getElementById(id) as T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setView(view: 'setup' | 'running') {
|
||||||
|
activeView = view
|
||||||
|
el('setup-view').hidden = view !== 'setup'
|
||||||
|
el('running-view').hidden = view !== 'running'
|
||||||
|
el('setup-tab').classList.toggle('active', view === 'setup')
|
||||||
|
el('running-tab').classList.toggle('active', view === 'running')
|
||||||
|
}
|
||||||
|
|
||||||
function readForm(): AppConfig {
|
function readForm(): AppConfig {
|
||||||
return {
|
return {
|
||||||
...config,
|
...config,
|
||||||
@ -304,6 +352,7 @@ function renderPrinters() {
|
|||||||
if (options.some((printer) => printer.name === current)) select.value = current
|
if (options.some((printer) => printer.name === current)) select.value = current
|
||||||
|
|
||||||
const selected = options.find((printer) => printer.name === select.value)
|
const selected = options.find((printer) => printer.name === select.value)
|
||||||
|
el('running-printer').textContent = selected?.name || config?.forwardPrinter || 'No physical printer selected'
|
||||||
const help = el('forward-printer-help')
|
const help = el('forward-printer-help')
|
||||||
if (selected?.address) {
|
if (selected?.address) {
|
||||||
help.textContent = `Detected RAW network endpoint: ${selected.address}`
|
help.textContent = `Detected RAW network endpoint: ${selected.address}`
|
||||||
@ -459,22 +508,38 @@ el('install-printer').addEventListener('click', async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button.disabled = true
|
button.disabled = true
|
||||||
state.textContent = 'Creating FieldLens Print… administrator approval may appear.'
|
let stoppedForRepair = false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
state.textContent = 'Stopping capture before printer repair…'
|
||||||
|
await invoke('stop_capture')
|
||||||
|
stoppedForRepair = true
|
||||||
|
await refreshStatus()
|
||||||
|
|
||||||
|
state.textContent = 'Repairing printers and migrating captured jobs… administrator approval may appear.'
|
||||||
virtualPrinterStatus = await invoke<VirtualPrinterStatus>('install_virtual_printer', { targetPrinter })
|
virtualPrinterStatus = await invoke<VirtualPrinterStatus>('install_virtual_printer', { targetPrinter })
|
||||||
config = await invoke<AppConfig>('save_config', { config: readForm() })
|
config = await invoke<AppConfig>('save_config', { config: readForm() })
|
||||||
await restartCapture()
|
|
||||||
|
state.textContent = 'Restarting capture and processing migrated backlog…'
|
||||||
|
const restarted = await ensureCaptureStarted()
|
||||||
|
if (!restarted) {
|
||||||
|
throw new Error(captureError || 'capture did not restart')
|
||||||
|
}
|
||||||
|
stoppedForRepair = false
|
||||||
|
|
||||||
await refreshPrinters()
|
await refreshPrinters()
|
||||||
|
setView('running')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
state.textContent = `Printer setup failed: ${String(error)}`
|
state.textContent = `Printer setup failed: ${String(error)}`
|
||||||
|
if (stoppedForRepair) {
|
||||||
|
await ensureCaptureStarted().catch(() => undefined)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
button.disabled = false
|
button.disabled = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
el('self-test').addEventListener('click', async () => {
|
async function submitCaptureSelfTest(button: HTMLButtonElement, state: HTMLElement) {
|
||||||
const button = el<HTMLButtonElement>('self-test')
|
|
||||||
const state = el('virtual-printer-state')
|
|
||||||
button.disabled = true
|
button.disabled = true
|
||||||
state.textContent = 'Submitting a no-paper capture self-test…'
|
state.textContent = 'Submitting a no-paper capture self-test…'
|
||||||
try {
|
try {
|
||||||
@ -486,6 +551,20 @@ el('self-test').addEventListener('click', async () => {
|
|||||||
} finally {
|
} finally {
|
||||||
button.disabled = false
|
button.disabled = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
el('self-test').addEventListener('click', () => {
|
||||||
|
void submitCaptureSelfTest(
|
||||||
|
el<HTMLButtonElement>('self-test'),
|
||||||
|
el('virtual-printer-state'),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
el('running-self-test').addEventListener('click', () => {
|
||||||
|
void submitCaptureSelfTest(
|
||||||
|
el<HTMLButtonElement>('running-self-test'),
|
||||||
|
el('running-action-state'),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
el('remove-printer').addEventListener('click', async () => {
|
el('remove-printer').addEventListener('click', async () => {
|
||||||
@ -512,7 +591,8 @@ el('save').addEventListener('click', async () => {
|
|||||||
|
|
||||||
el('start').addEventListener('click', async () => {
|
el('start').addEventListener('click', async () => {
|
||||||
config = await invoke<AppConfig>('save_config', { config: readForm() })
|
config = await invoke<AppConfig>('save_config', { config: readForm() })
|
||||||
await restartCapture()
|
const started = await restartCapture()
|
||||||
|
if (started) setView('running')
|
||||||
})
|
})
|
||||||
|
|
||||||
el('stop').addEventListener('click', async () => {
|
el('stop').addEventListener('click', async () => {
|
||||||
@ -521,6 +601,18 @@ el('stop').addEventListener('click', async () => {
|
|||||||
await refreshStatus()
|
await refreshStatus()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
el('setup-tab').addEventListener('click', () => setView('setup'))
|
||||||
|
el('running-tab').addEventListener('click', () => setView('running'))
|
||||||
|
|
||||||
|
el('running-stop').addEventListener('click', async () => {
|
||||||
|
const state = el('running-action-state')
|
||||||
|
state.textContent = 'Stopping capture…'
|
||||||
|
captureError = null
|
||||||
|
await invoke('stop_capture')
|
||||||
|
await refreshStatus()
|
||||||
|
state.textContent = 'Capture stopped. Open Setup to restart or change configuration.'
|
||||||
|
})
|
||||||
|
|
||||||
el('sync').addEventListener('click', async () => {
|
el('sync').addEventListener('click', async () => {
|
||||||
const result = await invoke<{ synced: number; pending: number }>('sync_pending')
|
const result = await invoke<{ synced: number; pending: number }>('sync_pending')
|
||||||
el('login-state').textContent = `Synced ${result.synced}. ${result.pending} pending.`
|
el('login-state').textContent = `Synced ${result.synced}. ${result.pending} pending.`
|
||||||
@ -710,6 +802,14 @@ writeForm(config)
|
|||||||
await refreshPrinters()
|
await refreshPrinters()
|
||||||
await ensureCaptureStarted()
|
await ensureCaptureStarted()
|
||||||
|
|
||||||
|
const readyForRunning = Boolean(
|
||||||
|
virtualPrinterStatus?.installed
|
||||||
|
&& config.forwardPrinter
|
||||||
|
&& status?.running
|
||||||
|
&& !captureError
|
||||||
|
)
|
||||||
|
setView(readyForRunning ? 'running' : 'setup')
|
||||||
|
|
||||||
currentVersion = await getVersion()
|
currentVersion = await getVersion()
|
||||||
bundleType = await getBundleType().catch(() => null)
|
bundleType = await getBundleType().catch(() => null)
|
||||||
el('version-label').textContent = `Version ${currentVersion}${bundleType ? ` · ${bundleType}` : ''}`
|
el('version-label').textContent = `Version ${currentVersion}${bundleType ? ` · ${bundleType}` : ''}`
|
||||||
|
|||||||
@ -543,3 +543,101 @@ select:focus {
|
|||||||
color: #9fcef5;
|
color: #9fcef5;
|
||||||
border-color: #294865;
|
border-color: #294865;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.topbar-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-tabs {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
border: 1px solid #202a36;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #0d131a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-tab {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
background: transparent;
|
||||||
|
color: #718094;
|
||||||
|
font-size: 10.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-tab:hover {
|
||||||
|
color: #dce4ed;
|
||||||
|
background: #151d27;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-tab.active {
|
||||||
|
background: #1a2b24;
|
||||||
|
color: #b9f3d5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-view[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card .settings-account {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running-summary-card .card-body {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running-route {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 18px;
|
||||||
|
border: 1px solid #222c38;
|
||||||
|
border-radius: 11px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #0d131a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running-route > div:first-child {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running-route strong {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running-action-state {
|
||||||
|
min-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 860px) {
|
||||||
|
.topbar-actions {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running-route {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user