From b2c3f0ccfd0d49f56bd30c7715f3132e10b60824 Mon Sep 17 00:00:00 2001 From: Nathaniel Schmitz Date: Wed, 6 Nov 2024 19:27:24 -0500 Subject: [PATCH] Refactor LitElement logic out into a superclass --- js/layer_manager.ts | 57 ++++++++++----------------------- js/layer_manager_row.ts | 71 +++++++++++------------------------------ js/lit_widget.ts | 57 +++++++++++++++++++++++++++++++++ js/utils.ts | 4 ++- 4 files changed, 94 insertions(+), 95 deletions(-) create mode 100644 js/lit_widget.ts diff --git a/js/layer_manager.ts b/js/layer_manager.ts index ce869ae1f7..4a3dd3f787 100644 --- a/js/layer_manager.ts +++ b/js/layer_manager.ts @@ -1,16 +1,20 @@ -import type { AnyModel, RenderProps } from "@anywidget/types"; -import { html, css, LitElement, PropertyValues, TemplateResult } from "lit"; +import type { RenderProps } from "@anywidget/types"; +import { html, css, TemplateResult } from "lit"; import { property } from "lit/decorators.js"; import { legacyStyles } from "./ipywidgets_styles"; -import { loadFonts, reverseMap, updateChildren } from "./utils"; +import { LitWidget } from "./lit_widget"; +import { loadFonts, updateChildren } from "./utils"; export interface LayerManagerModel { children: any; visible: boolean; } -export class LayerManager extends LitElement { +export class LayerManager extends LitWidget< + LayerManagerModel, + LayerManager +> { static get componentName() { return `layer-manager`; } @@ -35,34 +39,18 @@ export class LayerManager extends LitElement { `, ]; - private _model: AnyModel | undefined = undefined; - private static modelNameToViewName = new Map< + @property() visible: boolean = false; + + modelNameToViewName(): Map< keyof LayerManagerModel, keyof LayerManager | null - >([ - ["children", null], - ["visible", "visible"], - ]); - private static viewNameToModelName = reverseMap( - LayerManager.modelNameToViewName - ); - - set model(model: AnyModel) { - this._model = model; - for (const [modelKey, widgetKey] of LayerManager.modelNameToViewName) { - if (widgetKey) { - // Get initial values from the Python model. - (this as any)[widgetKey] = model.get(modelKey); - // Listen for updates to the model. - model.on(`change:${modelKey}`, () => { - (this as any)[widgetKey] = model.get(modelKey); - }); - } - } + > { + return new Map([ + ["children", null], + ["visible", "visible"], + ]); } - @property() visible: boolean = false; - render(): TemplateResult { return html`
@@ -82,19 +70,6 @@ export class LayerManager extends LitElement { `; } - updated(changedProperties: PropertyValues): void { - // Update the model properties so they're reflected in Python. - for (const [viewProp, _] of changedProperties) { - const castViewProp = viewProp as keyof LayerManager; - if (LayerManager.viewNameToModelName.has(castViewProp)) { - const modelProp = - LayerManager.viewNameToModelName.get(castViewProp); - this._model?.set(modelProp as any, this[castViewProp] as any); - } - } - this._model?.save_changes(); - } - private onLayerVisibilityChanged(event: Event): void { const target = event.target as HTMLInputElement; this.visible = target.checked; diff --git a/js/layer_manager_row.ts b/js/layer_manager_row.ts index 522ee39144..b9529e4b53 100644 --- a/js/layer_manager_row.ts +++ b/js/layer_manager_row.ts @@ -1,18 +1,12 @@ -import type { AnyModel, RenderProps } from "@anywidget/types"; -import { - css, - html, - nothing, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; +import type { RenderProps } from "@anywidget/types"; +import { css, html, nothing, TemplateResult } from "lit"; import { property } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { legacyStyles } from "./ipywidgets_styles"; import { materialStyles } from "./styles"; -import { loadFonts, reverseMap } from "./utils"; +import { loadFonts } from "./utils"; +import { LitWidget } from "./lit_widget"; export interface LayerManagerRowModel { name: string; @@ -21,7 +15,10 @@ export interface LayerManagerRowModel { is_loading: boolean; } -export class LayerManagerRow extends LitElement { +export class LayerManagerRow extends LitWidget< + LayerManagerRowModel, + LayerManagerRow +> { static get componentName() { return `layer-manager-row`; } @@ -110,35 +107,16 @@ export class LayerManagerRow extends LitElement { `, ]; - private _model: AnyModel | undefined = undefined; - private static modelNameToViewName = new Map< + modelNameToViewName(): Map< keyof LayerManagerRowModel, keyof LayerManagerRow | null - >([ - ["name", "name"], - ["visible", "visible"], - ["opacity", "opacity"], - ["is_loading", "isLoading"], - ]); - private static viewNameToModelName = reverseMap( - LayerManagerRow.modelNameToViewName - ); - - set model(model: AnyModel) { - this._model = model; - for (const [ - modelKey, - widgetKey, - ] of LayerManagerRow.modelNameToViewName) { - if (widgetKey) { - // Get initial values from the Python model. - (this as any)[widgetKey] = model.get(modelKey); - // Listen for updates to the model. - model.on(`change:${modelKey}`, () => { - (this as any)[widgetKey] = model.get(modelKey); - }); - } - } + > { + return new Map([ + ["name", "name"], + ["visible", "visible"], + ["opacity", "opacity"], + ["is_loading", "isLoading"], + ]); } @property() name: string = ""; @@ -219,19 +197,6 @@ export class LayerManagerRow extends LitElement { `; } - updated(changedProperties: PropertyValues): void { - // Update the model properties so they're reflected in Python. - for (const [viewProp, _] of changedProperties) { - const castViewProp = viewProp as keyof LayerManagerRow; - if (LayerManagerRow.viewNameToModelName.has(castViewProp)) { - const modelProp = - LayerManagerRow.viewNameToModelName.get(castViewProp); - this._model?.set(modelProp as any, this[castViewProp] as any); - } - } - this._model?.save_changes(); - } - private onLayerVisibilityChanged(_event: Event) { this.visible = !this.visible; } @@ -242,7 +207,7 @@ export class LayerManagerRow extends LitElement { } private onSettingsClicked(_: Event) { - this._model?.send({ type: "click", id: "settings" }); + this.model?.send({ type: "click", id: "settings" }); } private onDeleteClicked(_: Event) { @@ -250,7 +215,7 @@ export class LayerManagerRow extends LitElement { } private confirmDeletion(_: Event) { - this._model?.send({ type: "click", id: "delete" }); + this.model?.send({ type: "click", id: "delete" }); } private cancelDeletion(_: Event) { diff --git a/js/lit_widget.ts b/js/lit_widget.ts new file mode 100644 index 0000000000..bd4b188b1b --- /dev/null +++ b/js/lit_widget.ts @@ -0,0 +1,57 @@ +import { LitElement, PropertyValues } from "lit"; + +import { reverseMap } from "./utils"; + +export abstract class LitWidget< + ModelType, + SubclassType extends LitWidget +> extends LitElement { + private _model: any | undefined = undefined; // AnyModel + + abstract modelNameToViewName(): Map< + keyof ModelType, + keyof SubclassType | null + >; + + viewNameToModelName(): Map { + return reverseMap(this.modelNameToViewName()); + } + + set model(model: any) { + // TODO(naschmitz): model should be of type AnyModel. AnyModel + // requires a type that conforms to a non-exported member of anywidget. + this._model = model; + for (const [modelKey, widgetKey] of this.modelNameToViewName()) { + if (widgetKey) { + // Get initial values from the Python model. + (this as any)[widgetKey] = model.get(modelKey); + // Listen for updates to the model. + model.on(`change:${String(modelKey)}`, () => { + (this as any)[widgetKey] = model.get(modelKey); + }); + } + } + } + + get model(): any { + // TODO(naschmitz): model should be of type AnyModel. AnyModel + // requires a type that conforms to a non-exported member of anywidget. + return this._model; + } + + updated(changedProperties: PropertyValues): void { + // Update the model properties so they're reflected in Python. + const viewToModelMap = this.viewNameToModelName(); + for (const [viewProp, _] of changedProperties) { + const castViewProp = viewProp as keyof SubclassType; + if (viewToModelMap.has(castViewProp)) { + const modelProp = viewToModelMap.get(castViewProp); + this._model?.set( + modelProp as any, + this[castViewProp as keyof this] as any + ); + } + } + this._model?.save_changes(); + } +} diff --git a/js/utils.ts b/js/utils.ts index 76a28d107f..38623a7d96 100644 --- a/js/utils.ts +++ b/js/utils.ts @@ -38,7 +38,9 @@ export async function updateChildren( export function reverseMap(map: Map): Map { const reversedMap = new Map(); for (const [key, value] of map.entries()) { - reversedMap.set(value, key); + if (value != null) { + reversedMap.set(value, key); + } } return reversedMap; }