Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b521fbf
Everything always available in BottomBar
kube Dec 15, 2025
4c0d697
Extract styles in SimulationControls
kube Dec 15, 2025
07a0d61
Disable SimulateMode in ModeSelector for now
kube Dec 15, 2025
5b3891a
Extract styles in LeftSidebar and make all sections always visible
kube Dec 15, 2025
0d4da59
Extract styles in PlaceNode
kube Dec 17, 2025
b273e36
Make PlaceNode token count bigger
kube Dec 17, 2025
be34ae1
Make Simulation available in Edit Mode, and disable Simulate tab for now
kube Dec 17, 2025
8e72491
Fix BrokenMachines example
kube Dec 17, 2025
021a4ac
Wording
kube Dec 17, 2025
68dc0c5
Create GlassPanel component
kube Dec 17, 2025
e38f2a9
Centralize Resizable in GlassPanel
kube Dec 17, 2025
d7bd39f
Update GlassPanel style
kube Dec 17, 2025
3a247cd
Centralize UI Constants
kube Dec 18, 2025
bb0cb60
Better resizer handle
kube Dec 18, 2025
b59dd4f
Move RESIZE_HANDLE constants in UI constants
kube Dec 18, 2025
815b245
Make tabs a bit cleaner
kube Dec 18, 2025
a2501ff
Move activeBottomPanel to EditorStore
kube Dec 18, 2025
3c3e221
Rename DiagnosticsPanel to BottomPanel
kube Dec 18, 2025
866408c
Use Play/Pause/Stop icons
kube Dec 18, 2025
4c509a1
Open Diagnostics when clicking on Run Simulation when errors disable it
kube Dec 18, 2025
068778d
Remove tooltip from Global Parameters panel, just show text
kube Dec 18, 2025
0b60acc
Cleaner BottomPanel
kube Dec 18, 2025
1e0858a
Remove Simulation State from Simulation Settings
kube Dec 18, 2025
c4b6619
BottomPanel styles
kube Dec 18, 2025
b4ccb94
Add close button to BottomPanel
kube Dec 18, 2025
2dff2de
Diagnostics Indicator opens Diagnostics in BottomPanel, with Tooltip
kube Dec 18, 2025
d9aa09a
Add Toggle Panel button in BottomBar
kube Dec 18, 2025
7963943
Hide Place/Transition buttons in BottomBar when Simulation is running
kube Dec 18, 2025
51e084d
SDCPNView readonly when Simulation is running
kube Dec 18, 2025
0fa8f29
Make Token Types, Differential Equations and Global Parameters readon…
kube Dec 18, 2025
c466316
Make Diagnostics expanded by default
kube Dec 18, 2025
8947f36
Toolbar refinement + updates from demo review
kube Dec 19, 2025
612615e
Adjust bezelWidth for current BottomBar height
kube Dec 19, 2025
b9b4530
Swap Parameters and Computation sections
kube Dec 19, 2025
14eb381
Add tooltip on Time Step
kube Dec 19, 2025
f86ebfe
Format
kube Dec 19, 2025
19021ee
Remove dependency of SimulationProvider on EditorContext and reorder …
kube Dec 23, 2025
8e08ac6
Add useIsReadOnly hook
kube Dec 23, 2025
18166fc
Review: remove comment
kube Dec 23, 2025
5b09620
Remove globalMode check from TransitionProperties
kube Dec 23, 2025
ad258da
Fix format
kube Jan 2, 2026
0cdac41
Add changeset
kube Jan 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/warm-rabbits-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut": patch
---

Quick Simulation in Edit mode, and disable Simulate tab for now.
271 changes: 271 additions & 0 deletions libs/@hashintel/petrinaut/src/components/glass-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
import { css, cx } from "@hashintel/ds-helpers/css";
import {
type CSSProperties,
type ReactNode,
useCallback,
useEffect,
useRef,
useState,
} from "react";

import { RESIZE_HANDLE_OFFSET, RESIZE_HANDLE_SIZE } from "../constants/ui";

const panelContainerStyle = css({
position: "relative",
borderRadius: "[7px]",
backgroundColor: "[rgba(255, 255, 255, 0.7)]",
boxShadow: "[0 2px 11px rgba(0, 0, 0, 0.1)]",
border: "[1px solid rgba(255, 255, 255, 0.8)]",
});

const blurOverlayStyle = css({
position: "absolute",
inset: "[0]",
borderRadius: "[7px]",
pointerEvents: "none",
backdropFilter: "[blur(27px)]",
});

const contentContainerStyle = css({
position: "relative",
height: "[100%]",
width: "[100%]",
});

type ResizableEdge = "top" | "bottom" | "left" | "right";

interface ResizeConfig {
/** Which edge of the panel is resizable */
edge: ResizableEdge;
/** Callback when the size changes */
onResize: (newSize: number) => void;
/** Current size (width for left/right, height for top/bottom) */
size: number;
/** Minimum size constraint */
minSize?: number;
/** Maximum size constraint */
maxSize?: number;
}

