Skip to content

Commit

Permalink
refactor: canvas rerender/update-settings hooks (#400)
Browse files Browse the repository at this point in the history
  • Loading branch information
Charlie-XIAO authored Feb 3, 2025
1 parent dc49f26 commit c8bf6b1
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 140 deletions.
6 changes: 4 additions & 2 deletions src/canvas/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
useRenderWidgetsListener,
useShowToastListener,
useTheme,
useUpdateSettingsListener,
} from "./hooks";

const App = () => {
Expand All @@ -16,9 +17,10 @@ const App = () => {
useShallow((state) => Object.keys(state.widgets)),
);

useShowToastListener();
useRenderWidgetsListener();
useRemoveWidgetsListener();
useRenderWidgetsListener();
useShowToastListener();
useUpdateSettingsListener();

return (
<RadixTheme
Expand Down
3 changes: 2 additions & 1 deletion src/canvas/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./useRemoveWidgetsListener";
export * from "./useRenderListener";
export * from "./useRenderWidgetsListener";
export * from "./useShowToastListener";
export * from "./useTheme";
export * from "./useUpdateSettingsListener";
export * from "./useWidgetsStore";
113 changes: 0 additions & 113 deletions src/canvas/hooks/useRenderListener.tsx

This file was deleted.

97 changes: 97 additions & 0 deletions src/canvas/hooks/useRenderWidgetsListener.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useEffect, useRef } from "react";
import { listenToRenderWidgets } from "../../events";
import { invokeBundleWidget, invokeSetRenderReady } from "../../commands";
import {
Widget,
updateWidgetRender,
updateWidgetRenderError,
useWidgetsStore,
} from "./useWidgetsStore";

const BASE_URL = new URL(import.meta.url).origin;
const RAW_APIS_URL = new URL("/generated/raw-apis.js", BASE_URL).href;

export function useRenderWidgetsListener() {
const hasInited = useRef(false);

useEffect(() => {
const unlisten = listenToRenderWidgets(async (event) => {
const widgets = useWidgetsStore.getState().widgets;

const promises = event.payload.map(async ({ id, settings, code }) => {
let apisBlobUrl;
if (id in widgets) {
// APIs blob URL can be reused because the contents are dependent only
// on widget ID; the code blob URL will definitely change on re-render
// so we revoke it here
const widget = widgets[id];
apisBlobUrl = widget.apisBlobUrl;
if (widget.moduleBlobUrl !== undefined) {
URL.revokeObjectURL(widget.moduleBlobUrl);
}
} else {
const apisCode = window.__DESKULPT_CANVAS_INTERNALS__.apisWrapper
.replace("__DESKULPT_WIDGET_ID__", id)
.replace("__RAW_APIS_URL__", RAW_APIS_URL);
const apisBlob = new Blob([apisCode], {
type: "application/javascript",
});
apisBlobUrl = URL.createObjectURL(apisBlob);
}

if (code === undefined) {
// If code is not provided, we need to bundle the widget
try {
code = await invokeBundleWidget({
id,
baseUrl: BASE_URL,
apisBlobUrl,
});
} catch (error) {
updateWidgetRenderError(id, error, apisBlobUrl, settings);
return;
}
}

const moduleBlob = new Blob([code], { type: "application/javascript" });
const moduleBlobUrl = URL.createObjectURL(moduleBlob);
let module;
try {
module = await import(/* @vite-ignore */ moduleBlobUrl);
} catch (error) {
URL.revokeObjectURL(moduleBlobUrl);
updateWidgetRenderError(id, error, apisBlobUrl, settings);
return;
}

const widget = module.default as Widget;
if (widget === undefined || widget.Component === undefined) {
URL.revokeObjectURL(moduleBlobUrl);
updateWidgetRenderError(
id,
"The widget must provide a default export with a `Component` property.",
apisBlobUrl,
settings,
);
return;
}

updateWidgetRender(id, widget, moduleBlobUrl, apisBlobUrl, settings);
});

await Promise.all(promises);
});

if (!hasInited.current) {
invokeSetRenderReady()
.then(() => {
hasInited.current = true;
})
.catch(console.error);
}

return () => {
unlisten.then((f) => f()).catch(console.error);
};
}, []);
}
16 changes: 16 additions & 0 deletions src/canvas/hooks/useUpdateSettingsListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEffect } from "react";
import { listenToUpdateSettings } from "../../events";
import { updateWidgetSettings } from "./useWidgetsStore";

