LCOV - code coverage report
Current view: top level - dashql-app/src/session - session_view_mode.tsx Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 88 88
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as React from 'react';
       2              : 
       3              : import { useLocation, useNavigate } from 'react-router-dom';
       4              : 
       5              : /// How a session is shown
       6            1 : export enum SessionViewMode {
       7            1 :     Session = 0,
       8            1 :     Shell = 1,
       9            1 :     Dashboard = 2,
      10            1 :     Dataflow = 3,
      11            1 : }
      12              : 
      13              : /// The views that are part of the URL, as the value of the `view` query parameter
      14            1 : const VIEW_PARAMETERS: [SessionViewMode, string][] = [
      15            1 :     [SessionViewMode.Dashboard, 'dashboard'],
      16            1 :     [SessionViewMode.Dataflow, 'dataflow'],
      17            1 : ];
      18              : 
      19              : export interface SessionViewModeContextValue {
      20              :     mode: SessionViewMode;
      21              :     setMode: React.Dispatch<React.SetStateAction<SessionViewMode>>;
      22              : }
      23            1 : const SESSION_VIEW_MODE_CTX = React.createContext<SessionViewModeContextValue | null>(null);
      24            1 : export const useSessionViewMode = () => React.useContext(SESSION_VIEW_MODE_CTX)!;
      25              : 
      26              : /// The view of the `view` query parameter, the session if the view is unknown
      27            1 : export function sessionViewModeFromSearch(search: string): SessionViewMode {
      28            1 :     const value = new URLSearchParams(search).get('view');
      29            1 :     const view = VIEW_PARAMETERS.find(([, parameter]) => parameter === value);
      30            1 :     return view != null ? view[0] : SessionViewMode.Session;
      31            1 : }
      32              : 
      33            1 : export function sessionViewSearch(search: string, mode: SessionViewMode): string {
      34            1 :     const params = new URLSearchParams(search);
      35            1 :     const view = VIEW_PARAMETERS.find(([m]) => m === mode);
      36            1 :     if (view != null) params.set('view', view[1]);
      37            1 :     else params.delete('view');
      38            1 :     const next = params.toString();
      39            1 :     return next ? `?${next}` : '';
      40            1 : }
      41              : 
      42            1 : export function sessionViewNavigation(
      43            1 :     location: { pathname: string; search: string; hash: string; state: unknown },
      44            1 :     mode: SessionViewMode,
      45            1 : ) {
      46            1 :     return {
      47            1 :         to: {
      48            1 :             pathname: location.pathname,
      49            1 :             search: sessionViewSearch(location.search, mode),
      50            1 :             hash: location.hash,
      51            1 :         },
      52            1 :         options: { replace: true as const, state: location.state },
      53            1 :     };
      54            1 : }
      55              : 
      56              : /// Is a view mode part of the URL?
      57            1 : function inSearch(mode: SessionViewMode): boolean {
      58            1 :     return VIEW_PARAMETERS.some(([m]) => m === mode);
      59            1 : }
      60              : 
      61              : /// The view mode of the session. The dashboard and dataflow views are part of the URL.
      62            1 : export const SessionViewModeProvider: React.FC<React.PropsWithChildren> = (props: React.PropsWithChildren) => {
      63            1 :     const location = useLocation();
      64            1 :     const navigate = useNavigate();
      65              : 
      66            1 :     const [sessionViewMode, setSessionViewModeState] = React.useState(() => sessionViewModeFromSearch(location.search));
      67            1 :     const sessionViewModeRef = React.useRef(sessionViewMode);
      68            1 :     const pendingViewSearchRef = React.useRef<string | null>(null);
      69            1 :     sessionViewModeRef.current = sessionViewMode;
      70            1 :     const setSessionViewMode = React.useCallback<React.Dispatch<React.SetStateAction<SessionViewMode>>>((update) => {
      71            1 :         const next = typeof update === 'function' ? update(sessionViewModeRef.current) : update;
      72            1 :         sessionViewModeRef.current = next;
      73            1 :         setSessionViewModeState(next);
      74            1 :         const navigation = sessionViewNavigation(location, next);
      75            1 :         if (navigation.to.search !== location.search) {
      76            1 :             pendingViewSearchRef.current = navigation.to.search;
      77            1 :             navigate(navigation.to, navigation.options);
      78            1 :         }
      79            1 :     }, [location.pathname, location.search, location.hash, location.state, navigate]);
      80            1 :     const sessionViewModeValue = React.useMemo<SessionViewModeContextValue>(
      81            1 :         () => ({ mode: sessionViewMode, setMode: setSessionViewMode }),
      82            1 :         [sessionViewMode, setSessionViewMode],
      83            1 :     );
      84              : 
      85            1 :     React.useEffect(() => {
      86            1 :         const requested = sessionViewModeFromSearch(location.search);
      87            1 :         const parameter = new URLSearchParams(location.search).get('view');
      88              :         // An unknown view falls back to the session, also in the URL
      89            1 :         if (parameter != null && !inSearch(requested)) {
      90            1 :             pendingViewSearchRef.current = null;
      91            1 :             const navigation = sessionViewNavigation(location, SessionViewMode.Session);
      92            1 :             navigate(navigation.to, navigation.options);
      93            1 :             return;
      94            1 :         }
      95            1 :         if (pendingViewSearchRef.current != null) {
      96            1 :             if (pendingViewSearchRef.current === location.search) pendingViewSearchRef.current = null;
      97            1 :             return;
      98            1 :         }
      99            1 :         if (inSearch(requested) && sessionViewModeRef.current !== requested) {
     100            1 :             sessionViewModeRef.current = requested;
     101            1 :             setSessionViewModeState(requested);
     102            1 :         } else if (!inSearch(requested) && inSearch(sessionViewModeRef.current)) {
     103            1 :             sessionViewModeRef.current = SessionViewMode.Session;
     104            1 :             setSessionViewModeState(SessionViewMode.Session);
     105            1 :         }
     106            1 :     }, [location.search, navigate]);
     107              : 
     108            1 :     return (
     109            1 :         <SESSION_VIEW_MODE_CTX.Provider value={sessionViewModeValue}>
     110            1 :             {props.children}
     111            1 :         </SESSION_VIEW_MODE_CTX.Provider>
     112              :     );
     113            1 : };
        

Generated by: LCOV version 2.0-1