diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json
index d55b5b611..43d109cb6 100644
--- a/packages/dashboard/package.json
+++ b/packages/dashboard/package.json
@@ -42,7 +42,6 @@
"date-fns": "^2.30.0",
"debug": "^4.2.0",
"eventemitter3": "^4.0.7",
- "jsdom": "^24.1.1",
"keycloak-js": "^25.0.2",
"react": "^18.2.0",
"react-components": "workspace:*",
@@ -67,13 +66,13 @@
"@testing-library/dom": "^9.3.4",
"@testing-library/react": "^14.2.2",
"@testing-library/user-event": "^14.5.2",
- "@types/history": "^5.0.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"@vitest/coverage-v8": "^2.0.4",
"api-server": "file:../api-server",
"concurrently": "^8.2.2",
"eslint": "^8.57.0",
"history": "^5.3.0",
+ "jsdom": "^24.1.1",
"storybook": "^8.0.5",
"typescript": "~5.5.4",
"typescript-json-schema": "^0.64.0",
diff --git a/packages/dashboard/src/app.tsx b/packages/dashboard/src/app.tsx
index 6f59ada27..3793d9206 100644
--- a/packages/dashboard/src/app.tsx
+++ b/packages/dashboard/src/app.tsx
@@ -25,7 +25,7 @@ const homeWorkspace: WorkspaceState = {
windows: {
map: {
layout: { x: 0, y: 0, w: 12, h: 6 },
- component: mapApp.Component,
+ Component: mapApp.Component,
},
},
};
@@ -34,19 +34,19 @@ const robotsWorkspace: WorkspaceState = {
windows: {
robots: {
layout: { x: 0, y: 0, w: 7, h: 4 },
- component: robotsApp.Component,
+ Component: robotsApp.Component,
},
- map: { layout: { x: 8, y: 0, w: 5, h: 8 }, component: mapApp.Component },
- doors: { layout: { x: 0, y: 0, w: 7, h: 4 }, component: doorsApp.Component },
- lifts: { layout: { x: 0, y: 0, w: 7, h: 4 }, component: liftsApp.Component },
- mutexGroups: { layout: { x: 8, y: 0, w: 5, h: 4 }, component: robotMutexGroupsApp.Component },
+ map: { layout: { x: 8, y: 0, w: 5, h: 8 }, Component: mapApp.Component },
+ doors: { layout: { x: 0, y: 0, w: 7, h: 4 }, Component: doorsApp.Component },
+ lifts: { layout: { x: 0, y: 0, w: 7, h: 4 }, Component: liftsApp.Component },
+ mutexGroups: { layout: { x: 8, y: 0, w: 5, h: 4 }, Component: robotMutexGroupsApp.Component },
},
};
const tasksWorkspace: WorkspaceState = {
windows: {
- tasks: { layout: { x: 0, y: 0, w: 7, h: 8 }, component: tasksApp.Component },
- map: { layout: { x: 8, y: 0, w: 5, h: 8 }, component: mapApp.Component },
+ tasks: { layout: { x: 0, y: 0, w: 7, h: 8 }, Component: tasksApp.Component },
+ map: { layout: { x: 8, y: 0, w: 5, h: 8 }, Component: mapApp.Component },
},
};
diff --git a/packages/dashboard/src/components/micro-app.tsx b/packages/dashboard/src/components/micro-app.tsx
index 623f92c0f..087cc9097 100644
--- a/packages/dashboard/src/components/micro-app.tsx
+++ b/packages/dashboard/src/components/micro-app.tsx
@@ -31,16 +31,20 @@ export function createMicroApp
(
return {
appId,
displayName,
- Component: React.forwardRef((microAppProps: MicroAppProps, ref) => {
- const settings = useSettings();
- return (
-
- {/* TODO(koonpeng): Implement fallback */}
-
-
-
-
- );
- }) as React.ComponentType,
+ Component: React.forwardRef(
+ ({ children, ...otherProps }: React.PropsWithChildren, ref) => {
+ const settings = useSettings();
+ return (
+
+ {/* TODO(koonpeng): Implement fallback */}
+
+
+
+ {/* this contains the resize handle */}
+ {children}
+
+ );
+ },
+ ) as React.ComponentType,
};
}
diff --git a/packages/dashboard/src/components/workspace.tsx b/packages/dashboard/src/components/workspace.tsx
index 0432c7896..175e18577 100644
--- a/packages/dashboard/src/components/workspace.tsx
+++ b/packages/dashboard/src/components/workspace.tsx
@@ -1,9 +1,11 @@
import React from 'react';
import { WindowContainer, WindowLayout } from 'react-components';
+import { MicroAppProps } from './micro-app';
+
export interface WindowState {
layout: Omit;
- component: React.ComponentType;
+ Component: React.ComponentType;
}
export interface WorkspaceState {
@@ -13,9 +15,10 @@ export interface WorkspaceState {
export interface WorkspaceProps {
state: WorkspaceState;
onStateChange: (state: WorkspaceState) => void;
+ designMode?: boolean;
}
-export const Workspace = ({ state, onStateChange }: WorkspaceProps) => {
+export const Workspace = ({ state, onStateChange, designMode = false }: WorkspaceProps) => {
const layout: WindowLayout[] = Object.entries(state.windows).map(([k, w]) => ({
i: k,
...w.layout,
@@ -29,18 +32,33 @@ export const Workspace = ({ state, onStateChange }: WorkspaceProps) => {
newLayout.forEach((l) => (newState.windows[l.i].layout = l));
onStateChange(newState);
}}
+ designMode={designMode}
>
{Object.entries(state.windows).map(([k, w]) => (
-
+ {
+ const newState = { ...state };
+ delete newState.windows[k];
+ onStateChange(newState);
+ }}
+ />
))}
);
};
export interface StaticWorkspaceProps {
+ /**
+ * Initial state of the workspace. This is only used on the first render, changes on this
+ * after the initial render will not do anything.
+ */
initialState: WorkspaceState;
}
+/**
+ * A simple predefined workspace where the layout is fixed.
+ */
export const StaticWorkspace = ({ initialState }: StaticWorkspaceProps) => {
const [wsState, setWsState] = React.useState(initialState);
return ;
diff --git a/packages/react-components/lib/doors/door-table-datagrid.tsx b/packages/react-components/lib/doors/door-table-datagrid.tsx
index 124e71217..d8481cbe7 100644
--- a/packages/react-components/lib/doors/door-table-datagrid.tsx
+++ b/packages/react-components/lib/doors/door-table-datagrid.tsx
@@ -241,7 +241,6 @@ export function DoorDataGridTable({ doors, onDoorClick }: DoorDataGridTableProps
rowsPerPageOptions={[5]}
sx={{
fontSize: isScreenHeightLessThan800 ? '0.7rem' : 'inherit',
- overflowX: 'scroll',
}}
autoPageSize={isScreenHeightLessThan800}
density={isScreenHeightLessThan800 ? 'compact' : 'standard'}
diff --git a/packages/react-components/lib/window/window-container.tsx b/packages/react-components/lib/window/window-container.tsx
index 3946305ce..b5a6ea0c1 100644
--- a/packages/react-components/lib/window/window-container.tsx
+++ b/packages/react-components/lib/window/window-container.tsx
@@ -1,6 +1,7 @@
import 'react-grid-layout/css/styles.css';
import './no-rgl-animations.css';
+import { styled } from '@mui/material';
import React from 'react';
import { default as GridLayout_, Layout as WindowLayout, WidthProvider } from 'react-grid-layout';
@@ -58,11 +59,34 @@ export const WindowContainer: React.FC = ({
preventCollision
isResizable={designMode}
isDraggable={designMode}
- draggableHandle=".rgl-draggable"
onLayoutChange={onLayoutChange}
+ draggableCancel=".custom-resize-handle,.window-toolbar-items"
+ resizeHandle={}
>
{children}
);
};
+
+/**
+ * A custom resize handle that uses theme properties
+ */
+const ResizeHandle = styled('span')(({ theme }) => ({
+ position: 'absolute',
+ right: theme.spacing(1),
+ bottom: theme.spacing(1),
+ width: 20,
+ height: 20,
+ cursor: 'se-resize',
+ '&::after': {
+ content: '""',
+ position: 'absolute',
+ right: 0,
+ bottom: 0,
+ width: '0.5rem',
+ height: '0.5rem',
+ borderRight: `2px solid ${theme.palette.grey[500]}`,
+ borderBottom: `2px solid ${theme.palette.grey[500]}`,
+ },
+}));
diff --git a/packages/react-components/lib/window/window-toolbar.tsx b/packages/react-components/lib/window/window-toolbar.tsx
index 128427e6e..3934f4b68 100644
--- a/packages/react-components/lib/window/window-toolbar.tsx
+++ b/packages/react-components/lib/window/window-toolbar.tsx
@@ -3,15 +3,21 @@ import React from 'react';
export interface WindowToolbarProps extends AppBarProps {
title: string;
+ toolbarItemContainerProps?: React.ComponentProps;
}
-export const WindowToolbar: React.FC = ({ title, children, ...otherProps }) => {
+export const WindowToolbar: React.FC = ({
+ title,
+ toolbarItemContainerProps,
+ children,
+ ...otherProps
+}) => {
return (
{title}
- {children}
+ {children}
diff --git a/packages/react-components/lib/window/window.tsx b/packages/react-components/lib/window/window.tsx
index 2eb261239..8796beab3 100644
--- a/packages/react-components/lib/window/window.tsx
+++ b/packages/react-components/lib/window/window.tsx
@@ -1,6 +1,6 @@
import type {} from '@emotion/styled';
import CloseIcon from '@mui/icons-material/Close';
-import { Box, Grid, IconButton, Paper, PaperProps, styled, useTheme } from '@mui/material';
+import { Box, IconButton, Paper, PaperProps, styled, useTheme } from '@mui/material';
import React from 'react';
import { Layout } from 'react-grid-layout';
@@ -22,31 +22,52 @@ export const Window = styled(
) => {
const theme = useTheme();
+ // The resize marker injected by `react-grid-layout` must be a direct children, but we
+ // want to wrap the window components in a div so it shows a scrollbar. So we assume that
+ // the injected resize marker is always the last component and render it separately.
+ const childrenArr = React.Children.toArray(children);
+ const childComponents = childrenArr.slice(0, childrenArr.length - 1);
+ const resizeComponent = childrenArr[childrenArr.length - 1];
+
const windowManagerState = React.useContext(WindowManagerStateContext);
return (
:not(.custom-resize-handle)': {
+ pointerEvents: windowManagerState.designMode ? 'none' : undefined,
+ },
+ '& .window-toolbar-items': {
+ pointerEvents: 'auto',
+ },
...sx,
}}
{...otherProps}
>
-
-
- {toolbar}
- {windowManagerState.designMode && (
- onClose && onClose()}>
-
-
- )}
-
-
-
- {children}
+
+ {toolbar}
+ {windowManagerState.designMode && (
+ {
+ console.log('clicked');
+ onClose && onClose();
+ }}
+ >
+
+
+ )}
+
+
+ {childComponents}
+ {resizeComponent}
);
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c63a47276..9568a5a43 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -155,9 +155,6 @@ importers:
eventemitter3:
specifier: ^4.0.7
version: 4.0.7
- jsdom:
- specifier: ^24.1.1
- version: 24.1.1(canvas@2.11.2)
keycloak-js:
specifier: ^25.0.2
version: 25.0.2
@@ -225,9 +222,6 @@ importers:
'@testing-library/user-event':
specifier: ^14.5.2
version: 14.5.2(@testing-library/dom@9.3.4)
- '@types/history':
- specifier: ^5.0.0
- version: 5.0.0
'@vitejs/plugin-react-swc':
specifier: ^3.7.0
version: 3.7.0(vite@5.3.5(@types/node@20.14.12)(terser@5.31.6))
@@ -246,6 +240,9 @@ importers:
history:
specifier: ^5.3.0
version: 5.3.0
+ jsdom:
+ specifier: ^24.1.1
+ version: 24.1.1(canvas@2.11.2)
storybook:
specifier: ^8.0.5
version: 8.2.2(@babel/preset-env@7.24.8(@babel/core@7.24.8))
@@ -2668,10 +2665,6 @@ packages:
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
- '@types/history@5.0.0':
- resolution: {integrity: sha512-hy8b7Y1J8OGe6LbAjj3xniQrj3v6lsivCcrmf4TzSgPzLkhIeKgc5IZnT7ReIqmEuodjfO8EYAuoFvIrHi/+jQ==}
- deprecated: This is a stub types definition. history provides its own type definitions, so you do not need this installed.
-
'@types/http-cache-semantics@4.0.4':
resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
@@ -10857,10 +10850,6 @@ snapshots:
dependencies:
'@types/unist': 3.0.2
- '@types/history@5.0.0':
- dependencies:
- history: 5.3.0
-
'@types/http-cache-semantics@4.0.4': {}
'@types/http-errors@2.0.4': {}