Redesign UI for fixed-size window with floating guide overlay

- Convert NextStepGuide to collapsible floating overlay in bottom-right
- Implement fixed viewport layout (no page scrolling on desktop)
- Make color blocks scrollable with dynamic gradient indicator
- Add responsive behavior: scrollable on mobile, fixed on desktop
- Increase overlay opacity for better readability
- Enable full-height columns that expand to fill available space

🤖 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 Bruhn 2025-12-10 21:56:19 +01:00
parent 0dbfc751cb
commit bf3e397ddb
4 changed files with 367 additions and 265 deletions

View file

@ -108,9 +108,9 @@ function App() {
const StatusIcon = stateIcons[stateVisual.iconName]; const StatusIcon = stateIcons[stateVisual.iconName];
return ( return (
<div className="min-h-screen flex flex-col bg-gray-50 dark:bg-gray-900"> <div className="h-screen flex flex-col bg-gray-50 dark:bg-gray-900 overflow-hidden">
<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-4 sm:px-6 lg:px-8 py-3 shadow-lg border-b-2 border-blue-900/20 dark:border-blue-800/30"> <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-4 sm:px-6 lg:px-8 py-3 shadow-lg border-b-2 border-blue-900/20 dark:border-blue-800/30 flex-shrink-0">
<div className="max-w-[1600px] mx-auto grid grid-cols-1 lg:grid-cols-[280px_1fr] gap-4 lg:gap-8 items-center"> <div className="grid grid-cols-1 lg:grid-cols-[280px_1fr] gap-4 lg:gap-8 items-center">
{/* Machine Connection Status - Responsive width column */} {/* Machine Connection Status - Responsive width column */}
<div className="flex items-center gap-3 w-full lg:w-[280px]"> <div className="flex items-center gap-3 w-full lg:w-[280px]">
<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-green-400 rounded-full animate-pulse shadow-lg shadow-green-400/50" style={{ visibility: machine.isConnected ? 'visible' : 'hidden' }}></div>
@ -178,7 +178,7 @@ function App() {
</div> </div>
</header> </header>
<div className="flex-1 p-4 sm:p-5 lg:p-6 max-w-[1600px] w-full mx-auto"> <div className="flex-1 p-4 sm:p-5 lg:p-6 w-full overflow-y-auto lg:overflow-hidden flex flex-col">
{/* Global errors */} {/* Global errors */}
{machine.error && ( {machine.error && (
<div className={`px-6 py-4 rounded-lg border-l-4 mb-6 shadow-md hover:shadow-lg transition-shadow animate-fadeIn ${ <div className={`px-6 py-4 rounded-lg border-l-4 mb-6 shadow-md hover:shadow-lg transition-shadow animate-fadeIn ${
@ -225,9 +225,9 @@ function App() {
</div> </div>
)} )}
<div className="grid grid-cols-1 lg:grid-cols-[400px_1fr] gap-4 md:gap-5 lg:gap-6"> <div className="flex-1 grid grid-cols-1 lg:grid-cols-[400px_1fr] gap-4 md:gap-5 lg:gap-6 lg:overflow-hidden">
{/* Left Column - Controls */} {/* Left Column - Controls */}
<div className="flex flex-col gap-4 md:gap-5 lg:gap-6"> <div className="flex flex-col gap-4 md:gap-5 lg:gap-6 lg:overflow-hidden">
{/* Connect Button - Show when disconnected */} {/* Connect Button - Show when disconnected */}
{!machine.isConnected && ( {!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="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-gray-400 dark:border-gray-600">
@ -284,6 +284,7 @@ function App() {
{/* Progress Monitor - Show when pattern is uploaded */} {/* Progress Monitor - Show when pattern is uploaded */}
{machine.isConnected && patternUploaded && ( {machine.isConnected && patternUploaded && (
<div className="lg:flex-1 lg:min-h-0">
<ProgressMonitor <ProgressMonitor
machineStatus={machine.machineStatus} machineStatus={machine.machineStatus}
patternInfo={machine.patternInfo} patternInfo={machine.patternInfo}
@ -295,11 +296,12 @@ function App() {
onDeletePattern={handleDeletePattern} onDeletePattern={handleDeletePattern}
isDeleting={machine.isDeleting} isDeleting={machine.isDeleting}
/> />
</div>
)} )}
</div> </div>
{/* Right Column - Pattern Preview */} {/* Right Column - Pattern Preview */}
<div className="flex flex-col gap-4 md:gap-5 lg:gap-6"> <div className="flex flex-col lg:overflow-hidden lg:h-full">
{pesData ? ( {pesData ? (
<PatternCanvas <PatternCanvas
pesData={pesData} pesData={pesData}
@ -311,9 +313,9 @@ function App() {
isUploading={machine.uploadProgress > 0 && machine.uploadProgress < 100} isUploading={machine.uploadProgress > 0 && machine.uploadProgress < 100}
/> />
) : ( ) : (
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="lg:h-full bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md animate-fadeIn flex flex-col">
<h2 className="text-base lg:text-lg font-semibold mb-4 pb-2 border-b-2 border-gray-300 dark:border-gray-600 dark:text-white">Pattern Preview</h2> <h2 className="text-base lg:text-lg font-semibold mb-4 pb-2 border-b-2 border-gray-300 dark:border-gray-600 dark:text-white flex-shrink-0">Pattern Preview</h2>
<div className="flex items-center justify-center h-[400px] sm:h-[500px] lg:h-[600px] max-h-[70vh] bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-700 dark:to-gray-800 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600 relative overflow-hidden"> <div className="h-[400px] sm:h-[500px] lg:flex-1 flex items-center justify-center bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-700 dark:to-gray-800 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-600 relative overflow-hidden">
{/* Decorative background pattern */} {/* Decorative background pattern */}
<div className="absolute inset-0 opacity-5 dark:opacity-10"> <div className="absolute inset-0 opacity-5 dark:opacity-10">
<div className="absolute top-10 left-10 w-32 h-32 border-4 border-gray-400 dark:border-gray-500 rounded-full"></div> <div className="absolute top-10 left-10 w-32 h-32 border-4 border-gray-400 dark:border-gray-500 rounded-full"></div>
@ -354,8 +356,14 @@ function App() {
</div> </div>
</div> </div>
)} )}
</div>
</div>
{/* Next Step Guide - Below pattern preview */} {/* Bluetooth Device Picker (Electron only) */}
<BluetoothDevicePicker />
</div>
{/* Next Step Guide - Fixed floating overlay */}
<NextStepGuide <NextStepGuide
machineStatus={machine.machineStatus} machineStatus={machine.machineStatus}
isConnected={machine.isConnected} isConnected={machine.isConnected}
@ -366,12 +374,6 @@ function App() {
errorCode={machine.machineError} errorCode={machine.machineError}
/> />
</div> </div>
</div>
{/* Bluetooth Device Picker (Electron only) */}
<BluetoothDevicePicker />
</div>
</div>
); );
} }

View file

@ -1,4 +1,5 @@
import { InformationCircleIcon, ExclamationTriangleIcon } from '@heroicons/react/24/solid'; import { useState, useEffect } from 'react';
import { InformationCircleIcon, ExclamationTriangleIcon, XMarkIcon } from '@heroicons/react/24/solid';
import { MachineStatus } from '../types/machine'; import { MachineStatus } from '../types/machine';
import { getErrorDetails } from '../utils/errorCodeHelpers'; import { getErrorDetails } from '../utils/errorCodeHelpers';
@ -21,6 +22,15 @@ export function NextStepGuide({
errorMessage, errorMessage,
errorCode errorCode
}: NextStepGuideProps) { }: NextStepGuideProps) {
const [isExpanded, setIsExpanded] = useState(true);
// Expand when state changes
useEffect(() => {
setIsExpanded(true);
}, [machineStatus, isConnected, hasPattern, patternUploaded, hasError]);
// Render guide content based on state
const renderContent = () => {
// Don't show if there's an error - show detailed error guidance instead // Don't show if there's an error - show detailed error guidance instead
if (hasError) { if (hasError) {
const errorDetails = getErrorDetails(errorCode); const errorDetails = getErrorDetails(errorCode);
@ -28,20 +38,20 @@ export function NextStepGuide({
// Check if this is informational (like initialization steps) vs a real error // Check if this is informational (like initialization steps) vs a real error
if (errorDetails?.isInformational) { if (errorDetails?.isInformational) {
return ( return (
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-600 dark:border-blue-500 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="bg-blue-50 dark:bg-blue-900/95 border-l-4 border-blue-600 dark:border-blue-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<InformationCircleIcon className="w-8 h-8 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-1" /> <InformationCircleIcon className="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-blue-900 dark:text-blue-200 mb-2"> <h3 className="text-base font-semibold text-blue-900 dark:text-blue-200 mb-2">
{errorDetails.title} {errorDetails.title}
</h3> </h3>
<p className="text-blue-800 dark:text-blue-300 mb-3"> <p className="text-sm text-blue-800 dark:text-blue-300 mb-3">
{errorDetails.description} {errorDetails.description}
</p> </p>
{errorDetails.solutions && errorDetails.solutions.length > 0 && ( {errorDetails.solutions && errorDetails.solutions.length > 0 && (
<> <>
<h4 className="font-semibold text-blue-900 dark:text-blue-200 mb-2">Steps:</h4> <h4 className="text-sm font-semibold text-blue-900 dark:text-blue-200 mb-2">Steps:</h4>
<ol className="list-decimal list-inside text-sm text-blue-700 dark:text-blue-300 space-y-2"> <ol className="list-decimal list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1.5">
{errorDetails.solutions.map((solution, index) => ( {errorDetails.solutions.map((solution, index) => (
<li key={index} className="pl-2">{solution}</li> <li key={index} className="pl-2">{solution}</li>
))} ))}
@ -56,20 +66,20 @@ export function NextStepGuide({
// Regular error display for actual errors // Regular error display for actual errors
return ( return (
<div className="bg-red-50 dark:bg-red-900/20 border-l-4 border-red-600 dark:border-red-500 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="bg-red-50 dark:bg-red-900/95 border-l-4 border-red-600 dark:border-red-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<ExclamationTriangleIcon className="w-8 h-8 text-red-600 dark:text-red-400 flex-shrink-0 mt-1" /> <ExclamationTriangleIcon className="w-6 h-6 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-red-900 dark:text-red-200 mb-2"> <h3 className="text-base font-semibold text-red-900 dark:text-red-200 mb-2">
{errorDetails?.title || 'Error Occurred'} {errorDetails?.title || 'Error Occurred'}
</h3> </h3>
<p className="text-red-800 dark:text-red-300 mb-3"> <p className="text-sm text-red-800 dark:text-red-300 mb-3">
{errorDetails?.description || errorMessage || 'An error occurred. Please check the machine and try again.'} {errorDetails?.description || errorMessage || 'An error occurred. Please check the machine and try again.'}
</p> </p>
{errorDetails?.solutions && errorDetails.solutions.length > 0 && ( {errorDetails?.solutions && errorDetails.solutions.length > 0 && (
<> <>
<h4 className="font-semibold text-red-900 dark:text-red-200 mb-2">How to Fix:</h4> <h4 className="text-sm font-semibold text-red-900 dark:text-red-200 mb-2">How to Fix:</h4>
<ol className="list-decimal list-inside text-sm text-red-700 dark:text-red-300 space-y-2"> <ol className="list-decimal list-inside text-sm text-red-700 dark:text-red-300 space-y-1.5">
{errorDetails.solutions.map((solution, index) => ( {errorDetails.solutions.map((solution, index) => (
<li key={index} className="pl-2">{solution}</li> <li key={index} className="pl-2">{solution}</li>
))} ))}
@ -77,7 +87,7 @@ export function NextStepGuide({
</> </>
)} )}
{errorCode !== undefined && ( {errorCode !== undefined && (
<p className="text-xs text-red-600 dark:text-red-400 mt-4 font-mono"> <p className="text-xs text-red-600 dark:text-red-400 mt-3 font-mono">
Error Code: 0x{errorCode.toString(16).toUpperCase().padStart(2, '0')} Error Code: 0x{errorCode.toString(16).toUpperCase().padStart(2, '0')}
</p> </p>
)} )}
@ -90,12 +100,12 @@ export function NextStepGuide({
// Determine what to show based on current state // Determine what to show based on current state
if (!isConnected) { if (!isConnected) {
return ( return (
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-600 dark:border-blue-500 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="bg-blue-50 dark:bg-blue-900/95 border-l-4 border-blue-600 dark:border-blue-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<InformationCircleIcon className="w-8 h-8 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-1" /> <InformationCircleIcon className="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 1: Connect to Machine</h3> <h3 className="text-base font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 1: Connect to Machine</h3>
<p className="text-blue-800 dark:text-blue-300 mb-3">To get started, connect to your Brother embroidery machine via Bluetooth.</p> <p className="text-sm text-blue-800 dark:text-blue-300 mb-3">To get started, connect to your Brother embroidery machine via Bluetooth.</p>
<ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1"> <ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1">
<li>Make sure your machine is powered on</li> <li>Make sure your machine is powered on</li>
<li>Enable Bluetooth on your machine</li> <li>Enable Bluetooth on your machine</li>
@ -109,12 +119,12 @@ export function NextStepGuide({
if (!hasPattern) { if (!hasPattern) {
return ( return (
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-600 dark:border-blue-500 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="bg-blue-50 dark:bg-blue-900/95 border-l-4 border-blue-600 dark:border-blue-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<InformationCircleIcon className="w-8 h-8 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-1" /> <InformationCircleIcon className="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 2: Load Your Pattern</h3> <h3 className="text-base font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 2: Load Your Pattern</h3>
<p className="text-blue-800 dark:text-blue-300 mb-3">Choose a PES embroidery file from your computer to preview and upload.</p> <p className="text-sm text-blue-800 dark:text-blue-300 mb-3">Choose a PES embroidery file from your computer to preview and upload.</p>
<ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1"> <ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1">
<li>Click "Choose PES File" in the Pattern File section</li> <li>Click "Choose PES File" in the Pattern File section</li>
<li>Select your embroidery design (.pes file)</li> <li>Select your embroidery design (.pes file)</li>
@ -129,12 +139,12 @@ export function NextStepGuide({
if (!patternUploaded) { if (!patternUploaded) {
return ( return (
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-600 dark:border-blue-500 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="bg-blue-50 dark:bg-blue-900/95 border-l-4 border-blue-600 dark:border-blue-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<InformationCircleIcon className="w-8 h-8 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-1" /> <InformationCircleIcon className="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 3: Upload Pattern to Machine</h3> <h3 className="text-base font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 3: Upload Pattern to Machine</h3>
<p className="text-blue-800 dark:text-blue-300 mb-3">Send your pattern to the embroidery machine to prepare for sewing.</p> <p className="text-sm text-blue-800 dark:text-blue-300 mb-3">Send your pattern to the embroidery machine to prepare for sewing.</p>
<ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1"> <ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1">
<li>Review the pattern preview to ensure it's positioned correctly</li> <li>Review the pattern preview to ensure it's positioned correctly</li>
<li>Check the pattern size matches your hoop</li> <li>Check the pattern size matches your hoop</li>
@ -151,12 +161,12 @@ export function NextStepGuide({
switch (machineStatus) { switch (machineStatus) {
case MachineStatus.IDLE: case MachineStatus.IDLE:
return ( return (
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-600 dark:border-blue-500 p-6 rounded-lg shadow-md"> <div className="bg-blue-50 dark:bg-blue-900/95 border-l-4 border-blue-600 dark:border-blue-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<InformationCircleIcon className="w-8 h-8 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-1" /> <InformationCircleIcon className="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 4: Start Mask Trace</h3> <h3 className="text-base font-semibold text-blue-900 dark:text-blue-200 mb-2">Step 4: Start Mask Trace</h3>
<p className="text-blue-800 dark:text-blue-300 mb-3">The mask trace helps the machine understand the pattern boundaries.</p> <p className="text-sm text-blue-800 dark:text-blue-300 mb-3">The mask trace helps the machine understand the pattern boundaries.</p>
<ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1"> <ul className="list-disc list-inside text-sm text-blue-700 dark:text-blue-300 space-y-1">
<li>Click "Start Mask Trace" button in the Sewing Progress section</li> <li>Click "Start Mask Trace" button in the Sewing Progress section</li>
<li>The machine will trace the pattern outline</li> <li>The machine will trace the pattern outline</li>
@ -169,12 +179,12 @@ export function NextStepGuide({
case MachineStatus.MASK_TRACE_LOCK_WAIT: case MachineStatus.MASK_TRACE_LOCK_WAIT:
return ( return (
<div className="bg-yellow-50 dark:bg-yellow-900/20 border-l-4 border-yellow-600 dark:border-yellow-500 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="bg-yellow-50 dark:bg-yellow-900/95 border-l-4 border-yellow-600 dark:border-yellow-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<InformationCircleIcon className="w-8 h-8 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-1" /> <InformationCircleIcon className="w-6 h-6 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-200 mb-2">Machine Action Required</h3> <h3 className="text-base font-semibold text-yellow-900 dark:text-yellow-200 mb-2">Machine Action Required</h3>
<p className="text-yellow-800 dark:text-yellow-300 mb-3">The machine is ready to trace the pattern outline.</p> <p className="text-sm text-yellow-800 dark:text-yellow-300 mb-3">The machine is ready to trace the pattern outline.</p>
<ul className="list-disc list-inside text-sm text-yellow-700 dark:text-yellow-300 space-y-1"> <ul className="list-disc list-inside text-sm text-yellow-700 dark:text-yellow-300 space-y-1">
<li><strong>Press the button on your machine</strong> to confirm and start the mask trace</li> <li><strong>Press the button on your machine</strong> to confirm and start the mask trace</li>
<li>Ensure the hoop is properly attached</li> <li>Ensure the hoop is properly attached</li>
@ -187,12 +197,12 @@ export function NextStepGuide({
case MachineStatus.MASK_TRACING: case MachineStatus.MASK_TRACING:
return ( return (
<div className="bg-cyan-50 dark:bg-cyan-900/20 border-l-4 border-cyan-600 dark:border-cyan-500 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="bg-cyan-50 dark:bg-cyan-900/95 border-l-4 border-cyan-600 dark:border-cyan-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<InformationCircleIcon className="w-8 h-8 text-cyan-600 dark:text-cyan-400 flex-shrink-0 mt-1" /> <InformationCircleIcon className="w-6 h-6 text-cyan-600 dark:text-cyan-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-cyan-900 dark:text-cyan-200 mb-2">Mask Trace In Progress</h3> <h3 className="text-base font-semibold text-cyan-900 dark:text-cyan-200 mb-2">Mask Trace In Progress</h3>
<p className="text-cyan-800 dark:text-cyan-300 mb-3">The machine is tracing the pattern boundary. Please wait...</p> <p className="text-sm text-cyan-800 dark:text-cyan-300 mb-3">The machine is tracing the pattern boundary. Please wait...</p>
<ul className="list-disc list-inside text-sm text-cyan-700 dark:text-cyan-300 space-y-1"> <ul className="list-disc list-inside text-sm text-cyan-700 dark:text-cyan-300 space-y-1">
<li>Watch the machine trace the outline</li> <li>Watch the machine trace the outline</li>
<li>Verify the pattern fits within your hoop</li> <li>Verify the pattern fits within your hoop</li>
@ -206,12 +216,12 @@ export function NextStepGuide({
case MachineStatus.MASK_TRACE_COMPLETE: case MachineStatus.MASK_TRACE_COMPLETE:
case MachineStatus.SEWING_WAIT: case MachineStatus.SEWING_WAIT:
return ( return (
<div className="bg-green-50 dark:bg-green-900/20 border-l-4 border-green-600 dark:border-green-500 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="bg-green-50 dark:bg-green-900/95 border-l-4 border-green-600 dark:border-green-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<InformationCircleIcon className="w-8 h-8 text-green-600 dark:text-green-400 flex-shrink-0 mt-1" /> <InformationCircleIcon className="w-6 h-6 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-green-900 dark:text-green-200 mb-2">Step 5: Ready to Sew!</h3> <h3 className="text-base font-semibold text-green-900 dark:text-green-200 mb-2">Step 5: Ready to Sew!</h3>
<p className="text-green-800 dark:text-green-300 mb-3">The machine is ready to begin embroidering your pattern.</p> <p className="text-sm text-green-800 dark:text-green-300 mb-3">The machine is ready to begin embroidering your pattern.</p>
<ul className="list-disc list-inside text-sm text-green-700 dark:text-green-300 space-y-1"> <ul className="list-disc list-inside text-sm text-green-700 dark:text-green-300 space-y-1">
<li>Verify your thread colors are correct</li> <li>Verify your thread colors are correct</li>
<li>Ensure the fabric is properly hooped</li> <li>Ensure the fabric is properly hooped</li>
@ -224,12 +234,12 @@ export function NextStepGuide({
case MachineStatus.SEWING: case MachineStatus.SEWING:
return ( return (
<div className="bg-cyan-50 dark:bg-cyan-900/20 border-l-4 border-cyan-600 dark:border-cyan-500 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="bg-cyan-50 dark:bg-cyan-900/95 border-l-4 border-cyan-600 dark:border-cyan-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<InformationCircleIcon className="w-8 h-8 text-cyan-600 dark:text-cyan-400 flex-shrink-0 mt-1" /> <InformationCircleIcon className="w-6 h-6 text-cyan-600 dark:text-cyan-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-cyan-900 dark:text-cyan-200 mb-2">Step 6: Sewing In Progress</h3> <h3 className="text-base font-semibold text-cyan-900 dark:text-cyan-200 mb-2">Step 6: Sewing In Progress</h3>
<p className="text-cyan-800 dark:text-cyan-300 mb-3">Your embroidery is being stitched. Monitor the progress below.</p> <p className="text-sm text-cyan-800 dark:text-cyan-300 mb-3">Your embroidery is being stitched. Monitor the progress below.</p>
<ul className="list-disc list-inside text-sm text-cyan-700 dark:text-cyan-300 space-y-1"> <ul className="list-disc list-inside text-sm text-cyan-700 dark:text-cyan-300 space-y-1">
<li>Watch the progress bar and current stitch count</li> <li>Watch the progress bar and current stitch count</li>
<li>The machine will pause when a color change is needed</li> <li>The machine will pause when a color change is needed</li>
@ -242,12 +252,12 @@ export function NextStepGuide({
case MachineStatus.COLOR_CHANGE_WAIT: case MachineStatus.COLOR_CHANGE_WAIT:
return ( return (
<div className="bg-yellow-50 dark:bg-yellow-900/20 border-l-4 border-yellow-600 dark:border-yellow-500 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="bg-yellow-50 dark:bg-yellow-900/95 border-l-4 border-yellow-600 dark:border-yellow-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<InformationCircleIcon className="w-8 h-8 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-1" /> <InformationCircleIcon className="w-6 h-6 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-200 mb-2">Thread Change Required</h3> <h3 className="text-base font-semibold text-yellow-900 dark:text-yellow-200 mb-2">Thread Change Required</h3>
<p className="text-yellow-800 dark:text-yellow-300 mb-3">The machine needs a different thread color to continue.</p> <p className="text-sm text-yellow-800 dark:text-yellow-300 mb-3">The machine needs a different thread color to continue.</p>
<ul className="list-disc list-inside text-sm text-yellow-700 dark:text-yellow-300 space-y-1"> <ul className="list-disc list-inside text-sm text-yellow-700 dark:text-yellow-300 space-y-1">
<li>Check the color blocks section to see which thread is needed</li> <li>Check the color blocks section to see which thread is needed</li>
<li>Change to the correct thread color</li> <li>Change to the correct thread color</li>
@ -262,12 +272,12 @@ export function NextStepGuide({
case MachineStatus.STOP: case MachineStatus.STOP:
case MachineStatus.SEWING_INTERRUPTION: case MachineStatus.SEWING_INTERRUPTION:
return ( return (
<div className="bg-yellow-50 dark:bg-yellow-900/20 border-l-4 border-yellow-600 dark:border-yellow-500 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="bg-yellow-50 dark:bg-yellow-900/95 border-l-4 border-yellow-600 dark:border-yellow-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<InformationCircleIcon className="w-8 h-8 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-1" /> <InformationCircleIcon className="w-6 h-6 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-200 mb-2">Sewing Paused</h3> <h3 className="text-base font-semibold text-yellow-900 dark:text-yellow-200 mb-2">Sewing Paused</h3>
<p className="text-yellow-800 dark:text-yellow-300 mb-3">The embroidery has been paused or interrupted.</p> <p className="text-sm text-yellow-800 dark:text-yellow-300 mb-3">The embroidery has been paused or interrupted.</p>
<ul className="list-disc list-inside text-sm text-yellow-700 dark:text-yellow-300 space-y-1"> <ul className="list-disc list-inside text-sm text-yellow-700 dark:text-yellow-300 space-y-1">
<li>Check if everything is okay with the machine</li> <li>Check if everything is okay with the machine</li>
<li>Click "Resume Sewing" when ready to continue</li> <li>Click "Resume Sewing" when ready to continue</li>
@ -280,12 +290,12 @@ export function NextStepGuide({
case MachineStatus.SEWING_COMPLETE: case MachineStatus.SEWING_COMPLETE:
return ( return (
<div className="bg-green-50 dark:bg-green-900/20 border-l-4 border-green-600 dark:border-green-500 p-6 rounded-lg shadow-md animate-fadeIn"> <div className="bg-green-50 dark:bg-green-900/95 border-l-4 border-green-600 dark:border-green-500 p-4 rounded-lg shadow-lg backdrop-blur-sm">
<div className="flex items-start gap-4"> <div className="flex items-start gap-3">
<InformationCircleIcon className="w-8 h-8 text-green-600 dark:text-green-400 flex-shrink-0 mt-1" /> <InformationCircleIcon className="w-6 h-6 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5" />
<div className="flex-1"> <div className="flex-1">
<h3 className="text-lg font-semibold text-green-900 dark:text-green-200 mb-2">Step 7: Embroidery Complete!</h3> <h3 className="text-base font-semibold text-green-900 dark:text-green-200 mb-2">Step 7: Embroidery Complete!</h3>
<p className="text-green-800 dark:text-green-300 mb-3">Your embroidery is finished. Great work!</p> <p className="text-sm text-green-800 dark:text-green-300 mb-3">Your embroidery is finished. Great work!</p>
<ul className="list-disc list-inside text-sm text-green-700 dark:text-green-300 space-y-1"> <ul className="list-disc list-inside text-sm text-green-700 dark:text-green-300 space-y-1">
<li>Remove the hoop from the machine</li> <li>Remove the hoop from the machine</li>
<li>Press the Accept button on the machine</li> <li>Press the Accept button on the machine</li>
@ -301,4 +311,44 @@ export function NextStepGuide({
default: default:
return null; return null;
} }
};
// Render floating overlay
return (
<>
{/* Collapsed state - small info button in bottom-right */}
{!isExpanded && (
<div className="fixed bottom-4 right-4 z-50 animate-fadeIn">
<button
onClick={() => setIsExpanded(true)}
className="w-12 h-12 bg-blue-600 dark:bg-blue-700 hover:bg-blue-700 dark:hover:bg-blue-600 text-white rounded-full shadow-lg flex items-center justify-center transition-all hover:scale-110 animate-pulse"
aria-label="Show next step guide"
title="Show next step guide"
>
<InformationCircleIcon className="w-7 h-7" />
</button>
</div>
)}
{/* Expanded state - floating overlay */}
{isExpanded && (
<div className="fixed bottom-4 right-4 z-50 w-[90vw] max-w-[400px] max-h-[40vh] overflow-y-auto animate-slideInRight">
<div className="relative rounded-lg overflow-hidden shadow-xl">
{/* Close button */}
<button
onClick={() => setIsExpanded(false)}
className="absolute top-2 right-2 z-10 w-6 h-6 bg-white/90 dark:bg-gray-800/90 hover:bg-white dark:hover:bg-gray-700 rounded-full flex items-center justify-center transition-colors shadow-md"
aria-label="Minimize guide"
title="Minimize guide"
>
<XMarkIcon className="w-4 h-4 text-gray-700 dark:text-gray-300" />
</button>
{/* Content */}
{renderContent()}
</div>
</div>
)}
</>
);
} }

View file

@ -189,8 +189,8 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
const iconColor = pesData ? 'text-teal-600 dark:text-teal-400' : 'text-gray-600 dark:text-gray-400'; const iconColor = pesData ? 'text-teal-600 dark:text-teal-400' : 'text-gray-600 dark:text-gray-400';
return ( return (
<div className={`bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 ${borderColor}`}> <div className={`lg:h-full bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 ${borderColor} flex flex-col`}>
<div className="flex items-start gap-3 mb-3"> <div className="flex items-start gap-3 mb-3 flex-shrink-0">
<PhotoIcon className={`w-6 h-6 ${iconColor} flex-shrink-0 mt-0.5`} /> <PhotoIcon className={`w-6 h-6 ${iconColor} flex-shrink-0 mt-0.5`} />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">Pattern Preview</h3> <h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">Pattern Preview</h3>
@ -203,7 +203,7 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
)} )}
</div> </div>
</div> </div>
<div className="relative w-full h-[400px] sm:h-[500px] lg:h-[600px] max-h-[70vh] border border-gray-300 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-900 overflow-hidden" ref={containerRef}> <div className="relative w-full h-[400px] sm:h-[500px] lg:flex-1 lg:min-h-0 border border-gray-300 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-900 overflow-hidden" ref={containerRef}>
{containerSize.width > 0 && ( {containerSize.width > 0 && (
<Stage <Stage
width={containerSize.width} width={containerSize.width}
@ -291,7 +291,7 @@ export function PatternCanvas({ pesData, sewingProgress, machineInfo, initialPat
{/* Placeholder overlay when no pattern is loaded */} {/* Placeholder overlay when no pattern is loaded */}
{!pesData && ( {!pesData && (
<div className="flex items-center justify-center h-[400px] sm:h-[500px] lg:h-[600px] text-gray-600 dark:text-gray-400 italic"> <div className="flex items-center justify-center h-full text-gray-600 dark:text-gray-400 italic">
Load a PES file to preview the pattern Load a PES file to preview the pattern
</div> </div>
)} )}

View file

@ -1,3 +1,4 @@
import { useRef, useEffect, useState } from "react";
import { import {
CheckCircleIcon, CheckCircleIcon,
ArrowRightIcon, ArrowRightIcon,
@ -42,6 +43,10 @@ export function ProgressMonitor({
onResumeSewing, onResumeSewing,
isDeleting = false, isDeleting = false,
}: ProgressMonitorProps) { }: ProgressMonitorProps) {
const currentBlockRef = useRef<HTMLDivElement>(null);
const colorBlocksScrollRef = useRef<HTMLDivElement>(null);
const [showGradient, setShowGradient] = useState(true);
// State indicators // State indicators
const isMaskTraceComplete = const isMaskTraceComplete =
machineStatus === MachineStatus.MASK_TRACE_COMPLETE; machineStatus === MachineStatus.MASK_TRACE_COMPLETE;
@ -108,6 +113,40 @@ export function ProgressMonitor({
currentStitch >= block.startStitch && currentStitch < block.endStitch, currentStitch >= block.startStitch && currentStitch < block.endStitch,
); );
// Auto-scroll to current block
useEffect(() => {
if (currentBlockRef.current) {
currentBlockRef.current.scrollIntoView({
behavior: "smooth",
block: "nearest",
});
}
}, [currentBlockIndex]);
// Handle scroll to detect if at bottom
const handleColorBlocksScroll = () => {
if (colorBlocksScrollRef.current) {
const { scrollTop, scrollHeight, clientHeight } = colorBlocksScrollRef.current;
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 5; // 5px threshold
setShowGradient(!isAtBottom);
}
};
// Check initial scroll state and update on resize
useEffect(() => {
const checkScrollable = () => {
if (colorBlocksScrollRef.current) {
const { scrollHeight, clientHeight } = colorBlocksScrollRef.current;
const isScrollable = scrollHeight > clientHeight;
setShowGradient(isScrollable);
}
};
checkScrollable();
window.addEventListener('resize', checkScrollable);
return () => window.removeEventListener('resize', checkScrollable);
}, [colorBlocks]);
const stateIndicatorColors = { const stateIndicatorColors = {
idle: "bg-blue-50 dark:bg-blue-900/20 border-blue-600", idle: "bg-blue-50 dark:bg-blue-900/20 border-blue-600",
info: "bg-blue-50 dark:bg-blue-900/20 border-blue-600", info: "bg-blue-50 dark:bg-blue-900/20 border-blue-600",
@ -122,7 +161,7 @@ export function ProgressMonitor({
}; };
return ( return (
<div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-purple-600 dark:border-purple-500"> <div className="lg:h-full bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-purple-600 dark:border-purple-500 flex flex-col lg:overflow-hidden">
<div className="flex items-start gap-3 mb-3"> <div className="flex items-start gap-3 mb-3">
<ChartBarIcon className="w-6 h-6 text-purple-600 dark:text-purple-400 flex-shrink-0 mt-0.5" /> <ChartBarIcon className="w-6 h-6 text-purple-600 dark:text-purple-400 flex-shrink-0 mt-0.5" />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
@ -246,11 +285,16 @@ export function ProgressMonitor({
{/* Color Blocks */} {/* Color Blocks */}
{colorBlocks.length > 0 && ( {colorBlocks.length > 0 && (
<div className="mb-3"> <div className="mb-3 lg:flex-1 lg:min-h-0 flex flex-col">
<h4 className="text-xs font-semibold mb-2 text-gray-700 dark:text-gray-300"> <h4 className="text-xs font-semibold mb-2 text-gray-700 dark:text-gray-300 flex-shrink-0">
Color Blocks Color Blocks
</h4> </h4>
<div className="flex flex-col gap-2"> <div className="relative lg:flex-1 lg:min-h-0">
<div
ref={colorBlocksScrollRef}
onScroll={handleColorBlocksScroll}
className="lg:absolute lg:inset-0 flex flex-col gap-2 lg:overflow-y-auto scroll-smooth pr-1 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-gray-100 dark:[&::-webkit-scrollbar-track]:bg-gray-700 [&::-webkit-scrollbar-thumb]:bg-blue-600 dark:[&::-webkit-scrollbar-thumb]:bg-blue-500 [&::-webkit-scrollbar-thumb]:rounded-full"
>
{colorBlocks.map((block, index) => { {colorBlocks.map((block, index) => {
const isCompleted = currentStitch >= block.endStitch; const isCompleted = currentStitch >= block.endStitch;
const isCurrent = index === currentBlockIndex; const isCurrent = index === currentBlockIndex;
@ -268,6 +312,7 @@ export function ProgressMonitor({
return ( return (
<div <div
key={index} key={index}
ref={isCurrent ? currentBlockRef : null}
className={`p-2.5 rounded-lg border-2 transition-all duration-300 ${ className={`p-2.5 rounded-lg border-2 transition-all duration-300 ${
isCompleted isCompleted
? "border-green-600 bg-green-50 dark:bg-green-900/20" ? "border-green-600 bg-green-50 dark:bg-green-900/20"
@ -359,11 +404,16 @@ export function ProgressMonitor({
); );
})} })}
</div> </div>
{/* Gradient overlay to indicate more content below - only on desktop and when not at bottom */}
{showGradient && (
<div className="hidden lg:block absolute bottom-0 left-0 right-0 h-8 bg-gradient-to-t from-white dark:from-gray-800 to-transparent pointer-events-none" />
)}
</div>
</div> </div>
)} )}
{/* Action buttons */} {/* Action buttons */}
<div className="flex gap-2"> <div className="flex gap-2 flex-shrink-0">
{/* Resume has highest priority when available */} {/* Resume has highest priority when available */}
{canResumeSewing(machineStatus) && ( {canResumeSewing(machineStatus) && (
<button <button