From 958c795694e02c9a7e938b581e359f2671c313eb Mon Sep 17 00:00:00 2001 From: Jakob Schnitzer Date: Mon, 2 Dec 2024 19:25:17 +0100 Subject: [PATCH] use svelte rune to update props in frontend reports For now, this makes the props passed to these components deeply reactive as there is no easy way to opt out of it right now. This might slightly impact performance as all the props are proxified. --- frontend/src/reports/route.svelte.ts | 13 ++++++++++ frontend/src/reports/route.ts | 39 ++++++++++++++++++---------- 2 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 frontend/src/reports/route.svelte.ts diff --git a/frontend/src/reports/route.svelte.ts b/frontend/src/reports/route.svelte.ts new file mode 100644 index 000000000..c71278651 --- /dev/null +++ b/frontend/src/reports/route.svelte.ts @@ -0,0 +1,13 @@ +/** Create a reactive props proxy to allow updating of props. */ +export function updateable_props>( + raw_props: T, +): [props: T, update: (v: T) => void] { + // TODO: this makes it deeply reactive, which adds unnecessary overhead + const props = $state(raw_props); + return [ + props, + (new_props) => { + Object.assign(props, new_props); + }, + ]; +} diff --git a/frontend/src/reports/route.ts b/frontend/src/reports/route.ts index a40d9119a..4f7380409 100644 --- a/frontend/src/reports/route.ts +++ b/frontend/src/reports/route.ts @@ -2,6 +2,7 @@ import { type Component, mount, unmount } from "svelte"; import { log_error } from "../log"; import ErrorSvelte from "./Error.svelte"; +import { updateable_props } from "./route.svelte"; export interface FrontendRoute { readonly report: string; @@ -15,10 +16,17 @@ export interface FrontendRoute { } /** This class pairs the components and their load functions to use them in a type-safe way. */ -export class Route> implements FrontendRoute { +// The base type for the component props needs to be typed as Record to allow for T +// to be correctly inferred from the imported svelte components +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export class Route> implements FrontendRoute { /** The currently rendered instance - if loading failed, we render an error component. */ private instance?: - | { error: false; component: Record } + | { + error: false; + component: Record; + update_props: (v: T) => void; + } | { error: true; component: Record }; /** The currently rendered URL. */ @@ -46,7 +54,7 @@ export class Route> implements FrontendRoute { /** Destroy any components that might be rendered by this route. */ destroy(): void { if (this.instance !== undefined) { - unmount(this.instance.component); + void unmount(this.instance.component); } this.instance = undefined; } @@ -61,17 +69,20 @@ export class Route> implements FrontendRoute { previous?.destroy(); } try { - const props = await this.load(url); - // Check if the component is changed - otherwise only update the data. - // Svelte 5 removed component.$set - so always re-render now - // if (previous === this && this.instance?.error === false) { - //this.instance.component.$set(props); - this.destroy(); - target.innerHTML = ""; - this.instance = { - error: false, - component: mount(this.Component, { target, props }), - }; + const raw_props = await this.load(url); + // Check if the component is unchanged and only update the data in this case. + if (previous === this && this.instance?.error === false) { + this.instance.update_props(raw_props); + } else { + this.destroy(); + target.innerHTML = ""; + const [props, update_props] = updateable_props(raw_props); + this.instance = { + error: false, + component: mount(this.Component, { target, props }), + update_props, + }; + } } catch (error: unknown) { log_error(error); if (error instanceof Error) {