design: redesign product demo UI
This commit is contained in:
178
src/App.tsx
178
src/App.tsx
@@ -1,28 +1,24 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import Dashboard from './components/Dashboard';
|
||||
import Header from './components/Header';
|
||||
import HeroSection from './components/HeroSection';
|
||||
import ProductDemoSection from './components/ProductDemoSection';
|
||||
import ScenarioCards from './components/ScenarioCards';
|
||||
import CriteriaCards from './components/CriteriaCards';
|
||||
import EngineeringDetails from './components/EngineeringDetails';
|
||||
import { DEMO_STEPS } from './data/demoSteps';
|
||||
import { SCENARIOS } from './data/scenarios';
|
||||
import { createSimulation, setRunning, stepSimulation, stepSimulationToNextState } from './domain/simulation';
|
||||
import type { DemoStep, PresentationMode, ScenarioId, SimulationState } from './domain/types';
|
||||
|
||||
import GuidedDemoView from './components/GuidedDemoView';
|
||||
|
||||
const TICK_MS = 250;
|
||||
|
||||
function isFaultDemoStep(step: DemoStep): boolean {
|
||||
return step.scenarioId === 'jam' || step.scenarioId === 'emergency_stop';
|
||||
}
|
||||
import { createSimulation, setRunning, stepSimulationToNextState } from './domain/simulation';
|
||||
import type { ScenarioId, SimulationState } from './domain/types';
|
||||
|
||||
export default function App() {
|
||||
const [activeScenarioId, setActiveScenarioId] = useState<ScenarioId>('normal_flow');
|
||||
const [presentationMode, setPresentationMode] = useState<PresentationMode>('guided');
|
||||
const [safeDemoEnabled, setSafeDemoEnabled] = useState(true);
|
||||
const [demoStepIndex, setDemoStepIndex] = useState(0);
|
||||
const [pendingFaultStepId, setPendingFaultStepId] = useState<string | undefined>();
|
||||
|
||||
const activeScenario = useMemo(
|
||||
() => SCENARIOS.find((scenario) => scenario.id === activeScenarioId) ?? SCENARIOS[0],
|
||||
() => SCENARIOS.find((s) => s.id === activeScenarioId) ?? SCENARIOS[0],
|
||||
[activeScenarioId],
|
||||
);
|
||||
|
||||
const currentDemoStep = DEMO_STEPS[demoStepIndex];
|
||||
const [simulation, setSimulation] = useState<SimulationState>(() => createSimulation(activeScenario));
|
||||
|
||||
@@ -30,110 +26,70 @@ export default function App() {
|
||||
setSimulation(createSimulation(activeScenario));
|
||||
}, [activeScenario]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!simulation.running) {
|
||||
return undefined;
|
||||
// We are not using automatic interval in the new product demo by default to keep it step-by-step
|
||||
// but if the user wants it to run automatically, we can preserve it if needed.
|
||||
// Let's use handleNext to just step through.
|
||||
|
||||
const handleStartDemo = () => {
|
||||
document.getElementById('demo')?.scrollIntoView({ behavior: 'smooth' });
|
||||
if (!simulation.currentItem && demoStepIndex === 0) {
|
||||
setSimulation(stepSimulationToNextState(simulation));
|
||||
}
|
||||
|
||||
const timer = window.setInterval(() => {
|
||||
setSimulation((current) => stepSimulation(current, TICK_MS));
|
||||
}, TICK_MS);
|
||||
|
||||
return () => window.clearInterval(timer);
|
||||
}, [simulation.running]);
|
||||
|
||||
const handleStart = () => setSimulation((current) => setRunning(current, true));
|
||||
const handlePause = () => setSimulation((current) => setRunning(current, false));
|
||||
const handleReset = () => setSimulation(createSimulation(activeScenario));
|
||||
const handleStep = () => setSimulation((current) => stepSimulationToNextState(current));
|
||||
const handleScenarioChange = (scenarioId: ScenarioId) => {
|
||||
setPendingFaultStepId(undefined);
|
||||
setActiveScenarioId(scenarioId);
|
||||
};
|
||||
|
||||
const handleDemoStepChange = (nextIndex: number) => {
|
||||
setPendingFaultStepId(undefined);
|
||||
setDemoStepIndex(Math.max(0, Math.min(nextIndex, DEMO_STEPS.length - 1)));
|
||||
};
|
||||
|
||||
const handleApplyDemoStepScenario = (step: DemoStep) => {
|
||||
if (presentationMode === 'presentation' && safeDemoEnabled && isFaultDemoStep(step) && pendingFaultStepId !== step.id) {
|
||||
setPendingFaultStepId(step.id);
|
||||
return;
|
||||
}
|
||||
|
||||
setPendingFaultStepId(undefined);
|
||||
setActiveScenarioId(step.scenarioId);
|
||||
};
|
||||
|
||||
const handleRunPreferredAction = (step: DemoStep) => {
|
||||
if (activeScenarioId !== step.scenarioId) {
|
||||
handleApplyDemoStepScenario(step);
|
||||
return;
|
||||
}
|
||||
|
||||
if (step.preferredAction === 'start') {
|
||||
handleStart();
|
||||
} else if (step.preferredAction === 'pause') {
|
||||
handlePause();
|
||||
} else if (step.preferredAction === 'reset') {
|
||||
handleReset();
|
||||
const handleNext = () => {
|
||||
// If simulation is finished or errored, we might need to reset or move to next scenario
|
||||
const isFinished = simulation.machineState === 'RETURN_HOME' || simulation.machineState === 'FAULT' || simulation.machineState === 'EMERGENCY_STOP';
|
||||
|
||||
if (isFinished) {
|
||||
const nextIndex = (demoStepIndex + 1) % DEMO_STEPS.length;
|
||||
setDemoStepIndex(nextIndex);
|
||||
setActiveScenarioId(DEMO_STEPS[nextIndex].scenarioId);
|
||||
} else {
|
||||
handleStep();
|
||||
setSimulation((current) => stepSimulationToNextState(current));
|
||||
}
|
||||
};
|
||||
|
||||
const handleRestartDemo = () => {
|
||||
setPendingFaultStepId(undefined);
|
||||
setDemoStepIndex(0);
|
||||
setActiveScenarioId(DEMO_STEPS[0].scenarioId);
|
||||
const handleReset = () => {
|
||||
setSimulation(createSimulation(activeScenario));
|
||||
};
|
||||
|
||||
return presentationMode === 'guided' ? (
|
||||
<GuidedDemoView
|
||||
simulation={simulation}
|
||||
scenarios={SCENARIOS}
|
||||
activeScenarioId={activeScenarioId}
|
||||
presentationMode={presentationMode}
|
||||
safeDemoEnabled={safeDemoEnabled}
|
||||
demoSteps={DEMO_STEPS}
|
||||
demoStepIndex={demoStepIndex}
|
||||
currentDemoStep={currentDemoStep}
|
||||
pendingFaultStepId={pendingFaultStepId}
|
||||
onStart={handleStart}
|
||||
onPause={handlePause}
|
||||
onReset={handleReset}
|
||||
onStep={handleStep}
|
||||
onScenarioChange={handleScenarioChange}
|
||||
onPresentationModeChange={setPresentationMode}
|
||||
onSafeDemoChange={setSafeDemoEnabled}
|
||||
onDemoStepChange={handleDemoStepChange}
|
||||
onApplyDemoStepScenario={handleApplyDemoStepScenario}
|
||||
onRunPreferredAction={handleRunPreferredAction}
|
||||
onRestartDemo={handleRestartDemo}
|
||||
/>
|
||||
) : (
|
||||
<Dashboard
|
||||
simulation={simulation}
|
||||
scenarios={SCENARIOS}
|
||||
activeScenarioId={activeScenarioId}
|
||||
presentationMode={presentationMode}
|
||||
safeDemoEnabled={safeDemoEnabled}
|
||||
demoSteps={DEMO_STEPS}
|
||||
demoStepIndex={demoStepIndex}
|
||||
currentDemoStep={currentDemoStep}
|
||||
pendingFaultStepId={pendingFaultStepId}
|
||||
onStart={handleStart}
|
||||
onPause={handlePause}
|
||||
onReset={handleReset}
|
||||
onStep={handleStep}
|
||||
onScenarioChange={handleScenarioChange}
|
||||
onPresentationModeChange={setPresentationMode}
|
||||
onSafeDemoChange={setSafeDemoEnabled}
|
||||
onDemoStepChange={handleDemoStepChange}
|
||||
onApplyDemoStepScenario={handleApplyDemoStepScenario}
|
||||
onRunPreferredAction={handleRunPreferredAction}
|
||||
onRestartDemo={handleRestartDemo}
|
||||
/>
|
||||
const handleScenarioChange = (scenarioId: ScenarioId) => {
|
||||
setActiveScenarioId(scenarioId);
|
||||
// Find first step that matches this scenario
|
||||
const stepIdx = DEMO_STEPS.findIndex(s => s.scenarioId === scenarioId);
|
||||
if (stepIdx >= 0) setDemoStepIndex(stepIdx);
|
||||
document.getElementById('demo')?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="product-page">
|
||||
<Header simulation={simulation} />
|
||||
|
||||
<main className="product-main">
|
||||
<HeroSection onStartDemo={handleStartDemo} />
|
||||
|
||||
<ProductDemoSection
|
||||
simulation={simulation}
|
||||
onNext={handleNext}
|
||||
onReset={handleReset}
|
||||
demoStepTitle={currentDemoStep.title}
|
||||
/>
|
||||
|
||||
<ScenarioCards
|
||||
scenarios={SCENARIOS}
|
||||
activeScenarioId={activeScenarioId}
|
||||
onScenarioChange={handleScenarioChange}
|
||||
/>
|
||||
|
||||
<CriteriaCards />
|
||||
|
||||
<EngineeringDetails simulation={simulation} />
|
||||
</main>
|
||||
|
||||
<footer className="site-footer">
|
||||
<p>OZON Tech Hackathon 2026. Sorter Simulation.</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user