fix(map): align canonical map actions
This commit is contained in:
@@ -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="Сбросить камеру"/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user