From 0bbb55b5cd893889072e6b533f5b6934b3995922 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 30 Dec 2024 19:29:15 +0200 Subject: [PATCH] UI: Implement map layers sidebar. --- .../widget/lib/maps/leaflet/leaflet-tb.ts | 235 ++++++++++++++++++ .../components/widget/lib/maps/map-layer.ts | 35 ++- .../widget/lib/maps/map-widget.models.ts | 2 +- .../components/widget/lib/maps/map.models.ts | 2 +- .../home/components/widget/lib/maps/map.scss | 124 +++++++++ .../home/components/widget/lib/maps/map.ts | 88 +++++-- .../assets/locale/locale.constant-en_US.json | 2 + ui-ngx/src/typings/leaflet-extend-tb.d.ts | 44 +++- 8 files changed, 495 insertions(+), 37 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts index e69de29bb2..e458ed8ebc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts @@ -0,0 +1,235 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import L, { TB } from 'leaflet'; + +class SidebarControl extends L.Control { + + private readonly sidebar: JQuery; + + private current = $(); + private currentButton = $(); + + private map: L.Map; + + constructor(options: TB.SidebarControlOptions) { + super(options); + this.sidebar = $('
'); + this.options.container.append(this.sidebar); + const position = options?.position || 'topleft'; + if (['topleft', 'bottomleft'].includes(position)) { + this.options.container.addClass('tb-sidebar-left'); + } else { + this.options.container.addClass('tb-sidebar-right'); + } + } + + addPane(pane: JQuery): this { + pane.hide().appendTo(this.sidebar); + return this; + } + + togglePane(pane: JQuery, button: JQuery) { + const paneWidth = this.options?.paneWidth || 220; + const position = this.options?.position || 'topleft'; + + this.current.hide().trigger('hide'); + this.currentButton.removeClass('active'); + + if (this.current === pane) { + if (['topleft', 'bottomleft'].includes(position)) { + this.map.panBy([-paneWidth, 0], { animate: false }); + } + this.sidebar.hide(); + this.current = this.currentButton = $(); + } else { + this.sidebar.show(); + this.current = pane; + this.currentButton = button || $(); + if (['topleft', 'bottomleft'].includes(position)) { + this.map.panBy([paneWidth, 0], { animate: false }); + } + } + this.map.invalidateSize({ pan: false, animate: false }); + this.current.show().trigger('show'); + this.currentButton.addClass('active'); + } + + addTo(map: L.Map): this { + this.map = map; + return this; + } +} + +class SidebarPaneControl extends L.Control { + + private button: JQuery; + private $ui: JQuery; + + constructor(options: O) { + super(options); + } + + onAdd(map: L.Map): HTMLElement { + const $container = $("
") + .attr('class', 'leaflet-bar'); + + this.button = $("") + .attr('class', 'tb-control-button') + .attr('href', '#') + .html('
') + .on('click', (e) => { + this.toggle(e); + }); + if (this.options.buttonTitle) { + this.button.attr('title', this.options.buttonTitle); + } + this.button.appendTo($container); + + this.$ui = $('
') + .attr('class', this.options.uiClass); + + $('
') + .appendTo(this.$ui) + .append($('
') + .text(this.options.paneTitle)) + .append($('
') + .append($('') + .attr('aria-label', 'Close') + .bind('click', (e) => { + this.toggle(e); + }))); + + this.options.sidebar.addPane(this.$ui); + + this.onAddPane(map, this.button, this.$ui, (e) => { + this.toggle(e); + }); + + return $container[0]; + } + + public onAddPane(map: L.Map, button: JQuery, $ui: JQuery, toggle: (e: JQuery.MouseEventBase) => void) {} + + private toggle(e: JQuery.MouseEventBase) { + e.stopPropagation(); + e.preventDefault(); + if (!this.button.hasClass("disabled")) { + this.options.sidebar.togglePane(this.$ui, this.button); + } + //$(".leaflet-control .control-button").tooltip("hide"); + } +} + +class LayersControl extends SidebarPaneControl { + constructor(options: TB.LayersControlOptions) { + super(options); + } + + public onAddPane(map: L.Map, button: JQuery, $ui: JQuery, toggle: (e: JQuery.MouseEventBase) => void) { + const layers = this.options.layers; + const baseSection = $("
") + .attr('class', 'tb-layers-container') + .appendTo($ui); + + layers.forEach((layerData, i) => { + const id = 'map-ui-layer-' + i; + const buttonContainer = $('
') + .appendTo(baseSection); + const mapContainer = $('
') + .appendTo(buttonContainer); + const input = $('') + .prop('id', id) + .prop('checked', map.hasLayer(layerData.layer)) + .appendTo(buttonContainer); + + const item = $('