58 lines
3.3 KiB
TypeScript
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 };
|
|
}
|