interface GlassPanelProps {
/** Content to render inside the panel */
children: ReactNode;
/** Additional CSS class name for the panel container */
className?: string;
/** Inline styles for the panel container */
style?: CSSProperties;
/** Additional CSS class name for the content container */
contentClassName?: string;
/** Inline styles for the content container */
contentStyle?: CSSProperties;
/** Blur amount in pixels (default: 24) */
blur?: number;
/** Configuration for making the panel resizable */
resizable?: ResizeConfig;
}

const getResizeHandleStyle = (edge: ResizableEdge): CSSProperties => {
const base: CSSProperties = {
position: "absolute",
background: "transparent",
border: "none",
padding: 0,
zIndex: 1001,
};

switch (edge) {
case "top":
return {
...base,
top: RESIZE_HANDLE_OFFSET,
left: 0,
right: 0,
height: RESIZE_HANDLE_SIZE,
cursor: "ns-resize",
};
case "bottom":
return {
...base,
bottom: RESIZE_HANDLE_OFFSET,
left: 0,
right: 0,
height: RESIZE_HANDLE_SIZE,
cursor: "ns-resize",
};
case "left":
return {
...base,
top: 0,
left: RESIZE_HANDLE_OFFSET,
bottom: 0,
width: RESIZE_HANDLE_SIZE,
cursor: "ew-resize",
};
case "right":
return {
...base,
top: 0,
right: RESIZE_HANDLE_OFFSET,
bottom: 0,
width: RESIZE_HANDLE_SIZE,
cursor: "ew-resize",
};
}
};

const getCursorStyle = (edge: ResizableEdge): string => {
return edge === "top" || edge === "bottom" ? "ns-resize" : "ew-resize";
};

/**
* GlassPanel provides a frosted glass-like appearance with backdrop blur.
*
* Uses a separate overlay element for the backdrop-filter to avoid
* interfering with child components that use fixed/absolute positioning
* (e.g., Monaco Editor hover widgets).
*
* Optionally supports resizing from any edge with the `resizable` prop.
*/
export const GlassPanel: React.FC<GlassPanelProps> = ({
children,
className,
style,
contentClassName,
contentStyle,
blur = 24,
resizable,
}) => {
const [isResizing, setIsResizing] = useState(false);
const resizeStartPosRef = useRef(0);
const resizeStartSizeRef = useRef(0);

const handleResizeStart = useCallback(
(event: React.MouseEvent) => {
if (!resizable) {
return;
}

event.preventDefault();
setIsResizing(true);

const isVertical =
resizable.edge === "top" || resizable.edge === "bottom";
resizeStartPosRef.current = isVertical ? event.clientY : event.clientX;
resizeStartSizeRef.current = resizable.size;
},
[resizable],
);

const handleResizeMove = useCallback(
(event: MouseEvent) => {
if (!isResizing || !resizable) {
return;
}

const { edge, onResize, minSize = 100, maxSize = 800 } = resizable;
const isVertical = edge === "top" || edge === "bottom";
const currentPos = isVertical ? event.clientY : event.clientX;

// Calculate delta based on edge direction
// For top/left: dragging towards origin increases size
// For bottom/right: dragging away from origin increases size
let delta: number;
if (edge === "top" || edge === "left") {
delta = resizeStartPosRef.current - currentPos;
} else {
delta = currentPos - resizeStartPosRef.current;
}

const newSize = Math.max(
minSize,
Math.min(maxSize, resizeStartSizeRef.current + delta),
);

onResize(newSize);
},
[isResizing, resizable],
);

const handleResizeEnd = useCallback(() => {
setIsResizing(false);
}, []);

// Handle keyboard resize
const handleKeyDown = useCallback(
(event: React.KeyboardEvent) => {
if (!resizable) {
return;
}

const { edge, onResize, size, minSize = 100, maxSize = 800 } = resizable;
const step = 10;
let delta = 0;

if (edge === "top" || edge === "bottom") {
if (event.key === "ArrowUp") {
delta = edge === "top" ? step : -step;
} else if (event.key === "ArrowDown") {
delta = edge === "top" ? -step : step;
}
} else if (event.key === "ArrowLeft") {
delta = edge === "left" ? step : -step;
} else if (event.key === "ArrowRight") {
delta = edge === "left" ? -step : step;
}

if (delta !== 0) {
const newSize = Math.max(minSize, Math.min(maxSize, size + delta));
onResize(newSize);
}
},
[resizable],
);

// Global cursor and event listeners during resize
useEffect(() => {
if (!isResizing || !resizable) {
return;
}

document.addEventListener("mousemove", handleResizeMove);
document.addEventListener("mouseup", handleResizeEnd);
document.body.style.cursor = getCursorStyle(resizable.edge);
document.body.style.userSelect = "none";

return () => {
document.removeEventListener("mousemove", handleResizeMove);
document.removeEventListener("mouseup", handleResizeEnd);
document.body.style.cursor = "";
document.body.style.userSelect = "";
};
}, [isResizing, resizable, handleResizeMove, handleResizeEnd]);

return (
<div className={cx(panelContainerStyle, className)} style={style}>
{/* Resize handle */}
{resizable && (
<button
type="button"
aria-label={`Resize panel from ${resizable.edge}`}
onMouseDown={handleResizeStart}
onKeyDown={handleKeyDown}
style={getResizeHandleStyle(resizable.edge)}
/>
)}

{/* Blur overlay - separate from content to avoid affecting child positioning */}
<div
className={blurOverlayStyle}
style={blur !== 24 ? { backdropFilter: `blur(${blur}px)` } : undefined}
/>

{/* Content container */}
<div
className={cx(contentContainerStyle, contentClassName)}
style={contentStyle}
>
{children}
</div>
</div>
);
};
25 changes: 25 additions & 0 deletions libs/@hashintel/petrinaut/src/constants/ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* UI-related constants for the Petrinaut editor.
*/

