diff --git a/packages/suite-base/src/Workspace.tsx b/packages/suite-base/src/Workspace.tsx index bc31af6e58..1c6ad904fd 100644 --- a/packages/suite-base/src/Workspace.tsx +++ b/packages/suite-base/src/Workspace.tsx @@ -60,6 +60,10 @@ import { TopicList } from "@lichtblick/suite-base/components/TopicList"; import VariablesList from "@lichtblick/suite-base/components/VariablesList"; import { WorkspaceDialogs } from "@lichtblick/suite-base/components/WorkspaceDialogs"; import { useAppContext } from "@lichtblick/suite-base/context/AppContext"; +import { + LayoutData, + useCurrentLayoutActions, +} from "@lichtblick/suite-base/context/CurrentLayoutContext"; import { LayoutState, useCurrentLayoutSelector, @@ -565,22 +569,57 @@ function WorkspaceContent(props: WorkspaceProps): React.JSX.Element { [getMessagePipeline], ); + const { setCurrentLayout } = useCurrentLayoutActions(); + const targetUrlState = useMemo(() => { const deepLinks = props.deepLinks ?? []; return deepLinks[0] ? parseAppURLState(new URL(deepLinks[0])) : undefined; }, [props.deepLinks]); const [unappliedSourceArgs, setUnappliedSourceArgs] = useState( - targetUrlState ? { ds: targetUrlState.ds, dsParams: targetUrlState.dsParams } : undefined, + targetUrlState + ? { + ds: targetUrlState.ds, + dsParams: targetUrlState.dsParams, + layoutUrl: targetUrlState.layoutUrl, + } + : undefined, ); const selectEvent = useEvents(selectSelectEvent); + + const fetchLayoutFromUrl = async (layoutUrl: string) => { + if (!layoutUrl) { + return; + } + let res; + try { + res = await fetch(layoutUrl); + } catch { + log.debug(`Could not load the layout from ${layoutUrl}`); + return; + } + const parsedState: unknown = JSON.parse(await res.text()); + + if (typeof parsedState !== "object" || !parsedState) { + log.debug(`${layoutUrl} does not contain valid layout JSON`); + return; + } + + const layoutData = parsedState as LayoutData; + setCurrentLayout({ + data: layoutData, + }); + }; + // Load data source from URL. useEffect(() => { if (!unappliedSourceArgs) { return; } + let shouldUpdate; + // Apply any available data source args if (unappliedSourceArgs.ds) { log.debug("Initialising source from url", unappliedSourceArgs); @@ -589,7 +628,15 @@ function WorkspaceContent(props: WorkspaceProps): React.JSX.Element { params: unappliedSourceArgs.dsParams, }); selectEvent(unappliedSourceArgs.dsParams?.eventId); - setUnappliedSourceArgs({ ds: undefined, dsParams: undefined }); + shouldUpdate = true; + } + // Apply any available datasource args + if (unappliedSourceArgs.layoutUrl) { + fetchLayoutFromUrl(unappliedSourceArgs.layoutUrl); + shouldUpdate = true; + } + if (shouldUpdate) { + setUnappliedSourceArgs({ ds: undefined, dsParams: undefined, layoutUrl: undefined }); } }, [selectEvent, selectSource, unappliedSourceArgs, setUnappliedSourceArgs]); diff --git a/packages/suite-base/src/util/appURLState.ts b/packages/suite-base/src/util/appURLState.ts index fc6031ae5d..1eab5bb759 100644 --- a/packages/suite-base/src/util/appURLState.ts +++ b/packages/suite-base/src/util/appURLState.ts @@ -12,6 +12,7 @@ import { LayoutID } from "@lichtblick/suite-base/context/CurrentLayoutContext"; export type AppURLState = { ds?: string; + layoutUrl?: string; dsParams?: Record; layoutId?: LayoutID; time?: Time; @@ -43,6 +44,14 @@ export function updateAppURLState(url: URL, urlState: AppURLState): URL { } } + if ("layoutUrl" in urlState) { + if (urlState.layoutUrl) { + newURL.searchParams.set("layoutUrl", urlState.layoutUrl); + } else { + newURL.searchParams.delete("layoutUrl"); + } + } + if ("dsParams" in urlState) { [...newURL.searchParams].forEach(([k]) => { if (k.startsWith("ds.")) { @@ -69,6 +78,7 @@ export function updateAppURLState(url: URL, urlState: AppURLState): URL { */ export function parseAppURLState(url: URL): AppURLState | undefined { const ds = url.searchParams.get("ds") ?? undefined; + const layoutUrl = url.searchParams.get("layoutUrl"); const timeString = url.searchParams.get("time"); const time = timeString == undefined ? undefined : fromRFC3339String(timeString); const dsParams: Record = {}; @@ -83,6 +93,7 @@ export function parseAppURLState(url: URL): AppURLState | undefined { { time, ds, + layoutUrl, dsParams: _.isEmpty(dsParams) ? undefined : dsParams, }, _.isEmpty,