feat(web/mazenet): glide transitions for service fleet + inspector panels
This commit is contained in:
@@ -27,12 +27,14 @@ interface Props {
|
|||||||
onAddDecky?: (netId: string) => void;
|
onAddDecky?: (netId: string) => void;
|
||||||
setSelection?: (sel: Selection) => void;
|
setSelection?: (sel: Selection) => void;
|
||||||
pendingChanges?: number;
|
pendingChanges?: number;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Inspector: React.FC<Props> = ({
|
const Inspector: React.FC<Props> = ({
|
||||||
selection, nets, nodes, edges, topologyStatus, onClose,
|
selection, nets, nodes, edges, topologyStatus, onClose,
|
||||||
onDeleteNet, onDeleteNode, onDeleteEdge, onRemoveService, onAddDecky, setSelection,
|
onDeleteNet, onDeleteNode, onDeleteEdge, onRemoveService, onAddDecky, setSelection,
|
||||||
pendingChanges = 0,
|
pendingChanges = 0,
|
||||||
|
className = '',
|
||||||
}) => {
|
}) => {
|
||||||
const net = selection?.type === 'net' ? nets.find((n) => n.id === selection.id) : undefined;
|
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;
|
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';
|
const isObserved = node?.kind === 'observed';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="maze-inspector">
|
<aside className={`maze-inspector ${className}`}>
|
||||||
<div className="maze-inspector-title">
|
<div className="maze-inspector-title">
|
||||||
<Crosshair size={12} className="violet-accent" />
|
<Crosshair size={12} className="violet-accent" />
|
||||||
<span>INSPECTOR</span>
|
<span>INSPECTOR</span>
|
||||||
|
|||||||
@@ -63,7 +63,21 @@ body.maze-fullscreen .maze-shell {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
margin: 0 -32px -32px;
|
margin: 0 -32px -32px;
|
||||||
border-top: 1px solid var(--border);
|
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 ────────────────────────────────── */
|
/* ── Palette ────────────────────────────────── */
|
||||||
.maze-palette {
|
.maze-palette {
|
||||||
|
|||||||
@@ -626,12 +626,15 @@ const MazeNET: React.FC = () => {
|
|||||||
<div
|
<div
|
||||||
className="maze-shell"
|
className="maze-shell"
|
||||||
style={{
|
style={{
|
||||||
gridTemplateColumns: `${paletteOpen ? '240px ' : ''}1fr${inspectorOpen ? ' 320px' : ''}`,
|
gridTemplateColumns: `${paletteOpen ? '240px' : '0px'} 1fr ${inspectorOpen ? '320px' : '0px'}`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{paletteOpen && (
|
<Palette
|
||||||
<Palette services={services} archetypes={archetypes} startPaletteDrag={interaction.startPaletteDrag} />
|
services={services}
|
||||||
)}
|
archetypes={archetypes}
|
||||||
|
startPaletteDrag={interaction.startPaletteDrag}
|
||||||
|
className={paletteOpen ? '' : 'collapsed'}
|
||||||
|
/>
|
||||||
<Canvas
|
<Canvas
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
nets={nets}
|
nets={nets}
|
||||||
@@ -673,32 +676,31 @@ const MazeNET: React.FC = () => {
|
|||||||
{interaction.paletteDrag.label}
|
{interaction.paletteDrag.label}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{inspectorOpen && (
|
<Inspector
|
||||||
<Inspector
|
selection={selection}
|
||||||
selection={selection}
|
setSelection={setSelection}
|
||||||
setSelection={setSelection}
|
nets={nets}
|
||||||
nets={nets}
|
nodes={nodes}
|
||||||
nodes={nodes}
|
edges={edges}
|
||||||
edges={edges}
|
topologyStatus={topoStatus}
|
||||||
topologyStatus={topoStatus}
|
onClose={() => setInspectorOpen(false)}
|
||||||
onClose={() => setInspectorOpen(false)}
|
onDeleteNet={removeNet}
|
||||||
onDeleteNet={removeNet}
|
onDeleteNode={removeNode}
|
||||||
onDeleteNode={removeNode}
|
onDeleteEdge={removeEdge}
|
||||||
onDeleteEdge={removeEdge}
|
onRemoveService={removeServiceFromNode}
|
||||||
onRemoveService={removeServiceFromNode}
|
onAddDecky={(netId) => {
|
||||||
onAddDecky={(netId) => {
|
const net = nets.find((n) => n.id === netId);
|
||||||
const net = nets.find((n) => n.id === netId);
|
if (!net) return;
|
||||||
if (!net) return;
|
onPaletteDrop(
|
||||||
onPaletteDrop(
|
{ kind: 'archetype', slug: archetypes[0]?.slug ?? 'deaddeck',
|
||||||
{ kind: 'archetype', slug: archetypes[0]?.slug ?? 'deaddeck',
|
services: archetypes[0]?.services.slice(0, 2) ?? [],
|
||||||
services: archetypes[0]?.services.slice(0, 2) ?? [],
|
label: archetypes[0]?.name ?? 'DECKY',
|
||||||
label: archetypes[0]?.name ?? 'DECKY',
|
clientX: 0, clientY: 0 },
|
||||||
clientX: 0, clientY: 0 },
|
{ x: net.x + 40, y: net.y + 60 }, netId, null,
|
||||||
{ x: net.x + 40, y: net.y + 60 }, netId, null,
|
);
|
||||||
);
|
}}
|
||||||
}}
|
className={inspectorOpen ? '' : 'collapsed'}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ interface Props {
|
|||||||
services: ServiceDef[];
|
services: ServiceDef[];
|
||||||
archetypes: Archetype[];
|
archetypes: Archetype[];
|
||||||
startPaletteDrag: (d: Omit<PaletteDrag, 'clientX' | 'clientY'>, e: React.MouseEvent) => void;
|
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'>) =>
|
const start = (d: Omit<PaletteDrag, 'clientX' | 'clientY'>) =>
|
||||||
(e: React.MouseEvent) => {
|
(e: React.MouseEvent) => {
|
||||||
if (e.button !== 0) return;
|
if (e.button !== 0) return;
|
||||||
@@ -33,7 +34,7 @@ const Palette: React.FC<Props> = ({ services, archetypes, startPaletteDrag }) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="maze-palette">
|
<div className={`maze-palette ${className}`}>
|
||||||
<div className="palette-group">
|
<div className="palette-group">
|
||||||
<label>① NETWORKS</label>
|
<label>① NETWORKS</label>
|
||||||
<div className="palette-item" onMouseDown={start({ kind: 'network-subnet', slug: 'subnet', label: 'SUBNET' })}>
|
<div className="palette-item" onMouseDown={start({ kind: 'network-subnet', slug: 'subnet', label: 'SUBNET' })}>
|
||||||
|
|||||||
Reference in New Issue
Block a user