diff --git a/bicorder-app/public/favicon.svg b/bicorder-app/public/favicon.svg new file mode 100644 index 0000000..8b5fd82 --- /dev/null +++ b/bicorder-app/public/favicon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + # + diff --git a/bicorder-app/public/icon-192.png b/bicorder-app/public/icon-192.png new file mode 100644 index 0000000..e451b6d Binary files /dev/null and b/bicorder-app/public/icon-192.png differ diff --git a/bicorder-app/public/icon-512.png b/bicorder-app/public/icon-512.png new file mode 100644 index 0000000..96e1ecd Binary files /dev/null and b/bicorder-app/public/icon-512.png differ diff --git a/bicorder-app/src/App.svelte b/bicorder-app/src/App.svelte index c05934a..8daa13b 100644 --- a/bicorder-app/src/App.svelte +++ b/bicorder-app/src/App.svelte @@ -14,6 +14,86 @@ data.metadata.timestamp = new Date().toISOString(); } + // View mode and navigation state + type ViewMode = 'focused' | 'list'; + let viewMode: ViewMode = 'focused'; // Focused is default + let currentScreen = 0; + let refreshKey = 0; // Used to force component refresh in focused mode + + // Screen types + type Screen = + | { type: 'metadata' } + | { type: 'gradient'; setIndex: number; gradientIndex: number; gradient: Gradient; setName: string } + | { type: 'analysis'; index: number; gradient: AnalysisGradient } + | { type: 'export' }; + + // Calculate all screens based on current shortform setting + function calculateScreens(): Screen[] { + const screens: Screen[] = []; + + // Metadata screen + screens.push({ type: 'metadata' }); + + // Diagnostic gradient screens + data.diagnostic.forEach((diagnosticSet, setIndex) => { + diagnosticSet.gradients.forEach((gradient, gradientIndex) => { + if (!data.metadata.shortform || gradient.shortform) { + screens.push({ + type: 'gradient', + setIndex, + gradientIndex, + gradient, + setName: diagnosticSet.set_name + }); + } + }); + }); + + // Analysis screens (not in shortform) + if (!data.metadata.shortform) { + data.analysis.forEach((gradient, index) => { + screens.push({ type: 'analysis', index, gradient }); + }); + } + + // Export screen + screens.push({ type: 'export' }); + + return screens; + } + + $: screens = calculateScreens(); + $: currentScreenData = screens[currentScreen]; + $: totalScreens = screens.length; + + function goToNextScreen() { + if (currentScreen < totalScreens - 1) { + currentScreen++; + } + } + + function goToPrevScreen() { + if (currentScreen > 0) { + currentScreen--; + } + } + + function toggleViewMode() { + viewMode = viewMode === 'focused' ? 'list' : 'focused'; + } + + // Generate ASCII progress bar + function generateProgressBar(current: number, total: number): string { + const filled = '#'; + const empty = '-'; + const barLength = Math.min(total, 20); // Cap at 20 characters for display + const filledCount = Math.round((current / total) * barLength); + const emptyCount = barLength - filledCount; + return filled.repeat(filledCount) + empty.repeat(emptyCount); + } + + $: progressBar = generateProgressBar(currentScreen + 1, totalScreens); + // Load saved state from localStorage onMount(() => { const saved = localStorage.getItem('bicorder-state'); @@ -45,6 +125,27 @@ console.error('Failed to load saved state:', e); } } + + // Keyboard navigation for focused mode + function handleKeyDown(e: KeyboardEvent) { + if (viewMode !== 'focused') return; + + // Only navigate if not typing in an input + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { + return; + } + + if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { + e.preventDefault(); + goToNextScreen(); + } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { + e.preventDefault(); + goToPrevScreen(); + } + } + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); }); // Save state whenever it changes @@ -117,57 +218,181 @@
Protocol
BICORDER
+
- + {#if viewMode === 'list'} + + - {#each data.diagnostic as diagnosticSet, setIndex} -
-
{diagnosticSet.set_name.toUpperCase()}
-
{diagnosticSet.set_description}
+ {#each data.diagnostic as diagnosticSet, setIndex} +
+
{diagnosticSet.set_name.toUpperCase()}
+
{diagnosticSet.set_description}
- {#each diagnosticSet.gradients as gradient, gradientIndex} - {#if !data.metadata.shortform || gradient.shortform} - { - data.diagnostic[setIndex].gradients[gradientIndex].value = e.detail ?? null; + {#each diagnosticSet.gradients as gradient, gradientIndex} + {#if !data.metadata.shortform || gradient.shortform} + { + data.diagnostic[setIndex].gradients[gradientIndex].value = e.detail ?? null; + data = data; + }} + on:notes={(e) => { + data.diagnostic[setIndex].gradients[gradientIndex].notes = e.detail; + data = data; + }} + /> + {/if} + {/each} +
+ {/each} + +
+
ANALYSIS
+ + {#each data.analysis as analysisItem, index} + { + if (!analysisItem.automated) { + data.analysis[index].value = e.detail ?? null; data = data; - }} - on:notes={(e) => { - data.diagnostic[setIndex].gradients[gradientIndex].notes = e.detail; - data = data; - }} - /> - {/if} + } + }} + on:notes={(e) => { + data.analysis[index].notes = e.detail; + data = data; + }} + /> {/each}
- {/each} -
-
ANALYSIS
+ - {#each data.analysis as analysisItem, index} - { - if (!analysisItem.automated) { - data.analysis[index].value = e.detail ?? null; - data = data; - } - }} - on:notes={(e) => { - data.analysis[index].notes = e.detail; - data = data; - }} - /> - {/each} -
+ {:else} + +
+ {#if currentScreenData.type === 'metadata'} +
+ +
- + {:else if currentScreenData.type === 'gradient'} + {@const screen = currentScreenData} +
+
{screen.setName.toUpperCase()}
+ +
+
+
{screen.gradient.term_left}
+
{screen.gradient.term_left_description}
+
+ + {#key refreshKey} + { + data.diagnostic[screen.setIndex].gradients[screen.gradientIndex].value = e.detail ?? null; + data = data; + refreshKey++; // Force component refresh + }} + on:notes={(e) => { + data.diagnostic[screen.setIndex].gradients[screen.gradientIndex].notes = e.detail; + data = data; + refreshKey++; // Force component refresh + }} + /> + {/key} + +
+
{screen.gradient.term_right}
+
{screen.gradient.term_right_description}
+
+
+
+ + {:else if currentScreenData.type === 'analysis'} + {@const screen = currentScreenData} +
+
ANALYSIS
+ +
+
+
{screen.gradient.term_left}
+
{screen.gradient.term_left_description}
+
+ + {#key refreshKey} + { + if (!screen.gradient.automated) { + data.analysis[screen.index].value = e.detail ?? null; + data = data; + refreshKey++; // Force component refresh + } + }} + on:notes={(e) => { + data.analysis[screen.index].notes = e.detail; + data = data; + refreshKey++; // Force component refresh + }} + /> + {/key} + +
+
{screen.gradient.term_right}
+
{screen.gradient.term_right_description}
+
+
+
+ + {:else if currentScreenData.type === 'export'} +
+ +
+ {/if} +
+ + +
+ + +
+
{progressBar}
+
{currentScreen + 1} / {totalScreens}
+
+
+ {/if} diff --git a/bicorder-app/src/components/AnalysisDisplay.svelte b/bicorder-app/src/components/AnalysisDisplay.svelte index 716ba78..d40e1f2 100644 --- a/bicorder-app/src/components/AnalysisDisplay.svelte +++ b/bicorder-app/src/components/AnalysisDisplay.svelte @@ -4,6 +4,7 @@ import Tooltip from './Tooltip.svelte'; export let gradient: AnalysisGradient; + export let focusedMode: boolean = false; const dispatch = createEventDispatcher<{ change: number | null; @@ -98,12 +99,14 @@
-
-
- - {gradient.term_left} - -
+
+ {#if !focusedMode} +
+ + {gradient.term_left} + +
+ {/if}
-
- - {gradient.term_right} - -
+ {#if !focusedMode} +
+ + {gradient.term_right} + +
+ {/if}
@@ -200,6 +205,15 @@ margin-bottom: 0.5rem; } + .gradient-row.focused { + display: flex; + justify-content: center; + } + + .gradient-row.focused .bar-container { + width: 100%; + } + .term { text-align: center; font-size: 0.9rem; @@ -257,6 +271,11 @@ font-size: 0.9rem; } + .analysis-gradient .value-display { + margin-top: 0.5rem; + margin-bottom: 0.25rem; + } + .auto-label { opacity: 0.6; font-size: 0.8rem; diff --git a/bicorder-app/src/components/ExportControls.svelte b/bicorder-app/src/components/ExportControls.svelte index 427173c..b501a16 100644 --- a/bicorder-app/src/components/ExportControls.svelte +++ b/bicorder-app/src/components/ExportControls.svelte @@ -3,6 +3,7 @@ import type { BicorderState } from '../types'; export let data: BicorderState; + export let focusedMode: boolean = false; const dispatch = createEventDispatcher<{ reset: void; @@ -121,7 +122,7 @@ } -
+
-
- - {gradient.term_right} - -
+ {#if !focusedMode} +
+ + {gradient.term_right} + +
+ {/if}
@@ -179,6 +184,15 @@ margin-bottom: 0.5rem; } + .gradient-row.focused { + display: flex; + justify-content: center; + } + + .gradient-row.focused .bar-container { + width: 100%; + } + .term { text-align: center; font-size: 0.9rem; @@ -231,6 +245,11 @@ font-size: 0.9rem; } + .gradient-container .value-display { + margin-top: 0.5rem; + margin-bottom: 0.25rem; + } + .controls { margin-top: 0.5rem; display: flex;