refactor(ui): drop the unused panel drag and position code

No markup carries a drag handle, so nothing called it: panel position
controls keep only collapsed state and the layout-reset notice.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Opus 5.5 committed 2026-10-08 18:25:16 +04:00
1 parent 8e5e3cdd3b
commit 34d43c7e3f
7 files changed
+6 -389

No files matched your search

-1
View File
@@ -139,7 +139,6 @@ try {
const focusBefore = document.activeElement; const focusBefore = document.activeElement;
const stoppedBeforeRestoration = const stoppedBeforeRestoration =
ui._panelLayout.destroyed && ui._panelLayout.destroyed &&
ui._panelPosition.destroyed &&
ui._feedback.destroyed && ui._feedback.destroyed &&
ui._recording.destroyed && ui._recording.destroyed &&
cockpit.destroyed && cockpit.destroyed &&
-3
View File
@@ -60,8 +60,6 @@ export class PanelChrome {
this._cockpitContextCollapsedForDataPanel = false; this._cockpitContextCollapsedForDataPanel = false;
this._panelPosition = new PanelPositionControls({ this._panelPosition = new PanelPositionControls({
syncPanelCollapseButton: (panel) => this._syncPanelCollapseButton(panel), syncPanelCollapseButton: (panel) => this._syncPanelCollapseButton(panel),
layoutRightPanels: () => this._layoutRightPanels(),
syncCctvPanelViewport: () => this._syncCctvPanelViewport(),
showToast: (message) => this._showToast(message), showToast: (message) => this._showToast(message),
}); });
this._panelLayout = new PanelLayoutController({ this._panelLayout = new PanelLayoutController({
@@ -615,7 +613,6 @@ export class PanelChrome {
if (this._disposed) return; if (this._disposed) return;
this._disposed = true; this._disposed = true;
this._lifetime.destroy(); this._lifetime.destroy();
this._panelPosition.destroy();
this._panelLayout.destroy(); this._panelLayout.destroy();
for (const control of this._panelDisclosureControls || []) for (const control of this._panelDisclosureControls || [])
control.destroy(); control.destroy();
-2
View File
@@ -194,7 +194,6 @@ export class PanelLayoutController {
this._ppToggles.style.removeProperty('bottom'); this._ppToggles.style.removeProperty('bottom');
this._ppToggles.style.removeProperty('left'); this._ppToggles.style.removeProperty('left');
this._ppToggles.style.removeProperty('z-index'); this._ppToggles.style.removeProperty('z-index');
this._ppToggles.classList.remove('panel-draggable', 'panel-dragging');
this._ppToggles.querySelector('.pp-header-row')?.removeAttribute('title'); this._ppToggles.querySelector('.pp-header-row')?.removeAttribute('title');
stack.prepend(this._ppToggles); stack.prepend(this._ppToggles);
const globalContextPanel = document.getElementById('global-context-panel'); const globalContextPanel = document.getElementById('global-context-panel');
@@ -207,7 +206,6 @@ export class PanelLayoutController {
if (!panel) continue; if (!panel) continue;
for (const property of ['top', 'right', 'bottom', 'left', 'z-index']) for (const property of ['top', 'right', 'bottom', 'left', 'z-index'])
panel.style.removeProperty(property); panel.style.removeProperty(property);
panel.classList.remove('panel-draggable', 'panel-dragging');
stack.insertBefore(panel, globalContextPanel); stack.insertBefore(panel, globalContextPanel);
this._syncPanelCollapseButton(panel); this._syncPanelCollapseButton(panel);
} }
+5 -222
View File
@@ -1,49 +1,16 @@
/** Own panel position preferences, viewport clamping and drag listeners. */ /** Own panel collapsed-state preferences and the one-time layout-reset notice. */
/** Versioned localStorage namespace prefix to invalidate stale panel layouts. */ /** Versioned localStorage namespace prefix to invalidate stale panel layouts. */
const PANEL_LAYOUT_STORAGE_VERSION = 'v6'; const PANEL_LAYOUT_STORAGE_VERSION = 'v6';
/** /**
* Position keys are versioned separately from collapsed-state keys so layout * Position keys are versioned separately from collapsed-state keys. The rails
* default changes (e.g. right-rail origin) can reset positions without also * now lay panels out themselves and store no position; the version still names
* resetting every panel's open/closed preference. * the layout-reset notice and the documented key.
*/ */
const PANEL_POSITION_STORAGE_VERSION = 'v8'; const PANEL_POSITION_STORAGE_VERSION = 'v8';
/** Z ladder: panels promote within [100, 139]; voice pill 150, toast 200, clean-view-exit 300. */
const PANEL_Z_BASE = 100;
const PANEL_Z_MAX = 139;
export class PanelPositionControls { export class PanelPositionControls {
constructor({ constructor({ syncPanelCollapseButton, showToast }) {
syncPanelCollapseButton,
layoutRightPanels,
syncCctvPanelViewport,
showToast,
}) {
this._syncPanelCollapseButton = syncPanelCollapseButton; this._syncPanelCollapseButton = syncPanelCollapseButton;
this._layoutRightPanels = layoutRightPanels;
this._syncCctvPanelViewport = syncCctvPanelViewport;
this._showToast = showToast; this._showToast = showToast;
this._ppToggles = document.getElementById('pp-toggles');
this._panelZCounter = PANEL_Z_BASE + 10;
this._draggableResizeObserver = null;
this._cancelDrag = null;
this.removers = [];
this.destroyed = false;
}
listen(target, type, callback) {
if (this.destroyed || !target) return;
const listener = (event) => {
if (!this.destroyed) callback(event);
};
target.addEventListener(type, listener);
this.removers.push(() => target.removeEventListener(type, listener));
}
_reclampDraggablePanels() {
if (this.destroyed) return;
const el = this._ppToggles;
if (!el || !el.style.top || el.style.top === 'auto') return;
const top = parseInt(el.style.top, 10);
if (!Number.isFinite(top)) return;
el.style.top = `${this._clampToViewport(0, top, el).top}px`;
this._pinPanelToRight(el);
} }
_maybeNotifyLayoutReset() { _maybeNotifyLayoutReset() {
@@ -64,35 +31,6 @@ export class PanelPositionControls {
} }
} }
_initPanelDrag() {
if (this.destroyed) return;
const dragSpecs = [
{
id: 'pp-toggles',
panel: this._ppToggles,
handle: this._ppToggles?.querySelector('.panel-drag-handle.compact'),
},
].filter(Boolean);
for (const spec of dragSpecs) {
if (!spec.panel || !spec.handle) continue;
this._restorePanelPosition(spec.id, spec.panel);
this._makePanelDraggable(spec.id, spec.panel, spec.handle);
}
// A restored panel grows a frame or two later, so the restore-time clamp
// is stale: re-clamp on every size change.
if (this._ppToggles && typeof ResizeObserver !== 'undefined') {
this._draggableResizeObserver = new ResizeObserver(() =>
this._reclampDraggablePanels(),
);
this._draggableResizeObserver.observe(this._ppToggles);
}
}
_panelStorageKey(panelId) {
return `godsEyeView.${PANEL_POSITION_STORAGE_VERSION}.panelPos.${panelId}`;
}
_panelCollapseStorageKey(panelId) { _panelCollapseStorageKey(panelId) {
return `godsEyeView.${PANEL_LAYOUT_STORAGE_VERSION}.panelCollapsed.${panelId}`; return `godsEyeView.${PANEL_LAYOUT_STORAGE_VERSION}.panelCollapsed.${panelId}`;
} }
@@ -142,159 +80,4 @@ export class PanelPositionControls {
// storage unavailable // storage unavailable
} }
} }
_pinPanelToRight(panelEl) {
if (!panelEl) return;
const rect = panelEl.getBoundingClientRect();
const rightOffset = Math.max(6, Math.round(window.innerWidth - rect.right));
panelEl.style.right = `${rightOffset}px`;
panelEl.style.left = 'auto';
}
_restorePanelPosition(panelId, panelEl) {
try {
const raw = localStorage.getItem(this._panelStorageKey(panelId));
if (!raw) return;
const pos = JSON.parse(raw);
if (!pos || typeof pos.left !== 'number' || typeof pos.top !== 'number')
return;
// A position saved at another window size can land off-screen.
const { left, top } = this._clampToViewport(
Math.round(pos.left),
Math.round(pos.top),
panelEl,
);
panelEl.style.left = `${left}px`;
panelEl.style.top = `${top}px`;
panelEl.style.right = 'auto';
panelEl.style.bottom = 'auto';
if (panelId === 'pp-toggles') {
this._pinPanelToRight(panelEl);
}
} catch {
// ignore malformed saved panel position
}
}
_clampToViewport(left, top, panelEl) {
const rect = panelEl.getBoundingClientRect();
const maxLeft = Math.max(6, window.innerWidth - rect.width - 6);
const maxTop = Math.max(6, window.innerHeight - rect.height - 6);
return {
left: Math.max(6, Math.min(maxLeft, left)),
top: Math.max(6, Math.min(maxTop, top)),
};
}
_savePanelPosition(panelId, panelEl) {
const rect = panelEl.getBoundingClientRect();
try {
localStorage.setItem(
this._panelStorageKey(panelId),
JSON.stringify({
left: Math.round(rect.left),
top: Math.round(rect.top),
}),
);
} catch {
// storage unavailable
}
}
_promotePanelZ(panelEl) {
this._panelZCounter += 1;
if (this._panelZCounter > PANEL_Z_MAX) {
const promoted = [...document.querySelectorAll('.panel-draggable')]
.filter((el) => el.style.zIndex)
.sort((a, b) => Number(a.style.zIndex) - Number(b.style.zIndex));
let z = PANEL_Z_BASE + 1;
for (const el of promoted) {
el.style.zIndex = String(z);
z += 1;
}
this._panelZCounter = z;
}
panelEl.style.zIndex = String(this._panelZCounter);
}
_makePanelDraggable(panelId, panelEl, handleEl) {
// Z-order promotion: bring clicked panel to front of the stacking context
this.listen(panelEl, 'pointerdown', () => {
this._promotePanelZ(panelEl);
});
this.listen(handleEl, 'pointerdown', (event) => {
if (event.button !== 0) return;
if (event.target.closest('.panel-collapse-btn')) return;
if (
event.target.closest(
'input, select, option, button:not(.panel-collapse-btn)',
)
)
return;
this._cancelDrag?.();
event.preventDefault();
const rect = panelEl.getBoundingClientRect();
const startX = event.clientX;
const startY = event.clientY;
const offsetX = startX - rect.left;
const offsetY = startY - rect.top;
panelEl.style.left = `${rect.left}px`;
panelEl.style.top = `${rect.top}px`;
panelEl.style.right = 'auto';
panelEl.style.bottom = 'auto';
panelEl.classList.add('panel-dragging');
this._promotePanelZ(panelEl);
const onMove = (moveEvent) => {
const nextLeftRaw = moveEvent.clientX - offsetX;
const nextTopRaw = moveEvent.clientY - offsetY;
const maxLeft = Math.max(6, window.innerWidth - rect.width - 6);
const maxTop = Math.max(6, window.innerHeight - rect.height - 6);
const nextLeft = Math.max(6, Math.min(maxLeft, nextLeftRaw));
const nextTop = Math.max(6, Math.min(maxTop, nextTopRaw));
panelEl.style.left = `${nextLeft}px`;
panelEl.style.top = `${nextTop}px`;
if (panelId === 'pp-toggles') {
this._layoutRightPanels();
}
if (panelId === 'cctv-panel') {
this._syncCctvPanelViewport();
}
};
const cancel = () => {
panelEl.classList.remove('panel-dragging');
window.removeEventListener('pointermove', onMove);
window.removeEventListener('pointerup', onUp);
window.removeEventListener('pointercancel', onUp);
this._cancelDrag = null;
};
const onUp = () => {
cancel();
if (panelId === 'pp-toggles') {
this._pinPanelToRight(panelEl);
}
this._savePanelPosition(panelId, panelEl);
if (panelId === 'cctv-panel') {
this._syncCctvPanelViewport();
}
};
this._cancelDrag = cancel;
window.addEventListener('pointermove', onMove);
window.addEventListener('pointerup', onUp);
window.addEventListener('pointercancel', onUp);
});
}
destroy() {
if (this.destroyed) return;
this.destroyed = true;
this._cancelDrag?.();
for (const remove of this.removers.splice(0)) remove();
this._draggableResizeObserver?.disconnect();
this._draggableResizeObserver = null;
}
} }
-86
View File
@@ -576,16 +576,6 @@ export class ShellFacade {
return this._layerBindings._connectDirectionsCamera(...args); return this._layerBindings._connectDirectionsCamera(...args);
} }
/**
* On window resize, keep the draggable panel on-screen — a panel positioned near an edge can fall
* outside a now-smaller viewport (audit U2). pp-toggles is right-pinned, so re-pin (horizontal) and
* clamp its top. No-op until the panel has been positioned (explicit inline top).
* @returns {void}
*/
_reclampDraggablePanels() {
return this._panelPosition._reclampDraggablePanels();
}
/** /**
* Creates one CesiumJS PostProcessStage per visual style and registers * Creates one CesiumJS PostProcessStage per visual style and registers
* it with the scene. Each stage starts with intensity 0 (invisible) * it with the scene. Each stage starts with intensity 0 (invisible)
@@ -911,15 +901,6 @@ export class ShellFacade {
return this._panelChrome._maybeNotifyLayoutReset(...arguments); return this._panelChrome._maybeNotifyLayoutReset(...arguments);
} }
/**
* Sets up drag-to-reposition for legacy floating controls. The right rail
* and left accordion remain fixed so their HUD alignment is deterministic.
* @returns {void}
*/
_initPanelDrag() {
return this._panelPosition._initPanelDrag();
}
_persistAwarenessSelection(...args) { _persistAwarenessSelection(...args) {
return this._layerBindings._persistAwarenessSelection(...args); return this._layerBindings._persistAwarenessSelection(...args);
} }
@@ -980,15 +961,6 @@ export class ShellFacade {
return this._radioControls?._renderRadioState?.(...args); return this._radioControls?._renderRadioState?.(...args);
} }
/**
* Returns the versioned localStorage key for a panel's saved position.
* @param {string} panelId - DOM id of the panel.
* @returns {string} localStorage key.
*/
_panelStorageKey(panelId) {
return this._panelPosition._panelStorageKey(panelId);
}
/** /**
* Returns the versioned localStorage key for a panel's collapsed state. * Returns the versioned localStorage key for a panel's collapsed state.
* @param {string} panelId - DOM id of the panel. * @param {string} panelId - DOM id of the panel.
@@ -1083,64 +1055,6 @@ export class ShellFacade {
return this._panelChrome._syncPanelCollapseButton(...arguments); return this._panelChrome._syncPanelCollapseButton(...arguments);
} }
/**
* Converts a panel from left-positioned to right-anchored so it expands
* leftward on resize. Used for the right-rail parameter panel.
* @param {HTMLElement} panelEl - The panel to re-anchor.
* @returns {void}
*/
_pinPanelToRight(panelEl) {
return this._panelPosition._pinPanelToRight(panelEl);
}
/**
* Restores a panel's top/left position from localStorage.
* Right-rail panels are additionally pinned to the right edge.
* @param {string} panelId - DOM id of the panel.
* @param {HTMLElement} panelEl - The panel DOM element.
* @returns {void}
*/
_restorePanelPosition(panelId, panelEl) {
return this._panelPosition._restorePanelPosition(panelId, panelEl);
}
/**
* Clamp a desired left/top so the panel stays fully on-screen (6px inset), matching the drag
* clamp (ui.js ~1822). Width/height are position-independent, so reading the rect first is safe.
* @param {number} left - desired left (px)
* @param {number} top - desired top (px)
* @param {HTMLElement} panelEl - the panel element
* @returns {{left:number, top:number}}
*/
_clampToViewport(left, top, panelEl) {
return this._panelPosition._clampToViewport(left, top, panelEl);
}
/**
* Persists a panel's current bounding-rect position to localStorage.
* @param {string} panelId - DOM id of the panel.
* @param {HTMLElement} panelEl - The panel DOM element.
* @returns {void}
*/
_savePanelPosition(panelId, panelEl) {
return this._panelPosition._savePanelPosition(panelId, panelEl);
}
/**
* Promotes a panel to the top of the panel z band [PANEL_Z_BASE, PANEL_Z_MAX].
* Renormalizes all promoted panels when the band is exhausted so panels can
* never climb above the voice pill (150), toasts (200), or clean-view exit (300).
* @param {HTMLElement} panelEl - Panel to bring to front.
* @returns {void}
*/
_promotePanelZ(panelEl) {
return this._panelPosition._promotePanelZ(panelEl);
}
_makePanelDraggable(panelId, panelEl, handleEl) {
return this._panelPosition._makePanelDraggable(panelId, panelEl, handleEl);
}
_buildSharePanelState() { _buildSharePanelState() {
return this._panelChrome._buildSharePanelState(...arguments); return this._panelChrome._buildSharePanelState(...arguments);
} }
+1 -44
View File
@@ -3,7 +3,6 @@ import assert from 'node:assert/strict';
import { UiLifetime } from './uiLifetime.js'; import { UiLifetime } from './uiLifetime.js';
import { PanelChrome } from './panelChrome.js'; import { PanelChrome } from './panelChrome.js';
import { PanelLayoutController } from './panelLayoutController.js'; import { PanelLayoutController } from './panelLayoutController.js';
import { PanelPositionControls } from './panelPositionControls.js';
import { ShellFeedback } from './shellFeedback.js'; import { ShellFeedback } from './shellFeedback.js';
import { RecordingControls } from './recordingControls.js'; import { RecordingControls } from './recordingControls.js';
@@ -174,46 +173,6 @@ test('panel layout coalesces each rail and cannot rearm after destruction', () =
} }
}); });
test('panel disposal cancels a live drag without saving or accepting later pointer movement', () => {
const f = fixture();
try {
const owner = new PanelPositionControls({
syncPanelCollapseButton() {},
layoutRightPanels() {},
syncCctvPanelViewport() {},
showToast() {},
});
const panel = f.element(),
handle = f.element();
owner._makePanelDraggable('sample', panel, handle);
handle.dispatchEvent(
Object.assign(new Event('pointerdown', { cancelable: true }), {
button: 0,
clientX: 30,
clientY: 40,
}),
);
assert.equal(panel.classList.contains('panel-dragging'), true);
owner.destroy();
const before = { ...panel.style };
window.dispatchEvent(
Object.assign(new Event('pointermove'), { clientX: 500, clientY: 300 }),
);
window.dispatchEvent(new Event('pointerup'));
assert.deepEqual(panel.style, before);
assert.equal(panel.classList.contains('panel-dragging'), false);
owner._makePanelDraggable('sample', panel, handle);
owner._initPanelDrag();
owner._ppToggles.style.top = '10000px';
owner._reclampDraggablePanels();
assert.equal(owner._ppToggles.style.top, '10000px');
assert.equal(owner.removers.length, 0);
assert.deepEqual(f.writes, []);
} finally {
f.restore();
}
});
test('feedback disposal clears toast and polling work and rejects retained notices', () => { test('feedback disposal clears toast and polling work and rejects retained notices', () => {
const f = fixture(); const f = fixture();
try { try {
@@ -314,7 +273,7 @@ test('weather rail orders between CCTV and Context, resets positions, observes a
removeProperty: (key) => positions.delete(key), removeProperty: (key) => positions.delete(key),
positions, positions,
}; };
panel.classList.add('panel-draggable', 'panel-dragging', 'collapsed'); panel.classList.add('collapsed');
const button = f.element(); const button = f.element();
button.closest = () => panel; button.closest = () => panel;
const attributes = new Map(); const attributes = new Map();
@@ -349,8 +308,6 @@ test('weather rail orders between CCTV and Context, resets positions, observes a
assert.ok(synced.includes('weather-panel')); assert.ok(synced.includes('weather-panel'));
assert.ok(observed.has(weather)); assert.ok(observed.has(weather));
assert.equal(weather.style.positions.size, 0); assert.equal(weather.style.positions.size, 0);
assert.equal(weather.classList.contains('panel-draggable'), false);
assert.equal(weather.classList.contains('panel-dragging'), false);
assert.equal(weather.button.textContent, '◀'); assert.equal(weather.button.textContent, '◀');
assert.equal(weather.button.getAttribute('aria-expanded'), 'false'); assert.equal(weather.button.getAttribute('aria-expanded'), 'false');
assert.equal(weather.button.title, 'Expand WEATHER'); assert.equal(weather.button.title, 'Expand WEATHER');
-31
View File
@@ -72,37 +72,6 @@
text-shadow: 0 0 8px rgba(0, 212, 255, 0.3); text-shadow: 0 0 8px rgba(0, 212, 255, 0.3);
} }
.panel-drag-handle {
cursor: grab;
user-select: none;
}
.panel-drag-handle:active {
cursor: grabbing;
}
.panel-drag-handle.compact {
margin-left: auto;
margin-bottom: 2px;
padding: 2px 8px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.04);
font-family: var(--font-mono);
font-size: 8px;
letter-spacing: 1.4px;
color: var(--text-dim);
}
.panel-drag-handle.compact:hover {
color: var(--text-primary);
border-color: var(--glass-border-hover);
}
.panel-dragging {
opacity: 0.96;
}
.panel-title { .panel-title {
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 9px; font-size: 9px;