Drawing toolbar: docked left rail, Bootstrap Icons throughout
Field officers reported three problems with the drawing palette: it had no background and disappeared over light imagery, it sat centred at the top across the area being drawn, and its icons did not match one another. All three had distinct causes. Layout. The bar is now a two-column rail docked to the left edge on a solid panel, with its controls re-ordered into labelled Draw / Edit / Shape / File bands by MapView._buildToolbarRail(). Two columns because a single file of thirteen buttons plus labels runs past the bottom of a laptop map view; the rail measures 75 x 363 px. Adding ol-ext's own `ol-left` class also makes option bars (Select's Delete/Info, Split's sub-tools) fly out to the right rather than dropping downwards. Icons. ol-ext ships no icon font or sprite sheet — it draws each built-in tool as CSS ::before/::after shapes from borders and box-shadows, which is why our own Bootstrap-Icon tools looked foreign next to them. The generated shapes are suppressed and every button gets an icon from a single TOOLBAR_ICONS map. The glyph is injected as a <span>, not an <i>, because ol-ext styles `.ol-transform > button i::before` and would otherwise out-specify Bootstrap's `.bi::before` and blank the icon. Phones. The rail becomes a horizontally scrolling strip above the dock, which retires the two-row flex-break workaround and its `.ol-editbar-break` element. Touch targets stay at 36 px, and option bars open upward. Two things worth recording for anyone changing this later. The rail must not set `overflow` on either axis: the other axis then computes to `auto` too and clips the option bars that fly out past its right edge. And `display` in the phone media query must stay free of `!important`, since ol-ext shows and hides the whole bar with an inline style. Also picks up ol-offset, an EditBar default that was never explicitly enabled and had been sitting in the bar unlabelled; it is grouped under Shape. Verified in a headless browser at desktop and phone widths, in light and dark mode: icons, grouping, active-tool tint, option-bar placement, and that the bar still hides when Draw mode is off. Service Worker v14 -> v15. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f8e4a3a19d
commit
e9bb4d81c9
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
dist/assets/index-9k1yuCxX.js.map
vendored
Normal file
1
dist/assets/index-9k1yuCxX.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/index-D9GGSG-a.js.map
vendored
1
dist/assets/index-D9GGSG-a.js.map
vendored
File diff suppressed because one or more lines are too long
327
dist/index.html
vendored
327
dist/index.html
vendored
@ -112,6 +112,8 @@
|
||||
--destructive-foreground: #fff;
|
||||
--border: #1e1a4b1f;
|
||||
--ring: var(--brand-blue-strong);
|
||||
/* Floating map panels (drawing rail, its option bars) */
|
||||
--bar-bg: rgba(255, 255, 255, 0.94);
|
||||
|
||||
/* Typography */
|
||||
--font-display: "Bebas Neue", sans-serif;
|
||||
@ -1516,17 +1518,279 @@
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
Small-screen drawing toolbar: the EditBar is a single nowrap row
|
||||
by default and overflows narrow phones. Below the sm breakpoint we
|
||||
let it wrap and push the action group (undo / redo / save / snap)
|
||||
onto its own second row so every tool stays reachable.
|
||||
/* ================================================================
|
||||
Drawing toolbar — docked left rail
|
||||
----------------------------------------------------------------
|
||||
Field officers reported three problems with the palette: it had no
|
||||
background and vanished over light imagery, it sat centred at the top
|
||||
across the area being drawn, and its icons did not match each other.
|
||||
|
||||
The bar is now a two-column rail docked to the left edge, on a solid
|
||||
panel, with its controls banded into labelled groups by
|
||||
MapView._buildToolbarRail(). Two columns rather than one because a
|
||||
single file of 13 buttons plus labels runs past the bottom of a laptop
|
||||
map view.
|
||||
|
||||
`ol-left` is ol-ext's own class: it docks the bar left AND makes option
|
||||
bars (Select's Delete/Info, Split's sub-tools) fly out to the right
|
||||
instead of dropping downwards. We keep that behaviour and override only
|
||||
the layout.
|
||||
================================================================ */
|
||||
.ol-editbar.ol-editbar-rail {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 30px);
|
||||
gap: 3px;
|
||||
align-content: start;
|
||||
padding: 5px;
|
||||
background: var(--bar-bg);
|
||||
border: 1px solid rgba(30, 26, 75, 0.14);
|
||||
border-radius: 11px;
|
||||
box-shadow:
|
||||
0 6px 24px -6px rgba(30, 26, 75, 0.3),
|
||||
0 1px 3px rgba(30, 26, 75, 0.14);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
/* Deliberately NOT scrollable. The rail measures 75 x 363 px, which fits
|
||||
any map viewport we support, and setting overflow on either axis makes
|
||||
the other compute to `auto` as well — which clips the option bars that
|
||||
fly out past the right edge. */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Direct children only. A descendant selector here would also match the
|
||||
nested option bars — which are themselves .ol-control — and override the
|
||||
`display: none` that keeps them closed until their tool is active. */
|
||||
.ol-editbar.ol-editbar-rail.ol-left > .ol-control {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Group heading — spans both columns. */
|
||||
.ol-editbar-rail .ol-rail-label {
|
||||
grid-column: 1 / -1;
|
||||
font-size: 0.5rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted-foreground);
|
||||
text-align: center;
|
||||
padding: 4px 0 1px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.ol-editbar-rail .ol-rail-label:first-child {
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
/* The action group is one control holding four buttons; let it span the
|
||||
rail and lay its own buttons out on the same 2-column rhythm. */
|
||||
.ol-editbar-rail > .ol-editbar-actions {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.ol-editbar-rail > .ol-editbar-actions,
|
||||
.ol-editbar-rail > .ol-editbar-actions .ol-bar {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(2, 30px);
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
/* --- buttons ---------------------------------------------------- */
|
||||
.ol-editbar-rail .ol-control button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
background: transparent;
|
||||
color: var(--foreground);
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.13s ease, color 0.13s ease;
|
||||
}
|
||||
.ol-editbar-rail .ol-control button:hover {
|
||||
background: var(--muted);
|
||||
}
|
||||
/* The `.ol-left` is here for specificity, not layout: ol-ext paints the
|
||||
active tool with `.ol-control.ol-bar .ol-toggle.ol-active > button`
|
||||
(0,4,1) in its own brand cyan. Matching that and adding a class puts the
|
||||
LUSPA blue back. */
|
||||
.ol-editbar.ol-editbar-rail.ol-left .ol-control.ol-active > button,
|
||||
.ol-editbar.ol-editbar-rail.ol-left .ol-toggle.ol-active > button,
|
||||
.ol-editbar.ol-editbar-rail.ol-left .ol-toggle.ol-active > button:hover,
|
||||
.ol-editbar.ol-editbar-rail.ol-left .ol-control button.ol-active,
|
||||
.ol-editbar.ol-editbar-rail.ol-left .ol-snap-toggle.ol-active button {
|
||||
background: var(--primary);
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
.ol-editbar-rail .ol-control button:focus-visible {
|
||||
outline: 2px solid var(--ring);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.ol-editbar-rail .ol-control button i {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* --- suppress ol-ext's CSS-drawn icons --------------------------
|
||||
ol-ext has no icon font: each built-in tool is drawn with ::before and
|
||||
::after shapes built from borders and box-shadows. Those are switched
|
||||
off here so the Bootstrap Icon injected by _buildToolbarRail() is the
|
||||
only thing in the button. The `> button` shapes and the `> button i`
|
||||
shapes (Transform, Offset) both have to go.
|
||||
---------------------------------------------------------------- */
|
||||
/* Flex line-break used to start the toolbar's second row. Inert (removed
|
||||
from flow) on wider screens; activated inside the sm media query. */
|
||||
.ol-editbar-break {
|
||||
.ol-editbar-rail .ol-control > button::before,
|
||||
.ol-editbar-rail .ol-control > button::after {
|
||||
content: none !important;
|
||||
}
|
||||
/* The injected glyph is a <span>, so ol-ext's `> button i` rules for
|
||||
Transform and Offset cannot reach it — nothing to undo here, only the
|
||||
sizing ol-ext applies to any icon holder. */
|
||||
.ol-editbar-rail .ol-control > button > span.bi {
|
||||
position: static;
|
||||
width: auto;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
transform: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* --- option bars (Select ▸ Delete/Info, Split ▸ sub-tools) -------
|
||||
ol-ext anchors these to the control with `left: 100%`, which assumes a
|
||||
one-column rail — in two columns that lands the flyout on top of the
|
||||
neighbouring button. Making the controls static hands the containing
|
||||
block to the rail itself, so `left: 100%` clears the whole panel. The
|
||||
flyout then always appears at the same place (rail's right edge, centred)
|
||||
instead of jumping to whichever button was pressed.
|
||||
---------------------------------------------------------------- */
|
||||
.ol-editbar.ol-editbar-rail > .ol-control {
|
||||
position: static;
|
||||
}
|
||||
.ol-editbar-rail .ol-option-bar {
|
||||
top: 50% !important;
|
||||
bottom: auto !important;
|
||||
left: 100% !important;
|
||||
transform: translateY(-50%);
|
||||
background: var(--bar-bg);
|
||||
border-radius: 9px;
|
||||
box-shadow:
|
||||
0 4px 16px -4px rgba(30, 26, 75, 0.3),
|
||||
0 0 0 1px rgba(30, 26, 75, 0.14);
|
||||
padding: 4px;
|
||||
margin: 0 0 0 8px !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ol-editbar-rail .ol-option-bar::before {
|
||||
display: none;
|
||||
}
|
||||
.ol-editbar-rail .ol-option-bar .ol-control {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/* --- dark mode -------------------------------------------------- */
|
||||
.dark-mode {
|
||||
--bar-bg: rgba(28, 25, 64, 0.94);
|
||||
}
|
||||
.dark-mode .ol-editbar.ol-editbar-rail,
|
||||
.dark-mode .ol-editbar-rail .ol-option-bar {
|
||||
border-color: rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
.dark-mode .ol-editbar-rail .ol-control button {
|
||||
color: #eceaf7;
|
||||
}
|
||||
.dark-mode .ol-editbar-rail .ol-control button:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* --- fieldwork mode: larger touch targets ----------------------- */
|
||||
.fieldwork-mode .ol-editbar.ol-editbar-rail {
|
||||
grid-template-columns: repeat(2, 38px);
|
||||
}
|
||||
.fieldwork-mode .ol-editbar-rail > .ol-editbar-actions,
|
||||
.fieldwork-mode .ol-editbar-rail > .ol-editbar-actions .ol-bar {
|
||||
grid-template-columns: repeat(2, 38px);
|
||||
}
|
||||
.fieldwork-mode .ol-editbar-rail .ol-control button {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
Phones: the rail becomes a horizontally scrolling strip sitting
|
||||
above the bottom dock. Scrolling replaces the previous two-row
|
||||
wrap, so every tool stays reachable at full touch size.
|
||||
---------------------------------------------------------------- */
|
||||
@media (max-width: 576px) {
|
||||
/* `.ol-left` is repeated here for specificity: ol-ext's own
|
||||
`.ol-control.ol-bar.ol-left` sets top/transform, and without matching
|
||||
it the rail stays vertically centred and stretches to `bottom`. */
|
||||
.ol-editbar.ol-editbar-rail.ol-left {
|
||||
/* NOTE: display must NOT be !important — ol-ext toggles the bar with an
|
||||
inline display:none/'' (setVisible). An !important here would beat
|
||||
that and keep the toolbar visible even when Draw mode is off. */
|
||||
display: flex;
|
||||
grid-template-columns: none;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
top: auto;
|
||||
bottom: 12px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
transform: none;
|
||||
/* Horizontal scroll replaces the old two-row wrap. Option bars are
|
||||
re-anchored below to open upward, so clipping them is not a concern
|
||||
the way it is on desktop. */
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overscroll-behavior-x: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.ol-editbar.ol-editbar-rail.ol-left::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Labels become inline separators between bands rather than headings. */
|
||||
.ol-editbar-rail .ol-rail-label {
|
||||
grid-column: auto;
|
||||
padding: 0 6px 0 3px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
flex: none;
|
||||
}
|
||||
.ol-editbar-rail .ol-rail-label:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.ol-editbar-rail > .ol-editbar-actions,
|
||||
.ol-editbar-rail > .ol-editbar-actions .ol-bar {
|
||||
display: flex !important;
|
||||
grid-template-columns: none;
|
||||
flex: none;
|
||||
}
|
||||
.ol-editbar.ol-editbar-rail.ol-left .ol-control {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* Touch targets stay at the recommended minimum on a phone. */
|
||||
.ol-editbar-rail .ol-control button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
/* Option bars open upward here — there is no room to the right, and
|
||||
downward would land under the dock. */
|
||||
.ol-editbar.ol-editbar-rail .ol-option-bar {
|
||||
top: auto !important;
|
||||
bottom: 100% !important;
|
||||
left: 8px !important;
|
||||
transform: none;
|
||||
margin: 0 0 8px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
Iframe embed mode (see public/embed.php + src/embed-bridge.js).
|
||||
@ -1555,53 +1819,8 @@
|
||||
height: 100dvh;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.ol-editbar.ol-bar {
|
||||
/* NOTE: display must NOT be !important — ol-ext toggles the bar via an
|
||||
inline `display:none`/`''` (setVisible). A `!important` here would beat
|
||||
that inline style and keep the toolbar permanently visible even when
|
||||
Draw mode is off. Plain `display:flex` only applies when visible. */
|
||||
display: flex;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
row-gap: 4px;
|
||||
white-space: normal !important;
|
||||
max-width: calc(100vw - 12px);
|
||||
}
|
||||
|
||||
/* Full-width, zero-height break → forces everything after it (the
|
||||
action group + Split + Merge) onto a shared second row. */
|
||||
.ol-editbar.ol-bar > .ol-editbar-break {
|
||||
display: block;
|
||||
flex-basis: 100%;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Right-align the second row (action group + Split + Merge). The auto
|
||||
left-margin pushes them to the right end of the line, clearing the
|
||||
far-left zone where an active tool's option bar (e.g. Select's
|
||||
Delete/Info) drops down from row 1 — which otherwise overlaps them. */
|
||||
.ol-editbar.ol-bar > .ol-editbar-actions {
|
||||
/* !important: ol-ext's `.ol-control.ol-bar .ol-control { margin:0 }`
|
||||
has equal specificity and loads later, so it would otherwise cancel
|
||||
this auto-margin and the group would stay glued to the left. */
|
||||
margin-left: auto !important;
|
||||
}
|
||||
|
||||
/* Pull the second row (action group + Split + Merge — everything after
|
||||
the line-break) up ~10px. The zero-height break sits on its own flex
|
||||
line, stacking two row-gaps above the row; this negative top margin
|
||||
closes that extra space so row 2 aligns nicely under row 1. */
|
||||
.ol-editbar.ol-bar > .ol-editbar-break ~ .ol-control {
|
||||
margin-top: -8px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-D9GGSG-a.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-9k1yuCxX.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/openlayers-J9qS6Th1.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/pako-Xa-UToif.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/geotiff-BaoeLn6q.js">
|
||||
|
||||
10
dist/sw.js
vendored
10
dist/sw.js
vendored
@ -72,7 +72,15 @@
|
||||
// (Results + the Parameters that produced them) via a dependency-free
|
||||
// XLSX writer in src/analysis/xlsx.js; Add External Layer gains a COG
|
||||
// option with a URL pre-flight check.
|
||||
const CACHE_VERSION = 'v14';
|
||||
// v15: Drawing toolbar reworked into a docked left rail, after field officers
|
||||
// reported the palette had no background, sat centred over the area being
|
||||
// drawn, and mixed two icon styles. It is now a two-column panel on the
|
||||
// left edge with its tools banded into Draw / Edit / Shape / File, and
|
||||
// every icon — including ol-ext's own CSS-drawn ones — is a Bootstrap
|
||||
// Icon. On phones it becomes a horizontally scrolling strip above the
|
||||
// dock, which retires the previous two-row wrapping workaround.
|
||||
// New hashed bundle + updated index.html shell.
|
||||
const CACHE_VERSION = 'v15';
|
||||
const SHELL_CACHE = `shell-${CACHE_VERSION}`;
|
||||
const MODULES_CACHE = `modules-${CACHE_VERSION}`;
|
||||
const API_CACHE = `api-${CACHE_VERSION}`;
|
||||
|
||||
325
index.html
325
index.html
@ -112,6 +112,8 @@
|
||||
--destructive-foreground: #fff;
|
||||
--border: #1e1a4b1f;
|
||||
--ring: var(--brand-blue-strong);
|
||||
/* Floating map panels (drawing rail, its option bars) */
|
||||
--bar-bg: rgba(255, 255, 255, 0.94);
|
||||
|
||||
/* Typography */
|
||||
--font-display: "Bebas Neue", sans-serif;
|
||||
@ -1516,17 +1518,279 @@
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
Small-screen drawing toolbar: the EditBar is a single nowrap row
|
||||
by default and overflows narrow phones. Below the sm breakpoint we
|
||||
let it wrap and push the action group (undo / redo / save / snap)
|
||||
onto its own second row so every tool stays reachable.
|
||||
/* ================================================================
|
||||
Drawing toolbar — docked left rail
|
||||
----------------------------------------------------------------
|
||||
Field officers reported three problems with the palette: it had no
|
||||
background and vanished over light imagery, it sat centred at the top
|
||||
across the area being drawn, and its icons did not match each other.
|
||||
|
||||
The bar is now a two-column rail docked to the left edge, on a solid
|
||||
panel, with its controls banded into labelled groups by
|
||||
MapView._buildToolbarRail(). Two columns rather than one because a
|
||||
single file of 13 buttons plus labels runs past the bottom of a laptop
|
||||
map view.
|
||||
|
||||
`ol-left` is ol-ext's own class: it docks the bar left AND makes option
|
||||
bars (Select's Delete/Info, Split's sub-tools) fly out to the right
|
||||
instead of dropping downwards. We keep that behaviour and override only
|
||||
the layout.
|
||||
================================================================ */
|
||||
.ol-editbar.ol-editbar-rail {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 30px);
|
||||
gap: 3px;
|
||||
align-content: start;
|
||||
padding: 5px;
|
||||
background: var(--bar-bg);
|
||||
border: 1px solid rgba(30, 26, 75, 0.14);
|
||||
border-radius: 11px;
|
||||
box-shadow:
|
||||
0 6px 24px -6px rgba(30, 26, 75, 0.3),
|
||||
0 1px 3px rgba(30, 26, 75, 0.14);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
/* Deliberately NOT scrollable. The rail measures 75 x 363 px, which fits
|
||||
any map viewport we support, and setting overflow on either axis makes
|
||||
the other compute to `auto` as well — which clips the option bars that
|
||||
fly out past the right edge. */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Direct children only. A descendant selector here would also match the
|
||||
nested option bars — which are themselves .ol-control — and override the
|
||||
`display: none` that keeps them closed until their tool is active. */
|
||||
.ol-editbar.ol-editbar-rail.ol-left > .ol-control {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Group heading — spans both columns. */
|
||||
.ol-editbar-rail .ol-rail-label {
|
||||
grid-column: 1 / -1;
|
||||
font-size: 0.5rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted-foreground);
|
||||
text-align: center;
|
||||
padding: 4px 0 1px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.ol-editbar-rail .ol-rail-label:first-child {
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
/* The action group is one control holding four buttons; let it span the
|
||||
rail and lay its own buttons out on the same 2-column rhythm. */
|
||||
.ol-editbar-rail > .ol-editbar-actions {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.ol-editbar-rail > .ol-editbar-actions,
|
||||
.ol-editbar-rail > .ol-editbar-actions .ol-bar {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(2, 30px);
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
/* --- buttons ---------------------------------------------------- */
|
||||
.ol-editbar-rail .ol-control button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
background: transparent;
|
||||
color: var(--foreground);
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.13s ease, color 0.13s ease;
|
||||
}
|
||||
.ol-editbar-rail .ol-control button:hover {
|
||||
background: var(--muted);
|
||||
}
|
||||
/* The `.ol-left` is here for specificity, not layout: ol-ext paints the
|
||||
active tool with `.ol-control.ol-bar .ol-toggle.ol-active > button`
|
||||
(0,4,1) in its own brand cyan. Matching that and adding a class puts the
|
||||
LUSPA blue back. */
|
||||
.ol-editbar.ol-editbar-rail.ol-left .ol-control.ol-active > button,
|
||||
.ol-editbar.ol-editbar-rail.ol-left .ol-toggle.ol-active > button,
|
||||
.ol-editbar.ol-editbar-rail.ol-left .ol-toggle.ol-active > button:hover,
|
||||
.ol-editbar.ol-editbar-rail.ol-left .ol-control button.ol-active,
|
||||
.ol-editbar.ol-editbar-rail.ol-left .ol-snap-toggle.ol-active button {
|
||||
background: var(--primary);
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
.ol-editbar-rail .ol-control button:focus-visible {
|
||||
outline: 2px solid var(--ring);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.ol-editbar-rail .ol-control button i {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* --- suppress ol-ext's CSS-drawn icons --------------------------
|
||||
ol-ext has no icon font: each built-in tool is drawn with ::before and
|
||||
::after shapes built from borders and box-shadows. Those are switched
|
||||
off here so the Bootstrap Icon injected by _buildToolbarRail() is the
|
||||
only thing in the button. The `> button` shapes and the `> button i`
|
||||
shapes (Transform, Offset) both have to go.
|
||||
---------------------------------------------------------------- */
|
||||
/* Flex line-break used to start the toolbar's second row. Inert (removed
|
||||
from flow) on wider screens; activated inside the sm media query. */
|
||||
.ol-editbar-break {
|
||||
.ol-editbar-rail .ol-control > button::before,
|
||||
.ol-editbar-rail .ol-control > button::after {
|
||||
content: none !important;
|
||||
}
|
||||
/* The injected glyph is a <span>, so ol-ext's `> button i` rules for
|
||||
Transform and Offset cannot reach it — nothing to undo here, only the
|
||||
sizing ol-ext applies to any icon holder. */
|
||||
.ol-editbar-rail .ol-control > button > span.bi {
|
||||
position: static;
|
||||
width: auto;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
transform: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* --- option bars (Select ▸ Delete/Info, Split ▸ sub-tools) -------
|
||||
ol-ext anchors these to the control with `left: 100%`, which assumes a
|
||||
one-column rail — in two columns that lands the flyout on top of the
|
||||
neighbouring button. Making the controls static hands the containing
|
||||
block to the rail itself, so `left: 100%` clears the whole panel. The
|
||||
flyout then always appears at the same place (rail's right edge, centred)
|
||||
instead of jumping to whichever button was pressed.
|
||||
---------------------------------------------------------------- */
|
||||
.ol-editbar.ol-editbar-rail > .ol-control {
|
||||
position: static;
|
||||
}
|
||||
.ol-editbar-rail .ol-option-bar {
|
||||
top: 50% !important;
|
||||
bottom: auto !important;
|
||||
left: 100% !important;
|
||||
transform: translateY(-50%);
|
||||
background: var(--bar-bg);
|
||||
border-radius: 9px;
|
||||
box-shadow:
|
||||
0 4px 16px -4px rgba(30, 26, 75, 0.3),
|
||||
0 0 0 1px rgba(30, 26, 75, 0.14);
|
||||
padding: 4px;
|
||||
margin: 0 0 0 8px !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ol-editbar-rail .ol-option-bar::before {
|
||||
display: none;
|
||||
}
|
||||
.ol-editbar-rail .ol-option-bar .ol-control {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/* --- dark mode -------------------------------------------------- */
|
||||
.dark-mode {
|
||||
--bar-bg: rgba(28, 25, 64, 0.94);
|
||||
}
|
||||
.dark-mode .ol-editbar.ol-editbar-rail,
|
||||
.dark-mode .ol-editbar-rail .ol-option-bar {
|
||||
border-color: rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
.dark-mode .ol-editbar-rail .ol-control button {
|
||||
color: #eceaf7;
|
||||
}
|
||||
.dark-mode .ol-editbar-rail .ol-control button:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* --- fieldwork mode: larger touch targets ----------------------- */
|
||||
.fieldwork-mode .ol-editbar.ol-editbar-rail {
|
||||
grid-template-columns: repeat(2, 38px);
|
||||
}
|
||||
.fieldwork-mode .ol-editbar-rail > .ol-editbar-actions,
|
||||
.fieldwork-mode .ol-editbar-rail > .ol-editbar-actions .ol-bar {
|
||||
grid-template-columns: repeat(2, 38px);
|
||||
}
|
||||
.fieldwork-mode .ol-editbar-rail .ol-control button {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
Phones: the rail becomes a horizontally scrolling strip sitting
|
||||
above the bottom dock. Scrolling replaces the previous two-row
|
||||
wrap, so every tool stays reachable at full touch size.
|
||||
---------------------------------------------------------------- */
|
||||
@media (max-width: 576px) {
|
||||
/* `.ol-left` is repeated here for specificity: ol-ext's own
|
||||
`.ol-control.ol-bar.ol-left` sets top/transform, and without matching
|
||||
it the rail stays vertically centred and stretches to `bottom`. */
|
||||
.ol-editbar.ol-editbar-rail.ol-left {
|
||||
/* NOTE: display must NOT be !important — ol-ext toggles the bar with an
|
||||
inline display:none/'' (setVisible). An !important here would beat
|
||||
that and keep the toolbar visible even when Draw mode is off. */
|
||||
display: flex;
|
||||
grid-template-columns: none;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
top: auto;
|
||||
bottom: 12px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
transform: none;
|
||||
/* Horizontal scroll replaces the old two-row wrap. Option bars are
|
||||
re-anchored below to open upward, so clipping them is not a concern
|
||||
the way it is on desktop. */
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overscroll-behavior-x: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.ol-editbar.ol-editbar-rail.ol-left::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Labels become inline separators between bands rather than headings. */
|
||||
.ol-editbar-rail .ol-rail-label {
|
||||
grid-column: auto;
|
||||
padding: 0 6px 0 3px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
flex: none;
|
||||
}
|
||||
.ol-editbar-rail .ol-rail-label:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.ol-editbar-rail > .ol-editbar-actions,
|
||||
.ol-editbar-rail > .ol-editbar-actions .ol-bar {
|
||||
display: flex !important;
|
||||
grid-template-columns: none;
|
||||
flex: none;
|
||||
}
|
||||
.ol-editbar.ol-editbar-rail.ol-left .ol-control {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* Touch targets stay at the recommended minimum on a phone. */
|
||||
.ol-editbar-rail .ol-control button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
/* Option bars open upward here — there is no room to the right, and
|
||||
downward would land under the dock. */
|
||||
.ol-editbar.ol-editbar-rail .ol-option-bar {
|
||||
top: auto !important;
|
||||
bottom: 100% !important;
|
||||
left: 8px !important;
|
||||
transform: none;
|
||||
margin: 0 0 8px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
Iframe embed mode (see public/embed.php + src/embed-bridge.js).
|
||||
@ -1555,51 +1819,6 @@
|
||||
height: 100dvh;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.ol-editbar.ol-bar {
|
||||
/* NOTE: display must NOT be !important — ol-ext toggles the bar via an
|
||||
inline `display:none`/`''` (setVisible). A `!important` here would beat
|
||||
that inline style and keep the toolbar permanently visible even when
|
||||
Draw mode is off. Plain `display:flex` only applies when visible. */
|
||||
display: flex;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
row-gap: 4px;
|
||||
white-space: normal !important;
|
||||
max-width: calc(100vw - 12px);
|
||||
}
|
||||
|
||||
/* Full-width, zero-height break → forces everything after it (the
|
||||
action group + Split + Merge) onto a shared second row. */
|
||||
.ol-editbar.ol-bar > .ol-editbar-break {
|
||||
display: block;
|
||||
flex-basis: 100%;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Right-align the second row (action group + Split + Merge). The auto
|
||||
left-margin pushes them to the right end of the line, clearing the
|
||||
far-left zone where an active tool's option bar (e.g. Select's
|
||||
Delete/Info) drops down from row 1 — which otherwise overlaps them. */
|
||||
.ol-editbar.ol-bar > .ol-editbar-actions {
|
||||
/* !important: ol-ext's `.ol-control.ol-bar .ol-control { margin:0 }`
|
||||
has equal specificity and loads later, so it would otherwise cancel
|
||||
this auto-margin and the group would stay glued to the left. */
|
||||
margin-left: auto !important;
|
||||
}
|
||||
|
||||
/* Pull the second row (action group + Split + Merge — everything after
|
||||
the line-break) up ~10px. The zero-height break sits on its own flex
|
||||
line, stacking two row-gaps above the row; this negative top margin
|
||||
closes that extra space so row 2 aligns nicely under row 1. */
|
||||
.ol-editbar.ol-bar > .ol-editbar-break ~ .ol-control {
|
||||
margin-top: -8px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
10
public/sw.js
10
public/sw.js
@ -72,7 +72,15 @@
|
||||
// (Results + the Parameters that produced them) via a dependency-free
|
||||
// XLSX writer in src/analysis/xlsx.js; Add External Layer gains a COG
|
||||
// option with a URL pre-flight check.
|
||||
const CACHE_VERSION = 'v14';
|
||||
// v15: Drawing toolbar reworked into a docked left rail, after field officers
|
||||
// reported the palette had no background, sat centred over the area being
|
||||
// drawn, and mixed two icon styles. It is now a two-column panel on the
|
||||
// left edge with its tools banded into Draw / Edit / Shape / File, and
|
||||
// every icon — including ol-ext's own CSS-drawn ones — is a Bootstrap
|
||||
// Icon. On phones it becomes a horizontally scrolling strip above the
|
||||
// dock, which retires the previous two-row wrapping workaround.
|
||||
// New hashed bundle + updated index.html shell.
|
||||
const CACHE_VERSION = 'v15';
|
||||
const SHELL_CACHE = `shell-${CACHE_VERSION}`;
|
||||
const MODULES_CACHE = `modules-${CACHE_VERSION}`;
|
||||
const API_CACHE = `api-${CACHE_VERSION}`;
|
||||
|
||||
@ -93,6 +93,60 @@ import 'ol/ol.css';
|
||||
import 'ol-ext/dist/ol-ext.css';
|
||||
import '../styles/layerswitcher.css';
|
||||
|
||||
/**
|
||||
* Bootstrap Icon for every button in the drawing toolbar, keyed by the control's
|
||||
* class name.
|
||||
*
|
||||
* ol-ext ships no icon font or sprite sheet — it draws each built-in tool as CSS
|
||||
* ::before / ::after shapes made from borders and box-shadows. Our own tools were
|
||||
* always Bootstrap Icons, so the bar showed two visual languages at once. The
|
||||
* generated shapes are suppressed in CSS (`.ol-editbar-rail … { content: none }`)
|
||||
* and every button is given an icon from here instead, which makes this the single
|
||||
* place the toolbar's iconography is defined.
|
||||
*/
|
||||
const TOOLBAR_ICONS = {
|
||||
// ol-ext built-ins
|
||||
'ol-selection': 'bi-cursor',
|
||||
'ol-drawpoint': 'bi-geo-alt',
|
||||
'ol-drawline': 'bi-vector-pen',
|
||||
'ol-drawpolygon': 'bi-pentagon',
|
||||
'ol-drawregular': 'bi-square',
|
||||
'ol-drawhole': 'bi-record-circle',
|
||||
'ol-transform': 'bi-arrows-move',
|
||||
'ol-offset': 'bi-arrows-angle-expand',
|
||||
// ol-ext Select option bar
|
||||
'ol-delete': 'bi-trash3',
|
||||
'ol-info': 'bi-info-circle',
|
||||
// ours
|
||||
'ol-split': 'bi-signpost-split',
|
||||
'ol-split-line': 'bi-slash-lg',
|
||||
'ol-split-polygon': 'bi-scissors',
|
||||
'ol-split-divide': 'bi-grid-3x3-gap',
|
||||
'ol-merge': 'bi-union',
|
||||
'ol-undo': 'bi-arrow-counterclockwise',
|
||||
'ol-redo': 'bi-arrow-clockwise',
|
||||
'ol-save': 'bi-floppy',
|
||||
'ol-snap-toggle': 'bi-magnet',
|
||||
};
|
||||
|
||||
/**
|
||||
* Order and grouping of the left rail, top to bottom.
|
||||
*
|
||||
* ol-ext appends controls in construction order, which interleaves drawing and
|
||||
* editing tools. Officers reported the palette was hard to read, so the rail is
|
||||
* re-ordered into labelled bands: the eye lands on the band first, then the icon.
|
||||
* Controls not named here keep their place at the end of the bar.
|
||||
*/
|
||||
const TOOLBAR_GROUPS = [
|
||||
{ label: 'Draw', controls: ['ol-drawpoint', 'ol-drawline', 'ol-drawpolygon', 'ol-drawregular', 'ol-drawhole'] },
|
||||
{ label: 'Edit', controls: ['ol-selection', 'ol-transform'] },
|
||||
// ol-offset is an EditBar default we never explicitly enabled, so it was
|
||||
// sitting in the bar unlabelled. It is a genuine geometry tool, so it is
|
||||
// grouped here rather than removed.
|
||||
{ label: 'Shape', controls: ['ol-split', 'ol-merge', 'ol-offset'] },
|
||||
{ label: 'File', controls: ['ol-editbar-actions'] },
|
||||
];
|
||||
|
||||
export class MapView {
|
||||
constructor(targetId, options = {}) {
|
||||
this.options = options;
|
||||
@ -630,18 +684,10 @@ export class MapView {
|
||||
this.showMergeIdentifierPopup(evt.merged, evt.propsA, evt.propsB, evt.coordinate);
|
||||
});
|
||||
|
||||
// Small-screen layout: insert a zero-height, full-width flex line-break
|
||||
// immediately BEFORE the action group. On phones (see the .ol-editbar
|
||||
// media query) this forces the wrap to happen here, so the action group
|
||||
// (undo/redo/save/snap) together with the Split and Merge toggles all land
|
||||
// on a single second row instead of Split/Merge spilling onto a third row.
|
||||
// The break is display:none on wider screens, so desktop layout is unchanged.
|
||||
const editbarEl = this.editBar.element;
|
||||
if (editbarEl && extraBar.element && extraBar.element.parentNode === editbarEl) {
|
||||
const breakEl = document.createElement('div');
|
||||
breakEl.className = 'ol-editbar-break';
|
||||
editbarEl.insertBefore(breakEl, extraBar.element);
|
||||
}
|
||||
// The rail layout (see _buildToolbarRail) replaces the earlier small-screen
|
||||
// workaround, which inserted a flex line-break to push the action group onto
|
||||
// a second row. A two-column rail on desktop and a scrolling strip on phones
|
||||
// both fit without wrapping, so no break element is needed.
|
||||
|
||||
// 6b. SnapGuides — shows alignment guides while drawing.
|
||||
// Uses VectorImageLayer for GPU-friendly canvas rendering instead of
|
||||
@ -693,6 +739,11 @@ export class MapView {
|
||||
this._snapToggleBtn = snapToggleBtn;
|
||||
extraBar.addControl(snapToggleBtn);
|
||||
|
||||
// 7. Re-shape the bar into the docked left rail and swap every icon.
|
||||
// Runs last so that every control — including the snap toggle just
|
||||
// added to the action group — is already in the DOM.
|
||||
this._buildToolbarRail();
|
||||
|
||||
// Start hidden — use the full setEditMode(false) so the Select +
|
||||
// Modify interactions are deactivated (the EditBar constructor may
|
||||
// have re-activated them).
|
||||
@ -1001,6 +1052,66 @@ export class MapView {
|
||||
this._pointerMediaListener = onChange;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Drawing toolbar — docked left rail
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Turn the EditBar into the docked left rail.
|
||||
*
|
||||
* Two things happen here, both purely presentational — no interaction, tool or
|
||||
* event handler is touched:
|
||||
*
|
||||
* 1. Every button's icon is replaced with a Bootstrap Icon (see TOOLBAR_ICONS),
|
||||
* so the built-in tools stop looking like they came from a different program.
|
||||
* 2. The controls are re-ordered into the labelled bands in TOOLBAR_GROUPS and
|
||||
* the bar is given ol-ext's `ol-left` class, which docks it to the left edge
|
||||
* and — usefully — makes option bars (Select's Delete/Info, Split's three
|
||||
* sub-tools) fly out to the right instead of dropping downwards.
|
||||
*
|
||||
* Anything not named in TOOLBAR_GROUPS keeps its position at the end of the bar,
|
||||
* so a tool added later still appears rather than silently vanishing.
|
||||
*/
|
||||
_buildToolbarRail() {
|
||||
const bar = this.editBar?.element;
|
||||
if (!bar) return;
|
||||
|
||||
// `ol-left` is ol-ext's own left-dock class; `ol-editbar-rail` scopes our CSS.
|
||||
bar.classList.add('ol-left', 'ol-editbar-rail');
|
||||
|
||||
// --- 1. icons -----------------------------------------------------------
|
||||
for (const [className, icon] of Object.entries(TOOLBAR_ICONS)) {
|
||||
for (const ctrl of bar.querySelectorAll(`.${className}`)) {
|
||||
// Button controls ARE the button; Toggle controls wrap one.
|
||||
const button = ctrl.matches('button') ? ctrl : ctrl.querySelector(':scope > button');
|
||||
// A <span>, not an <i>: ol-ext draws the Transform and Offset icons with
|
||||
// `.ol-transform > button i::before`, which would out-specify Bootstrap's
|
||||
// `.bi::before` and blank the glyph. Bootstrap Icons work on any element,
|
||||
// so switching the tag sidesteps the collision entirely.
|
||||
if (button) button.innerHTML = `<span class="bi ${icon}" aria-hidden="true"></span>`;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 2. grouping --------------------------------------------------------
|
||||
// Appending an element that is already in the DOM moves it, so building the
|
||||
// fragment in group order and appending it re-sorts the bar in one reflow.
|
||||
const ordered = document.createDocumentFragment();
|
||||
for (const group of TOOLBAR_GROUPS) {
|
||||
const members = group.controls
|
||||
.map((className) => bar.querySelector(`:scope > .${className}`))
|
||||
.filter(Boolean);
|
||||
if (!members.length) continue;
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'ol-rail-label';
|
||||
label.textContent = group.label;
|
||||
label.setAttribute('aria-hidden', 'true');
|
||||
ordered.appendChild(label);
|
||||
for (const member of members) ordered.appendChild(member);
|
||||
}
|
||||
bar.appendChild(ordered);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Persistent Vertex Highlight Overlay
|
||||
// ============================================================================
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user