Integrate machine connection into header with fixed-width layout

- Redesigned header with grid layout (300px fixed + flexible columns)
- Moved machine connection status to header with connection indicator
- Added disconnect button and status badge to header
- Moved connect button to "Get Started" card in left column
- Workflow stepper now always visible in dedicated column
- Fixed layout shifts by using fixed-width column and flex-shrink-0
- Connection status truncates within fixed width to prevent pushing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Henrik 2025-12-07 13:02:54 +01:00
parent 99ed1adb68
commit 7346526cdc
2 changed files with 87 additions and 40 deletions

View file

@ -1,6 +1,5 @@
import { useState, useEffect, useCallback } from 'react';
import { useBrotherMachine } from './hooks/useBrotherMachine';
import { MachineConnection } from './components/MachineConnection';
import { FileUpload } from './components/FileUpload';
import { PatternCanvas } from './components/PatternCanvas';
import { ProgressMonitor } from './components/ProgressMonitor';
@ -10,7 +9,8 @@ import { PatternSummaryCard } from './components/PatternSummaryCard';
import type { PesPatternData } from './utils/pystitchConverter';
import { pyodideLoader } from './utils/pyodideLoader';
import { hasError } from './utils/errorCodeHelpers';
import { canDeletePattern } from './utils/machineStateHelpers';
import { canDeletePattern, getStateVisualInfo } from './utils/machineStateHelpers';
import { CheckCircleIcon, BoltIcon, PauseCircleIcon, ExclamationTriangleIcon, ArrowPathIcon, XMarkIcon } from '@heroicons/react/24/solid';
import './App.css';
function App() {
@ -94,23 +94,67 @@ function App() {
}
}
// Get state visual info for header status badge
const stateVisual = getStateVisualInfo(machine.machineStatus);
const stateIcons = {
ready: CheckCircleIcon,
active: BoltIcon,
waiting: PauseCircleIcon,
complete: CheckCircleIcon,
interrupted: PauseCircleIcon,
error: ExclamationTriangleIcon,
};
const StatusIcon = stateIcons[stateVisual.iconName];
return (
<div className="min-h-screen flex flex-col bg-gray-50 dark:bg-gray-900">
<header className="bg-gradient-to-r from-blue-600 via-blue-700 to-blue-800 dark:from-blue-700 dark:via-blue-800 dark:to-blue-900 px-8 py-3 shadow-lg border-b-2 border-blue-900/20 dark:border-blue-800/30">
<div className="max-w-[1600px] mx-auto flex items-center gap-8">
<h1 className="text-xl font-bold text-white whitespace-nowrap">SKiTCH Controller</h1>
{/* Workflow Stepper - Integrated in header when connected */}
{machine.isConnected && (
<div className="flex-1">
<WorkflowStepper
machineStatus={machine.machineStatus}
isConnected={machine.isConnected}
hasPattern={pesData !== null}
patternUploaded={patternUploaded}
/>
<div className="max-w-[1600px] mx-auto grid grid-cols-[300px_1fr] gap-8 items-center">
{/* Machine Connection Status - Fixed width column */}
<div className="flex items-center gap-3 w-[300px]">
<div className="w-2.5 h-2.5 bg-green-400 rounded-full animate-pulse shadow-lg shadow-green-400/50" style={{ visibility: machine.isConnected ? 'visible' : 'hidden' }}></div>
<div className="w-2.5 h-2.5 bg-gray-400 rounded-full -ml-2.5" style={{ visibility: !machine.isConnected ? 'visible' : 'hidden' }}></div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h1 className="text-sm font-bold text-white leading-tight">SKiTCH Controller</h1>
{machine.isPolling && (
<ArrowPathIcon className="w-3 h-3 text-blue-200 animate-spin" title="Auto-refreshing status" />
)}
</div>
<div className="flex items-center gap-2 mt-0.5 min-w-0">
<p className="text-xs text-blue-200 truncate">
{machine.isConnected ? (machine.machineInfo?.serialNumber || 'Connected') : 'Not Connected'}
</p>
{machine.isConnected && (
<>
<button
onClick={machine.disconnect}
className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-[10px] font-medium bg-white/10 hover:bg-white/20 text-blue-100 hover:text-white border border-white/20 hover:border-white/30 cursor-pointer transition-all flex-shrink-0"
title="Disconnect from machine"
aria-label="Disconnect from machine"
>
<XMarkIcon className="w-2.5 h-2.5" />
Disconnect
</button>
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-semibold bg-white/20 text-white border border-white/30 flex-shrink-0">
<StatusIcon className="w-2.5 h-2.5" />
{machine.machineStatusName}
</span>
</>
)}
</div>
</div>
)}
</div>
{/* Workflow Stepper - Flexible width column */}
<div>
<WorkflowStepper
machineStatus={machine.machineStatus}
isConnected={machine.isConnected}
hasPattern={pesData !== null}
patternUploaded={patternUploaded}
/>
</div>
</div>
</header>
@ -155,18 +199,28 @@ function App() {
<div className="grid grid-cols-1 lg:grid-cols-[400px_1fr] gap-6">
{/* Left Column - Controls */}
<div className="flex flex-col gap-6">
{/* Machine Connection - Always visible */}
<MachineConnection
isConnected={machine.isConnected}
machineInfo={machine.machineInfo}
machineStatus={machine.machineStatus}
machineStatusName={machine.machineStatusName}
machineError={machine.machineError}
isPolling={machine.isPolling}
onConnect={machine.connect}
onDisconnect={machine.disconnect}
onRefresh={machine.refreshStatus}
/>
{/* Connect Button - Show when disconnected */}
{!machine.isConnected && (
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-gray-400 dark:border-gray-600">
<div className="flex items-start gap-3 mb-3">
<div className="w-6 h-6 text-gray-600 dark:text-gray-400 flex-shrink-0 mt-0.5">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0" />
</svg>
</div>
<div className="flex-1 min-w-0">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">Get Started</h3>
<p className="text-xs text-gray-600 dark:text-gray-400">Connect to your embroidery machine</p>
</div>
</div>
<button
onClick={machine.connect}
className="w-full flex items-center justify-center gap-2 px-3 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded font-semibold text-xs hover:bg-blue-700 dark:hover:bg-blue-600 active:bg-blue-800 dark:active:bg-blue-500 transition-colors cursor-pointer"
>
Connect to Machine
</button>
</div>
)}
{/* Pattern File - Show during upload stage (before pattern is uploaded) */}
{machine.isConnected && !patternUploaded && (

View file

@ -85,8 +85,8 @@ export function MachineConnection({
<div className="flex items-start gap-3 mb-3">
<WifiIcon className="w-6 h-6 text-gray-600 dark:text-gray-400 flex-shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">Machine Connection</h3>
<p className="text-xs text-gray-600 dark:text-gray-400">Not connected</p>
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">Machine</h3>
<p className="text-xs text-gray-600 dark:text-gray-400">Ready to connect</p>
</div>
</div>
@ -102,17 +102,10 @@ export function MachineConnection({
<div className="flex items-start gap-3 mb-3">
<WifiIcon className="w-6 h-6 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white">Machine Connected</h3>
{isPolling && (
<span className="w-2 h-2 bg-blue-500 dark:bg-blue-400 rounded-full animate-pulse" title="Auto-refreshing" aria-label="Auto-refreshing machine status"></span>
)}
</div>
{machineInfo && (
<p className="text-xs text-gray-600 dark:text-gray-400 truncate" title={machineInfo.serialNumber}>
{machineInfo.serialNumber}
</p>
)}
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">Machine Info</h3>
<p className="text-xs text-gray-600 dark:text-gray-400">
{machineInfo?.modelNumber || 'Brother Embroidery Machine'}
</p>
</div>
</div>