feat(tgu-ops-ui): add spacecraft work editor tab

Port the TGU editor prototype into pcp-tgu-ops-ui as a new editor tab, add draft/conflict/pass logic with tests, extend the 2D map picking API, and include editor styles/theme aliases and task/prototype docs.

Validation: npm run test; npm run build in services/pcp-tgu-ops-ui.
This commit is contained in:
Дмитрий Соловьев
2026-05-30 15:28:34 +03:00
parent c3a1a8b4a1
commit f084775cbb
58 changed files with 6609 additions and 7 deletions
@@ -0,0 +1,115 @@
import type { TguPlanUi, TguPlatformUi } from "../../model/timelineTypes";
import { formatDuration } from "./editorPresentation";
type EditorToolbarProps = {
selectedSpacecraftId?: string;
selectedPlan?: TguPlanUi;
platforms: TguPlatformUi[];
canUndo: boolean;
canRedo: boolean;
checking: boolean;
conflictCount: number;
addedCount: number;
dirty: boolean;
applying: boolean;
windowLabel: string;
rightMode: "none" | "inspect" | "shoot" | "downlink";
onSetRightMode: (mode: "shoot" | "downlink") => void;
onUndo: () => void;
onRedo: () => void;
onCheck: () => void;
onApply: () => void;
onZoom: (factor: number) => void;
onPan: (factor: number) => void;
};
export function EditorToolbar({
selectedSpacecraftId,
selectedPlan,
platforms,
canUndo,
canRedo,
checking,
conflictCount,
addedCount,
dirty,
applying,
windowLabel,
rightMode,
onSetRightMode,
onUndo,
onRedo,
onCheck,
onApply,
onZoom,
onPan
}: EditorToolbarProps) {
const platformLabel = selectedSpacecraftId
? platforms.find((platform) => platform.spacecraftId === selectedSpacecraftId)?.name ?? selectedSpacecraftId
: "КА не выбран";
return (
<div className="tgu-editor-toolbar">
<div className="tgu-editor-toolbar__identity">
<span className="tgu-editor-type-dot" aria-hidden="true" />
<div>
<div className="tgu-editor-toolbar__title">{platformLabel}</div>
<div className="tgu-editor-toolbar__meta">
{selectedPlan ? `${selectedPlan.planId} · ${selectedPlan.status}` : "Выберите план на timeline"}
</div>
</div>
</div>
<div className="tgu-editor-toolbar__group">
<button
className={`tgu-button ${rightMode === "shoot" ? "tgu-button--primary" : ""}`}
type="button"
onClick={() => onSetRightMode("shoot")}
disabled={!selectedSpacecraftId}
>
+ Съёмка
</button>
<button
className={`tgu-button ${rightMode === "downlink" ? "tgu-button--primary" : ""}`}
type="button"
onClick={() => onSetRightMode("downlink")}
disabled={!selectedSpacecraftId}
>
+ Сброс
</button>
</div>
<div className="tgu-editor-toolbar__spacer" />
<div className="tgu-editor-window-control" aria-label="Окно редактора">
<button type="button" onClick={() => onPan(-0.25)} title="Назад по времени"></button>
<button type="button" onClick={() => onZoom(1.4)} title="Отдалить"></button>
<span>{windowLabel || formatDuration(0)}</span>
<button type="button" onClick={() => onZoom(0.7)} title="Приблизить">+</button>
<button type="button" onClick={() => onPan(0.25)} title="Вперёд по времени"></button>
</div>
<div className="tgu-editor-toolbar__group">
<button className="tgu-icon-button" type="button" onClick={onUndo} disabled={!canUndo} title="Отменить">
</button>
<button className="tgu-icon-button" type="button" onClick={onRedo} disabled={!canRedo} title="Вернуть">
</button>
</div>
<button
className={`tgu-button ${conflictCount > 0 ? "tgu-editor-button--warning" : ""}`}
type="button"
onClick={onCheck}
disabled={checking || !selectedSpacecraftId}
>
{checking ? "Проверка..." : conflictCount > 0 ? `Конфликты · ${conflictCount}` : "Проверить"}
</button>
<button className="tgu-button tgu-button--primary" type="button" onClick={onApply} disabled={!dirty || applying}>
{applying ? "Применение..." : `Применить${addedCount > 0 ? ` · +${addedCount}` : ""}`}
</button>
</div>
);
}