-
Notifications
You must be signed in to change notification settings - Fork 117
H-6281, FE-501: Add undo/redo support to Petrinaut demo app #8505
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
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
ebf0d3d
H-5641: Extract virtual file generation and add global Distribution type
kube b7a426b
H-5641: Change DistributionGaussian to Distribution.Gaussian namespace
kube 172e432
H-5641: Add runtime support for probabilistic transition kernels
kube 9813382
H-5641: Add changeset for probabilistic transition kernels
kube f7d34b4
H-5641: Add Distribution.map() and satellites launcher example
kube c3e4427
H-5641: Fix ESLint errors in distribution and transition code
kube b7ee80c
H-5641: Add Distribution.Lognormal(mu, sigma) support
kube e60e8f4
Remove width/height from example
kube 76a1dab
Update changeset to patch
kube 0580476
H-5641: Fix review feedback and enable supply chain examples
kube ed260ed
Update changeset
kube 0e399ad
Rename defect_threshold to quality_threshold in supply chain example
kube 676b471
Fix
kube 7d91cc5
Fix
kube 033dfd6
Last fix
kube 51a7939
H-6281: Add undo/redo support to Petrinaut demo app
kube 584586a
H-6281: Clear drag state after drag ends to prevent stale position reβ¦
kube 157374a
H-6281: Add changeset for undo/redo support
kube 1eb324e
H-6281: Remove window.confirm dialogs for deletions
kube e1f5a0f
Update changeset to patch
kube 729fe1f
H-6281: Replace undo/redo buttons with version history menu in TopBar
kube 0c4b53b
H-6281: Fix undo/redo bugs from AI review
kube 3eeab7b
H-6281: Fix stale closure in multi-node drags and deduplicate input cβ¦
kube b765bb1
H-6281: Improve version history menu UX
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
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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,5 @@ | ||
| --- | ||
| "@hashintel/petrinaut": patch | ||
| --- | ||
|
|
||
| Add probability distribution support to transition kernels (`Distribution.Gaussian`, `Distribution.Uniform`, `Distribution.Lognormal`) |
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,5 @@ | ||
| --- | ||
| "@hashintel/petrinaut": patch | ||
| --- | ||
|
|
||
| Add optional undo/redo support with version history UI, keyboard shortcuts (Cmd|Ctrl+Z / Cmd|Ctrl+Shift+Z), and drag debouncing |
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
151 changes: 151 additions & 0 deletions
151
libs/@hashintel/petrinaut/demo-site/main/app/use-undo-redo.ts
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,151 @@ | ||
| import { useRef, useState } from "react"; | ||
|
|
||
| import type { SDCPN } from "../../../src/core/types/sdcpn"; | ||
| import { isSDCPNEqual } from "../../../src/petrinaut"; | ||
|
|
||
| export type HistoryEntry = { | ||
| sdcpn: SDCPN; | ||
| timestamp: string; | ||
| }; | ||
|
|
||
| const MAX_HISTORY = 50; | ||
| const DEBOUNCE_MS = 500; | ||
|
|
||
| export function useUndoRedo(initialSDCPN: SDCPN) { | ||
| const historyRef = useRef<HistoryEntry[]>([ | ||
| { sdcpn: initialSDCPN, timestamp: new Date().toISOString() }, | ||
| ]); | ||
| const currentIndexRef = useRef(0); | ||
| const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); | ||
|
|
||
| /** | ||
| * Snapshot of render-visible values derived from the refs. | ||
| * Updated via `bump()` after every mutation so consumers re-render | ||
| * without reading refs during render. | ||
| */ | ||
| const [snapshot, setSnapshot] = useState({ | ||
| currentIndex: 0, | ||
| historyLength: 1, | ||
| }); | ||
| const bump = () => | ||
| setSnapshot({ | ||
| currentIndex: currentIndexRef.current, | ||
| historyLength: historyRef.current.length, | ||
| }); | ||
|
|
||
| const canUndo = snapshot.currentIndex > 0; | ||
| const canRedo = snapshot.currentIndex < snapshot.historyLength - 1; | ||
|
|
||
| const pushState = (sdcpn: SDCPN) => { | ||
| const current = historyRef.current[currentIndexRef.current]; | ||
|
|
||
| // No-op detection | ||
| if (current && isSDCPNEqual(current.sdcpn, sdcpn)) { | ||
| return; | ||
| } | ||
|
|
||
| // Debounce: coalesce rapid mutations into one entry | ||
| if (debounceTimerRef.current !== null) { | ||
kube marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| clearTimeout(debounceTimerRef.current); | ||
| // Replace the entry at currentIndex (it was a pending debounced entry) | ||
| historyRef.current[currentIndexRef.current] = { | ||
| sdcpn, | ||
| timestamp: new Date().toISOString(), | ||
| }; | ||
|
|
||
| debounceTimerRef.current = setTimeout(() => { | ||
| debounceTimerRef.current = null; | ||
| }, DEBOUNCE_MS); | ||
|
|
||
| bump(); | ||
| return; | ||
| } | ||
|
|
||
| // Truncate any redo entries | ||
| historyRef.current = historyRef.current.slice( | ||
| 0, | ||
| currentIndexRef.current + 1, | ||
| ); | ||
|
|
||
| // Push new entry | ||
| historyRef.current.push({ | ||
| sdcpn, | ||
| timestamp: new Date().toISOString(), | ||
| }); | ||
|
|
||
| // Enforce max history size | ||
| if (historyRef.current.length > MAX_HISTORY) { | ||
| historyRef.current = historyRef.current.slice( | ||
| historyRef.current.length - MAX_HISTORY, | ||
| ); | ||
| } | ||
|
|
||
| currentIndexRef.current = historyRef.current.length - 1; | ||
|
|
||
| // Start debounce window | ||
| debounceTimerRef.current = setTimeout(() => { | ||
| debounceTimerRef.current = null; | ||
| }, DEBOUNCE_MS); | ||
|
|
||
| bump(); | ||
| }; | ||
|
|
||
| const clearDebounce = () => { | ||
| if (debounceTimerRef.current !== null) { | ||
| clearTimeout(debounceTimerRef.current); | ||
| debounceTimerRef.current = null; | ||
| } | ||
| }; | ||
|
|
||
| const undo = (): SDCPN | null => { | ||
| if (currentIndexRef.current <= 0) { | ||
| return null; | ||
| } | ||
| clearDebounce(); | ||
| currentIndexRef.current -= 1; | ||
| bump(); | ||
| return historyRef.current[currentIndexRef.current]!.sdcpn; | ||
| }; | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const redo = (): SDCPN | null => { | ||
| if (currentIndexRef.current >= historyRef.current.length - 1) { | ||
| return null; | ||
| } | ||
| clearDebounce(); | ||
| currentIndexRef.current += 1; | ||
| bump(); | ||
| return historyRef.current[currentIndexRef.current]!.sdcpn; | ||
| }; | ||
kube marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const goToIndex = (index: number): SDCPN | null => { | ||
| if (index < 0 || index >= historyRef.current.length) { | ||
| return null; | ||
| } | ||
| clearDebounce(); | ||
| currentIndexRef.current = index; | ||
| bump(); | ||
| return historyRef.current[index]!.sdcpn; | ||
| }; | ||
|
|
||
| const reset = (sdcpn: SDCPN) => { | ||
| historyRef.current = [{ sdcpn, timestamp: new Date().toISOString() }]; | ||
| currentIndexRef.current = 0; | ||
| if (debounceTimerRef.current !== null) { | ||
| clearTimeout(debounceTimerRef.current); | ||
| debounceTimerRef.current = null; | ||
| } | ||
| bump(); | ||
| }; | ||
|
|
||
| return { | ||
| pushState, | ||
| undo, | ||
| redo, | ||
| goToIndex, | ||
| canUndo, | ||
| canRedo, | ||
| history: historyRef, | ||
| currentIndex: snapshot.currentIndex, | ||
| reset, | ||
| }; | ||
| } | ||
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.