From 82ad1a13b5f2b6e9a2212db77eeb1a6aa591fc59 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Mon, 14 Apr 2025 16:22:32 -0700 Subject: [PATCH] fix: charm reload on charm-list change --- jumble/src/components/CharmRunner.tsx | 23 +++++++++++------- jumble/src/utils/charms.ts | 34 +++++++++++++++++---------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/jumble/src/components/CharmRunner.tsx b/jumble/src/components/CharmRunner.tsx index 2d586a9ad..3ca797b1d 100644 --- a/jumble/src/components/CharmRunner.tsx +++ b/jumble/src/components/CharmRunner.tsx @@ -1,5 +1,5 @@ -import React, { useRef } from "react"; -import { render } from "@commontools/html"; +import React, { useMemo, useRef } from "react"; +import { render, type VNode } from "@commontools/html"; import { UI } from "@commontools/builder"; import { charmSchema, fixItCharm } from "@commontools/charm"; import { useCharmManager } from "@/contexts/CharmManagerContext.tsx"; @@ -7,7 +7,8 @@ import { useNavigate } from "react-router-dom"; import { LuX } from "react-icons/lu"; import { DitheredCube } from "@/components/DitherCube.tsx"; import { createPath } from "@/routes.ts"; -import { charmId } from "@/utils/charms.ts"; +import { Cell, Charm, charmId } from "@/utils/charms.ts"; + interface CharmLoaderProps { charmImport: () => Promise; argument?: any; @@ -16,7 +17,7 @@ interface CharmLoaderProps { } interface CharmRendererProps { - charm: any; + charm: Cell; argument?: any; className?: string; } @@ -81,10 +82,11 @@ function RawCharmRenderer({ charm, className = "" }: CharmRendererProps) { const [runtimeError, setRuntimeError] = React.useState(null); const [isFixing, setIsFixing] = React.useState(false); const { charmManager, currentReplica } = useCharmManager(); + const id = useMemo(() => charmId(charm), [charm]); const navigate = useNavigate(); // Store a reference to the current charm to detect changes - const prevCharmRef = useRef(charm); + const prevCharmRef = useRef(null); // Clear error when charm changes React.useEffect(() => { @@ -92,7 +94,7 @@ function RawCharmRenderer({ charm, className = "" }: CharmRendererProps) { setRuntimeError(null); prevCharmRef.current = charm; } - }, [charm]); + }, [id]); const handleFixIt = React.useCallback(async () => { if (!runtimeError || isFixing) return; @@ -111,7 +113,7 @@ function RawCharmRenderer({ charm, className = "" }: CharmRendererProps) { } finally { setIsFixing(false); } - }, [runtimeError, isFixing, charmManager, charm, currentReplica, navigate]); + }, [runtimeError, isFixing, charmManager, id, currentReplica, navigate]); React.useEffect(() => { const container = containerRef.current; @@ -127,7 +129,10 @@ function RawCharmRenderer({ charm, className = "" }: CharmRendererProps) { container.addEventListener("common-iframe-error", handleIframeError); - const cleanup = render(container, charm.asSchema(charmSchema).key(UI)); + const cleanup = render( + container, + charm.asSchema(charmSchema).key(UI) as Cell, + ); return () => { cleanup(); @@ -136,7 +141,7 @@ function RawCharmRenderer({ charm, className = "" }: CharmRendererProps) { container.innerHTML = ""; } }; - }, [charm]); + }, [id]); return ( <> diff --git a/jumble/src/utils/charms.ts b/jumble/src/utils/charms.ts index 1d2d886e2..308dee229 100644 --- a/jumble/src/utils/charms.ts +++ b/jumble/src/utils/charms.ts @@ -2,6 +2,8 @@ import { type Charm, CharmManager } from "@commontools/charm"; import { Cell, getEntityId } from "@commontools/runner"; import { NAME } from "@commontools/builder"; +export type { Cell, Charm }; + export function charmId(charm: Charm): string | undefined { const id = getEntityId(charm); return id ? id["/"] : undefined; @@ -12,50 +14,56 @@ export function charmId(charm: Charm): string | undefined { * @param charmManager The charm manager instance * @returns Promise that resolves to an array of mentionable charms (filtered out trash and pinned first) */ -export async function getMentionableCharms(charmManager: CharmManager): Promise[]> { +export async function getMentionableCharms( + charmManager: CharmManager, +): Promise[]> { // Sync all collections to ensure we have the latest data await Promise.all([ charmManager.sync(charmManager.getCharms()), charmManager.sync(charmManager.getPinned()), - charmManager.sync(charmManager.getTrash()) + charmManager.sync(charmManager.getTrash()), ]); - + // Get all collections const allCharms = charmManager.getCharms().get(); const pinnedCharms = charmManager.getPinned().get(); const trashedCharms = charmManager.getTrash().get(); - + // Create a set of trashed charm IDs for quick lookup const trashedIds = new Set( - trashedCharms.map(charm => charmId(charm)).filter((id): id is string => id !== undefined) + trashedCharms.map((charm) => charmId(charm)).filter((id): id is string => + id !== undefined + ), ); - + // Create a set of pinned charm IDs for quick lookup const pinnedIds = new Set( - pinnedCharms.map(charm => charmId(charm)).filter((id): id is string => id !== undefined) + pinnedCharms.map((charm) => charmId(charm)).filter((id): id is string => + id !== undefined + ), ); - + // Filter out trashed charms and those without IDs - const mentionableCharms = allCharms.filter(charm => { + const mentionableCharms = allCharms.filter((charm) => { const id = charmId(charm); return id !== undefined && !trashedIds.has(id); }); - + // Sort charms with pinned first, then by name return mentionableCharms.sort((a, b) => { const aId = charmId(a); const bId = charmId(b); - + // By this point both aId and bId should be defined, but check just in case if (!aId || !bId) { console.warn("Unexpected undefined ID in sort function"); return 0; } - + // Sort pinned first if (pinnedIds.has(aId) && !pinnedIds.has(bId)) return -1; if (!pinnedIds.has(aId) && pinnedIds.has(bId)) return 1; - + // Then sort by name const aName = a.get()?.[NAME] ?? "Untitled"; const bName = b.get()?.[NAME] ?? "Untitled";