Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ export default function DashboardDrawer({

// Define the applications that are accessible to users.
// Each application has an associated icon and path.
const [userDashboard, setUserDashboard] = useContext(ApplicationsContext);
const [userDashboard, setUserDashboard, , , setCurrentAppId] =
useContext(ApplicationsContext);

const theme = useTheme();

Expand Down Expand Up @@ -247,6 +248,7 @@ export default function DashboardDrawer({
userDashboard.map((g) => (g.title === group.title ? group : g)),
);
}
setCurrentAppId(newApp.id);
};

let isContextStateStable = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import React, { useEffect, useRef, useState } from "react";
import React, { useContext, useEffect, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import {
ListItemButton,
Expand All @@ -10,7 +10,7 @@ import {
useTheme,
TextField,
} from "@mui/material";
import { DragIndicator } from "@mui/icons-material";
import { DragIndicator, Apps } from "@mui/icons-material";
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
import {
draggable,
Expand All @@ -24,13 +24,12 @@ import {
} from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview";
import { ThemeProvider } from "../../contexts/ThemeProvider";
import { useSearchParamsUtils } from "../../hooks/searchParamsUtils";
import { useApplicationId } from "../../hooks/application";
import { ApplicationsContext } from "../../contexts/ApplicationsProvider";
import { DashboardGroup } from "../../types";

interface DrawerItemProps {
/** The item object containing the title, id, and icon. */
item: { title: string; id: string; icon: React.ComponentType };
/** The item object containing the title, id, and the appType. */
item: { title: string; id: string; type: string };
/** The index of the item. */
index: number;
/** The title of the group. */
Expand All @@ -53,7 +52,7 @@ interface DrawerItemProps {
* @returns The rendered JSX for the drawer item.
*/
export default function DrawerItem({
item: { title, id, icon },
item: { title, id, type },
index,
groupTitle,
renamingItemId,
Expand All @@ -67,11 +66,13 @@ export default function DrawerItem({
// Ref to use for the handle of the draggable element, must be a child of the draggable element
const handleRef = useRef(null);
const theme = useTheme();
const { setParam } = useSearchParamsUtils();
// Represents the closest edge to the mouse cursor
const [closestEdge, setClosestEdge] = useState<Edge | null>(null);

const appId = useApplicationId();
const [, , appList, appId, setCurrentAppId] = useContext(ApplicationsContext);
const { icon } = appList.find((app) => app.name === type) || {
icon: Apps,
};

useEffect(() => {
if (!dragRef.current || !handleRef.current) return;
Expand Down Expand Up @@ -186,7 +187,7 @@ export default function DrawerItem({
<ListItemButton
disableGutters
key={title}
onClick={() => setParam("appId", id)}
onClick={() => setCurrentAppId(id)}
sx={{ pl: 2, borderRadius: 2, pr: 1 }}
ref={dragRef}
selected={appId === id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
AccordionSummary,
TextField,
} from "@mui/material";
import { ExpandMore, Apps } from "@mui/icons-material";
import { ExpandMore } from "@mui/icons-material";
import React, { useEffect, useRef, useState } from "react";
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
import { DashboardGroup } from "../../types/DashboardGroup";
Expand Down Expand Up @@ -139,20 +139,22 @@ export default function DrawerItemGroup({
</AccordionSummary>
{/* Accordion details */}
<AccordionDetails>
{items.map(({ title: itemTitle, id, icon }, index) => (
<div onContextMenu={handleContextMenu("item", id)} key={id}>
<DrawerItem
item={{ title: itemTitle, id, icon: icon || Apps }}
index={index}
groupTitle={title}
renamingItemId={renamingItemId}
setRenamingItemId={setRenamingItemId}
renameValue={renameValue}
setRenameValue={setRenameValue}
setUserDashboard={setUserDashboard}
/>
</div>
))}
{items.map(({ title: itemTitle, id, type }, index) => {
return (
<div onContextMenu={handleContextMenu("item", id)} key={id}>
<DrawerItem
item={{ title: itemTitle, id, type }}
index={index}
groupTitle={title}
renamingItemId={renamingItemId}
setRenamingItemId={setRenamingItemId}
renameValue={renameValue}
setRenameValue={setRenameValue}
setUserDashboard={setUserDashboard}
/>
</div>
);
})}
</AccordionDetails>
</Accordion>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ interface LoginFormProps {
export function LoginForm({
logoURL = "/DIRAC-logo-minimal.png",
}: LoginFormProps) {
const { setPath } = useContext(NavigationContext);
const diracxUrl = useDiracxUrl();
const { metadata, error, isLoading } = useMetadata(diracxUrl);
const [selectedVO, setSelectedVO] = useState<string | null>(null);
Expand All @@ -41,6 +40,7 @@ export function LoginForm({
const { isAuthenticated, login } = useOidc(configuration?.scope);

const { getParam } = useSearchParamsUtils();
const { setPath } = useContext(NavigationContext);

// Login if not authenticated
useEffect(() => {
Expand Down
116 changes: 35 additions & 81 deletions packages/diracx-web-components/src/contexts/ApplicationsProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
"use client";

import React, {
createContext,
useCallback,
useMemo,
useEffect,
useState,
} from "react";
import { Monitor } from "@mui/icons-material";
import JSONCrush from "jsoncrush";
import { useSearchParamsUtils } from "../hooks/searchParamsUtils";
import React, { createContext, useEffect, useState } from "react";
import { applicationList } from "../components/ApplicationList";
import { DashboardGroup } from "../types/DashboardGroup";
import ApplicationMetadata from "../types/ApplicationMetadata";
import { DashboardItem } from "../types/DashboardItem";

// Create a context for the UserDashboard state
export const ApplicationsContext = createContext<
[
DashboardGroup[],
React.Dispatch<React.SetStateAction<DashboardGroup[]>>,
ApplicationMetadata[],
string, // Id of the current application
React.Dispatch<React.SetStateAction<string>>,
]
>([[], () => {}, []]);
>([[], () => {}, [], "", () => {}]);

interface ApplicationsProviderProps {
children: React.ReactNode;
Expand All @@ -43,90 +35,52 @@ export const ApplicationsProvider = ({
appList = applicationList,
defaultUserDashboard,
}: ApplicationsProviderProps) => {
const [userDashboard, setUserDashboard] = useState<DashboardGroup[]>([]);
const loadedDashboard = sessionStorage.getItem("savedDashboard");
const parsedDashboard: DashboardGroup[] = loadedDashboard
? JSON.parse(loadedDashboard)
: null;

const { getParam, setParam } = useSearchParamsUtils();

// save user dashboard to searchParams (but not icons)
const setUserDashboardParams = useCallback(
(
groups: DashboardGroup[] | ((prev: DashboardGroup[]) => DashboardGroup[]),
) => {
if (typeof groups === "function") {
groups = groups(userDashboard);
}
const newSections = groups.map((group) => {
return {
...group,
items: group.items.map((item) => {
return {
...item,
icon: () => null,
};
}),
};
});
setParam("dashboard", JSONCrush.crush(JSON.stringify(newSections)));
},
[setParam, userDashboard],
const [userDashboard, setUserDashboard] = useState<DashboardGroup[]>(
parsedDashboard || [],
);

// get user sections from searchParams
const groupsParams = useMemo(() => getParam("dashboard"), [getParam]);
const [currentAppId, setCurrentAppId] = useState<string>("");

useEffect(() => {
if (userDashboard.length !== 0) return;
if (groupsParams) {
const uncrushed = JSONCrush.uncrush(groupsParams);
try {
const newSections: DashboardGroup[] = JSON.parse(uncrushed).map(
(group: DashboardGroup) => {
group.items = group.items.map((item: DashboardItem) => {
return {
...item,
//get icon from appList
icon:
appList.find((app) => app.name === item.type)?.icon || null,
};
});
return group;
},
);
if (newSections !== userDashboard) {
setUserDashboard(newSections);
}
} catch (e) {
console.error("Error parsing user dashboard : ", uncrushed, e);
}
} else {
setUserDashboard(
defaultUserDashboard || [
{
title: "My dashboard",
extended: true,
items: [
{
title: "My Jobs",
type: "Job Monitor",
id: "JobMonitor0",
icon: Monitor,
},
],
},
],
);
}
}, [appList, defaultUserDashboard, groupsParams]);

setUserDashboard(
defaultUserDashboard || [
{
title: "My dashboard",
extended: true,
items: [
{
title: "My Jobs",
type: "Job Monitor",
id: "JobMonitor0",
},
],
},
],
);
}, [appList, defaultUserDashboard]);

// Save the dashboard in session storage
useEffect(() => {
sessionStorage.setItem("savedDashboard", JSON.stringify(userDashboard));
}, [userDashboard]);

return (
<ApplicationsContext.Provider
value={[
userDashboard,
(group) => {
setUserDashboard(group);
setUserDashboardParams(group);
},
appList,
currentAppId,
setCurrentAppId,
]}
>
{children}
Expand Down
15 changes: 5 additions & 10 deletions packages/diracx-web-components/src/hooks/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,22 @@

import { useContext, useMemo } from "react";
import { ApplicationsContext } from "../contexts/ApplicationsProvider";
import { useSearchParamsUtils } from "./searchParamsUtils";

/**
* Custom hook to access the application id from the URL
* Custom hook to access the application id from the context
* @returns the application id
*/
export function useApplicationId() {
const { getParam } = useSearchParamsUtils();

return useMemo(() => {
return getParam("appId");
}, [getParam]);
const [, , , appId] = useContext(ApplicationsContext);
return appId;
}

/**
* Custom hook to access the application title based on the application id
* @returns the application title
*/
export function useApplicationTitle() {
const [userDashboard] = useContext(ApplicationsContext);
const appId = useApplicationId();
const [userDashboard, , , appId] = useContext(ApplicationsContext);

return useMemo(() => {
if (!userDashboard || !appId) return null;
Expand All @@ -40,7 +35,7 @@ export function useApplicationTitle() {

/**
* Custom hook to access the application title based on the application id
* @returns the application title
* @returns the application type
*/
export function useApplicationType() {
const [userDashboard] = useContext(ApplicationsContext);
Expand Down
2 changes: 0 additions & 2 deletions packages/diracx-web-components/src/types/DashboardItem.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
"use client";

import { SvgIconComponent } from "@mui/icons-material";
import { InternalFilter } from "./Filter";

// Define the type for the Dashboard Item state
export interface DashboardItem {
title: string;
type: string;
id: string;
icon: SvgIconComponent | null;
data?: InternalFilter[];
}
1 change: 0 additions & 1 deletion packages/diracx-web-components/test/LoginForm.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { render, fireEvent, screen } from "@testing-library/react";
import React from "react";
import { LoginForm } from "../src/components/Login/LoginForm";
import { ThemeProvider } from "../src/contexts/ThemeProvider";
import { useMetadata } from "../src/hooks/metadata";
Expand Down
7 changes: 2 additions & 5 deletions packages/diracx-web/src/app/(dashboard)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
"use client";
import { useContext, useMemo } from "react";
import { useSearchParams } from "next/navigation";
import {
BaseApp,
applicationList,
} from "@dirac-grid/diracx-web-components/components";
import { ApplicationsContext } from "@dirac-grid/diracx-web-components/contexts";

export default function Page() {
const searchParams = useSearchParams();
const appId = searchParams.get("appId");
const [userDashboard] = useContext(ApplicationsContext);
const [userDashboard, , , appId] = useContext(ApplicationsContext);

const appType = useMemo(() => {
const group = userDashboard.find((group) =>
Expand All @@ -23,5 +20,5 @@ export default function Page() {
return applicationList.find((app) => app.name === appType)?.component;
}, [appType]);

return Component ? <Component /> : <BaseApp />;
return Component ? <Component key={appId} /> : <BaseApp />;
}
Loading