Skip to content

Commit d007e6a

Browse files
devtools events
1 parent ebb8bf5 commit d007e6a

File tree

2 files changed

+173
-1
lines changed

2 files changed

+173
-1
lines changed

editor/grida-canvas-react/devtools/index.tsx

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import React from "react";
44
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
55
import { Button } from "@/components/ui/button";
6+
import { Checkbox } from "@/components/ui/checkbox";
67
import {
78
CaretDownIcon,
89
CaretUpIcon,
@@ -25,6 +26,7 @@ import type grida from "@grida/schema";
2526
import { useCurrentEditor, useEditorState } from "../use-editor";
2627
import { useRecorder } from "../plugins/use-recorder";
2728
import { saveAs } from "file-saver";
29+
import { editor } from "@/grida-canvas/editor.i";
2830

2931
export function DevtoolsPanel() {
3032
const expandable = useDialogState();
@@ -75,6 +77,13 @@ export function DevtoolsPanel() {
7577
>
7678
Recorder
7779
</TabsTrigger>
80+
<TabsTrigger
81+
onClick={onTabClick}
82+
value="events"
83+
className="text-xs uppercase"
84+
>
85+
Events
86+
</TabsTrigger>
7887
</TabsList>
7988
</div>
8089
<CollapsibleTrigger asChild>
@@ -145,6 +154,9 @@ export function DevtoolsPanel() {
145154
<TabsContent value="recorder" className="h-full">
146155
<RecorderPanel />
147156
</TabsContent>
157+
<TabsContent value="events" className="h-full">
158+
<EventsPanel />
159+
</TabsContent>
148160
</CollapsibleContent>
149161
</Tabs>
150162
</Collapsible>
@@ -333,3 +345,163 @@ function RecorderPanel() {
333345
</div>
334346
);
335347
}
348+
349+
type PatchEvent = {
350+
timestamp: number;
351+
patches: editor.history.Patch[];
352+
action?: any;
353+
};
354+
355+
function EventsPanel() {
356+
const currentEditor = useCurrentEditor();
357+
const [events, setEvents] = React.useState<PatchEvent[]>([]);
358+
const [showNonDocumentPatches, setShowNonDocumentPatches] =
359+
React.useState(false);
360+
361+
React.useEffect(() => {
362+
// Subscribe to document changes with selector
363+
const unsubscribe = currentEditor.doc.subscribeWithSelector(
364+
(state) => state.document,
365+
(doc, next, prev, action, patches) => {
366+
if (!patches || patches.length === 0) return;
367+
368+
setEvents((prevEvents) => {
369+
const newEvent: PatchEvent = {
370+
timestamp: Date.now(),
371+
patches,
372+
action,
373+
};
374+
// Keep only the last 50 events
375+
const updated = [newEvent, ...prevEvents];
376+
return updated.slice(0, 50);
377+
});
378+
}
379+
);
380+
381+
return () => {
382+
unsubscribe();
383+
};
384+
}, [currentEditor]);
385+
386+
// Filter patches within events based on toggle
387+
const filteredEvents = React.useMemo(() => {
388+
return events
389+
.map((event) => {
390+
const filteredPatches = showNonDocumentPatches
391+
? event.patches
392+
: event.patches.filter((patch) => patch.path[0] === "document");
393+
394+
return {
395+
...event,
396+
patches: filteredPatches,
397+
};
398+
})
399+
.filter((event) => event.patches.length > 0); // Only show events that have patches after filtering
400+
}, [events, showNonDocumentPatches]);
401+
402+
return (
403+
<div className="h-full overflow-y-auto">
404+
<div className="p-4">
405+
<div className="flex items-center justify-between mb-4">
406+
<h3 className="text-sm font-medium">
407+
Recent Patches ({filteredEvents.length}/50)
408+
</h3>
409+
<div className="flex items-center gap-4">
410+
<div className="flex items-center gap-2">
411+
<Checkbox
412+
id="show-non-document-patches"
413+
checked={showNonDocumentPatches}
414+
onCheckedChange={(checked) =>
415+
setShowNonDocumentPatches(checked === true)
416+
}
417+
/>
418+
<label
419+
htmlFor="show-non-document-patches"
420+
className="text-xs text-muted-foreground cursor-pointer select-none"
421+
>
422+
Show non-document patches
423+
</label>
424+
</div>
425+
<Button
426+
variant="ghost"
427+
size="sm"
428+
onClick={() => setEvents([])}
429+
disabled={events.length === 0}
430+
>
431+
<TrashIcon className="w-4 h-4" />
432+
</Button>
433+
</div>
434+
</div>
435+
{filteredEvents.length === 0 ? (
436+
<div className="text-sm text-muted-foreground text-center py-8">
437+
No patches yet. Make changes to see them here.
438+
</div>
439+
) : (
440+
<div className="space-y-2">
441+
{filteredEvents.map((event, index) => (
442+
<Collapsible key={index}>
443+
<div className="border rounded-lg p-3">
444+
<CollapsibleTrigger className="w-full">
445+
<div className="flex items-center justify-between text-xs">
446+
<div className="flex items-center gap-2">
447+
<span className="font-mono text-muted-foreground">
448+
#{filteredEvents.length - index}
449+
</span>
450+
<span className="font-medium">
451+
{event.action?.type || "unknown"}
452+
</span>
453+
<span className="text-muted-foreground">
454+
{event.patches.length} patch
455+
{event.patches.length !== 1 ? "es" : ""}
456+
</span>
457+
</div>
458+
<span className="text-muted-foreground font-mono">
459+
{event.timestamp}
460+
</span>
461+
</div>
462+
</CollapsibleTrigger>
463+
<CollapsibleContent className="mt-2">
464+
<div className="space-y-1">
465+
{event.patches.map((patch, patchIndex) => (
466+
<div
467+
key={patchIndex}
468+
className="text-xs font-mono bg-muted p-2 rounded"
469+
>
470+
<div className="flex items-start gap-2">
471+
<span
472+
className={`font-bold ${
473+
patch.op === "add"
474+
? "text-green-600"
475+
: patch.op === "remove"
476+
? "text-red-600"
477+
: "text-blue-600"
478+
}`}
479+
>
480+
{patch.op}
481+
</span>
482+
<div className="flex-1">
483+
<div className="text-muted-foreground">
484+
{patch.path.join(" → ")}
485+
</div>
486+
{patch.value !== undefined && (
487+
<div className="mt-1 text-foreground">
488+
{typeof patch.value === "object"
489+
? JSON.stringify(patch.value, null, 2)
490+
: String(patch.value)}
491+
</div>
492+
)}
493+
</div>
494+
</div>
495+
</div>
496+
))}
497+
</div>
498+
</CollapsibleContent>
499+
</div>
500+
</Collapsible>
501+
))}
502+
</div>
503+
)}
504+
</div>
505+
</div>
506+
);
507+
}

editor/grida-canvas/editor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ class EditorDocumentStore
474474
stroke: this.backend === "dom" ? "stroke" : "strokes",
475475
},
476476
idgen: this.idgen,
477-
logger: this.log,
477+
logger: this.log.bind(this),
478478
};
479479

480480
const actions = Array.isArray(action) ? action : [action];

0 commit comments

Comments
 (0)