-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implements the basemap selector as anywidget (#2171)
* Implement LayerManager using LitElement + anywidget * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update static files * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Use non-minified JS files to work around property renaming issue * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Set up tests for layer_manager_row * Set up layer_manager_row test * Implement LayerManager using LitElement + anywidget * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update static files * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Use non-minified JS files to work around property renaming issue * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Clean up setuptools references in pyproject.toml * Clean up setuptools references in pyproject.toml * Fix dark mode and drop shadow issues in Colab * Remove common.css, load fonts using JS instead. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Rebuild * Remove extraneous files * Address comments from initial review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Ignore static files * Fix TS errors * Convert tsconfig.json to spaces and export model interfaces * Add TS tests for anywidgets * clean up styles * Add css classes for better testability * Add better css classes (p2), build before test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add a rough basemap-selector widget * Add tests for basemap selector widget * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Increase margin to 4px * Use primary styling to match old style * Address review comments. * Add type annotation --------- Co-authored-by: Nathaniel Schmitz <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Qiusheng Wu <[email protected]>
- Loading branch information
1 parent
113b3bd
commit b0bf541
Showing
6 changed files
with
256 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import type { RenderProps } from "@anywidget/types"; | ||
import { css, html, PropertyValues, TemplateResult } from "lit"; | ||
import { property, query } from "lit/decorators.js"; | ||
|
||
import { legacyStyles } from "./ipywidgets_styles"; | ||
import { LitWidget } from "./lit_widget"; | ||
import { materialStyles } from "./styles"; | ||
import { loadFonts } from "./utils"; | ||
|
||
export interface BasemapSelectorModel { | ||
basemaps: string[]; | ||
value: string; | ||
} | ||
|
||
export class BasemapSelector extends LitWidget< | ||
BasemapSelectorModel, | ||
BasemapSelector | ||
> { | ||
static get componentName(): string { | ||
return `basemap-selector`; | ||
} | ||
|
||
static styles = [ | ||
legacyStyles, | ||
materialStyles, | ||
css` | ||
.row-container { | ||
align-items: center; | ||
display: flex; | ||
height: 32px; | ||
width: 200px; | ||
} | ||
.row-button { | ||
font-size: 14px; | ||
height: 26px; | ||
margin: 4px; | ||
width: 26px; | ||
} | ||
`, | ||
]; | ||
|
||
modelNameToViewName(): Map< | ||
keyof BasemapSelectorModel, | ||
keyof BasemapSelector | ||
> { | ||
return new Map([ | ||
["basemaps", "basemaps"], | ||
["value", "value"], | ||
]); | ||
} | ||
|
||
@property({ type: Array }) basemaps: string[] = []; | ||
@property({ type: String }) value: string = ""; | ||
@query('select') selectElement!: HTMLSelectElement; | ||
|
||
render(): TemplateResult { | ||
return html` | ||
<div class="row-container"> | ||
<select class="legacy-select" @change=${this.onChange}> | ||
${this.basemaps.map((basemap) => html`<option>${basemap}</option>`)} | ||
</select> | ||
<button | ||
class="legacy-button primary row-button close-button" | ||
@click="${this.onCloseClicked}" | ||
> | ||
<span class="close-icon material-symbols-outlined">close</span> | ||
</button> | ||
</div>`; | ||
} | ||
|
||
override update(changedProperties: PropertyValues): void { | ||
if (changedProperties.has("value") && this.selectElement) { | ||
this.selectElement.value = this.value; | ||
} | ||
super.update(changedProperties); | ||
} | ||
|
||
private onChange(event: Event) { | ||
const target = event.target as HTMLInputElement; | ||
this.value = target.value; | ||
} | ||
|
||
private onCloseClicked(_: Event) { | ||
this.model?.send({ type: "click", id: "close" }); | ||
} | ||
} | ||
|
||
// Without this check, there's a component registry issue when developing locally. | ||
if (!customElements.get(BasemapSelector.componentName)) { | ||
customElements.define(BasemapSelector.componentName, BasemapSelector); | ||
} | ||
|
||
function render({ model, el }: RenderProps<BasemapSelectorModel>) { | ||
loadFonts(); | ||
const row = <BasemapSelector>( | ||
document.createElement(BasemapSelector.componentName) | ||
); | ||
row.model = model; | ||
el.appendChild(row); | ||
} | ||
|
||
export default { render }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { AnyModel } from "@anywidget/types"; | ||
import "../js/basemap_selector"; | ||
import { default as selectorRender, BasemapSelector, BasemapSelectorModel } from "../js/basemap_selector"; | ||
import { FakeAnyModel } from "./fake_anywidget"; | ||
|
||
describe("<basemap-selector>", () => { | ||
let selector: BasemapSelector; | ||
|
||
async function makeSelector(model: AnyModel<BasemapSelectorModel>) { | ||
const container = document.createElement("div"); | ||
selectorRender.render({ | ||
model, el: container, experimental: { | ||
invoke: () => new Promise(() => [model, []]), | ||
} | ||
}); | ||
const element = container.firstElementChild as BasemapSelector; | ||
document.body.appendChild(element); | ||
await element.updateComplete; | ||
return element; | ||
} | ||
|
||
beforeEach(async () => { | ||
selector = await makeSelector(new FakeAnyModel<BasemapSelectorModel>({ | ||
basemaps: ["select", "default", "bounded"], | ||
value: "default", | ||
})); | ||
}); | ||
|
||
afterEach(() => { | ||
Array.from(document.querySelectorAll("basemap-selector")).forEach((el) => { | ||
el.remove(); | ||
}) | ||
}); | ||
|
||
it("can be instantiated.", () => { | ||
expect(selector.shadowRoot?.querySelector("select")?.textContent).toContain("bounded"); | ||
}); | ||
|
||
it("renders the basemap options.", () => { | ||
const options = selector.shadowRoot?.querySelectorAll("option")!; | ||
expect(options.length).toBe(3); | ||
expect(options[0].textContent).toContain("select"); | ||
expect(options[1].textContent).toContain("default"); | ||
expect(options[2].textContent).toContain("bounded"); | ||
}); | ||
|
||
it("setting the value on model changes the value on select.", async () => { | ||
selector.value = "select"; | ||
await selector.updateComplete; | ||
expect(selector.selectElement.value).toBe("select"); | ||
}); | ||
|
||
it("sets value on model when option changes.", async () => { | ||
const setSpy = spyOn(FakeAnyModel.prototype, "set"); | ||
const saveSpy = spyOn(FakeAnyModel.prototype, "save_changes"); | ||
|
||
selector.selectElement.value = "select"; | ||
selector.selectElement.dispatchEvent(new Event('change')); | ||
|
||
await selector.updateComplete; | ||
expect(setSpy).toHaveBeenCalledOnceWith("value", "select"); | ||
expect(saveSpy).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it("emits close event when clicked.", async () => { | ||
const sendSpy = spyOn(FakeAnyModel.prototype, "send"); | ||
// Close button emits an event. | ||
(selector.shadowRoot?.querySelector(".close-button") as HTMLButtonElement).click(); | ||
await selector.updateComplete; | ||
expect(sendSpy).toHaveBeenCalledOnceWith({ | ||
type: "click", | ||
id: "close" | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters