pcp-tgu-ops-ui: вкладка Редактор — план, включения, нижняя панель
- Слева выводим даты начала/конца плана и корректные счётчики съёмки/сброса (из plan modes, а не из заглушки-черновика). - На таймлайне показываем реальные включения плана (read-only): убран фейковый «сброс на весь интервал»; вместо линии «сейчас» — маркеры начала и конца плана; клик по включению открывает его инфо справа. - Включения съёмки и сброса разнесены на два ряда (верх/низ) и различаются цветом, слева — подписи рядов. - Нижняя панель: активная дорожка резиновая — примыкает к низу и не пустует, при подъёме панели ряды расширяются; высота бара ограничена потолком и центрируется в ряду, чтобы узкие включения не вытягивались. - Время в редакторе парсится/форматируется через utils/utcTime.
This commit is contained in:
@@ -6,6 +6,7 @@ import type { RequestMapItem } from "../../model/requestTypes";
|
||||
import { stationPasses, type OrbitPassInfo, type PassRange } from "../tgu-map-2d/geometry/passes";
|
||||
import { fetchPointVisibility, fetchSatelliteRva, type PointVisibilityParam, type RvaZone } from "../../api/ballisticsApi";
|
||||
import { buildSurveyRoute, saveDropRoute, saveSurveyRoute, type SurveyRoute, type SurveyRouteAnchor } from "../../api/missionPlanningApi";
|
||||
import { parseUtc } from "../../utils/utcTime";
|
||||
import { Tgu2DMapView } from "../tgu-map-2d/Tgu2DMapView";
|
||||
import { parseWktPolygon } from "../tgu-map-2d/geometry/wktParser";
|
||||
import { buildStationMarkers } from "../tgu-map-2d/Tgu2DMapLayers";
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
deleteWork,
|
||||
duplicateWork,
|
||||
mutate,
|
||||
planModesToWorks,
|
||||
redo,
|
||||
seedDraft,
|
||||
undo,
|
||||
@@ -40,6 +42,7 @@ import type {
|
||||
EditorTimelineWindow,
|
||||
EditorVisibilityTrack,
|
||||
EditorVisibilityWindow,
|
||||
EditorWork,
|
||||
EditorWorkPatch,
|
||||
TguEditorTabProps
|
||||
} from "./model/editorTypes";
|
||||
@@ -68,7 +71,9 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
const idCounterRef = useRef(0);
|
||||
const targetCounterRef = useRef(0);
|
||||
const centerRef = useRef<HTMLElement>(null);
|
||||
const [mapRatio, setMapRatio] = useState(0.72);
|
||||
// Доля высоты под карту: по умолчанию карта крупная, а нижняя панель компактно
|
||||
// примыкает к низу. Подъём резайзера вверх уменьшает долю и расширяет панель.
|
||||
const [mapRatio, setMapRatio] = useState(0.8);
|
||||
const [history, setHistory] = useState<DraftHistory>(() => ({
|
||||
draft: seedDraft(selectedPlan, selectedSpacecraftId),
|
||||
past: [],
|
||||
@@ -124,14 +129,17 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
.map(([revolution, pts], idx) => ({
|
||||
index: idx + 1,
|
||||
revolution,
|
||||
startMs: Date.parse(pts[0].time),
|
||||
endMs: Date.parse(pts[pts.length - 1].time),
|
||||
startMs: parseUtc(pts[0].time),
|
||||
endMs: parseUtc(pts[pts.length - 1].time),
|
||||
}));
|
||||
}, [flightLineData]);
|
||||
const modes: EditorMode[] = DEFAULT_EDITOR_MODES;
|
||||
// Существующие включения плана (read-only) + правки из черновика — то, что видно на таймлайне.
|
||||
const planWorks = useMemo<EditorWork[]>(() => planModesToWorks(planModes), [planModes]);
|
||||
const displayWorks = useMemo<EditorWork[]>(() => [...planWorks, ...draft.works], [planWorks, draft.works]);
|
||||
const liveConflicts = useMemo(() => overlaps(draft.works), [draft.works]);
|
||||
const conflicts = useMemo(() => mergeConflictMaps(liveConflicts, serviceConflicts), [liveConflicts, serviceConflicts]);
|
||||
const selectedWork = draft.works.find((work) => work.id === selectedWorkId);
|
||||
const selectedWork = displayWorks.find((work) => work.id === selectedWorkId);
|
||||
const activeLabel = platformLabel(platforms, selectedSpacecraftId);
|
||||
const [shootPasses, setShootPasses] = useState<EditorVisibilityWindow[]>([]);
|
||||
const [shootPassesLoading, setShootPassesLoading] = useState(false);
|
||||
@@ -187,6 +195,10 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
|
||||
const dirty = history.past.length > 0 || draft.works.some((work) => work.isNew);
|
||||
const addedCount = draft.works.filter((work) => work.isNew).length;
|
||||
// Реальный состав плана берём из включений (plan modes), а не из черновика:
|
||||
// черновик сидируется заглушкой, когда у плана нет встроенных works.
|
||||
const planSurveyCount = useMemo(() => planModes.filter((mode) => mode.type === "SURVEY").length, [planModes]);
|
||||
const planDownlinkCount = useMemo(() => planModes.filter((mode) => mode.type === "DROP").length, [planModes]);
|
||||
|
||||
useEffect(() => {
|
||||
setHistory({ draft: seedDraft(selectedPlan, selectedSpacecraftId), past: [], future: [] });
|
||||
@@ -559,7 +571,7 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
const el = centerRef.current;
|
||||
if (!el) return;
|
||||
const dy = ev.clientY - startY;
|
||||
setMapRatio(Math.max(0.15, Math.min(0.80, startRatio + dy / el.clientHeight)));
|
||||
setMapRatio(Math.max(0.15, Math.min(0.86, startRatio + dy / el.clientHeight)));
|
||||
};
|
||||
|
||||
const onUp = () => {
|
||||
@@ -618,7 +630,8 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
selectedSpacecraftId={selectedSpacecraftId}
|
||||
selectedPlan={selectedPlan}
|
||||
platforms={platforms}
|
||||
draft={draft}
|
||||
surveyCount={planSurveyCount}
|
||||
downlinkCount={planDownlinkCount}
|
||||
conflictCount={conflictCount(conflicts)}
|
||||
contextSpacecraftIds={contextSpacecraftIds}
|
||||
onToggleContext={(spacecraftId) => {
|
||||
@@ -681,10 +694,11 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
</div>
|
||||
<EditorTimeline
|
||||
activeLabel={activeLabel}
|
||||
draft={draft.works}
|
||||
works={displayWorks}
|
||||
selectedWorkId={selectedWorkId}
|
||||
window={window}
|
||||
nowMs={Date.now()}
|
||||
planStartMs={selectedPlan?.startMs}
|
||||
planEndMs={selectedPlan?.endMs}
|
||||
conflicts={conflicts}
|
||||
contextLanes={contextLanes}
|
||||
visibilityTracks={visibilityTracks}
|
||||
@@ -699,7 +713,6 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
setRightMode("none");
|
||||
}
|
||||
}}
|
||||
onChangeWork={onChangeWork}
|
||||
/>
|
||||
</section>
|
||||
|
||||
@@ -711,6 +724,7 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
modes={modes}
|
||||
stations={stations}
|
||||
conflicts={conflicts[selectedWork.id] ?? []}
|
||||
readOnly={selectedWork.origin === "plan"}
|
||||
onUpdate={onChangeWork}
|
||||
onDelete={(workId) => {
|
||||
changeDraft((current) => deleteWork(current, workId));
|
||||
@@ -810,7 +824,7 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
}
|
||||
|
||||
function visibilityWindowFromParam(param: PointVisibilityParam): EditorVisibilityWindow {
|
||||
const timeMs = Date.parse(param.time);
|
||||
const timeMs = parseUtc(param.time);
|
||||
const half = SHOOT_WINDOW_MS / 2;
|
||||
return {
|
||||
startMs: timeMs - half,
|
||||
@@ -851,8 +865,7 @@ function buildStations(selectedPlan: TguEditorTabProps["selectedPlan"]): EditorG
|
||||
* зоны ЗРВ приходят без таймзоны, а времена съёмок — с offset; приводим к одной шкале.
|
||||
*/
|
||||
function localDateTimeMs(s: string): number {
|
||||
const hasZone = /[zZ]$|[+-]\d{2}:?\d{2}$/.test(s);
|
||||
return Date.parse(hasZone ? s : `${s}Z`);
|
||||
return parseUtc(s);
|
||||
}
|
||||
|
||||
function buildVisibilityTracks(
|
||||
|
||||
Reference in New Issue
Block a user