mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Gather Directions, Draw and Import in a Utilities panel
UTILITIES becomes its own panel in the left column, after Data Layers and CCTV and above Scenes. The layer panel renders the Directions row into the panel's list instead of Data Layers, and Draw and Import move there from DISPLAY with their ids unchanged. Radio, the other row under the old Utilities heading, stays in Data Layers under "Audio". The panel collapses and remembers its state like the other left panels; share links do not carry it. It hides in clean view, recording and Cockpit, takes the Cyber left-rail treatment, and sits collapsed in the phone's Layers sheet. Voice focuses the Directions row in its new list. The Directions, Draw, Import and form-field gates open UTILITIES to reach their controls.
This commit is contained in:
1 parent
aaa3ff46cf
commit
08dd88b2ce
26 files changed
+319
-87
No files matched your search
@@ -1,4 +1,9 @@
|
||||
import { getScopeMaskFeather, isScopeMaskEnabled, setScopeMaskEnabled, setScopeMaskFeather } from '../scopeMask.js';
|
||||
import {
|
||||
getScopeMaskFeather,
|
||||
isScopeMaskEnabled,
|
||||
setScopeMaskEnabled,
|
||||
setScopeMaskFeather,
|
||||
} from '../scopeMask.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import test from 'node:test';
|
||||
@@ -274,8 +279,14 @@ test('Display and voice HUD routes preserve explicit Cyber entry and exit author
|
||||
|
||||
test('scope choice and expanded feather survive every HUD layout and both restore paths', async (t) => {
|
||||
const { owner } = styleOwner(t, 'tactical');
|
||||
const previous = { enabled: isScopeMaskEnabled(), feather: getScopeMaskFeather() };
|
||||
t.after(() => { setScopeMaskEnabled(previous.enabled); setScopeMaskFeather(previous.feather); });
|
||||
const previous = {
|
||||
enabled: isScopeMaskEnabled(),
|
||||
feather: getScopeMaskFeather(),
|
||||
};
|
||||
t.after(() => {
|
||||
setScopeMaskEnabled(previous.enabled);
|
||||
setScopeMaskFeather(previous.feather);
|
||||
});
|
||||
Object.assign(owner.services, { setScopeMaskEnabled, setScopeMaskFeather });
|
||||
owner._scopeBtn = { checked: true };
|
||||
owner._scopeFeatherSlider = { value: '11', disabled: false };
|
||||
@@ -534,11 +545,11 @@ test('Cyber side panels share one width and one framed surface material', () =>
|
||||
);
|
||||
assert.match(
|
||||
cyberStyles,
|
||||
/#left-panel-stack\s*> :is\(#data-panel, #scene-panel\)\.collapsed \{\s*width: var\(--cyber-panel-collapsed-width\);/,
|
||||
/#left-panel-stack\s*> :is\(#data-panel, #utilities-panel, #scene-panel\)\.collapsed \{\s*width: var\(--cyber-panel-collapsed-width\);/,
|
||||
);
|
||||
assert.match(
|
||||
cyberStyles,
|
||||
/:is\(#data-panel, #scene-panel\)\.collapsed\s+:is\(\.data-panel-inner, \.scene-panel-inner\) \{[\s\S]*?height: 50px;[\s\S]*?min-height: 50px;/,
|
||||
/:is\(#data-panel, #utilities-panel, #scene-panel\)\.collapsed\s+:is\(\.data-panel-inner, \.scene-panel-inner\) \{[\s\S]*?height: 50px;[\s\S]*?min-height: 50px;/,
|
||||
);
|
||||
assert.match(
|
||||
cyberStyles,
|
||||
|
||||
@@ -439,7 +439,7 @@ test('the application composition wires the import and owns its teardown', () =>
|
||||
);
|
||||
assert.ok(tools.indexOf('defer(() => geoFileImport?.destroy());') > importAt);
|
||||
const markup = fs.readFileSync(
|
||||
new URL('./templates/display-controls.html', import.meta.url),
|
||||
new URL('./templates/layer-panels.html', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
for (const id of [
|
||||
@@ -450,7 +450,7 @@ test('the application composition wires the import and owns its teardown', () =>
|
||||
])
|
||||
assert.ok(
|
||||
markup.includes(`id="${id}"`),
|
||||
`${id} missing from the DISPLAY template`,
|
||||
`${id} missing from the UTILITIES template`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
+40
-8
@@ -87,13 +87,20 @@ const PANEL_GROUPS = [
|
||||
'weather-cyclones',
|
||||
],
|
||||
},
|
||||
// Directions renders in the Utilities panel, beside Draw and Import, and
|
||||
// needs no heading there.
|
||||
{
|
||||
label: msg('Utilities'),
|
||||
ids: ['directions', 'radio'],
|
||||
ids: ['directions'],
|
||||
containerId: 'utilities-toggles',
|
||||
},
|
||||
{
|
||||
label: msg('Audio'),
|
||||
ids: ['radio'],
|
||||
},
|
||||
];
|
||||
const PANEL_ORDER = PANEL_GROUPS.flatMap(({ label, ids }) =>
|
||||
ids.map((id) => ({ id, label })),
|
||||
const PANEL_ORDER = PANEL_GROUPS.flatMap(({ label, ids, containerId }) =>
|
||||
ids.map((id) => ({ id, label, containerId })),
|
||||
);
|
||||
const PANEL_POSITIONS = new Map(
|
||||
PANEL_ORDER.map(({ id }, index) => [id, index]),
|
||||
@@ -226,6 +233,12 @@ export class LayerPanel {
|
||||
if (this._destroyed) return;
|
||||
this._releaseBindings();
|
||||
this._toggleContainer = container;
|
||||
this._groupContainers = new Map();
|
||||
for (const { containerId } of PANEL_GROUPS) {
|
||||
const element =
|
||||
containerId && container?.ownerDocument?.getElementById?.(containerId);
|
||||
if (element) this._groupContainers.set(containerId, element);
|
||||
}
|
||||
this._weatherPanel?.destroy();
|
||||
this._weatherPanel = createWeatherPanel({
|
||||
clock: this.weatherClock,
|
||||
@@ -276,11 +289,27 @@ export class LayerPanel {
|
||||
this._recentImageryPanel = null;
|
||||
this._recentImageryFactory = null;
|
||||
this._toggleContainer = null;
|
||||
this._groupContainers = null;
|
||||
}
|
||||
/** The list a row renders into: its group's own, else Data Layers. */
|
||||
_containerFor(layerId) {
|
||||
const containerId = PANEL_ORDER[PANEL_POSITIONS.get(layerId)]?.containerId;
|
||||
return (
|
||||
(containerId && this._groupContainers?.get(containerId)) ||
|
||||
this._toggleContainer
|
||||
);
|
||||
}
|
||||
_rowFor(layerId) {
|
||||
return this._containerFor(layerId)?.querySelector(
|
||||
`[data-layer-id="${layerId}"]`,
|
||||
);
|
||||
}
|
||||
_renderToggles() {
|
||||
if (this._destroyed || !this._toggleContainer) return;
|
||||
this._releaseBindings();
|
||||
this._toggleContainer.innerHTML = '';
|
||||
for (const element of this._groupContainers?.values() || [])
|
||||
element.innerHTML = '';
|
||||
|
||||
const generation = this._generation;
|
||||
const layers = this.getAll()
|
||||
@@ -296,7 +325,12 @@ export class LayerPanel {
|
||||
const group =
|
||||
PANEL_ORDER[PANEL_POSITIONS.get(layer.id)]?.label ??
|
||||
msg('Other layers');
|
||||
if (group && group !== previousGroup) {
|
||||
const rowContainer = this._containerFor(layer.id);
|
||||
if (
|
||||
rowContainer === this._toggleContainer &&
|
||||
group &&
|
||||
group !== previousGroup
|
||||
) {
|
||||
const heading = document.createElement('h3');
|
||||
heading.className = 'data-layer-group-heading';
|
||||
heading.textContent = t(group);
|
||||
@@ -421,7 +455,7 @@ export class LayerPanel {
|
||||
}
|
||||
}
|
||||
|
||||
this._toggleContainer.appendChild(row);
|
||||
rowContainer.appendChild(row);
|
||||
}
|
||||
this._refreshWeatherPanel();
|
||||
}
|
||||
@@ -600,9 +634,7 @@ export class LayerPanel {
|
||||
return;
|
||||
}
|
||||
for (const layer of this.getAll()) {
|
||||
const row = this._toggleContainer.querySelector(
|
||||
`[data-layer-id="${layer.id}"]`,
|
||||
);
|
||||
const row = this._rowFor(layer.id);
|
||||
if (!row) continue;
|
||||
|
||||
const btn = row.querySelector('.data-toggle-btn');
|
||||
|
||||
@@ -30,6 +30,7 @@ const COCKPIT_ENTRY_COLLAPSE_PANEL_IDS = Object.freeze([
|
||||
'recent-imagery-panel',
|
||||
'street-level-panel',
|
||||
'scene-panel',
|
||||
'utilities-panel',
|
||||
'pp-toggles',
|
||||
'global-context-panel',
|
||||
'radio-panel',
|
||||
|
||||
@@ -2007,6 +2007,7 @@ body.cockpit-mode #left-panel-stack {
|
||||
max-height: none;
|
||||
transition: none;
|
||||
}
|
||||
body.cockpit-mode #left-panel-stack > #utilities-panel,
|
||||
body.cockpit-mode #left-panel-stack > #scene-panel {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -960,6 +960,7 @@ body.ui-clean-view #data-panel,
|
||||
body.ui-clean-view #cctv-panel,
|
||||
body.ui-clean-view #radio-panel,
|
||||
body.ui-clean-view #global-context-panel,
|
||||
body.ui-clean-view #utilities-panel,
|
||||
body.ui-clean-view #scene-panel {
|
||||
opacity: 0 !important;
|
||||
visibility: hidden !important;
|
||||
|
||||
@@ -1662,7 +1662,9 @@
|
||||
:root[data-ui-theme='cyber']
|
||||
body:not(.cockpit-mode)
|
||||
#left-panel-stack:has(> [data-panel-id]:not(.collapsed))
|
||||
> :is(#data-panel, #cctv-panel, #scene-panel):not(.collapsed) {
|
||||
> :is(#data-panel, #cctv-panel, #utilities-panel, #scene-panel):not(
|
||||
.collapsed
|
||||
) {
|
||||
flex: 1 1 auto;
|
||||
height: auto;
|
||||
max-height: none;
|
||||
@@ -1677,20 +1679,20 @@
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
#left-panel-stack
|
||||
> :is(#data-panel, #scene-panel) {
|
||||
> :is(#data-panel, #utilities-panel, #scene-panel) {
|
||||
--panel-expanded-width: var(--cyber-panel-width);
|
||||
width: var(--panel-expanded-width);
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
#left-panel-stack
|
||||
> :is(#data-panel, #scene-panel).collapsed {
|
||||
> :is(#data-panel, #utilities-panel, #scene-panel).collapsed {
|
||||
width: var(--cyber-panel-collapsed-width);
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
#left-panel-stack
|
||||
> :is(#data-panel, #scene-panel).collapsed
|
||||
> :is(#data-panel, #utilities-panel, #scene-panel).collapsed
|
||||
:is(.data-panel-inner, .scene-panel-inner) {
|
||||
box-sizing: border-box;
|
||||
height: 50px;
|
||||
@@ -1718,7 +1720,7 @@
|
||||
|
||||
:root[data-ui-theme='cyber']
|
||||
body:not(.cockpit-mode)
|
||||
:is(#data-panel, #scene-panel)
|
||||
:is(#data-panel, #utilities-panel, #scene-panel)
|
||||
.panel-collapse-btn {
|
||||
width: var(--cyber-panel-control-size);
|
||||
min-width: var(--cyber-panel-control-size);
|
||||
|
||||
@@ -1398,3 +1398,101 @@ body.recording-mode #right-context-rail > [data-panel-id] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Utilities Panel ─────────────────────────── */
|
||||
/* Directions (from the layer list), Draw and Import. Draw and Import read as
|
||||
rows like Directions above them (.data-toggle-row): icon and name, their
|
||||
options indented under the name. */
|
||||
#utilities-panel {
|
||||
--panel-expanded-width: 280px;
|
||||
width: var(--panel-expanded-width);
|
||||
}
|
||||
|
||||
#utilities-panel .panel-glow {
|
||||
position: absolute;
|
||||
inset: -20px;
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
var(--accent-dim) 0%,
|
||||
transparent 60%
|
||||
);
|
||||
filter: blur(20px);
|
||||
opacity: 0.25;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* The layer list takes its natural height; the panel scrolls as a whole. */
|
||||
.utilities-list {
|
||||
flex: 0 0 auto;
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.utilities-tools {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.utilities-tools .pp-toggle-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#utilities-panel .utilities-tools .pp-toggle-btn {
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
box-sizing: border-box;
|
||||
gap: 8px;
|
||||
padding: 8px 10px 6px;
|
||||
color: var(--text-primary);
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: none;
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
#utilities-panel .utilities-tools .pp-toggle-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
#utilities-panel .utilities-tools .pp-icon {
|
||||
flex: none;
|
||||
width: 16px;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
color: var(--text-primary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#utilities-panel .utilities-tools .pp-label {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.3px;
|
||||
text-transform: none;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Draw reads as on with the accent of an enabled layer row. */
|
||||
#utilities-panel
|
||||
.utilities-tools
|
||||
.pp-toggle-btn:is([aria-pressed='true'], .active) {
|
||||
background: var(--accent-dim);
|
||||
box-shadow: inset 2px 0 0 var(--accent);
|
||||
}
|
||||
|
||||
#utilities-panel
|
||||
.utilities-tools
|
||||
:is(.pp-slider-row, .geo-import-list, .geo-import-hint) {
|
||||
margin-left: 34px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#utilities-panel.collapsed .utilities-tools {
|
||||
display: none;
|
||||
}
|
||||
@@ -166,6 +166,9 @@ body.mobile-shell #right-context-rail > #recent-imagery-panel {
|
||||
/* Rail-scoped ids so a rail's own hiding rules (for example the Tactical HUD
|
||||
hiding collapsed siblings) never take a panel out of an open sheet. */
|
||||
body.mobile-shell[data-mobile-sheet='layers'] #left-panel-stack > #data-panel,
|
||||
body.mobile-shell[data-mobile-sheet='layers']
|
||||
#left-panel-stack
|
||||
> #utilities-panel,
|
||||
body.mobile-shell[data-mobile-sheet='layers'] #left-panel-stack > #scene-panel,
|
||||
body.mobile-shell[data-mobile-sheet='cameras']
|
||||
#right-context-rail
|
||||
|
||||
@@ -15,6 +15,7 @@ body.recording-mode #cctv-panel,
|
||||
body.recording-mode #radio-panel,
|
||||
body.recording-mode #global-context-panel,
|
||||
body.recording-mode #gev-voice-control,
|
||||
body.recording-mode #utilities-panel,
|
||||
body.recording-mode #scene-panel {
|
||||
opacity: 0 !important;
|
||||
visibility: hidden !important;
|
||||
|
||||
@@ -237,11 +237,13 @@
|
||||
#left-panel-stack > #cctv-panel,
|
||||
#left-panel-stack > #street-level-panel,
|
||||
#left-panel-stack > #global-context-panel,
|
||||
#left-panel-stack > #utilities-panel,
|
||||
#left-panel-stack > #scene-panel,
|
||||
#left-panel-stack > #data-panel.collapsed,
|
||||
#left-panel-stack > #cctv-panel.collapsed,
|
||||
#left-panel-stack > #street-level-panel.collapsed,
|
||||
#left-panel-stack > #global-context-panel.collapsed,
|
||||
#left-panel-stack > #utilities-panel.collapsed,
|
||||
#left-panel-stack > #scene-panel.collapsed {
|
||||
position: relative;
|
||||
top: auto;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#left-panel-stack > #data-panel,
|
||||
#left-panel-stack > #cctv-panel,
|
||||
#left-panel-stack > #global-context-panel,
|
||||
#left-panel-stack > #utilities-panel,
|
||||
#left-panel-stack > #scene-panel {
|
||||
position: relative;
|
||||
top: auto;
|
||||
@@ -35,6 +36,10 @@
|
||||
order: 2;
|
||||
}
|
||||
|
||||
#left-panel-stack > #utilities-panel {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
#left-panel-stack > #scene-panel {
|
||||
order: 4;
|
||||
}
|
||||
@@ -50,6 +55,7 @@
|
||||
|
||||
#left-panel-stack > #data-panel:not(.collapsed),
|
||||
#left-panel-stack > #cctv-panel:not(.collapsed),
|
||||
#left-panel-stack > #utilities-panel:not(.collapsed),
|
||||
#left-panel-stack > #scene-panel:not(.collapsed) {
|
||||
box-sizing: border-box;
|
||||
flex: 0 1 var(--left-panel-allocated-height, auto);
|
||||
@@ -62,7 +68,9 @@
|
||||
* the canonical control order and center their combined stack at 50vh.
|
||||
*/
|
||||
#left-panel-stack.layout-tail
|
||||
> :is(#data-panel, #cctv-panel, #scene-panel):not(.collapsed) {
|
||||
> :is(#data-panel, #cctv-panel, #utilities-panel, #scene-panel):not(
|
||||
.collapsed
|
||||
) {
|
||||
box-sizing: border-box;
|
||||
flex: 0 0 var(--left-panel-allocated-height);
|
||||
height: var(--left-panel-allocated-height);
|
||||
@@ -71,6 +79,7 @@
|
||||
|
||||
#left-panel-stack > #data-panel:not(.collapsed) .data-panel-inner,
|
||||
#left-panel-stack > #cctv-panel:not(.collapsed) .cctv-panel-inner,
|
||||
#left-panel-stack > #utilities-panel:not(.collapsed) .utilities-panel-inner,
|
||||
#left-panel-stack > #scene-panel:not(.collapsed) .scene-panel-inner {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
@@ -81,6 +90,7 @@
|
||||
}
|
||||
|
||||
#left-panel-stack > #cctv-panel:not(.collapsed) .cctv-panel-inner,
|
||||
#left-panel-stack > #utilities-panel:not(.collapsed) .utilities-panel-inner,
|
||||
#left-panel-stack > #scene-panel:not(.collapsed) .scene-panel-inner {
|
||||
overflow-y: auto;
|
||||
scrollbar-gutter: stable;
|
||||
@@ -360,6 +370,8 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
#scene-panel.collapsed .scene-shot-list,
|
||||
#scene-panel.collapsed .scene-progress,
|
||||
#scene-panel.collapsed #scene-status,
|
||||
#utilities-panel.collapsed .utilities-list,
|
||||
#utilities-panel.collapsed .utilities-tools,
|
||||
#param-slider-panel.collapsed #param-sliders,
|
||||
#location-bar.collapsed .poi-row-container,
|
||||
#location-bar.collapsed .location-bar-divider,
|
||||
@@ -370,6 +382,7 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
|
||||
#data-panel.collapsed,
|
||||
#cctv-panel.collapsed,
|
||||
#utilities-panel.collapsed,
|
||||
#scene-panel.collapsed {
|
||||
width: var(--left-collapsed-width);
|
||||
}
|
||||
@@ -381,6 +394,7 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
#data-panel.collapsed .panel-title,
|
||||
#cctv-panel.collapsed .panel-title,
|
||||
#radio-panel.collapsed .panel-title,
|
||||
#utilities-panel.collapsed .panel-title,
|
||||
#scene-panel.collapsed .panel-title {
|
||||
font-size: 8px;
|
||||
letter-spacing: 1.8px;
|
||||
@@ -391,6 +405,7 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
#cctv-panel.collapsed .cctv-panel-inner,
|
||||
#radio-panel.collapsed .radio-panel-inner,
|
||||
#scene-panel.collapsed .scene-panel-inner,
|
||||
#utilities-panel.collapsed .utilities-panel-inner,
|
||||
#param-slider-panel.collapsed,
|
||||
#location-bar.collapsed .location-inner {
|
||||
padding-bottom: 10px;
|
||||
@@ -402,6 +417,7 @@ html:not([lang='en']) #scene-panel .scene-controls .scene-btn {
|
||||
#radio-panel.collapsed .panel-header,
|
||||
#global-context-panel.collapsed .panel-header,
|
||||
#scene-panel.collapsed .panel-header,
|
||||
#utilities-panel.collapsed .panel-header,
|
||||
#param-slider-panel.collapsed .param-panel-header {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
@@ -117,41 +117,6 @@
|
||||
<span class="pp-slider-value" id="scope-feather-value">100%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pp-toggle-group">
|
||||
<button class="pp-toggle-btn" id="draw-toggle" title="Draw on the world — click vertices, double-click or Enter to finish, Esc to cancel" aria-pressed="false" data-i18n-title>
|
||||
<span class="pp-icon material-symbols-outlined" aria-hidden="true">draw</span>
|
||||
<span class="pp-label" data-i18n>Draw</span>
|
||||
</button>
|
||||
<div class="pp-slider-row" id="draw-mode-row">
|
||||
<span class="pp-slider-mini-label" data-i18n>Shape</span>
|
||||
<div class="gev-segmented pp-mode-seg" role="radiogroup" aria-label="Shape to draw" data-i18n-aria-label>
|
||||
<button class="gev-segment pp-mode-btn active" data-shape="area" role="radio" aria-checked="true" data-i18n>Area</button>
|
||||
<button class="gev-segment pp-mode-btn" data-shape="line" role="radio" aria-checked="false" data-i18n>Line</button>
|
||||
<button class="gev-segment pp-mode-btn" data-shape="pin" role="radio" aria-checked="false" data-i18n>Pin</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pp-slider-row" id="draw-label-row">
|
||||
<input type="text" class="pp-text-input" id="draw-label-input" maxlength="120" placeholder="Label (optional)" aria-label="Label for the drawn shape" data-i18n-placeholder data-i18n-aria-label autocomplete="off" spellcheck="false">
|
||||
<select class="pp-select draw-color-select gev-field gev-field--compact" id="draw-color-select" aria-label="Colour of the drawn shape" data-i18n-aria-label>
|
||||
<option value="primary" data-i18n>Primary</option>
|
||||
<option value="amber" data-i18n>Amber</option>
|
||||
<option value="cyan" data-i18n>Cyan</option>
|
||||
<option value="green" data-i18n>Green</option>
|
||||
<option value="red" data-i18n>Red</option>
|
||||
</select>
|
||||
<button class="pp-mode-btn draw-clear-btn" id="draw-clear" type="button" title="Remove every mark from the board" data-i18n-title data-i18n>Clear</button>
|
||||
<span class="draw-hint" id="draw-hint" aria-live="polite"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pp-toggle-group" id="geo-import-group">
|
||||
<button class="pp-toggle-btn" id="geo-import-button" type="button" title="Show your own GeoJSON, KML, KMZ or GPX files on the globe (or drop them onto it). File bytes stay on this device; overlays can appear in map screenshots shared with voice." data-i18n-title>
|
||||
<span class="pp-icon material-symbols-outlined" aria-hidden="true">upload_file</span>
|
||||
<span class="pp-label" data-i18n>Import</span>
|
||||
</button>
|
||||
<input type="file" id="geo-import-input" accept=".geojson,.json,.kml,.kmz,.gpx" multiple hidden aria-label="Choose GeoJSON, KML, KMZ or GPX files" data-i18n-aria-label>
|
||||
<ul class="geo-import-list" id="geo-import-list" aria-label="Imported files" data-i18n-aria-label></ul>
|
||||
<span class="draw-hint geo-import-hint" id="geo-import-hint" aria-live="polite"></span>
|
||||
</div>
|
||||
<div class="pp-toggle-group">
|
||||
<button class="pp-toggle-btn" id="celestial-toggle" title="Celestial ring — reveal the full globe" aria-pressed="false" data-i18n-title>
|
||||
<span class="pp-icon material-symbols-outlined" aria-hidden="true">flare</span>
|
||||
|
||||
@@ -142,6 +142,56 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Utilities Panel: Directions (from the layer list), Draw and Import -->
|
||||
<div id="utilities-panel" class="panel-collapsible collapsed" data-panel-id="utilities-panel">
|
||||
<div class="panel-glow"></div>
|
||||
<div class="data-panel-inner utilities-panel-inner">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title" data-i18n>UTILITIES</span>
|
||||
<span class="panel-divider"></span>
|
||||
<button class="panel-collapse-btn" data-collapse-target="utilities-panel" title="Collapse panel" data-i18n-title>+</button>
|
||||
</div>
|
||||
<div id="utilities-toggles" class="data-toggle-list utilities-list"></div>
|
||||
<div class="utilities-tools">
|
||||
<div class="pp-toggle-group">
|
||||
<button class="pp-toggle-btn" id="draw-toggle" title="Draw on the world — click vertices, double-click or Enter to finish, Esc to cancel" aria-pressed="false" data-i18n-title>
|
||||
<span class="pp-icon material-symbols-outlined" aria-hidden="true">draw</span>
|
||||
<span class="pp-label" data-i18n>Draw</span>
|
||||
</button>
|
||||
<div class="pp-slider-row" id="draw-mode-row">
|
||||
<span class="pp-slider-mini-label" data-i18n>Shape</span>
|
||||
<div class="gev-segmented pp-mode-seg" role="radiogroup" aria-label="Shape to draw" data-i18n-aria-label>
|
||||
<button class="gev-segment pp-mode-btn active" data-shape="area" role="radio" aria-checked="true" data-i18n>Area</button>
|
||||
<button class="gev-segment pp-mode-btn" data-shape="line" role="radio" aria-checked="false" data-i18n>Line</button>
|
||||
<button class="gev-segment pp-mode-btn" data-shape="pin" role="radio" aria-checked="false" data-i18n>Pin</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pp-slider-row" id="draw-label-row">
|
||||
<input type="text" class="pp-text-input" id="draw-label-input" maxlength="120" placeholder="Label (optional)" aria-label="Label for the drawn shape" data-i18n-placeholder data-i18n-aria-label autocomplete="off" spellcheck="false">
|
||||
<select class="pp-select draw-color-select gev-field gev-field--compact" id="draw-color-select" aria-label="Colour of the drawn shape" data-i18n-aria-label>
|
||||
<option value="primary" data-i18n>Primary</option>
|
||||
<option value="amber" data-i18n>Amber</option>
|
||||
<option value="cyan" data-i18n>Cyan</option>
|
||||
<option value="green" data-i18n>Green</option>
|
||||
<option value="red" data-i18n>Red</option>
|
||||
</select>
|
||||
<button class="pp-mode-btn draw-clear-btn" id="draw-clear" type="button" title="Remove every mark from the board" data-i18n-title data-i18n>Clear</button>
|
||||
<span class="draw-hint" id="draw-hint" aria-live="polite"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pp-toggle-group" id="geo-import-group">
|
||||
<button class="pp-toggle-btn" id="geo-import-button" type="button" title="Show your own GeoJSON, KML, KMZ or GPX files on the globe (or drop them onto it). File bytes stay on this device; overlays can appear in map screenshots shared with voice." data-i18n-title>
|
||||
<span class="pp-icon material-symbols-outlined" aria-hidden="true">upload_file</span>
|
||||
<span class="pp-label" data-i18n>Import</span>
|
||||
</button>
|
||||
<input type="file" id="geo-import-input" accept=".geojson,.json,.kml,.kmz,.gpx" multiple hidden aria-label="Choose GeoJSON, KML, KMZ or GPX files" data-i18n-aria-label>
|
||||
<ul class="geo-import-list" id="geo-import-list" aria-label="Imported files" data-i18n-aria-label></ul>
|
||||
<span class="draw-hint geo-import-hint" id="geo-import-hint" aria-live="polite"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scene Director Panel -->
|
||||
<div id="scene-panel" class="panel-collapsible collapsed" data-panel-id="scene-panel">
|
||||
<div class="panel-glow"></div>
|
||||
|
||||
Reference in new issue
Block a user