Files
NODEDC_MISSION_CORE/apps/node-agent/ui/src/usePresentation.ts
T

58 lines
3.3 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from "react";
import type { EnvironmentSettings, UploadedEnvironmentMedia } from "@nodedc/ui-core";
import { APIError, request } from "./api";
export function defaultPresentation(): EnvironmentSettings {
return { revision: 0, pages: { home: {
headerLabel: "Mission Core Node", eyebrow: "NODEDC / MISSION CORE NODE", title: "Mission Core Node",
description: "Подключение устройств, запись и просмотр данных на бортовом компьютере.",
primaryWorkspaceId: "sensors", secondaryWorkspaceId: "environment",
background: { enabled: false, imageDurationSeconds: 10, items: [] },
} } };
}
export function usePresentation(authorized: boolean) {
const [settings, setSettings] = useState(defaultPresentation);
const [state, setState] = useState<"loading" | "ready" | "saving" | "error">("loading");
const [error, setError] = useState<string | null>(null);
const epoch = useRef(0);
const refresh = useCallback(async () => {
if (!authorized) return;
const generation = ++epoch.current;
setState("loading");
try {
const next = await request<EnvironmentSettings & { schema: string }>("/api/presentation/settings");
if (generation !== epoch.current) return;
if (next.schema !== "missioncore.node.presentation/v1" || !next.pages.home || Object.keys(next.pages).length !== 1) throw new Error("Версия оформления главной не поддерживается.");
setSettings(next); setError(null); setState("ready");
} catch (reason) {
if (generation !== epoch.current) return;
setError(reason instanceof Error ? reason.message : "Не удалось загрузить оформление главной."); setState("error");
}
}, [authorized]);
useEffect(() => { void refresh(); return () => { epoch.current += 1; }; }, [refresh]);
const save = useCallback(async (draft: EnvironmentSettings) => {
setState("saving"); setError(null);
try {
const next = await request<EnvironmentSettings>("/api/presentation/settings", "PUT", draft);
setSettings(next); setState("ready"); return next;
} catch (reason) {
const message = reason instanceof Error ? reason.message : "Не удалось сохранить оформление главной.";
setError(message); setState("error"); throw new Error(message);
}
}, []);
const upload = useCallback(async (surfaceId: string, itemId: string, file: File): Promise<UploadedEnvironmentMedia> => {
if (surfaceId !== "home") throw new Error("Оформление доступно только для главной страницы.");
const response = await fetch(`/api/presentation/media/home/${encodeURIComponent(itemId)}`, {
method: "PUT", credentials: "same-origin", signal: AbortSignal.timeout(300000),
headers: { "Content-Type": file.type || "application/octet-stream", "X-NODEDC-File-Name": encodeURIComponent(file.name) }, body: file,
});
if (!response.ok) {
const body = await response.json().catch(() => ({}));
throw new APIError(body.error ?? "Не удалось загрузить фон главной страницы.", response.status);
}
return response.json();
}, []);
return { settings, state, error, refresh, save, upload };
}