export function useUpdateSettingsListener() {
useEffect(() => {
const unlisten = listenToUpdateSettings((event) => {
const { id, settings } = event.payload;
updateWidgetSettings(id, settings);
});

return () => {
unlisten.then((f) => f()).catch(console.error);
};
}, []);
}
11 changes: 11 additions & 0 deletions src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ export async function emitUpdateSettingsToManager(
await emitTo("manager", "update-settings", payload);
}

/**
* Emit the "update-settings" event to the canvas window.
*
* @param payload The payload of the event.
*/
export async function emitUpdateSettingsToCanvas(
payload: UpdateSettingsPayload,
) {
await emitTo("canvas", "update-settings", payload);
}

/**
* Listen to the "update-settings" event.
*
Expand Down
4 changes: 2 additions & 2 deletions src/manager/components/WidgetContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ const WidgetContent = ({
actionIcon={<LuRepeat />}
actionText="Re-render"
action={() =>
emitRenderWidgetsToCanvas([{ id, settings, bundle: true }]).then(
() => toast.success(`Re-rendered widget "${id}".`),
emitRenderWidgetsToCanvas([{ id }]).then(() =>
toast.success("Re-rendered widget."),
)
}
/>
Expand Down
15 changes: 5 additions & 10 deletions src/manager/components/WidgetContentSettingsList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Dispatch, SetStateAction } from "react";
import { WidgetSettings } from "../../types/backend";
import { ManagerWidgetState } from "../../types/frontend";
import { emitRenderWidgetsToCanvas } from "../../events";
import { emitUpdateSettingsToCanvas } from "../../events";
import { DataList, Flex } from "@radix-ui/themes";
import NumberInput from "../components/NumberInput";
import { FaTimes } from "react-icons/fa";
Expand Down Expand Up @@ -30,18 +30,13 @@ const WidgetContentSettingList = ({
setManagerWidgetStates,
}: WidgetContentSettingListProps) => {
function updateSetting(partialSettings: Partial<WidgetSettings>) {
const newSettings = { ...settings, ...partialSettings };
setManagerWidgetStates((prev) => ({
...prev,
[id]: { ...prev[id], settings: newSettings },
[id]: { ...prev[id], settings: { ...settings, ...partialSettings } },
}));
emitRenderWidgetsToCanvas([
{
id,
settings: newSettings,
bundle: false,
},
]).catch(console.error);
emitUpdateSettingsToCanvas({ id, settings: partialSettings }).catch(
console.error,
);
}

return (
Expand Down
6 changes: 1 addition & 5 deletions src/manager/hooks/useManagerWidgetStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,7 @@ export default function useManagerWidgetStates(): UseManagerWidgetStatesOutput {
);
setManagerWidgetStates(newManagerWidgetStates); // Direct replacement

const payload = addedStates.map(([id, { settings }]) => ({
id,
settings,
bundle: true,
}));
const payload = addedStates.map(([id, { settings }]) => ({ id, settings }));
if (initial) {
await invokeEmitOnRenderReady({ payload });
} else {
Expand Down
6 changes: 1 addition & 5 deletions src/manager/tabs/WidgetsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@ const WidgetsTab = ({

const rerenderAction = async () => {
await emitRenderWidgetsToCanvas(
managerWidgetStatesArray.map(([id, { settings }]) => ({
id,
settings,
bundle: true,
})),
managerWidgetStatesArray.map(([id, { settings }]) => ({ id, settings })),
);
toast.success(`Re-rendered ${managerWidgetStatesArray.length} widgets.`);
};
Expand Down
4 changes: 2 additions & 2 deletions src/types/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export interface ManagerWidgetState {
*/
export type RenderWidgetsPayload = {
id: string;
bundle: boolean;
settings: WidgetSettings;
settings?: WidgetSettings;
code?: string;
}[];

/**
Expand Down

0 comments on commit c8bf6b1

Please sign in to comment.