-
Hello {accessTokenPayload["preferred_username"]}
To start with, select an application in the side bar
diff --git a/packages/diracx-web-components/components/DashboardLayout/ApplicationDialog.stories.tsx b/packages/diracx-web-components/components/DashboardLayout/ApplicationDialog.stories.tsx
index f1a6320e..9931a9b6 100644
--- a/packages/diracx-web-components/components/DashboardLayout/ApplicationDialog.stories.tsx
+++ b/packages/diracx-web-components/components/DashboardLayout/ApplicationDialog.stories.tsx
@@ -1,13 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
-import { ThemeProvider } from "@mui/material";
import { useArgs } from "@storybook/core/preview-api";
import { ApplicationsContext } from "../../contexts/ApplicationsProvider";
import { useOidcAccessToken } from "../../mocks/react-oidc.mock";
import { applicationList } from "../ApplicationList";
-import { useMUITheme } from "../../hooks/theme";
+import { ThemeProvider } from "../../contexts/ThemeProvider";
import ApplicationDialog from "./ApplicationDialog";
const meta = {
@@ -31,9 +30,8 @@ const meta = {
);
},
(Story) => {
- const theme = useMUITheme();
return (
-
+
);
diff --git a/packages/diracx-web-components/components/DashboardLayout/ApplicationDialog.tsx b/packages/diracx-web-components/components/DashboardLayout/ApplicationDialog.tsx
index 232a2314..2412674e 100644
--- a/packages/diracx-web-components/components/DashboardLayout/ApplicationDialog.tsx
+++ b/packages/diracx-web-components/components/DashboardLayout/ApplicationDialog.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import { useState, useContext } from "react";
import {
Dialog,
DialogTitle,
@@ -12,6 +12,15 @@ import {
import { Close, SvgIconComponent } from "@mui/icons-material";
import { ApplicationsContext } from "@/contexts/ApplicationsProvider";
+interface AppDialogProps {
+ /** Determines whether the dialog is open or not. */
+ appDialogOpen: boolean;
+ /** Function to set the open state of the dialog. */
+ setAppDialogOpen: React.Dispatch>;
+ /** Function to handle the creation of a new application. */
+ handleCreateApp: (name: string, icon: SvgIconComponent) => void;
+}
+
/**
* Renders a dialog component for creating a new application.
*
@@ -22,16 +31,9 @@ export default function AppDialog({
appDialogOpen,
setAppDialogOpen,
handleCreateApp,
-}: {
- /** Determines whether the dialog is open or not. */
- appDialogOpen: boolean;
- /** Function to set the open state of the dialog. */
- setAppDialogOpen: React.Dispatch>;
- /** Function to handle the creation of a new application. */
- handleCreateApp: (name: string, icon: SvgIconComponent) => void;
-}) {
- const [appType, setAppType] = React.useState("");
- const applicationList = React.useContext(ApplicationsContext)[2];
+}: AppDialogProps) {
+ const [appType, setAppType] = useState("");
+ const applicationList = useContext(ApplicationsContext)[2];
return (
);
},
diff --git a/packages/diracx-web-components/components/DashboardLayout/DashboardDrawer.tsx b/packages/diracx-web-components/components/DashboardLayout/DashboardDrawer.tsx
index 6edcb0e0..2f92b458 100644
--- a/packages/diracx-web-components/components/DashboardLayout/DashboardDrawer.tsx
+++ b/packages/diracx-web-components/components/DashboardLayout/DashboardDrawer.tsx
@@ -12,20 +12,15 @@ import {
Popover,
TextField,
Toolbar,
+ useTheme,
} from "@mui/material";
import { MenuBook, Add, SvgIconComponent } from "@mui/icons-material";
-import React, {
- ReactEventHandler,
- useContext,
- useEffect,
- useState,
-} from "react";
+import React, { useContext, useEffect, useState } from "react";
import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
import { extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
import DrawerItemGroup from "./DrawerItemGroup";
import AppDialog from "./ApplicationDialog";
import { ApplicationsContext } from "@/contexts/ApplicationsProvider";
-import { useMUITheme } from "@/hooks/theme";
import { DashboardGroup } from "@/types";
interface DashboardDrawerProps {
@@ -36,7 +31,7 @@ interface DashboardDrawerProps {
/** The width of the drawer. */
width: number;
/** The function to handle the drawer toggle. */
- handleDrawerToggle: ReactEventHandler;
+ handleDrawerToggle: React.ReactEventHandler;
/** The URL for the logo image. */
logoURL?: string;
}
@@ -48,16 +43,22 @@ interface DashboardDrawerProps {
* @param {DashboardDrawerProps} props - The props for the DashboardDrawer component.
* @returns {JSX.Element} The rendered DashboardDrawer component.
*/
-export default function DashboardDrawer(props: DashboardDrawerProps) {
+export default function DashboardDrawer({
+ variant,
+ mobileOpen,
+ width,
+ handleDrawerToggle,
+ logoURL = "/DIRAC-logo.png",
+}: DashboardDrawerProps) {
// Determine the container for the Drawer based on whether the window object exists.
const container =
window !== undefined ? () => window.document.body : undefined;
// Check if the drawer is in "temporary" mode.
- const isTemporary = props.variant === "temporary";
+ const isTemporary = variant === "temporary";
// Whether the modal for Application Creation is open
const [appDialogOpen, setAppDialogOpen] = useState(false);
- const [contextMenu, setContextMenu] = React.useState<{
+ const [contextMenu, setContextMenu] = useState<{
mouseX: number;
mouseY: number;
} | null>(null);
@@ -67,18 +68,16 @@ export default function DashboardDrawer(props: DashboardDrawerProps) {
id: string | null;
}>({ type: null, id: null });
- const [popAnchorEl, setPopAnchorEl] = React.useState
(
- null,
- );
- const [renameValue, setRenameValue] = React.useState("");
+ const [popAnchorEl, setPopAnchorEl] = useState(null);
+ const [renamingItemId, setRenamingItemId] = useState(null);
+ const [renamingGroupId, setRenamingGroupId] = useState(null);
+ const [renameValue, setRenameValue] = useState("");
// Define the applications that are accessible to users.
// Each application has an associated icon and path.
const [userDashboard, setUserDashboard] = useContext(ApplicationsContext);
- const logoURL = props.logoURL || "/DIRAC-logo.png";
-
- const theme = useMUITheme();
+ const theme = useTheme();
useEffect(() => {
// Handle changes to app instances when drag and drop occurs.
@@ -306,8 +305,14 @@ export default function DashboardDrawer(props: DashboardDrawerProps) {
handleCloseContextMenu();
};
- const handleRenameClick = (event: React.MouseEvent) => {
- setPopAnchorEl(event.currentTarget);
+ const handleRenameClick = () => {
+ if (contextState.type === "group") {
+ setRenamingGroupId(contextState.id);
+ } else if (contextState.type === "item") {
+ setRenamingItemId(contextState.id);
+ }
+ setRenameValue("");
+ handleCloseContextMenu();
};
const popClose = () => {
@@ -352,9 +357,9 @@ export default function DashboardDrawer(props: DashboardDrawerProps) {
<>
))}
@@ -443,8 +454,9 @@ export default function DashboardDrawer(props: DashboardDrawerProps) {
{contextState.type && (
)}
-
-
+ {contextState.type === null && (
+
+ )}
{
- const theme = useMUITheme();
return (
-
+
-
+
);
},
],
diff --git a/packages/diracx-web-components/components/DashboardLayout/DrawerItem.tsx b/packages/diracx-web-components/components/DashboardLayout/DrawerItem.tsx
index 994d4aa2..10f72dd7 100644
--- a/packages/diracx-web-components/components/DashboardLayout/DrawerItem.tsx
+++ b/packages/diracx-web-components/components/DashboardLayout/DrawerItem.tsx
@@ -1,10 +1,12 @@
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import {
ListItemButton,
ListItemIcon,
Icon,
ListItemText,
+ useTheme,
+ TextField,
} from "@mui/material";
import { DragIndicator } from "@mui/icons-material";
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
@@ -19,11 +21,29 @@ import {
extractClosestEdge,
} 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 as MUIThemeProvider } from "@mui/material/styles";
import { ThemeProvider } from "@/contexts/ThemeProvider";
-import { useMUITheme } from "@/hooks/theme";
import { useSearchParamsUtils } from "@/hooks/searchParamsUtils";
import { useApplicationId } from "@/hooks/application";
+import { DashboardGroup } from "@/types";
+
+interface DrawerItemProps {
+ /** The item object containing the title, id, and icon. */
+ item: { title: string; id: string; icon: React.ComponentType };
+ /** The index of the item. */
+ index: number;
+ /** The title of the group. */
+ groupTitle: string;
+ /** The ID of the item being renamed. */
+ renamingItemId: string | null;
+ /** The function to set the renaming item ID. */
+ setRenamingItemId: React.Dispatch>;
+ /** The value of the rename input. */
+ renameValue: string;
+ /** The function to set the rename input value. */
+ setRenameValue: React.Dispatch>;
+ /** The function to set the user dashboard state. */
+ setUserDashboard: React.Dispatch>;
+}
/**
* Represents a drawer item component.
@@ -34,19 +54,17 @@ export default function DrawerItem({
item: { title, id, icon },
index,
groupTitle,
-}: {
- /** The item object containing the title, id, and icon. */
- item: { title: string; id: string; icon: React.ComponentType };
- /** The index of the item. */
- index: number;
- /** The title of the group. */
- groupTitle: string;
-}) {
+ renamingItemId,
+ setRenamingItemId,
+ renameValue,
+ setRenameValue,
+ setUserDashboard,
+}: DrawerItemProps) {
// Ref to use for the draggable element
- const dragRef = React.useRef(null);
+ const dragRef = useRef(null);
// Ref to use for the handle of the draggable element, must be a child of the draggable element
- const handleRef = React.useRef(null);
- const theme = useMUITheme();
+ const handleRef = useRef(null);
+ const theme = useTheme();
const { setParam } = useSearchParamsUtils();
// Represents the closest edge to the mouse cursor
const [closestEdge, setClosestEdge] = useState(null);
@@ -75,15 +93,13 @@ export default function DrawerItem({
// Wraps the preview in the theme provider to ensure the correct theme is applied
// This is necessary because the preview is rendered outside the main app
-
-
-
-
-
+
+
+
,
);
return () => root.unmount();
@@ -143,6 +159,26 @@ export default function DrawerItem({
);
}, [index, groupTitle, icon, theme, title, id]);
+ // Handle renaming of the item
+ const handleItemRename = () => {
+ if (renameValue.trim() === "") return;
+ setUserDashboard((groups) =>
+ groups.map((group) => {
+ if (group.title === groupTitle) {
+ return {
+ ...group,
+ items: group.items.map((item) =>
+ item.id === id ? { ...item, title: renameValue } : item,
+ ),
+ };
+ }
+ return group;
+ }),
+ );
+ setRenamingItemId(null);
+ setRenameValue("");
+ };
+
return (
<>
-
+ {renamingItemId === id ? (
+ setRenameValue(e.target.value)}
+ onBlur={handleItemRename}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ handleItemRename();
+ } else if (e.key === "Escape") {
+ setRenamingItemId(null);
+ }
+ }}
+ autoFocus
+ size="small"
+ />
+ ) : (
+
+ )}
-
+
diff --git a/packages/diracx-web-components/components/DashboardLayout/DrawerItemGroup.stories.tsx b/packages/diracx-web-components/components/DashboardLayout/DrawerItemGroup.stories.tsx
index 798d2fbf..112fe598 100644
--- a/packages/diracx-web-components/components/DashboardLayout/DrawerItemGroup.stories.tsx
+++ b/packages/diracx-web-components/components/DashboardLayout/DrawerItemGroup.stories.tsx
@@ -2,11 +2,10 @@ import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { useArgs } from "@storybook/core/preview-api";
import { Paper } from "@mui/material";
-import { ThemeProvider as MUIThemeProvider } from "@mui/material/styles";
import { Dashboard } from "@mui/icons-material";
-import { useMUITheme } from "../../hooks/theme";
import { useOidc, useOidcAccessToken } from "../../mocks/react-oidc.mock";
import { DashboardGroup } from "../../types/DashboardGroup";
+import { ThemeProvider } from "../../contexts/ThemeProvider";
import DrawerItemGroup from "./DrawerItemGroup";
const meta = {
@@ -18,13 +17,12 @@ const meta = {
tags: ["autodocs"],
decorators: [
(Story) => {
- const theme = useMUITheme();
return (
-
+
-
+
);
},
],
diff --git a/packages/diracx-web-components/components/DashboardLayout/DrawerItemGroup.tsx b/packages/diracx-web-components/components/DashboardLayout/DrawerItemGroup.tsx
index a2b07184..d04120ef 100644
--- a/packages/diracx-web-components/components/DashboardLayout/DrawerItemGroup.tsx
+++ b/packages/diracx-web-components/components/DashboardLayout/DrawerItemGroup.tsx
@@ -1,11 +1,40 @@
"use client";
-import { Accordion, AccordionDetails, AccordionSummary } from "@mui/material";
+import {
+ Accordion,
+ AccordionDetails,
+ AccordionSummary,
+ TextField,
+} from "@mui/material";
import { ExpandMore, Apps } from "@mui/icons-material";
-import React, { useEffect } from "react";
+import React, { useEffect, useRef, useState } from "react";
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
import DrawerItem from "./DrawerItem";
import { DashboardGroup } from "@/types/DashboardGroup";
+interface DrawerItemGroupProps {
+ /** The group object containing the title, expanded state, and items. */
+ group: DashboardGroup;
+ /** The function to set the user dashboard state. */
+ setUserDashboard: React.Dispatch>;
+ /** The function to handle the context menu. */
+ handleContextMenu: (
+ type: "group" | "item" | null,
+ id: string | null,
+ ) => (event: React.MouseEvent) => void;
+ /** The ID of the group being renamed. */
+ renamingGroupId: string | null;
+ /** The function to set the renaming group ID. */
+ setRenamingGroupId: React.Dispatch>;
+ /** The ID of the item being renamed. */
+ renamingItemId: string | null;
+ /** The function to set the renaming item ID. */
+ setRenamingItemId: React.Dispatch>;
+ /** The value of the rename input. */
+ renameValue: string;
+ /** The function to set the rename input value. */
+ setRenameValue: React.Dispatch>;
+}
+
/**
* Represents a group of items in a drawer.
*
@@ -17,21 +46,17 @@ export default function DrawerItemGroup({
group: { title, extended: expanded, items },
setUserDashboard,
handleContextMenu,
-}: {
- /** The group object containing the title, expanded state, and items. */
- group: DashboardGroup;
- /** The function to set the user dashboard state. */
- setUserDashboard: React.Dispatch>;
- /** The function to handle the context menu. */
- handleContextMenu: (
- type: "group" | "item" | null,
- id: string | null,
- ) => (event: React.MouseEvent) => void;
-}) {
+ renamingGroupId,
+ setRenamingGroupId,
+ renamingItemId,
+ setRenamingItemId,
+ renameValue,
+ setRenameValue,
+}: DrawerItemGroupProps) {
// Ref to use for the drag and drop target
- const dropRef = React.useRef(null);
+ const dropRef = useRef(null);
// State to track whether the user is hovering over the item during a drag operation
- const [hovered, setHovered] = React.useState(false);
+ const [hovered, setHovered] = useState(false);
useEffect(() => {
if (!dropRef.current) return;
@@ -61,6 +86,19 @@ export default function DrawerItemGroup({
),
);
};
+
+ // Handle renaming of the group
+ const handleGroupRename = () => {
+ if (renameValue.trim() === "") return;
+ setUserDashboard((groups) =>
+ groups.map((group) =>
+ group.title === title ? { ...group, title: renameValue } : group,
+ ),
+ );
+ setRenamingGroupId(null);
+ setRenameValue("");
+ };
+
return (
{/* Accordion summary */}
- }>{title}
+ }>
+ {renamingGroupId === title ? (
+ setRenameValue(e.target.value)}
+ onBlur={handleGroupRename}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ handleGroupRename();
+ } else if (e.key === "Escape") {
+ setRenamingGroupId(null);
+ }
+ }}
+ autoFocus
+ size="small"
+ />
+ ) : (
+ {title}
+ )}
+
{/* Accordion details */}
{items.map(({ title: itemTitle, id, icon }, index) => (
@@ -82,6 +139,11 @@ export default function DrawerItemGroup({
item={{ title: itemTitle, id, icon: icon || Apps }}
index={index}
groupTitle={title}
+ renamingItemId={renamingItemId}
+ setRenamingItemId={setRenamingItemId}
+ renameValue={renameValue}
+ setRenameValue={setRenameValue}
+ setUserDashboard={setUserDashboard}
/>
))}
diff --git a/packages/diracx-web-components/components/DashboardLayout/ProfileButton.stories.tsx b/packages/diracx-web-components/components/DashboardLayout/ProfileButton.stories.tsx
index 31f54922..8b919929 100644
--- a/packages/diracx-web-components/components/DashboardLayout/ProfileButton.stories.tsx
+++ b/packages/diracx-web-components/components/DashboardLayout/ProfileButton.stories.tsx
@@ -2,9 +2,8 @@ import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Paper } from "@mui/material";
-import { ThemeProvider as MUIThemeProvider } from "@mui/material/styles";
-import { useMUITheme } from "../../hooks/theme";
import { useOidc, useOidcAccessToken } from "../../mocks/react-oidc.mock";
+import { ThemeProvider } from "../../contexts/ThemeProvider";
import { ProfileButton } from "./ProfileButton";
const meta = {
@@ -16,13 +15,12 @@ const meta = {
tags: ["autodocs"],
decorators: [
(Story) => {
- const theme = useMUITheme();
return (
-