From 3e3ce0b8f57b272b9126a7719ab836c45f00dc5a Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Thu, 23 Jan 2025 18:08:04 +0100 Subject: [PATCH 1/4] fix: better computation of tooltip bottom position Co-authored-by: David Larlet --- umap/static/umap/js/modules/ui/base.js | 3 ++- umap/static/umap/js/modules/ui/tooltip.js | 25 ++++++++++++++--------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/umap/static/umap/js/modules/ui/base.js b/umap/static/umap/js/modules/ui/base.js index 725e3c9b..c2a91024 100644 --- a/umap/static/umap/js/modules/ui/base.js +++ b/umap/static/umap/js/modules/ui/base.js @@ -33,8 +33,9 @@ export class Positioned { anchorBottom(el) { this.container.className = 'tooltip-bottom' const coords = this.getPosition(el) + const selfCoords = this.getPosition(this.container) this.setPosition({ - left: coords.left, + left: coords.left + coords.width / 2 - selfCoords.width / 2, top: coords.bottom + 11, }) } diff --git a/umap/static/umap/js/modules/ui/tooltip.js b/umap/static/umap/js/modules/ui/tooltip.js index 88a859f9..579afc09 100644 --- a/umap/static/umap/js/modules/ui/tooltip.js +++ b/umap/static/umap/js/modules/ui/tooltip.js @@ -1,24 +1,29 @@ -import { DomEvent, DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js' +import { DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js' import { translate } from '../i18n.js' import { Positioned } from './base.js' +import * as Utils from '../utils.js' export default class Tooltip extends Positioned { constructor(parent) { super() this.parent = parent - this.container = DomUtil.create('div', 'with-transition', this.parent) - this.container.id = 'umap-tooltip-container' + this.container = Utils.loadTemplate( + '
' + ) + this.parent.appendChild(this.container) DomEvent.disableClickPropagation(this.container) - DomEvent.on(this.container, 'contextmenu', DomEvent.stopPropagation) // Do not activate our custom context menu. - DomEvent.on(this.container, 'wheel', DomEvent.stopPropagation) - DomEvent.on(this.container, 'MozMousePixelScroll', DomEvent.stopPropagation) + this.container.addEventListener('contextmenu', (event) => event.stopPropagation()) // Do not activate our custom context menu. + this.container.addEventListener('wheel', (event) => event.stopPropagation()) + this.container.addEventListener('MozMousePixelScroll', (event) => + event.stopPropagation() + ) } open(opts) { const showIt = () => { + this.container.innerHTML = Utils.escapeHTML(opts.content) + this.parent.classList.add('umap-tooltip') this.openAt(opts) - L.DomUtil.addClass(this.parent, 'umap-tooltip') - this.container.innerHTML = U.Utils.escapeHTML(opts.content) } this.TOOLTIP_ID = window.setTimeout(L.bind(showIt, this), opts.delay || 0) const id = this.TOOLTIP_ID @@ -26,7 +31,7 @@ export default class Tooltip extends Positioned { this.close(id) } if (opts.anchor) { - L.DomEvent.once(opts.anchor, 'mouseout', closeIt) + opts.anchor.addEventListener('mouseout', closeIt, { once: true }) } if (opts.duration !== Number.POSITIVE_INFINITY) { window.setTimeout(closeIt, opts.duration || 3000) @@ -41,6 +46,6 @@ export default class Tooltip extends Positioned { this.container.className = '' this.container.innerHTML = '' this.setPosition({}) - L.DomUtil.removeClass(this.parent, 'umap-tooltip') + this.parent.classList.remove('umap-tooltip') } } From 62be6450bb74aaac10023427f9b158e2ee609458 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Thu, 23 Jan 2025 18:26:48 +0100 Subject: [PATCH 2/4] feat(sync): show a very minimal list of connected peers cf #2267 Co-authored-by: David Larlet --- umap/static/umap/js/modules/sync/engine.js | 5 ++--- umap/static/umap/js/modules/ui/bar.js | 14 +++++++++----- umap/static/umap/js/modules/ui/tooltip.js | 6 +++++- umap/static/umap/js/modules/umap.js | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/umap/static/umap/js/modules/sync/engine.js b/umap/static/umap/js/modules/sync/engine.js index 05994544..62106352 100644 --- a/umap/static/umap/js/modules/sync/engine.js +++ b/umap/static/umap/js/modules/sync/engine.js @@ -146,9 +146,8 @@ export class SyncEngine { updater.applyMessage(operation) } - getNumberOfConnectedPeers() { - if (this.peers) return Object.keys(this.peers).length - return 0 + getPeers() { + return this.peers || {} } /** diff --git a/umap/static/umap/js/modules/ui/bar.js b/umap/static/umap/js/modules/ui/bar.js index 80202616..b929c8e5 100644 --- a/umap/static/umap/js/modules/ui/bar.js +++ b/umap/static/umap/js/modules/ui/bar.js @@ -2,6 +2,7 @@ import { DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js' import { translate } from '../i18n.js' import { WithTemplate } from '../utils.js' import ContextMenu from './contextmenu.js' +import * as Utils from '../utils.js' const TOP_BAR_TEMPLATE = `
@@ -96,13 +97,16 @@ export class TopBar extends WithTemplate { } }) - const connectedPeers = this._umap.sync.getNumberOfConnectedPeers() this.elements.peers.addEventListener('mouseover', () => { - if (!connectedPeers) return + const connectedPeers = this._umap.sync.getPeers() + if (!Object.keys(connectedPeers).length) return + const ul = Utils.loadTemplate( + `
    ${Object.entries(connectedPeers) + .map(([id, name]) => `
  • ${name || translate('Anonymous')}
  • `) + .join('')}
` + ) this._umap.tooltip.open({ - content: translate('{connectedPeers} peer(s) currently connected to this map', { - connectedPeers: connectedPeers, - }), + content: ul, anchor: this.elements.peers, position: 'bottom', delay: 500, diff --git a/umap/static/umap/js/modules/ui/tooltip.js b/umap/static/umap/js/modules/ui/tooltip.js index 579afc09..277eea73 100644 --- a/umap/static/umap/js/modules/ui/tooltip.js +++ b/umap/static/umap/js/modules/ui/tooltip.js @@ -21,7 +21,11 @@ export default class Tooltip extends Positioned { open(opts) { const showIt = () => { - this.container.innerHTML = Utils.escapeHTML(opts.content) + if (opts.content.nodeType === 1) { + this.container.appendChild(opts.content) + } else { + this.container.innerHTML = Utils.escapeHTML(opts.content) + } this.parent.classList.add('umap-tooltip') this.openAt(opts) } diff --git a/umap/static/umap/js/modules/umap.js b/umap/static/umap/js/modules/umap.js index ab4d94fe..db75d79f 100644 --- a/umap/static/umap/js/modules/umap.js +++ b/umap/static/umap/js/modules/umap.js @@ -1351,7 +1351,7 @@ export default class Umap extends ServerStored { numberOfConnectedPeers: () => { Utils.eachElement('.connected-peers span', (el) => { if (this.sync.websocketConnected) { - el.textContent = this.sync.getNumberOfConnectedPeers() + el.textContent = Object.keys(this.sync.getPeers()).length } else { el.textContent = translate('Disconnected') } From 70f87d863606576ee4e363cf24f6ddf8d1b3242b Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 27 Jan 2025 18:33:38 +0100 Subject: [PATCH 3/4] feat: add "accent" mode for tooltip, and use it for peers list Co-authored-by: David Larlet --- umap/static/umap/css/tooltip.css | 42 +++++++++------------ umap/static/umap/js/modules/ui/bar.js | 2 + umap/static/umap/js/modules/ui/base.js | 28 +++----------- umap/static/umap/js/modules/ui/tooltip.js | 7 ++-- umap/static/umap/vars.css | 1 + umap/tests/integration/test_draw_polygon.py | 6 +-- 6 files changed, 32 insertions(+), 54 deletions(-) diff --git a/umap/static/umap/css/tooltip.css b/umap/static/umap/css/tooltip.css index 6af26178..0c40f79f 100644 --- a/umap/static/umap/css/tooltip.css +++ b/umap/static/umap/css/tooltip.css @@ -1,22 +1,25 @@ -#umap-tooltip-container { - line-height: 20px; +.umap-tooltip-container { padding: 5px 10px; width: auto; + min-width: 100px; + max-width: 300px; position: absolute; box-shadow: var(--block-shadow); display: none; - background-color: rgba(40, 40, 40, 0.9); color: #eeeeec; - font-size: 0.8em; border-radius: 2px; z-index: var(--zindex-tooltip); font-weight: normal; - max-width: 300px; + --tooltip-color: var(--color-darkGray); + background-color: var(--tooltip-color); } -.umap-tooltip #umap-tooltip-container { +.tooltip-accent { + --tooltip-color: var(--color-lightCyan); +} +.umap-tooltip .umap-tooltip-container { display: block; } -#umap-tooltip-container.tooltip-top:after { +.umap-tooltip-container.tooltip-top:after { top: 100%; left: calc(50% - 11px); border: solid transparent; @@ -25,11 +28,11 @@ width: 0; position: absolute; pointer-events: none; - border-top-color: rgba(30, 30, 30, 0.8); + border-top-color: var(--tooltip-color); border-width: 11px; margin-left: calc(-50% + 21px); } -#umap-tooltip-container.tooltip-bottom:before { +.umap-tooltip-container.tooltip-bottom:before { top: -22px; left: calc(50% - 11px); border: solid transparent; @@ -38,22 +41,13 @@ width: 0; position: absolute; pointer-events: none; - border-top-color: rgba(30, 30, 30, 0.7); + border-top-color: var(--tooltip-color); border-width: 11px; transform: rotate(180deg); } -#umap-tooltip-container.tooltip.tooltip-left:after { - left: 100%; - top: 50%; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; - border-color: rgba(136, 183, 213, 0); - border-left-color: #333; - border-width: 11px; - margin-top: -10px; +.tooltip-accent li { + background-color: var(--color-light); + color: var(--color-dark); + padding: var(--small-box-padding); + margin-bottom: var(--small-box-padding); } - diff --git a/umap/static/umap/js/modules/ui/bar.js b/umap/static/umap/js/modules/ui/bar.js index b929c8e5..8d6bca66 100644 --- a/umap/static/umap/js/modules/ui/bar.js +++ b/umap/static/umap/js/modules/ui/bar.js @@ -102,6 +102,7 @@ export class TopBar extends WithTemplate { if (!Object.keys(connectedPeers).length) return const ul = Utils.loadTemplate( `
    ${Object.entries(connectedPeers) + .sort((el) => el !== this._umap.user?.name) .map(([id, name]) => `
  • ${name || translate('Anonymous')}
  • `) .join('')}
` ) @@ -111,6 +112,7 @@ export class TopBar extends WithTemplate { position: 'bottom', delay: 500, duration: 5000, + accent: true, }) }) diff --git a/umap/static/umap/js/modules/ui/base.js b/umap/static/umap/js/modules/ui/base.js index c2a91024..db4aff9a 100644 --- a/umap/static/umap/js/modules/ui/base.js +++ b/umap/static/umap/js/modules/ui/base.js @@ -2,27 +2,18 @@ export class Positioned { openAt({ anchor, position }) { if (anchor && position === 'top') { this.anchorTop(anchor) - } else if (anchor && position === 'left') { - this.anchorLeft(anchor) } else if (anchor && position === 'bottom') { this.anchorBottom(anchor) - } else { - this.anchorAbsolute() } } - anchorAbsolute() { - this.container.className = '' - const left = - this.parent.offsetLeft + - this.parent.clientWidth / 2 - - this.container.clientWidth / 2 - const top = this.parent.offsetTop + 75 - this.setPosition({ top: top, left: left }) + toggleClassPosition(position) { + this.container.classList.toggle('tooltip-bottom', position === 'bottom') + this.container.classList.toggle('tooltip-top', position === 'top') } anchorTop(el) { - this.container.className = 'tooltip-top' + this.toggleClassPosition('top') const coords = this.getPosition(el) this.setPosition({ left: coords.left - 10, @@ -31,7 +22,7 @@ export class Positioned { } anchorBottom(el) { - this.container.className = 'tooltip-bottom' + this.toggleClassPosition('bottom') const coords = this.getPosition(el) const selfCoords = this.getPosition(this.container) this.setPosition({ @@ -40,15 +31,6 @@ export class Positioned { }) } - anchorLeft(el) { - this.container.className = 'tooltip-left' - const coords = this.getPosition(el) - this.setPosition({ - top: coords.top, - right: document.documentElement.offsetWidth - coords.left + 11, - }) - } - getPosition(el) { return el.getBoundingClientRect() } diff --git a/umap/static/umap/js/modules/ui/tooltip.js b/umap/static/umap/js/modules/ui/tooltip.js index 277eea73..2c4362e0 100644 --- a/umap/static/umap/js/modules/ui/tooltip.js +++ b/umap/static/umap/js/modules/ui/tooltip.js @@ -7,9 +7,7 @@ export default class Tooltip extends Positioned { constructor(parent) { super() this.parent = parent - this.container = Utils.loadTemplate( - '
' - ) + this.container = Utils.loadTemplate('
') this.parent.appendChild(this.container) DomEvent.disableClickPropagation(this.container) this.container.addEventListener('contextmenu', (event) => event.stopPropagation()) // Do not activate our custom context menu. @@ -20,6 +18,7 @@ export default class Tooltip extends Positioned { } open(opts) { + this.container.classList.toggle('tooltip-accent', Boolean(opts.accent)) const showIt = () => { if (opts.content.nodeType === 1) { this.container.appendChild(opts.content) @@ -47,7 +46,7 @@ export default class Tooltip extends Positioned { // in the meantime. Eg. after a mouseout from the anchor. window.clearTimeout(id) if (id && id !== this.TOOLTIP_ID) return - this.container.className = '' + this.toggleClassPosition() this.container.innerHTML = '' this.setPosition({}) this.parent.classList.remove('umap-tooltip') diff --git a/umap/static/umap/vars.css b/umap/static/umap/vars.css index 4d755937..86a11654 100644 --- a/umap/static/umap/vars.css +++ b/umap/static/umap/vars.css @@ -38,6 +38,7 @@ --control-size: 36px; --border-radius: 4px; --box-padding: 20px; + --small-box-padding: 4px; --box-margin: 14px; --text-margin: 7px; diff --git a/umap/tests/integration/test_draw_polygon.py b/umap/tests/integration/test_draw_polygon.py index b58f91f7..0a5b05b1 100644 --- a/umap/tests/integration/test_draw_polygon.py +++ b/umap/tests/integration/test_draw_polygon.py @@ -484,12 +484,12 @@ def test_vertexmarker_not_shown_if_too_many(live_server, map, page, settings): page.get_by_role("button", name="Import data", exact=True).click() page.locator("path").click() page.get_by_role("link", name="Toggle edit mode (⇧+Click)").click() - expect(page.locator("#umap-tooltip-container")).to_contain_text( + expect(page.locator(".umap-tooltip-container")).to_contain_text( "Please zoom in to edit the geometry" ) expect(page.locator(".leaflet-vertex-icon")).to_be_hidden() page.get_by_label("Zoom in").click() - expect(page.locator("#umap-tooltip-container")).to_contain_text( + expect(page.locator(".umap-tooltip-container")).to_contain_text( "Please zoom in to edit the geometry" ) page.get_by_label("Zoom in").click() @@ -497,6 +497,6 @@ def test_vertexmarker_not_shown_if_too_many(live_server, map, page, settings): page.get_by_label("Zoom out").click() page.wait_for_timeout(500) expect(page.locator(".leaflet-vertex-icon")).to_be_hidden() - expect(page.locator("#umap-tooltip-container")).to_contain_text( + expect(page.locator(".umap-tooltip-container")).to_contain_text( "Please zoom in to edit the geometry" ) From 1eb1f320db1b86c54729e310089bc27cb79ac9be Mon Sep 17 00:00:00 2001 From: David Larlet Date: Thu, 30 Jan 2025 14:15:56 -0500 Subject: [PATCH 4/4] chore: iterate on tooltips design --- umap/static/umap/css/bar.css | 5 +++++ umap/static/umap/css/tooltip.css | 22 +++++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/umap/static/umap/css/bar.css b/umap/static/umap/css/bar.css index 510ab3c4..7ff22c1f 100644 --- a/umap/static/umap/css/bar.css +++ b/umap/static/umap/css/bar.css @@ -32,6 +32,11 @@ background-color: var(--color-lightCyan); color: var(--color-dark); } +.dark [type=button].connected-peers:hover +{ + text-decoration: none; + border: 0.5px solid var(--color-brightCyan); +} .dark .off.connected-peers { background-color: var(--color-lightGray); color: var(--color-darkGray); diff --git a/umap/static/umap/css/tooltip.css b/umap/static/umap/css/tooltip.css index 0c40f79f..0a5e6d4d 100644 --- a/umap/static/umap/css/tooltip.css +++ b/umap/static/umap/css/tooltip.css @@ -7,11 +7,12 @@ box-shadow: var(--block-shadow); display: none; color: #eeeeec; - border-radius: 2px; + border-radius: var(--border-radius); z-index: var(--zindex-tooltip); font-weight: normal; --tooltip-color: var(--color-darkGray); background-color: var(--tooltip-color); + --arrow-size: 8px; } .tooltip-accent { --tooltip-color: var(--color-lightCyan); @@ -21,7 +22,7 @@ } .umap-tooltip-container.tooltip-top:after { top: 100%; - left: calc(50% - 11px); + left: calc(50% - var(--arrow-size)); border: solid transparent; content: " "; height: 0; @@ -29,12 +30,12 @@ position: absolute; pointer-events: none; border-top-color: var(--tooltip-color); - border-width: 11px; - margin-left: calc(-50% + 21px); + border-width: var(--arrow-size); + margin-left: calc(-50% + 2 * var(--arrow-size)); } .umap-tooltip-container.tooltip-bottom:before { - top: -22px; - left: calc(50% - 11px); + top: calc(var(--arrow-size) * -2); + left: calc(50% - var(--arrow-size)); border: solid transparent; content: " "; height: 0; @@ -42,12 +43,19 @@ position: absolute; pointer-events: none; border-top-color: var(--tooltip-color); - border-width: 11px; + border-width: var(--arrow-size); transform: rotate(180deg); } +.tooltip-accent ul { + padding-top: var(--small-box-padding); + padding-bottom: var(--small-box-padding); +} .tooltip-accent li { background-color: var(--color-light); color: var(--color-dark); padding: var(--small-box-padding); margin-bottom: var(--small-box-padding); } +.tooltip-accent li:last-of-type { + margin-bottom: 0; +}