add overlay layers
This commit is contained in:
81
web/src/lib/components/map/style_switcher.svelte
Normal file
81
web/src/lib/components/map/style_switcher.svelte
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { StyleSwitcherControlOptions } from "$lib/vendor/maplibre-style-switcher/style-switcher-control";
|
||||||
|
import RadioGroup from "../base/radio_group.svelte";
|
||||||
|
import { _ } from "svelte-i18n";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
settings: StyleSwitcherControlOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { settings }: Props = $props();
|
||||||
|
|
||||||
|
let container: HTMLDivElement;
|
||||||
|
|
||||||
|
const mapStyles = $derived(settings.styles);
|
||||||
|
|
||||||
|
let showSwitcher: boolean = $state(false);
|
||||||
|
|
||||||
|
function toggleSwitcher() {
|
||||||
|
showSwitcher = !showSwitcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideSwitcher() {
|
||||||
|
showSwitcher = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getElement() {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={container}
|
||||||
|
class="maplibregl-ctrl maplibregl-ctrl-group relative z-10"
|
||||||
|
>
|
||||||
|
<button onclick={toggleSwitcher} aria-label="toggle style switcher">
|
||||||
|
<i class="fa fa-layer-group text-black"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="absolute bg-menu-background rounded-lg px-4 py-2 mt-2 shadow-xl space-y-3"
|
||||||
|
class:hidden={!showSwitcher}
|
||||||
|
style="transform: translateX(calc(-100% + 29px))"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-x-12">
|
||||||
|
<span class="font-semibold whitespace-nowrap text-base"
|
||||||
|
>Map style</span
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="fa fa-close !rounded-full"
|
||||||
|
onclick={hideSwitcher}
|
||||||
|
aria-label="close style switcher"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
<RadioGroup
|
||||||
|
name="completed"
|
||||||
|
items={mapStyles}
|
||||||
|
bind:selected={settings.state.selectedStyle}
|
||||||
|
onchange={(style) => settings.onMapStyleSwitch(settings.styles.indexOf(style), style)}
|
||||||
|
></RadioGroup>
|
||||||
|
<hr class="border-input-border" />
|
||||||
|
<span class="font-semibold whitespace-nowrap text-base">Layers</span>
|
||||||
|
{#each settings.layers as l, i}
|
||||||
|
<div class="flex items-center mt-2 mb-4">
|
||||||
|
<input
|
||||||
|
id="{l.text}-layer-checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
checked={settings.state.selectedLayers[l.text]}
|
||||||
|
class="w-4 h-4 bg-input-background accent-primary border-input-border focus:ring-input-ring focus:ring-2"
|
||||||
|
onchange={(e) => {
|
||||||
|
const checked = (e.target as HTMLInputElement).checked;
|
||||||
|
settings.onLayerChange(checked, l);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label for="{l.text}-layer-checkbox" class="ms-2 text-sm"
|
||||||
|
>{$_(l.text)}</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -9,18 +9,24 @@
|
|||||||
import { findStartAndEndPoints } from "$lib/util/geojson_util";
|
import { findStartAndEndPoints } from "$lib/util/geojson_util";
|
||||||
import { toGeoJson } from "$lib/util/gpx_util";
|
import { toGeoJson } from "$lib/util/gpx_util";
|
||||||
import {
|
import {
|
||||||
|
baseMapStyles,
|
||||||
createMarkerFromWaypoint,
|
createMarkerFromWaypoint,
|
||||||
createPopupFromTrail,
|
createPopupFromTrail,
|
||||||
FontawesomeMarker,
|
FontawesomeMarker,
|
||||||
|
overlayLayers,
|
||||||
} from "$lib/util/maplibre_util";
|
} from "$lib/util/maplibre_util";
|
||||||
import type { ElevationProfileControl } from "$lib/vendor/maplibre-elevation-profile/elevationprofile-control";
|
import type { ElevationProfileControl } from "$lib/vendor/maplibre-elevation-profile/elevationprofile-control";
|
||||||
import { FullscreenControl } from "$lib/vendor/maplibre-fullscreen/fullscreen-control";
|
import { FullscreenControl } from "$lib/vendor/maplibre-fullscreen/fullscreen-control";
|
||||||
import MaplibreGraticule from "$lib/vendor/maplibre-graticule/maplibre-graticule";
|
import MaplibreGraticule from "$lib/vendor/maplibre-graticule/maplibre-graticule";
|
||||||
import { StyleSwitcherControl } from "$lib/vendor/maplibre-style-switcher/style-switcher-control";
|
import {
|
||||||
|
StyleSwitcherControl,
|
||||||
|
type StyleSwitcherControlOptions,
|
||||||
|
} from "$lib/vendor/maplibre-style-switcher/style-switcher-control";
|
||||||
import type { Feature, FeatureCollection, GeoJSON } from "geojson";
|
import type { Feature, FeatureCollection, GeoJSON } from "geojson";
|
||||||
import * as M from "maplibre-gl";
|
import * as M from "maplibre-gl";
|
||||||
import "maplibre-gl/dist/maplibre-gl.css";
|
import "maplibre-gl/dist/maplibre-gl.css";
|
||||||
import { onDestroy, onMount, untrack } from "svelte";
|
import { onDestroy, onMount, untrack } from "svelte";
|
||||||
|
import type { RadioItem } from "../base/radio_group.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
trails?: Trail[];
|
trails?: Trail[];
|
||||||
@@ -92,6 +98,8 @@
|
|||||||
let epc: ElevationProfileControl;
|
let epc: ElevationProfileControl;
|
||||||
let graticule: MaplibreGraticule;
|
let graticule: MaplibreGraticule;
|
||||||
|
|
||||||
|
let mapState: StyleSwitcherControlOptions["state"];
|
||||||
|
|
||||||
let layers: Record<
|
let layers: Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
@@ -237,6 +245,16 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (mapLoaded) {
|
||||||
|
for (const [k, v] of Object.entries(mapState.selectedLayers)) {
|
||||||
|
if (v) {
|
||||||
|
const layer = overlayLayers.find((l) => l.text === k);
|
||||||
|
if (!layer) continue;
|
||||||
|
addOverlayLayer(layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!drawing &&
|
!drawing &&
|
||||||
fitBounds !== "off" &&
|
fitBounds !== "off" &&
|
||||||
@@ -864,36 +882,13 @@
|
|||||||
value: t.url,
|
value: t.url,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
{
|
...baseMapStyles,
|
||||||
text: "OpenFreeMap",
|
|
||||||
value: "/styles/ofm.json",
|
|
||||||
thumbnail: "https://tile.openstreetmap.org/1/0/0.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "OpenTopoMap",
|
|
||||||
value: "/styles/otm.json",
|
|
||||||
thumbnail: "https://tile.opentopomap.org/1/0/0.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Carto Light",
|
|
||||||
value: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
|
|
||||||
thumbnail:
|
|
||||||
"https://basemaps.cartocdn.com/light_all/1/0/0@2x.png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Carto Dark",
|
|
||||||
value: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
|
|
||||||
thumbnail:
|
|
||||||
"https://basemaps.cartocdn.com/dark_all/1/0/0@2x.png",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
let preferredMapStyleIndex = mapStyles.findIndex(
|
|
||||||
(s) => s.text === localStorage.getItem("layer"),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (preferredMapStyleIndex == -1) {
|
mapState = JSON.parse(
|
||||||
preferredMapStyleIndex = 0;
|
localStorage.getItem("map-state") ??
|
||||||
}
|
'{"selectedLayers": {}, "selectedStyle": 0}',
|
||||||
|
);
|
||||||
|
|
||||||
if (!mapContainer) {
|
if (!mapContainer) {
|
||||||
return;
|
return;
|
||||||
@@ -902,9 +897,7 @@
|
|||||||
const finalMapOptions: M.MapOptions = {
|
const finalMapOptions: M.MapOptions = {
|
||||||
...{
|
...{
|
||||||
container: mapContainer,
|
container: mapContainer,
|
||||||
style:
|
style: mapStyles[mapState.selectedStyle].value,
|
||||||
mapStyles[preferredMapStyleIndex].value ??
|
|
||||||
mapStyles[0].value,
|
|
||||||
center: [initialState.lng, initialState.lat],
|
center: [initialState.lng, initialState.lat],
|
||||||
zoom: initialState.zoom,
|
zoom: initialState.zoom,
|
||||||
},
|
},
|
||||||
@@ -932,14 +925,26 @@
|
|||||||
|
|
||||||
const switcherControl = new StyleSwitcherControl({
|
const switcherControl = new StyleSwitcherControl({
|
||||||
styles: mapStyles,
|
styles: mapStyles,
|
||||||
onSwitch: (style) => {
|
layers: overlayLayers,
|
||||||
|
onMapStyleSwitch: (i, style) => {
|
||||||
layers = {};
|
layers = {};
|
||||||
map?.setStyle(style.value);
|
map?.setStyle(style.value);
|
||||||
localStorage.setItem("layer", style.text);
|
mapState.selectedStyle = i;
|
||||||
|
localStorage.setItem("map-state", JSON.stringify(mapState));
|
||||||
},
|
},
|
||||||
selectedIndex:
|
onLayerChange: (checked, layer) => {
|
||||||
preferredMapStyleIndex !== -1 ? preferredMapStyleIndex : 0,
|
mapState.selectedLayers[layer.text] = checked;
|
||||||
|
localStorage.setItem("map-state", JSON.stringify(mapState));
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
addOverlayLayer(layer);
|
||||||
|
} else {
|
||||||
|
removeOverlayLayer(layer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state: mapState,
|
||||||
});
|
});
|
||||||
|
|
||||||
map.addControl(
|
map.addControl(
|
||||||
new M.NavigationControl({ visualizePitch: showTerrain }),
|
new M.NavigationControl({ visualizePitch: showTerrain }),
|
||||||
);
|
);
|
||||||
@@ -1176,6 +1181,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addOverlayLayer(layer: RadioItem) {
|
||||||
|
map?.addSource(layer.text + "overlay-source", {
|
||||||
|
type: "raster",
|
||||||
|
tiles: [layer.value],
|
||||||
|
tileSize: 256,
|
||||||
|
});
|
||||||
|
|
||||||
|
map?.addLayer({
|
||||||
|
id: layer.text + "overlay-layer",
|
||||||
|
type: "raster",
|
||||||
|
source: layer.text + "overlay-source",
|
||||||
|
minzoom: 4,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeOverlayLayer(layer: RadioItem) {
|
||||||
|
map?.removeLayer(layer.text + "overlay-layer");
|
||||||
|
map?.removeSource(layer.text + "overlay-source");
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={handleKeydown} on:keyup={handleKeyup} />
|
<svelte:window on:keydown={handleKeydown} on:keyup={handleKeyup} />
|
||||||
|
|||||||
@@ -7,10 +7,47 @@ import { theme } from "$lib/stores/theme_store";
|
|||||||
import M from "maplibre-gl";
|
import M from "maplibre-gl";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
import { handleFromRecordWithIRI } from "./activitypub_util";
|
||||||
import { getFileURL } from "./file_util";
|
import { getFileURL } from "./file_util";
|
||||||
import { formatDistance, formatElevation, formatTimeHHMM } from "./format_util";
|
import { formatDistance, formatElevation, formatTimeHHMM } from "./format_util";
|
||||||
import { icons } from "./icon_util";
|
import { icons } from "./icon_util";
|
||||||
import { handleFromRecordWithIRI } from "./activitypub_util";
|
import type { RadioItem } from "$lib/components/base/radio_group.svelte";
|
||||||
|
|
||||||
|
|
||||||
|
export const baseMapStyles: RadioItem[] = [
|
||||||
|
{
|
||||||
|
text: "OpenFreeMap",
|
||||||
|
value: "/styles/ofm.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "OpenTopoMap",
|
||||||
|
value: "/styles/otm.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Carto Light",
|
||||||
|
value: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Carto Dark",
|
||||||
|
value: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const overlayLayers: RadioItem[] = [
|
||||||
|
{
|
||||||
|
text: "hiking",
|
||||||
|
value: "https://tile.waymarkedtrails.org/hiking/{z}/{x}/{y}.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "cycling",
|
||||||
|
value: "https://tile.waymarkedtrails.org/cycling/{z}/{x}/{y}.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "MTB",
|
||||||
|
value: "https://tile.waymarkedtrails.org/mtb/{z}/{x}/{y}.png",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
export class FontawesomeMarker extends M.Marker {
|
export class FontawesomeMarker extends M.Marker {
|
||||||
constructor(options: { icon: string, fontSize?: string, width?: number, backgroundColor?: string, fontColor?: string, id?: string }, markerOptions?: M.MarkerOptions) {
|
constructor(options: { icon: string, fontSize?: string, width?: number, backgroundColor?: string, fontColor?: string, id?: string }, markerOptions?: M.MarkerOptions) {
|
||||||
|
|||||||
@@ -1,27 +1,25 @@
|
|||||||
import * as M from "maplibre-gl";
|
import type { RadioItem } from "$lib/components/base/radio_group.svelte";
|
||||||
|
import StyleSwitcher from "$lib/components/map/style_switcher.svelte";
|
||||||
import { type ControlPosition, type IControl } from "maplibre-gl";
|
import { type ControlPosition, type IControl } from "maplibre-gl";
|
||||||
|
import { mount } from "svelte";
|
||||||
/**
|
/**
|
||||||
* Style switcher control options
|
* Style switcher control options
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type MapStyle = {
|
|
||||||
text: string;
|
|
||||||
value: string;
|
|
||||||
thumbnail?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StyleSwitcherControlOptions = {
|
export type StyleSwitcherControlOptions = {
|
||||||
styles: MapStyle[];
|
styles: RadioItem[];
|
||||||
onSwitch: (style: MapStyle) => void
|
layers: RadioItem[];
|
||||||
selectedIndex: number;
|
onMapStyleSwitch: (index: number, style: RadioItem) => void
|
||||||
|
onLayerChange: (checked: boolean, layer: RadioItem) => void
|
||||||
|
|
||||||
|
state: {
|
||||||
|
selectedStyle: number;
|
||||||
|
selectedLayers: Record<string, boolean>
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export class StyleSwitcherControl implements IControl {
|
export class StyleSwitcherControl implements IControl {
|
||||||
private buttonContainer?: HTMLDivElement;
|
private container?: HTMLElement;
|
||||||
private toggleButton?: HTMLButtonElement;
|
|
||||||
private isSwitcherShown = false;
|
|
||||||
private iconSpan?: HTMLSpanElement;
|
|
||||||
private styleLis: HTMLLIElement[] = [];
|
|
||||||
|
|
||||||
private switcherContainer?: HTMLDivElement;
|
private switcherContainer?: HTMLDivElement;
|
||||||
private settings: StyleSwitcherControlOptions;
|
private settings: StyleSwitcherControlOptions;
|
||||||
@@ -36,117 +34,17 @@ export class StyleSwitcherControl implements IControl {
|
|||||||
return this.switcherContainer;
|
return this.switcherContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd(map: M.Map): HTMLElement {
|
onAdd(): HTMLElement {
|
||||||
this.buttonContainer = document.createElement("div");
|
this.container = document.createElement('div');
|
||||||
|
mount(StyleSwitcher, { target: this.container, props: { settings: this.settings } });
|
||||||
this.buttonContainer.classList.add(
|
return this.container;
|
||||||
"maplibregl-ctrl",
|
|
||||||
"maplibregl-ctrl-group",
|
|
||||||
"relative",
|
|
||||||
"z-10"
|
|
||||||
);
|
|
||||||
this.toggleButton = document.createElement("button");
|
|
||||||
this.buttonContainer.appendChild(this.toggleButton);
|
|
||||||
// this.buttonContainer.classList.add("w-16", "aspect-square")
|
|
||||||
this.iconSpan = document.createElement("i");
|
|
||||||
this.iconSpan.classList.add("fa", "fa-layer-group", "text-black");
|
|
||||||
this.toggleButton.appendChild(this.iconSpan);
|
|
||||||
this.toggleButton.addEventListener("click", this.toggleSwitcher.bind(this));
|
|
||||||
|
|
||||||
|
|
||||||
this.switcherContainer = document.createElement("div");
|
|
||||||
this.switcherContainer.style.setProperty("display", "none");
|
|
||||||
this.switcherContainer.classList.add("bg-menu-background", "rounded-lg", "py-3", "mt-2", "shadow-xl")
|
|
||||||
this.switcherContainer.style.setProperty("position", "absolute");
|
|
||||||
this.switcherContainer.style.setProperty("transform", "translateX(calc(-100% + 29px))");
|
|
||||||
// this.switcherContainer.style.setProperty("max-width", "120px");
|
|
||||||
|
|
||||||
const headingDiv = document.createElement("div");
|
|
||||||
headingDiv.classList.add("flex", "items-center", "gap-x-12", "px-3");
|
|
||||||
const closeButton = document.createElement("button");
|
|
||||||
closeButton.classList.add("fa", "fa-close", "!rounded-full");
|
|
||||||
closeButton.addEventListener("click", () => this.hideSwitcher());
|
|
||||||
const switcherContainerHeading = document.createElement("span")
|
|
||||||
switcherContainerHeading.classList.add("text-lg", "font-semibold", "whitespace-nowrap")
|
|
||||||
switcherContainerHeading.textContent = "Select map style"
|
|
||||||
headingDiv.appendChild(switcherContainerHeading);
|
|
||||||
headingDiv.appendChild(closeButton);
|
|
||||||
|
|
||||||
this.switcherContainer.appendChild(headingDiv)
|
|
||||||
|
|
||||||
const buttonDiv = document.createElement("ul")
|
|
||||||
buttonDiv.classList.add("mt-2", "max-h-64", "overflow-y-scroll");
|
|
||||||
this.settings.styles.forEach((style, i) => {
|
|
||||||
const styleLi = document.createElement("li");
|
|
||||||
styleLi.classList.add("flex", "items-center", "gap-x-4", "px-3", "py-2", "cursor-pointer", "hover:bg-menu-item-background-hover")
|
|
||||||
if (style.thumbnail) {
|
|
||||||
const styleImg = document.createElement("img");
|
|
||||||
styleImg.classList.add("w-12", "h-12", "rounded-md")
|
|
||||||
styleImg.src = style.thumbnail;
|
|
||||||
styleLi.appendChild(styleImg)
|
|
||||||
} else {
|
|
||||||
const styleIconContainer = document.createElement("i");
|
|
||||||
styleIconContainer.classList.add("w-12", "h-12", "rounded-md", "bg-blue-200", "flex", "items-center", "justify-center")
|
|
||||||
const styleIcon = document.createElement("i");
|
|
||||||
styleIcon.classList.add("fa", "fa-map-location-dot", "text-xl", "text-black")
|
|
||||||
styleIconContainer.appendChild(styleIcon)
|
|
||||||
styleLi.appendChild(styleIconContainer)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styleName = document.createElement("span");
|
|
||||||
styleName.classList.add("!text-sm")
|
|
||||||
if (i == this.settings.selectedIndex) {
|
|
||||||
styleName.classList.add("font-semibold")
|
|
||||||
}
|
|
||||||
styleName.textContent = style.text;
|
|
||||||
styleLi.appendChild(styleName)
|
|
||||||
|
|
||||||
styleLi.addEventListener("click", () => {
|
|
||||||
this.hideSwitcher();
|
|
||||||
this.styleLis?.at(this.settings.selectedIndex)?.lastElementChild?.classList.remove("font-semibold")
|
|
||||||
this.settings.selectedIndex = i;
|
|
||||||
styleName.classList.add("font-semibold")
|
|
||||||
this.settings.onSwitch(style)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.styleLis?.push(styleLi)
|
|
||||||
buttonDiv.appendChild(styleLi)
|
|
||||||
})
|
|
||||||
this.switcherContainer.appendChild(buttonDiv)
|
|
||||||
this.buttonContainer.appendChild(this.switcherContainer);
|
|
||||||
|
|
||||||
|
|
||||||
return this.buttonContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private toggleSwitcher() {
|
|
||||||
if (!this.switcherContainer) return;
|
|
||||||
|
|
||||||
if (this.isSwitcherShown) {
|
|
||||||
this.hideSwitcher();
|
|
||||||
} else {
|
|
||||||
this.showSwitcher();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showSwitcher() {
|
|
||||||
this.switcherContainer?.style.setProperty("display", "inherit");
|
|
||||||
this.isSwitcherShown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
hideSwitcher() {
|
|
||||||
this.switcherContainer?.style.setProperty("display", "none");
|
|
||||||
this.isSwitcherShown = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemove(): void {
|
onRemove(): void {
|
||||||
// remove button
|
// remove button
|
||||||
if (this.buttonContainer?.parentNode) {
|
if (this.container?.parentNode) {
|
||||||
this.buttonContainer.parentNode.removeChild(this.buttonContainer);
|
this.container.parentNode.removeChild(this.container);
|
||||||
}
|
}
|
||||||
this.buttonContainer = undefined;
|
|
||||||
this.toggleButton = undefined;
|
|
||||||
this.isSwitcherShown = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultPosition?: (() => ControlPosition) | undefined;
|
getDefaultPosition?: (() => ControlPosition) | undefined;
|
||||||
|
|||||||
Reference in New Issue
Block a user