Skip to content

Commit

Permalink
Refactor LitElement logic out into a superclass
Browse files Browse the repository at this point in the history
  • Loading branch information
naschmitz committed Nov 7, 2024
1 parent 1d01bdc commit 8658cc2
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 95 deletions.
57 changes: 16 additions & 41 deletions js/layer_manager.ts
Original file line number Diff line number Diff line change
@@ -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`;
}
Expand All @@ -35,34 +39,18 @@ export class LayerManager extends LitElement {
`,
];

private _model: AnyModel<LayerManagerModel> | 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<LayerManagerModel>) {
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`
<div class="container">
Expand All @@ -82,19 +70,6 @@ export class LayerManager extends LitElement {
`;
}

updated(changedProperties: PropertyValues<LayerManager>): 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;
Expand Down
71 changes: 18 additions & 53 deletions js/layer_manager_row.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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`;
}
Expand Down Expand Up @@ -110,35 +107,16 @@ export class LayerManagerRow extends LitElement {
`,
];

private _model: AnyModel<LayerManagerRowModel> | 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<LayerManagerRowModel>) {
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 = "";
Expand Down Expand Up @@ -219,19 +197,6 @@ export class LayerManagerRow extends LitElement {
`;
}

updated(changedProperties: PropertyValues<LayerManagerRow>): 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;
}
Expand All @@ -242,15 +207,15 @@ 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) {
this.isConfirmDialogVisible = true;
}

private confirmDeletion(_: Event) {
this._model?.send({ type: "click", id: "delete" });
this.model?.send({ type: "click", id: "delete" });
}

private cancelDeletion(_: Event) {
Expand Down
57 changes: 57 additions & 0 deletions js/lit_widget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { LitElement, PropertyValues } from "lit";

import { reverseMap } from "./utils";

export abstract class LitWidget<
ModelType,
SubclassType extends LitWidget<any, any>
> extends LitElement {
private _model: any | undefined = undefined; // AnyModel<ModelType>

abstract modelNameToViewName(): Map<
keyof ModelType,
keyof SubclassType | null
>;

viewNameToModelName(): Map<keyof SubclassType | null, keyof ModelType> {
return reverseMap(this.modelNameToViewName());
}

set model(model: any) {
// TODO(naschmitz): model should be of type AnyModel<ModelType>. 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<ModelType>. AnyModel
// requires a type that conforms to a non-exported member of anywidget.
return this._model;
}

updated(changedProperties: PropertyValues<SubclassType>): 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();
}
}
4 changes: 3 additions & 1 deletion js/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ export async function updateChildren(
export function reverseMap<K, V>(map: Map<K, V>): Map<V, K> {
const reversedMap = new Map<V, K>();
for (const [key, value] of map.entries()) {
reversedMap.set(value, key);
if (value != null) {
reversedMap.set(value, key);
}
}
return reversedMap;
}

0 comments on commit 8658cc2

Please sign in to comment.