Add shared automation and WebUI editing primitives
This commit is contained in:
+112
-8
@@ -1,4 +1,4 @@
|
||||
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
|
||||
import { Navigate, Route, Routes, useLocation } from "react-router";
|
||||
import { lazy, useEffect, useMemo, useState } from "react";
|
||||
import { fetchSession, fetchShellAuth, updateProfile } from "./api/auth";
|
||||
import { fetchPlatformModules, fetchPlatformPublicModules, fetchPlatformStatus } from "./api/platform";
|
||||
@@ -11,7 +11,11 @@ import { PermissionBoundary } from "./components/AccessBoundary";
|
||||
import { firstAccessibleRoute, loadInstalledPublicWebModules, loadInstalledWebModules, loadRemotePublicWebModules, loadRemoteWebModules, moduleInstalled, navItemsForModules, publicRouteContributionsForModules, routeContributionsForModules, uiCapability } from "./platform/modules";
|
||||
import { PlatformModulesProvider } from "./platform/ModuleContext";
|
||||
import { PlatformViewProvider } from "./platform/ViewContext";
|
||||
import { PLATFORM_VIEW_CHANGED_EVENT } from "./platform/views";
|
||||
import {
|
||||
PLATFORM_VIEW_CHANGED_EVENT,
|
||||
PLATFORM_WORKFLOW_VIEW_CHANGED_EVENT,
|
||||
type WorkflowViewChangedEventDetail
|
||||
} from "./platform/views";
|
||||
import { PLATFORM_MODULES_CHANGED_EVENT } from "./platform/moduleEvents";
|
||||
import { UnsavedChangesProvider } from "./components/UnsavedChangesGuard";
|
||||
import { PlatformLanguageProvider, type PlatformLanguage } from "./i18n/LanguageContext";
|
||||
@@ -45,7 +49,9 @@ export default function App() {
|
||||
const [backendReachable, setBackendReachable] = useState(true);
|
||||
const [systemLanguages, setSystemLanguages] = useState<{available: PlatformLanguage[];enabled: string[];defaultLanguage: string;} | null>(null);
|
||||
const [reloginMessage, setReloginMessage] = useState("");
|
||||
const [viewProjection, setViewProjection] = useState<EffectiveViewProjection | null>(null);
|
||||
const [baseViewProjection, setBaseViewProjection] = useState<EffectiveViewProjection | null>(null);
|
||||
const [workflowViewProjection, setWorkflowViewProjection] = useState<EffectiveViewProjection | null>(null);
|
||||
const viewProjection = workflowViewProjection ?? baseViewProjection;
|
||||
|
||||
const webModules = useMemo(() => mergeWebModules(localWebModules, remoteWebModules), [localWebModules, remoteWebModules]);
|
||||
const publicWebModules = useMemo(() => mergeWebModules(localPublicWebModules, remotePublicWebModules), [localPublicWebModules, remotePublicWebModules]);
|
||||
@@ -65,7 +71,8 @@ export default function App() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!auth || !viewsRuntime) {
|
||||
setViewProjection(null);
|
||||
setBaseViewProjection(null);
|
||||
setWorkflowViewProjection(null);
|
||||
return;
|
||||
}
|
||||
const currentAuth = auth;
|
||||
@@ -74,17 +81,22 @@ export default function App() {
|
||||
async function loadEffectiveView() {
|
||||
try {
|
||||
const projection = await currentRuntime.loadEffectiveView(settings, currentAuth);
|
||||
if (!cancelled) setViewProjection(projection);
|
||||
if (!cancelled) setBaseViewProjection(projection);
|
||||
} catch (error) {
|
||||
if (!cancelled) setViewProjection(null);
|
||||
if (!cancelled) setBaseViewProjection(null);
|
||||
console.error("Failed to load the effective View", error);
|
||||
}
|
||||
}
|
||||
function reloadNormalView() {
|
||||
setWorkflowViewProjection(null);
|
||||
clearStoredWorkflowView(currentAuth);
|
||||
void loadEffectiveView();
|
||||
}
|
||||
void loadEffectiveView();
|
||||
window.addEventListener(PLATFORM_VIEW_CHANGED_EVENT, loadEffectiveView);
|
||||
window.addEventListener(PLATFORM_VIEW_CHANGED_EVENT, reloadNormalView);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.removeEventListener(PLATFORM_VIEW_CHANGED_EVENT, loadEffectiveView);
|
||||
window.removeEventListener(PLATFORM_VIEW_CHANGED_EVENT, reloadNormalView);
|
||||
};
|
||||
}, [
|
||||
auth?.user?.id,
|
||||
@@ -96,6 +108,36 @@ export default function App() {
|
||||
viewsRuntime
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!auth || !viewsRuntime) {
|
||||
setWorkflowViewProjection(null);
|
||||
return;
|
||||
}
|
||||
const currentAuth = auth;
|
||||
setWorkflowViewProjection(loadStoredWorkflowView(currentAuth));
|
||||
function applyWorkflowView(event: Event) {
|
||||
const detail = (event as CustomEvent<WorkflowViewChangedEventDetail>).detail;
|
||||
const projection = detail?.projection ?? null;
|
||||
setWorkflowViewProjection(projection);
|
||||
storeWorkflowView(currentAuth, projection, detail?.contextId);
|
||||
}
|
||||
window.addEventListener(
|
||||
PLATFORM_WORKFLOW_VIEW_CHANGED_EVENT,
|
||||
applyWorkflowView
|
||||
);
|
||||
return () => {
|
||||
window.removeEventListener(
|
||||
PLATFORM_WORKFLOW_VIEW_CHANGED_EVENT,
|
||||
applyWorkflowView
|
||||
);
|
||||
};
|
||||
}, [
|
||||
auth?.user?.id,
|
||||
auth?.active_tenant?.id,
|
||||
auth?.tenant.id,
|
||||
viewsRuntime
|
||||
]);
|
||||
|
||||
function updateSettings(next: ApiSettings) {
|
||||
setSettings(next);
|
||||
saveApiSettings(next);
|
||||
@@ -662,3 +704,65 @@ function mergeWebModules(localModules: PlatformWebModule[], remoteModules: Platf
|
||||
})];
|
||||
|
||||
}
|
||||
|
||||
const WORKFLOW_VIEW_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
|
||||
|
||||
type StoredWorkflowView = {
|
||||
projection: EffectiveViewProjection;
|
||||
contextId?: string | null;
|
||||
expiresAt: number;
|
||||
};
|
||||
|
||||
function workflowViewStorageKey(auth: AuthInfo): string {
|
||||
const tenant = auth.active_tenant ?? auth.tenant;
|
||||
return `govoplan:workflow-view:${tenant.id}:${auth.user.account_id}`;
|
||||
}
|
||||
|
||||
function loadStoredWorkflowView(
|
||||
auth: AuthInfo
|
||||
): EffectiveViewProjection | null {
|
||||
try {
|
||||
const key = workflowViewStorageKey(auth);
|
||||
const raw = window.sessionStorage.getItem(key);
|
||||
if (!raw) return null;
|
||||
const stored = JSON.parse(raw) as Partial<StoredWorkflowView>;
|
||||
if (
|
||||
typeof stored.expiresAt !== "number"
|
||||
|| stored.expiresAt <= Date.now()
|
||||
|| !stored.projection
|
||||
|| !Array.isArray(stored.projection.visibleSurfaceIds)
|
||||
) {
|
||||
window.sessionStorage.removeItem(key);
|
||||
return null;
|
||||
}
|
||||
return stored.projection;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function storeWorkflowView(
|
||||
auth: AuthInfo,
|
||||
projection: EffectiveViewProjection | null,
|
||||
contextId?: string | null
|
||||
): void {
|
||||
try {
|
||||
const key = workflowViewStorageKey(auth);
|
||||
if (!projection) {
|
||||
window.sessionStorage.removeItem(key);
|
||||
return;
|
||||
}
|
||||
const stored: StoredWorkflowView = {
|
||||
projection,
|
||||
contextId,
|
||||
expiresAt: Date.now() + WORKFLOW_VIEW_SESSION_TTL_MS
|
||||
};
|
||||
window.sessionStorage.setItem(key, JSON.stringify(stored));
|
||||
} catch {
|
||||
// Session persistence is optional; the in-memory projection still applies.
|
||||
}
|
||||
}
|
||||
|
||||
function clearStoredWorkflowView(auth: AuthInfo): void {
|
||||
storeWorkflowView(auth, null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user