feat: route UI commands through facade

This commit is contained in:
2026-08-02 02:54:23 -04:00
parent 4ef1885149
commit f20c83f21e
2 changed files with 11 additions and 10 deletions

View File

@@ -252,12 +252,12 @@ function Workspace({ workbench, setWorkbench, leftTab, setLeftTab, rightTab, set
<div className="workspace-toolbar">
<div className="workbench-picker"><WorkbenchIcon size={17} /><select value={workbench} onChange={(event) => setWorkbench(event.target.value as Workbench)} aria-label="Workbench">{Object.keys(workbenchDefinitions).map((option) => <option key={option}>{option}</option>)}</select><ChevronDown size={14} /></div>
<div className="toolbar-divider" />
<IconButton icon={FilePlus2} label="New document" onClick={() => showNotice('New document prepared')} />
<IconButton icon={FilePlus2} label="New document" onClick={() => { facade.gui.command.execute({ commandId: 'new-document' }); showNotice('New document prepared') }} />
<IconButton icon={FolderOpen} label="Open project" onClick={() => showNotice('Project browser opened')} />
<IconButton icon={Save} label="Save project" onClick={() => showNotice('Saved to local workspace')} />
<IconButton icon={Save} label="Save project" onClick={() => { facade.gui.command.execute({ commandId: 'save' }); showNotice('Saved to local workspace') }} />
<div className="toolbar-divider" />
<IconButton icon={Box} label="Create body" onClick={() => showNotice('Create Body task opened')} active={workbench === 'Part Design'} />
<IconButton icon={CircleDot} label="Create sketch" onClick={() => showNotice('Select a plane to create a sketch')} />
<IconButton icon={Box} label="Create body" onClick={() => { facade.gui.command.execute({ commandId: 'create-body' }); showNotice('Create Body task opened') }} active={workbench === 'Part Design'} />
<IconButton icon={CircleDot} label="Create sketch" onClick={() => { facade.gui.command.execute({ commandId: 'create-sketch' }); showNotice('Select a plane to create a sketch') }} />
<IconButton icon={Rotate3D} label="Transform" />
<IconButton icon={SlidersHorizontal} label="Measure" />
<div className="toolbar-spacer" />
@@ -283,8 +283,8 @@ function Workspace({ workbench, setWorkbench, leftTab, setLeftTab, rightTab, set
<div className="viewport-bottom-left"><div className="view-chip"><Circle size={8} fill="currentColor" /> Perspective</div><div className="view-chip">Grid 10 mm</div></div>
<div className="viewport-bottom-right"><div className="axis-widget"><span className="axis-x">X</span><span className="axis-y">Y</span><span className="axis-z">Z</span><div className="axis-origin" /></div></div>
</section>
<aside className="task-dock right-panel"><div className="task-dock-title"><div><span className="eyebrow">Task panel</span><h2>{workbenchDefinitions[workbench].taskTitle}</h2></div><IconButton icon={MoreHorizontal} label="Task panel actions" /></div><TaskPanel workbench={workbench} showNotice={showNotice} /></aside>
<FunctionRail workbench={workbench} showNotice={showNotice} />
<aside className="task-dock right-panel"><div className="task-dock-title"><div><span className="eyebrow">Task panel</span><h2>{workbenchDefinitions[workbench].taskTitle}</h2></div><IconButton icon={MoreHorizontal} label="Task panel actions" /></div><TaskPanel workbench={workbench} facade={facade} showNotice={showNotice} /></aside>
<FunctionRail workbench={workbench} facade={facade} showNotice={showNotice} />
</div>
<div className={`bottom-drawer ${bottomOpen ? 'is-open' : ''}`}>
<div className="bottom-drawer-header"><div className="drawer-tabs"><button className="is-active"><PanelBottom size={14} />Report view <span className="tab-count">2</span></button><button><Clock3 size={14} />Jobs <span className="tab-count tab-count-green">1</span></button><button><Code2 size={14} />Diagnostics</button></div><button className="drawer-toggle" onClick={() => setBottomOpen(!bottomOpen)} title={bottomOpen ? 'Collapse bottom panel' : 'Expand bottom panel'}>{bottomOpen ? <ChevronDown size={15} /> : <ChevronRight size={15} />}</button></div>
@@ -298,9 +298,9 @@ function WorkbenchNavBar({ workbench, setWorkbench, showNotice }: { workbench: W
return <div className="workbench-nav" aria-label="Workbench navigation"><span className="nav-caption">Workbench</span>{pinnedWorkbenches.map((item) => { const IconComponent = workbenchMeta[item].icon; return <button key={item} className={workbench === item ? 'is-active' : ''} onClick={() => { setWorkbench(item); showNotice(`${item} workbench loaded`) }}><IconComponent size={13} /><span>{item}</span></button> })}<button className="nav-more" onClick={() => showNotice('All workbenches available from the picker')}><MoreHorizontal size={14} /><span>More workbenches</span></button></div>
}
function FunctionRail({ workbench, showNotice }: { workbench: Workbench; showNotice: (message: string) => void }) {
function FunctionRail({ workbench, facade, showNotice }: { workbench: Workbench; facade: BitBybitWebCadFacade; showNotice: (message: string) => void }) {
const groups = workbenchDefinitions[workbench].groups
return <aside className="function-rail" aria-label={`${workbench} commands`}>{groups.map((group) => <div className="rail-group" key={group.label}><span className="rail-group-label">{group.label}</span>{group.commands.slice(0, 5).map((item) => { const IconComponent = iconMap[item.icon] || MoreHorizontal; return <button key={item.id} title={`${item.label}${item.shortcut ? ` (${item.shortcut})` : ''}`} aria-label={item.label} onClick={() => showNotice(`${item.label} command staged`)}><IconComponent size={15} /></button> })}</div>)}</aside>
return <aside className="function-rail" aria-label={`${workbench} commands`}>{groups.map((group) => <div className="rail-group" key={group.label}><span className="rail-group-label">{group.label}</span>{group.commands.slice(0, 5).map((item) => { const IconComponent = iconMap[item.icon] || MoreHorizontal; const state = facade.gui.command.getState(item.id); const title = state.status === 'disabled' && state.reason ? `${item.label}: ${state.reason}` : `${item.label}${item.shortcut ? ` (${item.shortcut})` : ''}`; return <button key={item.id} title={title} aria-label={title} disabled={state.status === 'disabled'} onClick={() => { facade.gui.command.execute({ commandId: item.id }); showNotice(`${item.label} command staged`) }}><IconComponent size={15} /></button> })}</div>)}</aside>
}
function ModelTree({ document, selectedObject, setSelectedObject, showNotice }: { document: DocumentSnapshot; selectedObject: string; setSelectedObject: (id: string) => void; showNotice: (message: string) => void }) {
@@ -330,10 +330,10 @@ function TreeItem({ item, level, expanded, onToggle, selectedObject, setSelected
</>
}
function TaskPanel({ workbench, showNotice }: { workbench: Workbench; showNotice: (message: string) => void }) {
function TaskPanel({ workbench, facade, showNotice }: { workbench: Workbench; facade: BitBybitWebCadFacade; showNotice: (message: string) => void }) {
const definition = workbenchDefinitions[workbench]
const isSketch = workbench === 'Sketcher'
return <div className="task-panel"><div className="task-actions-top"><button className="button button-primary" onClick={() => showNotice('Task accepted')}>OK</button><button className="button button-outline" onClick={() => showNotice('Preview applied')}>Apply</button><button className="button button-quiet" onClick={() => showNotice('Task cancelled')}>Cancel</button></div><div className="task-header"><div className="task-icon"><Pencil size={16} /></div><div><span className="eyebrow">Active command</span><h2>{isSketch ? 'Edit Sketch' : definition.taskTitle}</h2></div><Badge tone="cyan">Preview</Badge></div><div className="task-body"><div className="task-step"><span className="step-index">1</span><div><strong>{isSketch ? 'Geometry and constraints' : definition.objectType}</strong><span>{definition.taskSummary}</span></div></div><label className="field-label">Primary value <span className="field-unit">mm</span><input className="field-input" value={isSketch ? 'Fully constrained' : '42.00'} readOnly /></label><label className="field-label">Operation<select className="field-input"><option>{workbench === 'Part Design' ? 'Dimension' : 'Contextual preview'}</option><option>Through all</option><option>Up to face</option></select></label><label className="check-row"><input type="checkbox" defaultChecked /><span>Preview result in viewport</span></label><div className="task-note"><AlertTriangle size={14} /><span>Changes remain local until the document is recomputed.</span></div></div></div>
return <div className="task-panel"><div className="task-actions-top"><button className="button button-primary" onClick={() => { facade.task.apply(); showNotice('Task accepted') }}>OK</button><button className="button button-outline" onClick={() => { facade.task.update({ preview: true }); showNotice('Preview applied') }}>Apply</button><button className="button button-quiet" onClick={() => { facade.task.cancel(); showNotice('Task cancelled') }}>Cancel</button></div><div className="task-header"><div className="task-icon"><Pencil size={16} /></div><div><span className="eyebrow">Active command</span><h2>{isSketch ? 'Edit Sketch' : definition.taskTitle}</h2></div><Badge tone="cyan">Preview</Badge></div><div className="task-body"><div className="task-step"><span className="step-index">1</span><div><strong>{isSketch ? 'Geometry and constraints' : definition.objectType}</strong><span>{definition.taskSummary}</span></div></div><label className="field-label">Primary value <span className="field-unit">mm</span><input className="field-input" value={isSketch ? 'Fully constrained' : '42.00'} readOnly /></label><label className="field-label">Operation<select className="field-input"><option>{workbench === 'Part Design' ? 'Dimension' : 'Contextual preview'}</option><option>Through all</option><option>Up to face</option></select></label><label className="check-row"><input type="checkbox" defaultChecked /><span>Preview result in viewport</span></label><div className="task-note"><AlertTriangle size={14} /><span>Changes remain local until the document is recomputed.</span></div></div></div>
}
function DataProperties({ selectedObject, showNotice }: { selectedObject: string; showNotice: (message: string) => void }) {

View File

@@ -342,6 +342,7 @@ button:focus-visible, input:focus-visible, select:focus-visible { outline: 2px s
.rail-group-label { width: 100%; overflow: hidden; color: var(--text-muted); font-size: 7px; text-align: center; text-transform: uppercase; letter-spacing: .05em; white-space: nowrap; text-overflow: ellipsis; }
.rail-group button { width: 30px; height: 27px; display: grid; place-items: center; border: 1px solid transparent; border-radius: 3px; background: transparent; color: var(--text-muted); cursor: pointer; }
.rail-group button:hover { color: var(--cyan); background: var(--cyan-soft); border-color: #2c6568; }
.rail-group button:disabled { opacity: .3; cursor: not-allowed; }
.model-task-summary { padding: 17px 14px; color: var(--text-muted); }
.model-task-summary p { font-size: 11px; line-height: 1.5; margin: 10px 0 15px; }
.model-task-summary .button { width: 100%; }