From 5ceda7b2a32cc7cb5aa884b041a3646375fa4e33 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 18 Nov 2024 17:36:43 +0100 Subject: [PATCH 1/2] chore: move editToolBar and captionBar to modules This also tries to rework a bit the "reflow" process, which how to edit the UI when something changes in the data. The idea is to build the whole HTML, then: - the `propagate` method tries to change the UI with targetted CSS selector, but anywhere - while `render` hide/show some elements This is not the destination, but just another step. We certainly need to refactor the SCHEMA to make the next step (as we want to associate some data change to UI change). --- umap/static/umap/css/bar.css | 233 ++++++++++++++++++ umap/static/umap/js/modules/help.js | 7 - umap/static/umap/js/modules/permissions.js | 4 + umap/static/umap/js/modules/rendering/map.js | 200 --------------- umap/static/umap/js/modules/slideshow.js | 16 +- umap/static/umap/js/modules/ui/bar.js | 187 ++++++++++++++ umap/static/umap/js/modules/umap.js | 99 +++++--- umap/static/umap/js/modules/utils.js | 8 +- umap/static/umap/js/umap.controls.js | 2 +- umap/static/umap/map.css | 246 +------------------ umap/templates/umap/css.html | 1 + 11 files changed, 514 insertions(+), 489 deletions(-) create mode 100644 umap/static/umap/css/bar.css create mode 100644 umap/static/umap/js/modules/ui/bar.js diff --git a/umap/static/umap/css/bar.css b/umap/static/umap/css/bar.css new file mode 100644 index 00000000..bd2e8358 --- /dev/null +++ b/umap/static/umap/css/bar.css @@ -0,0 +1,233 @@ +.umap-main-edit-toolbox [type="button"] { + color: #fff; + font-size: 1.2em; + border: none; + background-color: var(--color-darkGray); + width: auto; + margin-bottom: 0; +} +.umap-main-edit-toolbox [type="button"]:hover { + text-decoration: underline; +} + +.leaflet-container [type="button"].umap-help-link { + font-size: 12px; + padding-bottom: 3px; + background-color: inherit; +} +.leaflet-container .edit-save, +.leaflet-container .edit-cancel, +.leaflet-container .edit-disable, +.leaflet-container .connected-peers +{ + display: block; + border: none; + font-size: 12px; + border-radius: 20px; + color: #f8f8f8; + height: 32px; + line-height: 30px; + padding: 0 20px; +} +.leaflet-container .connected-peers, +.dark [type="button"].connected-peers:hover +{ + background-color: var(--color-lightCyan); + color: var(--color-dark); +} + +.leaflet-container .edit-disable:before, +.leaflet-container .edit-save:before, +.leaflet-container .edit-cancel:before, +.leaflet-container .connected-peers:before { + display: inline-block; + width: 19px; + height: 24px; + background-position: -50px -122px; + background-repeat: no-repeat; + background-image: url('../img/16-white.svg'); + vertical-align: middle; + content: ' '; + text-align: center; +} + +.leaflet-container .connected-peers:before { + background-image: url('../img/16.svg'); +} + +.leaflet-container .edit-disable span, +.leaflet-container .edit-save span, +.leaflet-container .edit-cancel span, +.leaflet-container .connected-peers span{ + margin-inline-start: 10px; +} +.leaflet-container .edit-save:before { + background-position: -148px -2px; +} +.leaflet-container .edit-disable:before { + background-position: -50px -25px; +} +.leaflet-container .connected-peers:before { + background-position: -2px -95px; +} +.leaflet-container .edit-cancel, +.leaflet-container .edit-disable, +.leaflet-container .connected-peers{ + border: 0.5px solid rgba(153, 153, 153, 0.40); +} +.leaflet-container .edit-cancel:hover, +.leaflet-container .edit-disable:hover { + border: 0.5px solid rgba(153, 153, 153, 0.80); + text-decoration: none; +} +.leaflet-container .edit-save { + opacity: 0.5; + cursor: not-allowed; + border-radius: 16px; + border: 0.5px solid rgba(153, 153, 153, 0.40); + background: rgba(153, 153, 153, 0.10); +} +.dark [type="button"].edit-save:hover { + background: rgba(153, 153, 153, 0.10); + text-decoration: none; +} +.umap-is-dirty .edit-save { + opacity: 1; + cursor: pointer; + border: 0.5px solid rgba(66, 236, 230, 0.40); + background: rgba(66, 236, 230, 0.10); + color: #42ECE6; +} +.umap-is-dirty .edit-save:before { + background-position: -148px -26px; +} +.umap-is-dirty .dark [type="button"].edit-save:hover { + border-color: rgba(66, 236, 230, 0.80); + background: rgba(66, 236, 230, 0.10); +} +.leaflet-container .edit-save, +.leaflet-container .edit-cancel, +.leaflet-container .edit-disable, +.umap-edit-enabled .edit-enable { + display: none; +} +.umap-edit-enabled .edit-save, +.umap-edit-enabled .edit-disable, +.umap-edit-enabled.umap-is-dirty .edit-cancel { + display: inline-block; +} +.umap-is-dirty .edit-disable { + display: none; +} +.umap-caption-bar { + display: none; +} +.umap-main-edit-toolbox { + top: -46px; + position: absolute; + width: 100%; + left: 0; + right: 0; + height: 46px; + padding: 0 10px; + text-align: start; + line-height: var(--control-size); + cursor: auto; + border-bottom: 1px solid #222; + z-index: var(--zindex-panels); + display: flex; + justify-content: space-between; + background-color: var(--background-color); + color: var(--text-color); +} +.umap-left-edit-toolbox, +.umap-right-edit-toolbox { + display: flex; + column-gap: 10px; +} +.umap-right-edit-toolbox { + align-items: baseline; +} + +.umap-main-edit-toolbox .logo { + width: 39px; + height: 100%; +} +.umap-main-edit-toolbox .logo a { + background-image: url('../img/logo_small.svg'); + background-position: 0 center; + background-repeat: no-repeat; + display: inline-block; + width: 39px; + height: 100%; + vertical-align: middle; + text-indent: -9999px; +} +.umap-main-edit-toolbox .map-name { + display: inline-block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-weight: bold; + text-align: start; +} +.umap-main-edit-toolbox .share-status { + font-size: 1em; + font-style: italic; + overflow: hidden; + text-overflow: ellipsis; +} +.map-name:after { + content: '\00a0'; + padding-inline-start: 3px; + width: 1ch; + display: inline-block; +} +.umap-is-dirty .map-name:after { + content: '*'; +} +.umap-edit-enabled .umap-main-edit-toolbox { + top: 0; +} +.umap-caption-bar h3, +.umap-main-edit-toolbox h3 { + display: inline; +} +.umap-caption-bar button { + margin-inline-start: 10px; +} +.umap-caption-bar button + button:before { + content: '|'; + padding-inline-end: 10px; +} +.umap-main-edit-toolbox .umap-user { + color: #fff; +} +.umap-main-edit-toolbox .umap-user:hover { + text-decoration: underline; +} +.umap-main-edit-toolbox .umap-user:after { + content: '|'; + padding-inline-start: 20px; + display: inline-block; /* Prevents underline on hover. */ +} +.umap-caption-bar-enabled .umap-caption-bar { + display: block; + height: var(--footer-height); + background-color: #fff; + width: 100%; + position: absolute; + left: 0; + bottom: 0; + right: 0; + padding: var(--gutter); + text-align: start; + line-height: 100%; + cursor: auto; + border-top: 1px solid var(--color-lightGray); + opacity: 0.93; + z-index: var(--zindex-panels); +} +.umap-caption-bar-enabled { + --current-footer-height: var(--footer-height); +} diff --git a/umap/static/umap/js/modules/help.js b/umap/static/umap/js/modules/help.js index 0530dd1d..b6c2a88e 100644 --- a/umap/static/umap/js/modules/help.js +++ b/umap/static/umap/js/modules/help.js @@ -226,13 +226,6 @@ export default class Help { return button } - getStartedLink(container) { - const button = DomUtil.createButton('umap-help-link', container, translate('Help')) - button.textContent = translate('Help') - button.addEventListener('click', () => this.showGetStarted()) - return button - } - parse(container) { for (const element of container.querySelectorAll('[data-help]')) { this.button(element, element.dataset.help.split(',')) diff --git a/umap/static/umap/js/modules/permissions.js b/umap/static/umap/js/modules/permissions.js index 5d2ece72..17552afd 100644 --- a/umap/static/umap/js/modules/permissions.js +++ b/umap/static/umap/js/modules/permissions.js @@ -27,6 +27,10 @@ export class MapPermissions extends ServerStored { ) } + render() { + this._umap.render(['properties.permissions']) + } + isOwner() { return Boolean(this._umap.properties.user?.is_owner) } diff --git a/umap/static/umap/js/modules/rendering/map.js b/umap/static/umap/js/modules/rendering/map.js index 9c4c5bfa..607ee22e 100644 --- a/umap/static/umap/js/modules/rendering/map.js +++ b/umap/static/umap/js/modules/rendering/map.js @@ -12,7 +12,6 @@ import { translate } from '../i18n.js' import { uMapAlert as Alert } from '../../components/alerts/alert.js' import * as Utils from '../utils.js' import * as Icon from './icon.js' -import ContextMenu from '../ui/contextmenu.js' // Those options are not saved on the server, so they can live here // instead of in umap.properties @@ -104,10 +103,6 @@ const ControlsMixin = { }, renderControls: function () { - const hasSlideshow = Boolean(this.options.slideshow?.active) - const barEnabled = this.options.captionBar || hasSlideshow - document.body.classList.toggle('umap-caption-bar-enabled', barEnabled) - document.body.classList.toggle('umap-slideshow-enabled', hasSlideshow) for (const control of Object.values(this._controls)) { this.removeControl(control) } @@ -150,198 +145,6 @@ const ControlsMixin = { if (this._umap.getProperty('scaleControl')) this._controls.scale.addTo(this) this._controls.tilelayers.setLayers() }, - - renderEditToolbar: function () { - const className = 'umap-main-edit-toolbox' - const container = - document.querySelector(`.${className}`) || - DomUtil.create('div', `${className} with-transition dark`, this._controlContainer) - container.innerHTML = '' - const leftContainer = DomUtil.create('div', 'umap-left-edit-toolbox', container) - const rightContainer = DomUtil.create('div', 'umap-right-edit-toolbox', container) - const logo = DomUtil.create('div', 'logo', leftContainer) - DomUtil.createLink('', logo, 'uMap', '/', null, translate('Go to the homepage')) - const nameButton = DomUtil.createButton('map-name', leftContainer, '') - DomEvent.on(nameButton, 'mouseover', () => { - this._umap.tooltip.open({ - content: translate('Edit the title of the map'), - anchor: nameButton, - position: 'bottom', - delay: 500, - duration: 5000, - }) - }) - const shareStatusButton = DomUtil.createButton( - 'share-status', - leftContainer, - '', - this._umap.permissions.edit, - this._umap.permissions - ) - DomEvent.on(shareStatusButton, 'mouseover', () => { - this._umap.tooltip.open({ - content: translate('Update who can see and edit the map'), - anchor: shareStatusButton, - position: 'bottom', - delay: 500, - duration: 5000, - }) - }) - if (this.options.editMode === 'advanced') { - DomEvent.on(nameButton, 'click', this._umap.editCaption, this._umap) - DomEvent.on( - shareStatusButton, - 'click', - this._umap.permissions.edit, - this._umap.permissions - ) - } - if (this.options.user?.id) { - const button = U.Utils.loadTemplate(` - - `) - rightContainer.appendChild(button) - const menu = new ContextMenu({ className: 'dark', fixed: true }) - const actions = [ - { - label: translate('New map'), - action: this._umap.urls.get('map_new'), - }, - { - label: translate('My maps'), - action: this._umap.urls.get('user_dashboard'), - }, - { - label: translate('My teams'), - action: this._umap.urls.get('user_teams'), - }, - ] - if (this._umap.urls.has('user_profile')) { - actions.push({ - label: translate('My profile'), - action: this._umap.urls.get('user_profile'), - }) - } - button.addEventListener('click', () => { - menu.openBelow(button, actions) - }) - } - - const connectedPeers = this._umap.sync.getNumberOfConnectedPeers() - if (connectedPeers !== 0) { - const connectedPeersCount = DomUtil.createButton( - 'leaflet-control-connected-peers', - rightContainer, - '' - ) - DomEvent.on(connectedPeersCount, 'mouseover', () => { - this._umap.tooltip.open({ - content: translate( - '{connectedPeers} peer(s) currently connected to this map', - { - connectedPeers: connectedPeers, - } - ), - anchor: connectedPeersCount, - position: 'bottom', - delay: 500, - duration: 5000, - }) - }) - - const updateConnectedPeersCount = () => { - connectedPeersCount.innerHTML = this._umap.sync.getNumberOfConnectedPeers() - } - updateConnectedPeersCount() - } - - this._umap.help.getStartedLink(rightContainer) - const controlEditCancel = DomUtil.createButton( - 'leaflet-control-edit-cancel', - rightContainer, - DomUtil.add('span', '', null, translate('Cancel edits')), - () => this._umap.askForReset() - ) - DomEvent.on(controlEditCancel, 'mouseover', () => { - this._umap.tooltip.open({ - content: this._umap.help.displayLabel('CANCEL'), - anchor: controlEditCancel, - position: 'bottom', - delay: 500, - duration: 5000, - }) - }) - const controlEditDisable = DomUtil.createButton( - 'leaflet-control-edit-disable', - rightContainer, - DomUtil.add('span', '', null, translate('View')), - this._umap.disableEdit, - this._umap - ) - DomEvent.on(controlEditDisable, 'mouseover', () => { - this._umap.tooltip.open({ - content: this._umap.help.displayLabel('PREVIEW'), - anchor: controlEditDisable, - position: 'bottom', - delay: 500, - duration: 5000, - }) - }) - const controlEditSave = DomUtil.createButton( - 'leaflet-control-edit-save button', - rightContainer, - DomUtil.add('span', '', null, translate('Save')), - () => this._umap.saveAll() - ) - DomEvent.on(controlEditSave, 'mouseover', () => { - this._umap.tooltip.open({ - content: this._umap.help.displayLabel('SAVE'), - anchor: controlEditSave, - position: 'bottom', - delay: 500, - duration: 5000, - }) - }) - }, - - renderCaptionBar: function () { - if (this.options.noControl) return - const container = - this._controlContainer.querySelector('.umap-caption-bar') || - DomUtil.create('div', 'umap-caption-bar', this._controlContainer) - container.innerHTML = '' - const name = DomUtil.create('h3', 'map-name', container) - DomEvent.disableClickPropagation(container) - this._umap.addAuthorLink(container) - if (this._umap.getProperty('captionMenus')) { - DomUtil.createButton( - 'umap-about-link flat', - container, - translate('Open caption'), - () => this._umap.openCaption() - ) - DomUtil.createButton( - 'umap-open-browser-link flat', - container, - translate('Browse data'), - () => this.openBrowser('data') - ) - if (this.options.facetKey) { - DomUtil.createButton( - 'umap-open-filter-link flat', - container, - translate('Filter data'), - () => this.openBrowser('filters') - ) - } - } - this._umap.onceDatalayersLoaded(() => { - this._umap.slideshow.renderToolbox(container) - }) - }, } const ManageTilelayerMixin = { @@ -482,8 +285,6 @@ export const LeafletMap = BaseMap.extend({ renderUI: function () { setOptions(this, this._umap.properties) this.initTileLayers() - this.renderCaptionBar() - this.renderEditToolbar() // Needs tilelayer to exist for minimap this.renderControls() this.handleLimitBounds() @@ -576,6 +377,5 @@ export const LeafletMap = BaseMap.extend({ initEditTools: function () { this.editTools = new U.Editable(this._umap) - this.renderEditToolbar() }, }) diff --git a/umap/static/umap/js/modules/slideshow.js b/umap/static/umap/js/modules/slideshow.js index ef8ab0fa..496d3537 100644 --- a/umap/static/umap/js/modules/slideshow.js +++ b/umap/static/umap/js/modules/slideshow.js @@ -26,13 +26,9 @@ export default class Slideshow extends WithTemplate { this.play() }, this) } - leafletMap.on( - 'edit:enabled', - function () { - this.stop() - }, - this - ) + leafletMap.on('edit:enabled', () => { + this.stop() + }) } set current(feature) { @@ -85,9 +81,13 @@ export default class Slideshow extends WithTemplate { spinner.style.animation = 'none' } + isEnabled() { + return Boolean(this.properties.active) + } + play() { if (this._id) return - if (this._umap.editEnabled || !this._umap.properties.slideshow.active) return + if (this._umap.editEnabled || !this.isEnabled()) return L.DomUtil.addClass(document.body, this.CLASSNAME) this._id = window.setInterval(L.bind(this.loop, this), this.properties.delay) this.startSpinner() diff --git a/umap/static/umap/js/modules/ui/bar.js b/umap/static/umap/js/modules/ui/bar.js new file mode 100644 index 00000000..479f1a37 --- /dev/null +++ b/umap/static/umap/js/modules/ui/bar.js @@ -0,0 +1,187 @@ +import { DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js' +import { translate } from '../i18n.js' +import { WithTemplate } from '../utils.js' +import ContextMenu from './contextmenu.js' + +const TOP_BAR_TEMPLATE = ` +
+
+ + + +
+
+ + + + + + +
+
` + +export class TopBar extends WithTemplate { + constructor(umap, parent) { + super() + this._umap = umap + this._menu = new ContextMenu({ className: 'dark', fixed: true }) + this.loadTemplate(TOP_BAR_TEMPLATE) + this.parent = parent + } + + setup() { + this.parent.appendChild(this.element) + this.elements.name.addEventListener('mouseover', () => { + this._umap.tooltip.open({ + content: translate('Edit the title of the map'), + anchor: this.elements.name, + position: 'bottom', + delay: 500, + duration: 5000, + }) + }) + this.elements.share.addEventListener('mouseover', () => { + this._umap.tooltip.open({ + content: translate('Update who can see and edit the map'), + anchor: this.elements.share, + position: 'bottom', + delay: 500, + duration: 5000, + }) + }) + if (this._umap.properties.editMode === 'advanced') { + this.elements.name.addEventListener('click', () => this._umap.editCaption()) + this.elements.share.addEventListener('click', () => this._umap.permissions.edit()) + } + this.elements.user.addEventListener('click', () => { + if (this._umap.properties.user?.id) { + const actions = [ + { + label: translate('New map'), + action: this._umap.urls.get('map_new'), + }, + { + label: translate('My maps'), + action: this._umap.urls.get('user_dashboard'), + }, + { + label: translate('My teams'), + action: this._umap.urls.get('user_teams'), + }, + ] + if (this._umap.urls.has('user_profile')) { + actions.push({ + label: translate('My profile'), + action: this._umap.urls.get('user_profile'), + }) + } + this._menu.openBelow(this.elements.user, actions) + } + }) + + const connectedPeers = this._umap.sync.getNumberOfConnectedPeers() + this.elements.peers.dataset.connected = connectedPeers + this.elements.peers.addEventListener('mouseover', () => { + if (!connectedPeers) return + this._umap.tooltip.open({ + content: translate('{connectedPeers} peer(s) currently connected to this map', { + connectedPeers: connectedPeers, + }), + anchor: this.elements.peers, + position: 'bottom', + delay: 500, + duration: 5000, + }) + }) + + this.elements.help.addEventListener('click', () => this._umap.showGetStarted()) + this.elements.cancel.addEventListener('click', () => this._umap.askForReset()) + this.elements.cancel.addEventListener('mouseover', () => { + this._umap.tooltip.open({ + content: this._umap.help.displayLabel('CANCEL'), + anchor: this.elements.cancel, + position: 'bottom', + delay: 500, + duration: 5000, + }) + }) + this.elements.view.addEventListener('click', () => this._umap.disableEdit()) + this.elements.view.addEventListener('mouseover', () => { + this._umap.tooltip.open({ + content: this._umap.help.displayLabel('PREVIEW'), + anchor: this.elements.view, + position: 'bottom', + delay: 500, + duration: 5000, + }) + }) + this.elements.save.addEventListener('click', () => this._umap.saveAll()) + this.elements.save.addEventListener('mouseover', () => { + this._umap.tooltip.open({ + content: this._umap.help.displayLabel('SAVE'), + anchor: this.elements.save, + position: 'bottom', + delay: 500, + duration: 5000, + }) + }) + this.redraw() + } + + redraw() { + this.elements.peers.hidden = !this._umap.getProperty('syncEnabled') + } +} + +const BOTTOM_BAR_TEMPLATE = ` +
+

+ + + + +
+` + +export class BottomBar extends WithTemplate { + constructor(umap, slideshow, parent) { + super() + this._umap = umap + this._slideshow = slideshow + this.loadTemplate(BOTTOM_BAR_TEMPLATE) + this.parent = parent + } + + setup() { + this.parent.appendChild(this.element) + DomEvent.disableClickPropagation(this.element) + this._umap.addAuthorLink(this.elements.author) + this.elements.caption.addEventListener('click', () => this._umap.openCaption()) + this.elements.browse.addEventListener('click', () => this._umap.openBrowser('data')) + this.elements.filter.addEventListener('click', () => + this._umap.openBrowser('filters') + ) + this._slideshow.renderToolbox(this.element) + this.redraw() + } + + redraw() { + const hasSlideshow = this._slideshow.isEnabled() + const barEnabled = this._umap.properties.captionBar || hasSlideshow + document.body.classList.toggle('umap-caption-bar-enabled', barEnabled) + document.body.classList.toggle('umap-slideshow-enabled', hasSlideshow) + const showMenus = this._umap.getProperty('captionMenus') + this.elements.caption.hidden = !showMenus + this.elements.browse.hidden = !showMenus + this.elements.filter.hidden = !showMenus || !this._umap.properties.facetKey + } +} diff --git a/umap/static/umap/js/modules/umap.js b/umap/static/umap/js/modules/umap.js index d65ff077..26ed68e5 100644 --- a/umap/static/umap/js/modules/umap.js +++ b/umap/static/umap/js/modules/umap.js @@ -13,6 +13,7 @@ import { LeafletMap } from './rendering/map.js' import URLs from './urls.js' import { Panel, EditPanel, FullPanel } from './ui/panel.js' import Dialog from './ui/dialog.js' +import { BottomBar, TopBar } from './ui/bar.js' import Tooltip from './ui/tooltip.js' import ContextMenu from './ui/contextmenu.js' import { Request, ServerRequest } from './request.js' @@ -102,12 +103,14 @@ export default class Umap extends ServerStored { this.panel = new Panel(this, this._leafletMap) this.dialog = new Dialog({ className: 'dark' }) + this.topBar = new TopBar(this, this._leafletMap._controlContainer) + this.bottomBar = new BottomBar( + this, + this.slideshow, + this._leafletMap._controlContainer + ) this.tooltip = new Tooltip(this._leafletMap._controlContainer) this.contextmenu = new ContextMenu() - if (this.hasEditMode()) { - this.editPanel = new EditPanel(this, this._leafletMap) - this.fullPanel = new FullPanel(this, this._leafletMap) - } this.server = new ServerRequest() this.request = new Request() this.facets = new Facets(this) @@ -117,6 +120,13 @@ export default class Umap extends ServerStored { this.share = new Share(this) this.rules = new Rules(this) + if (this.hasEditMode()) { + this.editPanel = new EditPanel(this, this._leafletMap) + this.fullPanel = new FullPanel(this, this._leafletMap) + this._leafletMap.initEditTools() + this.topBar.setup() + } + this.datalayersFromQueryString = this.searchParams.get('datalayers') if (this.datalayersFromQueryString) { this.datalayersFromQueryString = this.datalayersFromQueryString @@ -180,14 +190,11 @@ export default class Umap extends ServerStored { await this.loadDataFromQueryString() } - if (this.hasEditMode()) { - this._leafletMap.initEditTools() - } - if (!this.properties.noControl) { this.initShortcuts() this._leafletMap.on('contextmenu', (e) => this.onContextMenu(e)) this.onceDataLoaded(this.setViewFromQueryString) + this.bottomBar.setup() this.propagate() } @@ -633,22 +640,6 @@ export default class Umap extends ServerStored { this.fire('saved') } - propagate() { - let els = document.querySelectorAll('.map-name') - for (const el of els) { - el.textContent = this.getDisplayName() - } - const status = this.permissions.getShareStatusDisplay() - els = document.querySelectorAll('.share-status') - for (const el of els) { - if (status) { - el.textContent = translate('Visibility: {status}', { - status: status, - }) - } - } - } - getDisplayName() { return this.properties.name || translate('Untitled map') } @@ -1004,7 +995,13 @@ export default class Umap extends ServerStored { ], ] const slideshowBuilder = new U.FormBuilder(this, slideshowFields, { - callback: () => this.slideshow.load(), + callback: () => { + this.slideshow.load() + // FIXME when we refactor formbuilder: this callback is called in a 'postsync' + // event, which comes after the call of `setter` method, which will call the + // map.render method, which should do this redraw. + this.bottomBar.redraw() + }, umap: this, }) slideshow.appendChild(slideshowBuilder.build()) @@ -1261,10 +1258,8 @@ export default class Umap extends ServerStored { } render(fields) { - if (fields.includes('numberOfConnectedPeers')) { - this._leafletMap.renderEditToolbar() - this.propagate() - } + const impacted = this.propagate(fields) + if (impacted) return // No need to run a wider reflow const impacts = Utils.getImpactsFromSchema(fields) for (const impact of impacts) { @@ -1272,7 +1267,8 @@ export default class Umap extends ServerStored { case 'ui': this._leafletMap.renderUI() this.browser.redraw() - this.propagate() + this.topBar.redraw() + this.bottomBar.redraw() break case 'data': this.eachVisibleDataLayer((datalayer) => { @@ -1294,6 +1290,49 @@ export default class Umap extends ServerStored { } } + // This method does a targeted update of the UI, + // it whould be merged with `render`` method and the + // SCHEMA at some point + propagate(fields = []) { + const impacts = { + 'properties.name': () => { + Utils.eachElement('.map-name', (el) => { + el.textContent = this.getDisplayName() + }) + }, + user: () => { + Utils.eachElement('.umap-user .username', (el) => { + if (this.properties.user?.id) { + el.textContent = this.properties.user.name + } + }) + }, + 'properties.permissions': () => { + const status = this.permissions.getShareStatusDisplay() + if (status) { + Utils.eachElement('.share-status', (el) => { + el.textContent = translate('Visibility: {status}', { + status: status, + }) + }) + } + }, + numberOfConnectedPeers: () => { + Utils.eachElement('.connected-peers', (el) => { + el.textContent = this.sync.getNumberOfConnectedPeers() + }) + }, + } + let impacted = false + for (const [field, impact] of Object.entries(impacts)) { + if (!fields.length || fields.includes(field)) { + impact() + impacted = true + } + } + return impacted + } + // TODO: allow to control the default datalayer // (edit and viewing) // cf https://github.com/umap-project/umap/issues/585 diff --git a/umap/static/umap/js/modules/utils.js b/umap/static/umap/js/modules/utils.js index b9fe128e..2e12083e 100644 --- a/umap/static/umap/js/modules/utils.js +++ b/umap/static/umap/js/modules/utils.js @@ -25,8 +25,6 @@ export function checkId(string) { return /^[A-Za-z0-9]{5}$/.test(string) } - - function _getPropertyName(field) { const filtered_field = ['options.', 'properties.'].reduce( (acc, prefix) => acc.replace(prefix, ''), @@ -440,3 +438,9 @@ export function deepEqual(object1, object2) { export function slugify(str) { return (str || 'data').replace(/[^a-z0-9]/gi, '_').toLowerCase() } + +export function eachElement(selector, callback) { + for (const el of document.querySelectorAll(selector)) { + callback(el) + } +} diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 777c866c..54061492 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -389,7 +389,7 @@ U.EditControl = L.Control.extend({ }, onAdd: function (map) { - const container = L.DomUtil.create('div', 'leaflet-control-edit-enable') + const container = L.DomUtil.create('div', 'edit-enable') const enableEditing = L.DomUtil.createButton( '', container, diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css index 480127cd..7faf6210 100644 --- a/umap/static/umap/map.css +++ b/umap/static/umap/map.css @@ -45,9 +45,6 @@ html[dir="rtl"] .leaflet-tooltip-pane > * { .umap-edit-enabled { --current-header-height: var(--header-height); } -.umap-caption-bar-enabled { - --current-footer-height: var(--footer-height); -} .leaflet-top { top: var(--current-header-height); } @@ -166,7 +163,7 @@ html[dir="rtl"] .leaflet-tooltip-pane > * { min-height: 23px; height: 23px; } -.leaflet-control-edit-enable [type="button"]:before { +.edit-enable [type="button"]:before { content: ' '; width: 24px; height: 24px; @@ -175,7 +172,7 @@ html[dir="rtl"] .leaflet-tooltip-pane > * { background-image: url('./img/16-white.svg'); background-position: -48px -48px; } -.leaflet-control-edit-enable [type="button"] { +.edit-enable [type="button"] { width: initial; padding: 0 20px; background-color: #353c3e; @@ -187,7 +184,7 @@ html[dir="rtl"] .leaflet-tooltip-pane > * { display: block; } .leaflet-control-toolbar .leaflet-toolbar-icon.dark:hover, -.leaflet-control-edit-enable [type="button"]:hover { +.edit-enable [type="button"]:hover { background-color: #4d5759; } .umap-permanent-credits-container { @@ -476,245 +473,12 @@ ul.photon-autocomplete { .umap-edit-actions li:hover { background-color: #353c3e; } - - -/* ********************************* */ -/* Edit main toolbox */ -/* ********************************* */ -.umap-main-edit-toolbox [type="button"] { - color: #fff; - font-size: 1.2em; - border: none; - background-color: var(--color-darkGray); - width: auto; - margin-bottom: 0; -} -.umap-main-edit-toolbox [type="button"]:hover { - text-decoration: underline; -} - -.leaflet-container [type="button"].umap-help-link { - font-size: 12px; - padding-bottom: 3px; - background-color: inherit; -} -.leaflet-container .leaflet-control-edit-save, -.leaflet-container .leaflet-control-edit-cancel, -.leaflet-container .leaflet-control-edit-disable, -.leaflet-container .leaflet-control-connected-peers -{ - display: block; - border: none; - font-size: 12px; - border-radius: 20px; - color: #f8f8f8; - height: 32px; - line-height: 30px; - padding: 0 20px; -} -.leaflet-container .leaflet-control-connected-peers, -.dark [type="button"].leaflet-control-connected-peers:hover -{ - background-color: var(--color-lightCyan); - color: var(--color-dark); -} - -.leaflet-container .leaflet-control-edit-disable:before, -.leaflet-container .leaflet-control-edit-save:before, -.leaflet-container .leaflet-control-edit-cancel:before, -.leaflet-container .leaflet-control-connected-peers:before { - display: inline-block; - width: 19px; - height: 24px; - background-position: -50px -122px; - background-repeat: no-repeat; - background-image: url('./img/16-white.svg'); - vertical-align: middle; - content: ' '; - text-align: center; -} - -.leaflet-container .leaflet-control-connected-peers:before { - background-image: url('./img/16.svg'); -} - -.leaflet-container .leaflet-control-edit-disable span, -.leaflet-container .leaflet-control-edit-save span, -.leaflet-container .leaflet-control-edit-cancel span, -.leaflet-container .leaflet-control-connected-peers span{ - margin-inline-start: 10px; -} -.leaflet-container .leaflet-control-edit-save:before { - background-position: -148px -2px; -} -.leaflet-container .leaflet-control-edit-disable:before { - background-position: -50px -25px; -} -.leaflet-container .leaflet-control-connected-peers:before { - background-position: -2px -95px; -} -.leaflet-container .leaflet-control-edit-cancel, -.leaflet-container .leaflet-control-edit-disable, -.leaflet-container .leaflet-control-connected-peers{ - border: 0.5px solid rgba(153, 153, 153, 0.40); -} -.leaflet-container .leaflet-control-edit-cancel:hover, -.leaflet-container .leaflet-control-edit-disable:hover { - border: 0.5px solid rgba(153, 153, 153, 0.80); - text-decoration: none; -} -.leaflet-container .leaflet-control-edit-save { - opacity: 0.5; - cursor: not-allowed; - border-radius: 16px; - border: 0.5px solid rgba(153, 153, 153, 0.40); - background: rgba(153, 153, 153, 0.10); -} -.dark [type="button"].leaflet-control-edit-save:hover { - background: rgba(153, 153, 153, 0.10); - text-decoration: none; -} -.umap-is-dirty .leaflet-control-edit-save { - opacity: 1; - cursor: pointer; - border: 0.5px solid rgba(66, 236, 230, 0.40); - background: rgba(66, 236, 230, 0.10); - color: #42ECE6; -} -.umap-is-dirty .leaflet-control-edit-save:before { - background-position: -148px -26px; -} -.umap-is-dirty .dark [type="button"].leaflet-control-edit-save:hover { - border-color: rgba(66, 236, 230, 0.80); - background: rgba(66, 236, 230, 0.10); -} -.leaflet-container .leaflet-control-edit-save, -.leaflet-container .leaflet-control-edit-cancel, -.leaflet-container .leaflet-control-edit-disable, -.umap-edit-enabled .leaflet-control-edit-enable { - display: none; -} -.umap-edit-enabled .leaflet-control-edit-save, -.umap-edit-enabled .leaflet-control-edit-disable, -.umap-edit-enabled.umap-is-dirty .leaflet-control-edit-cancel { - display: inline-block; -} -.umap-is-dirty .leaflet-control-edit-disable { - display: none; -} -.umap-caption-bar { - display: none; -} -.umap-main-edit-toolbox { - top: -46px; - position: absolute; - width: 100%; - left: 0; - right: 0; - height: 46px; - padding: 0 10px; - text-align: start; - line-height: var(--control-size); - cursor: auto; - border-bottom: 1px solid #222; - z-index: var(--zindex-panels); - display: flex; - justify-content: space-between; - background-color: var(--background-color); - color: var(--text-color); -} -.umap-left-edit-toolbox, -.umap-right-edit-toolbox { - display: flex; - column-gap: 10px; -} -.umap-right-edit-toolbox { - align-items: baseline; -} - -.umap-main-edit-toolbox .logo { - width: 39px; - height: 100%; -} -.umap-main-edit-toolbox .logo a { - background-image: url('./img/logo_small.svg'); - background-position: 0 center; - background-repeat: no-repeat; - display: inline-block; - width: 39px; - height: 100%; - vertical-align: middle; - text-indent: -9999px; -} -.umap-main-edit-toolbox .map-name { - display: inline-block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - font-weight: bold; - text-align: start; -} -.umap-main-edit-toolbox .share-status { - font-size: 1em; - font-style: italic; - overflow: hidden; - text-overflow: ellipsis; -} -.map-name:after { - content: '\00a0'; - padding-inline-start: 3px; - width: 1ch; - display: inline-block; -} -.umap-is-dirty .map-name:after { - content: '*'; -} -.umap-edit-enabled .umap-main-edit-toolbox { - top: 0; -} -.umap-caption-bar h3, -.umap-main-edit-toolbox h3 { - display: inline; -} -.umap-caption-bar button { - margin-inline-start: 10px; -} -.umap-caption-bar button + button:before { - content: '|'; - padding-inline-end: 10px; -} -.umap-main-edit-toolbox .umap-user { - color: #fff; -} -.umap-main-edit-toolbox .umap-user:hover { - text-decoration: underline; -} -.umap-main-edit-toolbox .umap-user:after { - content: '|'; - padding-inline-start: 20px; - display: inline-block; /* Prevents underline on hover. */ -} -.umap-caption-bar-enabled .umap-caption-bar { - display: block; - height: var(--footer-height); - background-color: #fff; - width: 100%; - position: absolute; - left: 0; - bottom: 0; - right: 0; - padding: var(--gutter); - text-align: start; - line-height: 100%; - cursor: auto; - border-top: 1px solid var(--color-lightGray); - opacity: 0.93; - z-index: var(--zindex-panels); -} .umap-help { font-style: italic; } + + .umap-datalayer-version { padding: 5px 0; border-bottom: 1px solid #202425; diff --git a/umap/templates/umap/css.html b/umap/templates/umap/css.html index ba4b0cf8..74b94993 100644 --- a/umap/templates/umap/css.html +++ b/umap/templates/umap/css.html @@ -35,4 +35,5 @@ + From 346597533ca6564cbdb5cabc53a23238cb07f3d2 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Tue, 19 Nov 2024 13:16:02 +0100 Subject: [PATCH 2/2] chore: a bit of CSS cleaning in bar.css --- umap/static/umap/base.css | 6 +- umap/static/umap/css/bar.css | 79 +-- umap/static/umap/css/icon.css | 19 +- umap/static/umap/img/16-white.svg | 12 +- umap/static/umap/img/16.svg | 2 +- umap/static/umap/img/source/16-white.svg | 16 +- umap/static/umap/img/source/16.svg | 754 +---------------------- umap/static/umap/js/modules/ui/bar.js | 16 +- umap/static/umap/js/modules/umap.js | 2 +- umap/static/umap/vars.css | 1 + 10 files changed, 80 insertions(+), 827 deletions(-) diff --git a/umap/static/umap/base.css b/umap/static/umap/base.css index 631e9c51..22fbdcee 100644 --- a/umap/static/umap/base.css +++ b/umap/static/umap/base.css @@ -254,8 +254,8 @@ input[type="submit"] { } .dark .button, .dark [type="button"] { - background-color: #2a2e30; - color: #eeeeec; + background-color: var(--color-darkerGray); + color: var(--text-color); border: 1px solid #1b1f20; } .dark .button:hover, @@ -264,7 +264,7 @@ input[type="submit"] { background-color: #2e3436; } .dark a { - color: #eeeeec; + color: var(--text-color); } button.flat, [type="button"].flat, diff --git a/umap/static/umap/css/bar.css b/umap/static/umap/css/bar.css index bd2e8358..eb22f16e 100644 --- a/umap/static/umap/css/bar.css +++ b/umap/static/umap/css/bar.css @@ -1,17 +1,16 @@ -.umap-main-edit-toolbox [type="button"] { +.umap-main-edit-toolbox [type=button] { color: #fff; - font-size: 1.2em; + font-size: 1em; border: none; background-color: var(--color-darkGray); width: auto; margin-bottom: 0; } -.umap-main-edit-toolbox [type="button"]:hover { +.umap-main-edit-toolbox [type=button]:hover { text-decoration: underline; } -.leaflet-container [type="button"].umap-help-link { - font-size: 12px; +.leaflet-container [type=button].umap-help-link { padding-bottom: 3px; background-color: inherit; } @@ -22,54 +21,18 @@ { display: block; border: none; - font-size: 12px; border-radius: 20px; - color: #f8f8f8; height: 32px; line-height: 30px; padding: 0 20px; } .leaflet-container .connected-peers, -.dark [type="button"].connected-peers:hover +.dark [type=button].connected-peers:hover { background-color: var(--color-lightCyan); color: var(--color-dark); } -.leaflet-container .edit-disable:before, -.leaflet-container .edit-save:before, -.leaflet-container .edit-cancel:before, -.leaflet-container .connected-peers:before { - display: inline-block; - width: 19px; - height: 24px; - background-position: -50px -122px; - background-repeat: no-repeat; - background-image: url('../img/16-white.svg'); - vertical-align: middle; - content: ' '; - text-align: center; -} - -.leaflet-container .connected-peers:before { - background-image: url('../img/16.svg'); -} - -.leaflet-container .edit-disable span, -.leaflet-container .edit-save span, -.leaflet-container .edit-cancel span, -.leaflet-container .connected-peers span{ - margin-inline-start: 10px; -} -.leaflet-container .edit-save:before { - background-position: -148px -2px; -} -.leaflet-container .edit-disable:before { - background-position: -50px -25px; -} -.leaflet-container .connected-peers:before { - background-position: -2px -95px; -} .leaflet-container .edit-cancel, .leaflet-container .edit-disable, .leaflet-container .connected-peers{ @@ -87,7 +50,10 @@ border: 0.5px solid rgba(153, 153, 153, 0.40); background: rgba(153, 153, 153, 0.10); } -.dark [type="button"].edit-save:hover { +.leaflet-container .icon-save { + display: none; +} +.dark button.edit-save:hover { background: rgba(153, 153, 153, 0.10); text-decoration: none; } @@ -96,12 +62,15 @@ cursor: pointer; border: 0.5px solid rgba(66, 236, 230, 0.40); background: rgba(66, 236, 230, 0.10); - color: #42ECE6; + color: var(--color-brightCyan); } -.umap-is-dirty .edit-save:before { - background-position: -148px -26px; +.umap-is-dirty .icon-save { + display: inline-block; } -.umap-is-dirty .dark [type="button"].edit-save:hover { +.umap-is-dirty .icon-save-disabled { + display: none; +} +.umap-is-dirty .dark button.edit-save:hover { border-color: rgba(66, 236, 230, 0.80); background: rgba(66, 236, 230, 0.10); } @@ -123,12 +92,13 @@ display: none; } .umap-main-edit-toolbox { - top: -46px; + /* Show a transition from top to bottom when opening it */ + top: calc(var(--header-height) * -1); position: absolute; width: 100%; left: 0; right: 0; - height: 46px; + height: var(--header-height); padding: 0 10px; text-align: start; line-height: var(--control-size); @@ -172,7 +142,6 @@ text-align: start; } .umap-main-edit-toolbox .share-status { - font-size: 1em; font-style: italic; overflow: hidden; text-overflow: ellipsis; @@ -200,9 +169,6 @@ content: '|'; padding-inline-end: 10px; } -.umap-main-edit-toolbox .umap-user { - color: #fff; -} .umap-main-edit-toolbox .umap-user:hover { text-decoration: underline; } @@ -211,8 +177,8 @@ padding-inline-start: 20px; display: inline-block; /* Prevents underline on hover. */ } -.umap-caption-bar-enabled .umap-caption-bar { - display: block; +.umap-caption-bar { + display: none; height: var(--footer-height); background-color: #fff; width: 100%; @@ -228,6 +194,9 @@ opacity: 0.93; z-index: var(--zindex-panels); } +.umap-caption-bar-enabled .umap-caption-bar { + display: block; +} .umap-caption-bar-enabled { --current-footer-height: var(--footer-height); } diff --git a/umap/static/umap/css/icon.css b/umap/static/umap/css/icon.css index b76020b1..43042a54 100644 --- a/umap/static/umap/css/icon.css +++ b/umap/static/umap/css/icon.css @@ -8,7 +8,6 @@ background-color: initial; } .icon-16 { - background-image: url('../img/16.svg'); height: 24px; line-height: 24px; width: 24px; @@ -43,6 +42,14 @@ html[dir="rtl"] .icon { .dark .icon-24 { background-image: url('../img/24-white.svg'); } +.icon-16.icon-black, +.icon-16 { + background-image: url('../img/16.svg'); +} +.icon-24.icon-black, +.icon-24 { + background-image: url('../img/24.svg'); +} .icon-add { background-position: var(--tile) var(--tile); } @@ -106,6 +113,9 @@ html[dir="rtl"] .icon { .icon-marker { background-position: calc(var(--tile) * 3) calc(var(--tile) * 5); } +.icon-peers { + background-position: calc(var(--tile) * 3) calc(var(--tile) * 2); +} .icon-polygon { background-position: var(--tile) calc(var(--tile) * 3); } @@ -124,6 +134,13 @@ html[dir="rtl"] .icon { .expanded .icon-resize { background-position: calc(var(--tile) * 2) calc(var(--tile) * 6); } +.icon-save { + background-position: calc(var(--tile) * 6) var(--tile); +} +/* FIXME move to a 16-disabled.svg sprite ? */ +.icon-save-disabled { + background-position: calc(var(--tile) * 6) 0; +} .icon-search { background-position: var(--tile) calc(var(--tile) * 5); } diff --git a/umap/static/umap/img/16-white.svg b/umap/static/umap/img/16-white.svg index 86704816..00f6dc67 100644 --- a/umap/static/umap/img/16-white.svg +++ b/umap/static/umap/img/16-white.svg @@ -37,9 +37,6 @@   - - - @@ -196,5 +193,14 @@ + + + + + + + + + diff --git a/umap/static/umap/img/16.svg b/umap/static/umap/img/16.svg index 85400b49..35212270 100644 --- a/umap/static/umap/img/16.svg +++ b/umap/static/umap/img/16.svg @@ -1 +1 @@ -image/svg+xml   +image/svg+xml   diff --git a/umap/static/umap/img/source/16-white.svg b/umap/static/umap/img/source/16-white.svg index 9e99a16a..85fcbc16 100644 --- a/umap/static/umap/img/source/16-white.svg +++ b/umap/static/umap/img/source/16-white.svg @@ -1,7 +1,7 @@ - + @@ -16,7 +16,7 @@ - + @@ -56,9 +56,6 @@   - - - @@ -218,5 +215,14 @@ + + + + + + + + + diff --git a/umap/static/umap/img/source/16.svg b/umap/static/umap/img/source/16.svg index 19fa5f22..2af171a6 100644 --- a/umap/static/umap/img/source/16.svg +++ b/umap/static/umap/img/source/16.svg @@ -1,756 +1,4 @@ -image/svg+xml   +image/svg+xml   diff --git a/umap/static/umap/js/modules/ui/bar.js b/umap/static/umap/js/modules/ui/bar.js index 479f1a37..87e70e8e 100644 --- a/umap/static/umap/js/modules/ui/bar.js +++ b/umap/static/umap/js/modules/ui/bar.js @@ -11,19 +11,26 @@ const TOP_BAR_TEMPLATE = `
- + - - -
@@ -89,7 +96,6 @@ export class TopBar extends WithTemplate { }) const connectedPeers = this._umap.sync.getNumberOfConnectedPeers() - this.elements.peers.dataset.connected = connectedPeers this.elements.peers.addEventListener('mouseover', () => { if (!connectedPeers) return this._umap.tooltip.open({ diff --git a/umap/static/umap/js/modules/umap.js b/umap/static/umap/js/modules/umap.js index 26ed68e5..b0696827 100644 --- a/umap/static/umap/js/modules/umap.js +++ b/umap/static/umap/js/modules/umap.js @@ -1318,7 +1318,7 @@ export default class Umap extends ServerStored { } }, numberOfConnectedPeers: () => { - Utils.eachElement('.connected-peers', (el) => { + Utils.eachElement('.connected-peers span', (el) => { el.textContent = this.sync.getNumberOfConnectedPeers() }) }, diff --git a/umap/static/umap/vars.css b/umap/static/umap/vars.css index 181a5ed7..4c8f5eb3 100644 --- a/umap/static/umap/vars.css +++ b/umap/static/umap/vars.css @@ -5,6 +5,7 @@ --color-lightGray: #ddd; --color-mediumGray: #3e4444; --color-darkGray: #323737; + --color-darkerGray: #2a2e30; --color-light: white; --color-dark: black; --color-limeGreen: #b9f5d2;