diff --git a/umap/static/umap/css/bar.css b/umap/static/umap/css/bar.css index 7f90ad64..c95664dc 100644 --- a/umap/static/umap/css/bar.css +++ b/umap/static/umap/css/bar.css @@ -29,6 +29,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 6af26178..0a5e6d4d 100644 --- a/umap/static/umap/css/tooltip.css +++ b/umap/static/umap/css/tooltip.css @@ -1,59 +1,61 @@ -#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; + border-radius: var(--border-radius); z-index: var(--zindex-tooltip); font-weight: normal; - max-width: 300px; + --tooltip-color: var(--color-darkGray); + background-color: var(--tooltip-color); + --arrow-size: 8px; } -.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); + left: calc(50% - var(--arrow-size)); border: solid transparent; content: " "; height: 0; width: 0; position: absolute; pointer-events: none; - border-top-color: rgba(30, 30, 30, 0.8); - border-width: 11px; - margin-left: calc(-50% + 21px); + border-top-color: var(--tooltip-color); + 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); +.umap-tooltip-container.tooltip-bottom:before { + top: calc(var(--arrow-size) * -2); + left: calc(50% - var(--arrow-size)); border: solid transparent; content: " "; height: 0; width: 0; position: absolute; pointer-events: none; - border-top-color: rgba(30, 30, 30, 0.7); - border-width: 11px; + border-top-color: var(--tooltip-color); + border-width: var(--arrow-size); 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 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; } - 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 de446352..15e21b9c 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,17 +97,22 @@ 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( + `` + ) 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, 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 725e3c9b..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,23 +22,15 @@ 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({ - left: coords.left, + left: coords.left + coords.width / 2 - selfCoords.width / 2, top: coords.bottom + 11, }) } - 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 88a859f9..2c4362e0 100644 --- a/umap/static/umap/js/modules/ui/tooltip.js +++ b/umap/static/umap/js/modules/ui/tooltip.js @@ -1,24 +1,32 @@ -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) { + this.container.classList.toggle('tooltip-accent', Boolean(opts.accent)) const showIt = () => { + 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) - 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 +34,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) @@ -38,9 +46,9 @@ 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({}) - L.DomUtil.removeClass(this.parent, 'umap-tooltip') + this.parent.classList.remove('umap-tooltip') } } diff --git a/umap/static/umap/js/modules/umap.js b/umap/static/umap/js/modules/umap.js index abafa2f3..8594cd64 100644 --- a/umap/static/umap/js/modules/umap.js +++ b/umap/static/umap/js/modules/umap.js @@ -1357,7 +1357,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') } diff --git a/umap/static/umap/vars.css b/umap/static/umap/vars.css index c294c0f5..effda252 100644 --- a/umap/static/umap/vars.css +++ b/umap/static/umap/vars.css @@ -39,6 +39,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" )