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); max-width: min(58%, 42rem);
} }
.mission-map__toolbar { .mission-map__map-actions {
position: absolute; position: absolute;
z-index: 4; z-index: 5;
right: 50%; top: 1rem;
bottom: 1rem; right: 1rem;
left: auto; display: flex;
transform: translateX(50%); 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 { .mission-map__layers {
position: absolute; position: absolute;
z-index: 5; z-index: 5;
top: 1rem; top: calc(1rem + var(--nodedc-icon-button-size) + 0.6rem);
right: 1rem; right: 1rem;
display: grid; display: grid;
width: min(23rem, calc(100% - 2rem)); width: min(23rem, calc(100% - 2rem));
@@ -180,8 +198,13 @@
} }
.mission-map__layers { .mission-map__layers {
top: 0.65rem; top: calc(0.65rem + var(--nodedc-icon-button-size) + 0.6rem);
right: 0.65rem; right: 0.65rem;
width: calc(100% - 1.3rem); width: calc(100% - 1.3rem);
} }
.mission-map__map-actions {
top: 0.65rem;
right: 0.65rem;
}
} }
@@ -1,7 +1,6 @@
import { import {
useCallback, useCallback,
useEffect, useEffect,
useMemo,
useRef, useRef,
useState, useState,
type CSSProperties, type CSSProperties,
@@ -24,7 +23,6 @@ import {
MapGlassSurface, MapGlassSurface,
RangeControl, RangeControl,
StatusBadge, StatusBadge,
Toolbar,
Window, Window,
WindowFooterActions, WindowFooterActions,
} from "@nodedc/ui-react"; } from "@nodedc/ui-react";
@@ -48,8 +46,6 @@ const editableInspectorSections = new Set<MapInspectorSection>([
"tile-cache", "tile-cache",
]); ]);
type ToolbarAction = "settings" | "layers" | "reset-view";
export function WorldMapWorkspace({ export function WorldMapWorkspace({
definition, definition,
}: { }: {
@@ -105,15 +101,6 @@ export function WorldMapWorkspace({
} }
}, [controller, draft]); }, [controller, draft]);
const resetRenderer = useCallback(() => {
operatorCameraInteraction.current = false;
updateDraft((current) => {
current.view.camera = null;
return current;
});
setRendererGeneration((generation) => generation + 1);
}, [updateDraft]);
const retryRenderer = useCallback(() => { const retryRenderer = useCallback(() => {
operatorCameraInteraction.current = false; operatorCameraInteraction.current = false;
setRendererGeneration((generation) => generation + 1); setRendererGeneration((generation) => generation + 1);
@@ -137,35 +124,6 @@ export function WorldMapWorkspace({
}); });
}, [updateDraft]); }, [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 status = runtimePresentation(runtimeState);
const viewBlocking = !viewInitialized && controller.state !== "ready"; const viewBlocking = !viewInitialized && controller.state !== "ready";
const runtimeBlocking = runtimeState.phase === "gateway-unavailable" const runtimeBlocking = runtimeState.phase === "gateway-unavailable"
@@ -234,15 +192,30 @@ export function WorldMapWorkspace({
/> />
) : null} ) : null}
<Toolbar<ToolbarAction> <div className="mission-map__map-actions" aria-label="Управление картой">
className="mission-map__toolbar" <IconButton
placement="bottom" className="mission-map__map-action"
label="Инструменты карты" label="Настройки карты"
minSize={42} aria-pressed={settingsOpen}
maxSize={54} onClick={() => {
lensCount={3} setSettingsOpen((open) => !open);
items={toolbarItems} 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 ? ( {layersOpen ? (
<MapGlassSurface <MapGlassSurface
@@ -1,4 +1,5 @@
import assert from "node:assert/strict"; import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { after, before, test } from "node:test"; import { after, before, test } from "node:test";
import { createServer } from "vite"; 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, /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="Сбросить камеру"/);
});