diff --git a/umap/static/umap/base.css b/umap/static/umap/base.css index 631e9c513..22fbdcee0 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 new file mode 100644 index 000000000..eb22f16e8 --- /dev/null +++ b/umap/static/umap/css/bar.css @@ -0,0 +1,202 @@ +.umap-main-edit-toolbox [type=button] { + color: #fff; + font-size: 1em; + 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 { + 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; + border-radius: 20px; + 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-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); +} +.leaflet-container .icon-save { + display: none; +} +.dark 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: var(--color-brightCyan); +} +.umap-is-dirty .icon-save { + display: inline-block; +} +.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); +} +.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 { + /* 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: var(--header-height); + 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-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: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 { + display: none; + 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 .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 b76020b1a..43042a540 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 867048160..00f6dc672 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 85400b492..352122700 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 9e99a16ab..85fcbc168 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 19fa5f226..2af171a67 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/help.js b/umap/static/umap/js/modules/help.js index 0530dd1df..b6c2a88e4 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 5d2ece729..17552afd2 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 9c4c5bfa8..607ee22ef 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(` - - ${this.options.user.name} - - `) - 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 ef8ab0fab..496d35377 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 000000000..87e70e8e6 --- /dev/null +++ b/umap/static/umap/js/modules/ui/bar.js @@ -0,0 +1,193 @@ +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.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 d65ff0772..b06968276 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 span', (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 b9fe128e4..2e12083e4 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 777c866cc..540614928 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 480127cd5..7faf62105 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/static/umap/vars.css b/umap/static/umap/vars.css index 181a5ed7b..4c8f5eb3a 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; diff --git a/umap/templates/umap/css.html b/umap/templates/umap/css.html index ba4b0cf8b..74b949935 100644 --- a/umap/templates/umap/css.html +++ b/umap/templates/umap/css.html @@ -35,4 +35,5 @@ +