feat(web/mazenet): glide transitions for service fleet + inspector panels

This commit is contained in:
2026-04-22 18:16:17 -04:00
parent e0231bf990
commit 1674316788
4 changed files with 52 additions and 33 deletions

View File

@@ -27,12 +27,14 @@ interface Props {
onAddDecky?: (netId: string) => void;
setSelection?: (sel: Selection) => void;
pendingChanges?: number;
className?: string;
}
const Inspector: React.FC<Props> = ({
selection, nets, nodes, edges, topologyStatus, onClose,
onDeleteNet, onDeleteNode, onDeleteEdge, onRemoveService, onAddDecky, setSelection,
pendingChanges = 0,
className = '',
}) => {
const net = selection?.type === 'net' ? nets.find((n) => n.id === selection.id) : undefined;
const node = selection?.type === 'node' ? nodes.find((n) => n.id === selection.id) : undefined;
@@ -58,7 +60,7 @@ const Inspector: React.FC<Props> = ({
const isObserved = node?.kind === 'observed';
return (
<aside className="maze-inspector">
<aside className={`maze-inspector ${className}`}>
<div className="maze-inspector-title">
<Crosshair size={12} className="violet-accent" />
<span>INSPECTOR</span>

View File

@@ -63,7 +63,21 @@ body.maze-fullscreen .maze-shell {
min-height: 0;
margin: 0 -32px -32px;
border-top: 1px solid var(--border);
transition: grid-template-columns 260ms cubic-bezier(0.4, 0, 0.2, 1);
}
.maze-palette,
.maze-inspector {
transition: opacity 200ms ease, transform 260ms cubic-bezier(0.4, 0, 0.2, 1);
min-width: 0;
}
.maze-palette.collapsed,
.maze-inspector.collapsed {
opacity: 0;
pointer-events: none;
overflow: hidden;
}
.maze-palette.collapsed { transform: translateX(-8px); }
.maze-inspector.collapsed { transform: translateX(8px); }
/* ── Palette ────────────────────────────────── */
.maze-palette {

View File

@@ -626,12 +626,15 @@ const MazeNET: React.FC = () => {
<div
className="maze-shell"
style={{
gridTemplateColumns: `${paletteOpen ? '240px ' : ''}1fr${inspectorOpen ? ' 320px' : ''}`,
gridTemplateColumns: `${paletteOpen ? '240px' : '0px'} 1fr ${inspectorOpen ? '320px' : '0px'}`,
}}
>
{paletteOpen && (
<Palette services={services} archetypes={archetypes} startPaletteDrag={interaction.startPaletteDrag} />
)}
<Palette
services={services}
archetypes={archetypes}
startPaletteDrag={interaction.startPaletteDrag}
className={paletteOpen ? '' : 'collapsed'}
/>
<Canvas
ref={canvasRef}
nets={nets}
@@ -673,32 +676,31 @@ const MazeNET: React.FC = () => {
{interaction.paletteDrag.label}
</div>
)}
{inspectorOpen && (
<Inspector
selection={selection}
setSelection={setSelection}
nets={nets}
nodes={nodes}
edges={edges}
topologyStatus={topoStatus}
onClose={() => setInspectorOpen(false)}
onDeleteNet={removeNet}
onDeleteNode={removeNode}
onDeleteEdge={removeEdge}
onRemoveService={removeServiceFromNode}
onAddDecky={(netId) => {
const net = nets.find((n) => n.id === netId);
if (!net) return;
onPaletteDrop(
{ kind: 'archetype', slug: archetypes[0]?.slug ?? 'deaddeck',
services: archetypes[0]?.services.slice(0, 2) ?? [],
label: archetypes[0]?.name ?? 'DECKY',
clientX: 0, clientY: 0 },
{ x: net.x + 40, y: net.y + 60 }, netId, null,
);
}}
/>
)}
<Inspector
selection={selection}
setSelection={setSelection}
nets={nets}
nodes={nodes}
edges={edges}
topologyStatus={topoStatus}
onClose={() => setInspectorOpen(false)}
onDeleteNet={removeNet}
onDeleteNode={removeNode}
onDeleteEdge={removeEdge}
onRemoveService={removeServiceFromNode}
onAddDecky={(netId) => {
const net = nets.find((n) => n.id === netId);
if (!net) return;
onPaletteDrop(
{ kind: 'archetype', slug: archetypes[0]?.slug ?? 'deaddeck',
services: archetypes[0]?.services.slice(0, 2) ?? [],
label: archetypes[0]?.name ?? 'DECKY',
clientX: 0, clientY: 0 },
{ x: net.x + 40, y: net.y + 60 }, netId, null,
);
}}
className={inspectorOpen ? '' : 'collapsed'}
/>
</div>
</div>
);

View File

@@ -22,9 +22,10 @@ interface Props {
services: ServiceDef[];
archetypes: Archetype[];
startPaletteDrag: (d: Omit<PaletteDrag, 'clientX' | 'clientY'>, e: React.MouseEvent) => void;
className?: string;
}
const Palette: React.FC<Props> = ({ services, archetypes, startPaletteDrag }) => {
const Palette: React.FC<Props> = ({ services, archetypes, startPaletteDrag, className = '' }) => {
const start = (d: Omit<PaletteDrag, 'clientX' | 'clientY'>) =>
(e: React.MouseEvent) => {
if (e.button !== 0) return;
@@ -33,7 +34,7 @@ const Palette: React.FC<Props> = ({ services, archetypes, startPaletteDrag }) =>
};
return (
<div className="maze-palette">
<div className={`maze-palette ${className}`}>
<div className="palette-group">
<label> NETWORKS</label>
<div className="palette-item" onMouseDown={start({ kind: 'network-subnet', slug: 'subnet', label: 'SUBNET' })}>