// Panel margin (spacing around panels)
export const PANEL_MARGIN = 10;

// Resize handle
export const RESIZE_HANDLE_SIZE = 20;
export const RESIZE_HANDLE_OFFSET = -Math.floor(RESIZE_HANDLE_SIZE / 2);

// Left Sidebar
export const DEFAULT_LEFT_SIDEBAR_WIDTH = 320;
export const MIN_LEFT_SIDEBAR_WIDTH = 280;
export const MAX_LEFT_SIDEBAR_WIDTH = 500;

// Properties Panel (right side)
export const DEFAULT_PROPERTIES_PANEL_WIDTH = 450;
export const MIN_PROPERTIES_PANEL_WIDTH = 250;
export const MAX_PROPERTIES_PANEL_WIDTH = 800;

// Bottom Panel
export const DEFAULT_BOTTOM_PANEL_HEIGHT = 180;
export const MIN_BOTTOM_PANEL_HEIGHT = 100;
export const MAX_BOTTOM_PANEL_HEIGHT = 600;
2 changes: 1 addition & 1 deletion libs/@hashintel/petrinaut/src/examples/broken-machines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ export const productionMachines: { title: string; petriNetDefinition: SDCPN } =
lambdaType: "predicate",
lambdaCode: "export default Lambda(() => true)",
transitionKernelCode:
"/**\n* This function defines the kernel for the transition.\n* It receives tokens from input places,\n* and any global parameters defined,\n* and should return tokens for output places keyed by place name.\n*/\nexport default TransitionKernel((tokens) => {\n // tokens is an object which looks like:\n // { PlaceA: [{ x: 0, y: 0 }], PlaceB: [...] }\n // where 'x' and 'y' are examples of dimensions (properties)\n // of the token's type.\n\n // Return an object with output place names as keys\n return {\n MachinesBeingRepaired: [\n { machine_damage_ratio: tokens.BrokenMachines[0].machine_damage_ratio }\n ],\n };\n});",
"/**\n* This function defines the kernel for the transition.\n* It receives tokens from input places,\n* and any global parameters defined,\n* and should return tokens for output places keyed by place name.\n*/\nexport default TransitionKernel((tokens) => {\n // tokens is an object which looks like:\n // { PlaceA: [{ x: 0, y: 0 }], PlaceB: [...] }\n // where 'x' and 'y' are examples of dimensions (properties)\n // of the token's type.\n\n // Return an object with output place names as keys\n return {\n MachinesBeingRepaired: [\n { machine_damage_ratio: tokens.MachinesToRepair[0].machine_damage_ratio }\n ],\n };\n});",
x: 1635,
y: 480,
width: 160,
Expand Down
1 change: 0 additions & 1 deletion libs/@hashintel/petrinaut/src/feature-flags.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export const FEATURE_FLAGS = {
RUNNING_MAN_ICON: true,
REORDER_TRANSITION_ARCS: false,
};
17 changes: 7 additions & 10 deletions libs/@hashintel/petrinaut/src/petrinaut.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,7 @@ export type PetrinautProps = {
/**
* Create a new net and load it into the editor.
*/
createNewNet: (params: {
petriNetDefinition: SDCPN;
title: string;
}) => void;
createNewNet: (params: { petriNetDefinition: SDCPN; title: string }) => void;
/**
* Whether to hide controls relating to net loading, creation and title.
*/
Expand Down Expand Up @@ -105,14 +102,14 @@ export const Petrinaut = ({
}: PetrinautProps) => {
return (
<SDCPNProvider {...rest}>
<EditorProvider>
<CheckerProvider>
<SimulationProvider>
<CheckerProvider>
<SimulationProvider>
<EditorProvider>
<MonacoSetup />
<EditorView hideNetManagementControls={hideNetManagementControls} />
</SimulationProvider>
</CheckerProvider>
</EditorProvider>
</EditorProvider>
</SimulationProvider>
</CheckerProvider>
</SDCPNProvider>
);
};
Loading
Loading