-
Notifications
You must be signed in to change notification settings - Fork 109
H-5768: Quick Simulation in Edit Mode, and disable Simulate temporarily #8195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kube
wants to merge
40
commits into
main
Choose a base branch
from
cf/h-5768-integrate-quick-simulation-in-edit-mode
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
2b7d4e3
Everything always available in BottomBar
kube dbd340d
Extract styles in SimulationControls
kube 8084653
Disable SimulateMode in ModeSelector for now
kube de12b66
Extract styles in LeftSidebar and make all sections always visible
kube 381bbdd
Extract styles in PlaceNode
kube b14cf14
Make PlaceNode token count bigger
kube 99e93dc
Make Simulation available in Edit Mode, and disable Simulate tab for now
kube 6b81da4
Fix BrokenMachines example
kube e52e6af
Wording
kube e721aa9
Create GlassPanel component
kube 93baa8c
Centralize Resizable in GlassPanel
kube fba89ec
Update GlassPanel style
kube 73f6dbf
Centralize UI Constants
kube bf5f347
Better resizer handle
kube 079fa5b
Move RESIZE_HANDLE constants in UI constants
kube 296b2d8
Make tabs a bit cleaner
kube 160da9a
Move activeBottomPanel to EditorStore
kube 7559909
Rename DiagnosticsPanel to BottomPanel
kube ee73389
Use Play/Pause/Stop icons
kube 3465b28
Open Diagnostics when clicking on Run Simulation when errors disable it
kube 2416ef8
Remove tooltip from Global Parameters panel, just show text
kube 3e0f7e0
Cleaner BottomPanel
kube 365087c
Remove Simulation State from Simulation Settings
kube 8b4cbab
BottomPanel styles
kube 68ab8ef
Add close button to BottomPanel
kube 8d28fab
Diagnostics Indicator opens Diagnostics in BottomPanel, with Tooltip
kube 914d97b
Add Toggle Panel button in BottomBar
kube 1794c8d
Hide Place/Transition buttons in BottomBar when Simulation is running
kube 6e01e42
SDCPNView readonly when Simulation is running
kube c35739e
Make Token Types, Differential Equations and Global Parameters readonβ¦
kube d7d27dc
Make Diagnostics expanded by default
kube 8afa254
Toolbar refinement + updates from demo review
kube a314960
Adjust bezelWidth for current BottomBar height
kube b41dd58
Swap Parameters and Computation sections
kube ca274c9
Add tooltip on Time Step
kube 87b914c
Format
kube fc0baa7
Remove dependency of SimulationProvider on EditorContext and reorder β¦
kube 0c3962d
Add useIsReadOnly hook
kube 5f7fc61
Review: remove comment
kube 96c2265
Remove globalMode check from TransitionProperties
kube File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
271 changes: 271 additions & 0 deletions
271
libs/@hashintel/petrinaut/src/components/glass-panel.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"; | ||
CiaranMn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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} | ||
kube marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /> | ||
|
|
||
| {/* Content container */} | ||
| <div | ||
| className={cx(contentContainerStyle, contentClassName)} | ||
| style={contentStyle} | ||
| > | ||
| {children} | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.