Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 8 additions & 16 deletions map/static/map/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,25 +395,18 @@ var EditorTool = (function() {

// ---- Toolbar (edit count / undo / redo) ---------------------------------------

// The top bar centers the search box between its two flex side sections
// (equal-width while there's room, but content-floored on narrow
// windows), so "centered" for top notifications means the SEARCH BOX's
// center, not the viewport's -- align to it directly.
function alignToolbar() {
var searchBox = document.getElementById("searchBox");
if (!searchBox || !toolbar) {
return;
}
var box = searchBox.getBoundingClientRect();
if (box.width > 0) {
toolbar.style.left = (box.left + box.width / 2) + "px";
}
}
// No JS centring here any more. This used to measure the search box and set
// an inline `left`, because the top bar was a row of floating islands over
// the map and "centred" could not be expressed in CSS. The app bar and the
// map's overlay layer are now both window-width, so plain CSS centring lines
// the toolbar up with the search field for free -- and the inline `left` it
// used to write was in VIEWPORT coordinates while the element's containing
// block is the overlay layer, which is exactly what threw the toolbar off
// centre once that layer stopped being the whole viewport.

function updateToolbar() {
var any = actions.length > 0 || redoStack.length > 0;
toolbar.style.display = any ? "flex" : "none";
alignToolbar();
editCountEl.textContent = actions.length + " edit" + (actions.length === 1 ? "" : "s");
undoBtn.disabled = applyInFlight || actions.length === 0;
redoBtn.disabled = applyInFlight || redoStack.length === 0;
Expand Down Expand Up @@ -1193,7 +1186,6 @@ var EditorTool = (function() {
e.returnValue = "";
}
});
window.addEventListener("resize", alignToolbar);
});

return {
Expand Down
76 changes: 39 additions & 37 deletions map/static/map/map.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
--dock-left-inset: var(--dock-left-width);
--dock-right-inset: 0px;


/* One shared feel for anything that moves, and the lifted shadow the
genuinely floating things (hints, popovers, tooltip) share. Docked
surfaces get a border instead -- they are attached, not hovering. */
Expand Down Expand Up @@ -386,22 +387,40 @@ body.dockResizing {
the part of it you can actually SEE, so this layer -- unlike #map -- insets
by whatever the docks are covering. It is the only thing that needs to know
the dock widths, and it costs the map nothing to tell it. */
/* The layer the still-floating things live in: the hint bars, the selection
action bar, the active-filter banner.

It spans the FULL window, exactly like #map, and never changes size for any
reason but a window resize. That is what makes its centre the window's
centre -- the same thing the app bar centres its search field on -- so a
hint always lines up with the search field above it, whatever the docks are
doing. Two earlier versions insetted it by the docks so hints would centre
on the *visible* map: that put them 109px out of line with the search field,
and, once a dock made the layer narrower than a wide bar, the auto margins
collapsed and the bar drifted further still.

Trade-off: a bar wider than the gap between the docks runs underneath one.
The docks paint above this layer (z-index 20 vs 15), so it is clipped rather
than overlapping, and at any normal window width there is room to spare.

Children are centred by `justify-self`, NOT by `left`/`right`/margins. That
is not a style preference: an absolutely positioned child that is
display:none while this layer's width changes can come back with a stale
USED value for a percentage or resolved `left` -- measured as declared
`left: 0px` but used `left: 800px`, which put the editor toolbar a quarter
of the window off-centre until something forced it to reflow. Grid alignment
never computes a `left` at all, so there is nothing to go stale. Children
still place themselves vertically with top/bottom. */
#mapOverlays {
position: fixed;
top: var(--appbar-height);
left: var(--dock-left-inset);
right: var(--dock-right-inset);
bottom: 0;
inset: var(--appbar-height) 0 0 0;
display: grid;
pointer-events: none;
z-index: 15;
transition: left 0.12s var(--panel-ease), right 0.12s var(--panel-ease);
}

@media (prefers-reduced-motion: reduce) {
#mapOverlays { transition: none; }
}

#mapOverlays > * {
justify-self: center;
pointer-events: auto;
}

Expand Down Expand Up @@ -1344,8 +1363,6 @@ button:focus-visible, select:focus-visible, input[type="checkbox"]:focus-visible
#selectionPanel {
position: absolute;
bottom: 22px;
left: 50%;
transform: translateX(-50%);
z-index: 600;
display: flex;
align-items: center;
Expand Down Expand Up @@ -1375,8 +1392,6 @@ button:focus-visible, select:focus-visible, input[type="checkbox"]:focus-visible
#editorToolbar {
position: absolute;
top: 58px;
left: 50%;
transform: translateX(-50%);
z-index: 600;
display: flex;
align-items: center;
Expand All @@ -1401,8 +1416,6 @@ button:focus-visible, select:focus-visible, input[type="checkbox"]:focus-visible
#editorHint {
position: absolute;
bottom: 64px;
left: 50%;
transform: translateX(-50%);
z-index: 1401;
padding: 7px 14px;
background: var(--surface-2);
Expand Down Expand Up @@ -1503,8 +1516,6 @@ button:focus-visible, select:focus-visible, input[type="checkbox"]:focus-visible
#activeFilterBanner {
position: absolute;
top: 68px; /* Just below the floating search pill. */
left: 50%;
transform: translateX(-50%);
z-index: 500;
display: flex;
align-items: center;
Expand Down Expand Up @@ -2605,8 +2616,6 @@ button:focus-visible, select:focus-visible, input[type="checkbox"]:focus-visible
#networkHint {
position: absolute;
bottom: 22px;
left: 50%;
transform: translateX(-50%);
z-index: 600;
padding: 8px 16px;
background: var(--surface-2);
Expand Down Expand Up @@ -2958,8 +2967,13 @@ button:focus-visible, select:focus-visible, input[type="checkbox"]:focus-visible
}

