fix(map): align canonical map actions

This commit is contained in:
DCCONSTRUCTIONS
2026-07-29 17:00:50 +03:00
parent 8e3710b493
commit f2a4dfe863
3 changed files with 69 additions and 59 deletions
+31 -8
View File
@@ -39,19 +39,37 @@
max-width: min(58%, 42rem);
}
.mission-map__toolbar {
.mission-map__map-actions {
position: absolute;
z-index: 4;
right: 50%;
bottom: 1rem;
left: auto;
transform: translateX(50%);
z-index: 5;
top: 1rem;
right: 1rem;
display: flex;
gap: 0.5rem;
}
.mission-map__map-action.nodedc-icon-button {
background: var(--nodedc-map-glass-bg);
color: var(--nodedc-map-glass-text);
box-shadow: var(--nodedc-glass-dropdown-shadow);
backdrop-filter: blur(var(--nodedc-blur-control));
-webkit-backdrop-filter: blur(var(--nodedc-blur-control));
}
.mission-map__map-action.nodedc-icon-button:hover:not(:disabled) {
background: var(--nodedc-map-glass-hover);
color: var(--nodedc-map-glass-text);
}
.mission-map__map-action.nodedc-icon-button[aria-pressed="true"] {
background: var(--nodedc-map-glass-active);
color: var(--nodedc-glass-control-active-text);
}
.mission-map__layers {
position: absolute;
z-index: 5;
top: 1rem;
top: calc(1rem + var(--nodedc-icon-button-size) + 0.6rem);
right: 1rem;
display: grid;
width: min(23rem, calc(100% - 2rem));
@@ -180,8 +198,13 @@
}
.mission-map__layers {
top: 0.65rem;
top: calc(0.65rem + var(--nodedc-icon-button-size) + 0.6rem);
right: 0.65rem;
width: calc(100% - 1.3rem);
}
.mission-map__map-actions {
top: 0.65rem;
right: 0.65rem;
}
}
@@ -1,7 +1,6 @@
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
type CSSProperties,
@@ -24,7 +23,6 @@ import {
MapGlassSurface,
RangeControl,
StatusBadge,
Toolbar,
Window,
WindowFooterActions,
} from "@nodedc/ui-react";
@@ -48,8 +46,6 @@ const editableInspectorSections = new Set<MapInspectorSection>([
"tile-cache",
]);
type ToolbarAction = "settings" | "layers" | "reset-view";
export function WorldMapWorkspace({
definition,
}: {
@@ -105,15 +101,6 @@ export function WorldMapWorkspace({
}
}, [controller, draft]);
const resetRenderer = useCallback(() => {
operatorCameraInteraction.current = false;
updateDraft((current) => {
current.view.camera = null;
return current;
});
setRendererGeneration((generation) => generation + 1);
}, [updateDraft]);
const retryRenderer = useCallback(() => {
operatorCameraInteraction.current = false;
setRendererGeneration((generation) => generation + 1);
@@ -137,35 +124,6 @@ export function WorldMapWorkspace({
});
}, [updateDraft]);
const toolbarItems = useMemo(() => [
{
id: "settings" as const,
label: "Настройки карты",
icon: "settings" as const,
active: settingsOpen,
onSelect: () => {
setSettingsOpen((open) => !open);
setLayersOpen(false);
},
},
{
id: "layers" as const,
label: "Слои карты",
icon: "grid" as const,
active: layersOpen,
onSelect: () => {
setLayersOpen((open) => !open);
setSettingsOpen(false);
},
},
{
id: "reset-view" as const,
label: "Сбросить камеру",
icon: "target" as const,
onSelect: resetRenderer,
},
], [layersOpen, resetRenderer, settingsOpen]);
const status = runtimePresentation(runtimeState);
const viewBlocking = !viewInitialized && controller.state !== "ready";
const runtimeBlocking = runtimeState.phase === "gateway-unavailable"
@@ -234,15 +192,30 @@ export function WorldMapWorkspace({
/>
) : null}
<Toolbar<ToolbarAction>
className="mission-map__toolbar"
placement="bottom"
label="Инструменты карты"
minSize={42}
maxSize={54}
lensCount={3}
items={toolbarItems}
/>
<div className="mission-map__map-actions" aria-label="Управление картой">
<IconButton
className="mission-map__map-action"
label="Настройки карты"
aria-pressed={settingsOpen}
onClick={() => {
setSettingsOpen((open) => !open);
setLayersOpen(false);
}}
>
<Icon name="settings" size={18} />
</IconButton>
<IconButton
className="mission-map__map-action"
label="Слои карты"
aria-pressed={layersOpen}
onClick={() => {
setLayersOpen((open) => !open);
setSettingsOpen(false);
}}
>
<Icon name="grid" size={18} />
</IconButton>
</div>
{layersOpen ? (
<MapGlassSurface
@@ -1,4 +1,5 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { after, before, test } from "node:test";
import { createServer } from "vite";
@@ -105,3 +106,16 @@ test("map view fails closed on credentials, runtime URLs and unbound selection",
/stable subject id|стабильный subject id/i,
);
});
test("map workspace keeps the canonical top actions without a bottom toolbar", async () => {
const source = await readFile(
new URL("../src/workspaces/map/WorldMapWorkspace.tsx", import.meta.url),
"utf8",
);
assert.doesNotMatch(source, /<Toolbar/);
assert.match(source, /className="mission-map__map-actions"/);
assert.match(source, /label="Настройки карты"/);
assert.match(source, /label="Слои карты"/);
assert.doesNotMatch(source, /label="Сбросить камеру"/);
});