/* Below this the pills lose their labels -- the icons are unambiguous next to
each other, and the search field is worth more than the words. */
@media (max-width: 980px) {
each other, and the search field is worth more than the words.
The threshold is set by geometry, not taste: the app bar centres its search
field by giving both side sections an equal share of the leftover space, and
that only holds while each side's content FITS its share. With labels the
right-hand cluster needs ~260px, and it stops fitting at about this width --
below which the search field would start drifting off-centre. */
@media (max-width: 1150px) {
#mainSearchWrap {
flex: 1 1 auto;
}
Expand Down Expand Up @@ -2991,14 +3005,6 @@ button:focus-visible, select:focus-visible, input[type="checkbox"]:focus-visible
width: min(var(--dock-right-width), 78vw);
}

/* At this size a dock covers most of the map, so insetting the hints inside
what is left would squeeze them to nothing -- let them use the whole
window and sit under the dock instead. */
#mapOverlays {
left: 0;
right: 0;
}

/* Dragging a width is a pointer-and-space affordance; neither applies here. */
.dockResizeHandle {
display: none;
Expand All @@ -3017,18 +3023,14 @@ button:focus-visible, select:focus-visible, input[type="checkbox"]:focus-visible
}
}

/* Leaflet's controls live inside #map, which now runs under the tool dock --
push the right-hand ones (zoom, attribution) clear of it. */
/* Leaflet's controls live inside #map, which runs under the docks -- push them
clear. Each side insets by its own dock (unlike #mapOverlays above): these
hug an edge rather than centring on anything, so there is nothing to keep
symmetric. Untransitioned for the same reason as #mapOverlays. */
.leaflet-right {
right: var(--dock-right-inset);
transition: right 0.12s var(--panel-ease);
}

.leaflet-left {
left: var(--dock-left-inset);
transition: left 0.12s var(--panel-ease);
}

@media (prefers-reduced-motion: reduce) {
.leaflet-right, .leaflet-left { transition: none; }
}
62 changes: 62 additions & 0 deletions tools/ui_behaviour.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,68 @@ def holdsStill(label, action):
check("map never moves: dragging the dock's width", not bad,
"%d/%d frames" % (len(bad), len(rows)))

# ---- Everything centred agrees on one centre ------------------------------
#
# The app bar centres its search field on the window; the floating hints,
# the selection bar and the dialogs must line up with it. They have drifted
# apart twice: once when the map's overlay layer insetted by each dock
# separately (109px out), and once from a leftover JS hack that measured the
# search box and wrote an inline `left` in VIEWPORT coordinates onto an
# element whose containing block was the overlay layer (a quarter of the
# window out). Both were invisible to every other check.
page.evaluate("""() => {
const show = (i, how) => { const e = document.getElementById(i); if (e) e.style.display = how; };
show('activeFilterBanner', 'flex'); show('selectionPanel', 'flex');
show('editorToolbar', 'flex'); show('editorHint', 'block'); show('networkHint', 'block');
}""")
page.wait_for_timeout(400)
CENTRES = """() => {
const c = el => { if (!el) return null; const r = el.getBoundingClientRect();
return r.width ? Math.round(r.left + r.width / 2) : null; };
const id = s => document.getElementById(s);
return { window: Math.round(window.innerWidth / 2),
searchPill: c(id('searchBox')),
filterBanner: c(id('activeFilterBanner')),
selectionBar: c(id('selectionPanel')),
editorToolbar: c(id('editorToolbar')),
editorHint: c(id('editorHint')),
networkHint: c(id('networkHint')) };
}"""

def centred(label):
c = page.evaluate(CENTRES)
mid = c.pop("window")
off = ["%s %+d" % (k, v - mid) for k, v in c.items()
if v is not None and abs(v - mid) > 1]
check("centred on the window: " + label, not off, ", ".join(off))

centred("dock open")
page.evaluate("NetworkTool.open()")
page.wait_for_timeout(700)
centred("tool dock open")
page.evaluate("NetworkTool.close()")
page.evaluate("document.getElementById('menuButton').click()")
page.wait_for_timeout(700)
centred("dock hidden")
page.evaluate("document.getElementById('menuButton').click()")
page.wait_for_timeout(500)
# The altitude rail's 4px track has its own centring inside the 64px rail.
track = page.evaluate("""() => {
const rail = document.getElementById('altitudePanel').getBoundingClientRect();
const bg = document.querySelector('.altitudeTrackBg').getBoundingClientRect();
return [Math.round(bg.width), Math.round((bg.left + bg.width/2) - (rail.left + rail.width/2))];
}""")
check("altitude track is 4px and centred in the rail",
track[0] == 4 and abs(track[1]) <= 1, track)

# These were forced visible; leave them hidden or they sit over the map and
# swallow the clicks the checks below need.
page.evaluate("""() => {
['activeFilterBanner','selectionPanel','editorToolbar','editorHint','networkHint']
.forEach(i => { const e = document.getElementById(i); if (e) e.style.display = 'none'; });
}""")
page.wait_for_timeout(300)

# ---- Search --------------------------------------------------------------
page.click("#mainSearchInput")
page.fill("#mainSearchInput", "constructor")
Expand Down
Loading