Skip to content

Commit e5d1f63

Browse files
committed
diagram properties
Signed-off-by: Aaron Chong <aaronchong@google.com>
1 parent d76fb94 commit e5d1f63

File tree

10 files changed

+313
-25
lines changed

10 files changed

+313
-25
lines changed

diagram-editor/dist.tar.gz

2.66 KB
Binary file not shown.

diagram-editor/frontend/api.preprocessed.schema.json

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -238,20 +238,14 @@
238238
},
239239
"description": {
240240
"description": "Optional text to describe the workflow.",
241-
"type": [
242-
"string",
243-
"null"
244-
]
241+
"type": "string"
245242
},
246243
"example_inputs": {
247244
"description": "Examples of inputs that can be used with this workflow.",
248245
"items": {
249-
"type": "string"
246+
"$ref": "#/$defs/ExampleInput"
250247
},
251-
"type": [
252-
"array",
253-
"null"
254-
]
248+
"type": "array"
255249
},
256250
"extensions": {
257251
"additionalProperties": true,
@@ -586,6 +580,19 @@
586580
}
587581
]
588582
},
583+
"ExampleInput": {
584+
"properties": {
585+
"description": {
586+
"type": "string"
587+
},
588+
"value": true
589+
},
590+
"required": [
591+
"value",
592+
"description"
593+
],
594+
"type": "object"
595+
},
589596
"ForkCloneSchema": {
590597
"description": "If the request is cloneable, clone it into multiple responses that can\n each be sent to a different operation. The `next` property is an array.\n\n This creates multiple simultaneous branches of execution within the\n workflow. Usually when you have multiple branches you will either\n * race - connect all branches to `terminate` and the first branch to\n finish \"wins\" the race and gets to the be output\n * join - connect each branch into a buffer and then use the `join`\n operation to reunite them\n * collect - TODO(@mxgrey): [add the collect operation](https://github.com/open-rmf/crossflow/issues/59)\n\n # Examples\n ```\n # crossflow::Diagram::from_json_str(r#\"\n {\n \"version\": \"0.1.0\",\n \"start\": \"begin_race\",\n \"ops\": {\n \"begin_race\": {\n \"type\": \"fork_clone\",\n \"next\": [\n \"ferrari\",\n \"mustang\"\n ]\n },\n \"ferrari\": {\n \"type\": \"node\",\n \"builder\": \"drive\",\n \"config\": \"ferrari\",\n \"next\": { \"builtin\": \"terminate\" }\n },\n \"mustang\": {\n \"type\": \"node\",\n \"builder\": \"drive\",\n \"config\": \"mustang\",\n \"next\": { \"builtin\": \"terminate\" }\n }\n }\n }\n # \"#)?;\n # Ok::<_, serde_json::Error>(())",
591598
"properties": {

diagram-editor/frontend/command-panel.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Button, ButtonGroup, styled, Tooltip } from '@mui/material';
1+
import { Button, ButtonGroup, styled, Tooltip, useTheme } from '@mui/material';
22
import { type NodeChange, Panel } from '@xyflow/react';
33
import React from 'react';
44
import AutoLayoutButton from './auto-layout-button';
@@ -7,6 +7,7 @@ import { EditorMode, useEditorMode } from './editor-mode';
77
import type { DiagramEditorNode } from './nodes';
88
import { MaterialSymbol } from './nodes';
99
import { RunButton } from './run-button';
10+
import DiagramPropertiesDrawer from './diagram-properties-drawer';
1011

1112
export interface CommandPanelProps {
1213
onNodeChanges: (changes: NodeChange<DiagramEditorNode>[]) => void;
@@ -31,15 +32,32 @@ function CommandPanel({
3132
onExportClick,
3233
onLoadDiagram,
3334
}: CommandPanelProps) {
35+
const theme = useTheme();
3436
const [openEditTemplatesDialog, setOpenEditTemplatesDialog] =
3537
React.useState(false);
38+
const [openDiagramPropertiesDrawer, setOpenDiagramPropertiesDrawer] =
39+
React.useState(false);
3640
const [editorMode] = useEditorMode();
3741

3842
return (
3943
<>
4044
<Panel position="top-center">
4145
<ButtonGroup variant="contained">
4246
{editorMode.mode === EditorMode.Normal && <RunButton />}
47+
{editorMode.mode === EditorMode.Normal && (
48+
<Tooltip title="Diagram properties">
49+
<Button
50+
onClick={() => setOpenDiagramPropertiesDrawer((prev) => !prev)}
51+
sx={
52+
openDiagramPropertiesDrawer
53+
? { backgroundColor: theme.palette.primary.light }
54+
: undefined
55+
}
56+
>
57+
<MaterialSymbol symbol="info" />
58+
</Button>
59+
</Tooltip>
60+
)}
4361
{editorMode.mode === EditorMode.Normal && (
4462
<Tooltip title="Templates">
4563
<Button onClick={() => setOpenEditTemplatesDialog(true)}>
@@ -84,6 +102,10 @@ function CommandPanel({
84102
open={openEditTemplatesDialog}
85103
onClose={() => setOpenEditTemplatesDialog(false)}
86104
/>
105+
<DiagramPropertiesDrawer
106+
open={openDiagramPropertiesDrawer}
107+
onClose={() => setOpenDiagramPropertiesDrawer(false)}
108+
/>
87109
</>
88110
);
89111
}

diagram-editor/frontend/diagram-editor.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { ExportDiagramDialog } from './export-diagram-dialog';
4747
import { defaultEdgeData, EditEdgeForm, EditNodeForm } from './forms';
4848
import EditScopeForm from './forms/edit-scope-form';
4949
import { type LoadContext, LoadContextProvider } from './load-context-provider';
50+
import { type DiagramProperties, DiagramPropertiesProvider } from './diagram-properties-provider';
5051
import { NodeManager, NodeManagerProvider } from './node-manager';
5152
import {
5253
type DiagramEditorNode,
@@ -119,20 +120,26 @@ interface ProvidersProps {
119120
loadContext: LoadContext | null;
120121
nodeManager: NodeManager;
121122
edges: DiagramEditorEdge[];
123+
diagramProperties: DiagramProperties;
122124
}
123125

124126
function Providers({
125127
editorModeContext,
126128
loadContext,
127129
nodeManager,
128130
edges,
131+
diagramProperties,
129132
children,
130133
}: React.PropsWithChildren<ProvidersProps>) {
131134
return (
132135
<EditorModeProvider value={editorModeContext}>
133136
<LoadContextProvider value={loadContext}>
134137
<NodeManagerProvider value={nodeManager}>
135-
<EdgesProvider value={edges}>{children}</EdgesProvider>
138+
<EdgesProvider value={edges}>
139+
<DiagramPropertiesProvider value={diagramProperties}>
140+
{children}
141+
</DiagramPropertiesProvider>
142+
</EdgesProvider>
136143
</NodeManagerProvider>
137144
</LoadContextProvider>
138145
</EditorModeProvider>
@@ -509,12 +516,17 @@ function DiagramEditor() {
509516
const [loadContext, setLoadContext] = React.useState<LoadContext | null>(
510517
null,
511518
);
519+
const [diagramProperties, setDiagramProperties] =
520+
React.useState<DiagramProperties>({});
512521

513522
const loadDiagram = React.useCallback(
514523
async (jsonStr: string) => {
515524
try {
516525
const [diagram, { graph, isRestored }] = await loadDiagramJson(jsonStr);
517526
setLoadContext({ diagram });
527+
setDiagramProperties({
528+
description: diagram.description,
529+
example_inputs: diagram.example_inputs });
518530
// do not perform auto layout if the diagram is restored from previous state.
519531
if (!isRestored) {
520532
const changes = autoLayout(graph.nodes, graph.edges, LAYOUT_OPTIONS);
@@ -604,6 +616,7 @@ function DiagramEditor() {
604616
loadContext={loadContext}
605617
nodeManager={nodeManager}
606618
edges={edges}
619+
diagramProperties={diagramProperties}
607620
>
608621
<ReactFlow
609622
nodes={nodes}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import {
2+
Button,
3+
Dialog,
4+
DialogActions,
5+
DialogContent,
6+
DialogTitle,
7+
Divider,
8+
Drawer,
9+
List,
10+
ListItem,
11+
ListItemButton,
12+
ListItemIcon,
13+
ListItemText,
14+
Stack,
15+
TextField,
16+
Tooltip,
17+
Typography,
18+
useTheme,
19+
} from '@mui/material';
20+
import React from 'react';
21+
import { useDiagramProperties } from './diagram-properties-provider';
22+
23+
export interface DiagramPropertiesDrawerProps {
24+
open: boolean;
25+
onClose: () => void;
26+
}
27+
28+
function DiagramPropertiesDrawer({ open, onClose }: DiagramPropertiesDrawerProps) {
29+
const diagramProperties = useDiagramProperties();
30+
const theme = useTheme();
31+
32+
return (
33+
<Drawer
34+
sx={{
35+
width: 500,
36+
// // width: drawerWidth,
37+
// flexShrink: 0,
38+
// // '& .MuiDrawer-paper': {
39+
// // width: drawerWidth,
40+
// // },
41+
}}
42+
variant="persistent"
43+
anchor="right"
44+
open={open}
45+
>
46+
<List>
47+
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
48+
<ListItem key={text} disablePadding>
49+
<ListItemButton>
50+
{/* <ListItemIcon>
51+
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
52+
</ListItemIcon> */}
53+
<ListItemText primary={text} />
54+
</ListItemButton>
55+
</ListItem>
56+
))}
57+
</List>
58+
</Drawer>
59+
);
60+
61+
// React.useEffect(() => {
62+
// const diagram = exportDiagram(registry, nodeManager, edges, templates);
63+
// if (loadContext?.diagram.extensions) {
64+
// diagram.extensions = loadContext.diagram.extensions;
65+
// }
66+
// if (loadContext?.diagram.description) {
67+
// diagram.description = loadContext.diagram.description;
68+
// }
69+
// if (loadContext?.diagram.example_inputs) {
70+
// diagram.example_inputs = loadContext.diagram.example_inputs;
71+
// }
72+
// // await saveState(diagram, {
73+
// // nodes: [...nodeManager.nodes],
74+
// // edges: [...edges],
75+
// // });
76+
77+
// setDescription(diagram.description ?? '');
78+
// setExampleInputs(diagram.example_inputs ?? []);
79+
// }, [edges, loadContext, nodeManager, registry, templates]);
80+
81+
// return (
82+
// <Dialog
83+
// open={open}
84+
// onClose={onClose}
85+
// fullWidth
86+
// maxWidth="sm"
87+
// keepMounted={false}
88+
// >
89+
// <DialogTitle>Diagram information</DialogTitle>
90+
// <DialogContent>
91+
// <Stack spacing={2}>
92+
// <Typography variant="h6">Description</Typography>
93+
// <TextField
94+
// fullWidth
95+
// multiline
96+
// rows={5}
97+
// variant="outlined"
98+
// value={description}
99+
// slotProps={{
100+
// htmlInput: { sx: { fontFamily: 'monospace' } },
101+
// }}
102+
// onChange={(d) => setDescription(d.target.value)}
103+
// sx={{ backgroundColor: theme.palette.background.paper }}
104+
// />
105+
// <Typography variant="h6">Example inputs</Typography>
106+
// <List disablePadding sx={{ maxHeight: '24rem', overflow: 'auto' }}>
107+
// {exampleInputs.length > 0 ? (
108+
// exampleInputs.map((input, index) => (
109+
// <ListItem key={index} divider>
110+
// <Stack
111+
// direction="row"
112+
// alignItems="center"
113+
// width="100%"
114+
// height="3em"
115+
// >
116+
// <TextField
117+
// size="small"
118+
// fullWidth
119+
// value={input}
120+
// onChange={(ev) => {
121+
// setExampleInputs((prev) => {
122+
// let updated = [...prev];
123+
// updated[index] = ev.target.value;
124+
// return updated;
125+
// });
126+
// }}
127+
// />
128+
// <Tooltip title="Delete">
129+
// <Button
130+
// variant="outlined"
131+
// color="error"
132+
// onClick={() =>
133+
// setExampleInputs((prev) => {
134+
// let updated = [...prev];
135+
// delete updated[index];
136+
// return updated;
137+
// })
138+
// }
139+
// >
140+
// <MaterialSymbol symbol="delete" />
141+
// </Button>
142+
// </Tooltip>
143+
// </Stack>
144+
// </ListItem>
145+
// ))
146+
// ) : (
147+
// <ListItem divider>
148+
// <ListItemText
149+
// slotProps={{
150+
// primary: { color: theme.palette.text.disabled },
151+
// }}
152+
// >
153+
// No example available
154+
// </ListItemText>
155+
// </ListItem>
156+
// )}
157+
// </List>
158+
// <Divider />
159+
// <ListItem>
160+
// <Stack justifyContent="center" width="100%">
161+
// <Button
162+
// onClick={() => {
163+
// setExampleInputs((prev) => { return [...prev, '']; });
164+
// }}
165+
// >
166+
// <MaterialSymbol symbol="add" />
167+
// </Button>
168+
// </Stack>
169+
// </ListItem>
170+
// </Stack>
171+
// </DialogContent>
172+
// <DialogActions>
173+
// <Button
174+
// onClick={async () => {
175+
176+
177+
// await saveState(diagram, {
178+
// nodes: [...nodeManager.nodes],
179+
// edges: [...edges],
180+
// });
181+
// }}
182+
// >
183+
// Save
184+
// </Button>
185+
// <Button
186+
// onClick={() => {
187+
// setDescription(loadContext?.diagram.description ?? '');
188+
// setExampleInputs(loadContext?.diagram.example_inputs ?? []);
189+
// onClose();
190+
// }}
191+
// >
192+
// Close
193+
// </Button>
194+
// </DialogActions>
195+
// </Dialog>
196+
// );
197+
}
198+
199+
export default DiagramPropertiesDrawer;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createContext, useContext } from 'react';
2+
import type { ExampleInput } from './types/api';
3+
4+
export interface DiagramProperties {
5+
description?: string;
6+
example_inputs?: ExampleInput[];
7+
}
8+
9+
const DiagramPropertiesContextComp =
10+
createContext<DiagramProperties | null>(null);
11+
12+
export const DiagramPropertiesProvider =
13+
DiagramPropertiesContextComp.Provider;
14+
15+
export const useDiagramProperties = (): DiagramProperties | null => {
16+
return useContext(DiagramPropertiesContextComp);
17+
};

diagram-editor/frontend/export-diagram-dialog.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ function ExportDiagramDialogInternal({
4545
if (loadContext?.diagram.extensions) {
4646
diagram.extensions = loadContext.diagram.extensions;
4747
}
48+
if (loadContext?.diagram.description) {
49+
diagram.description = loadContext.diagram.description;
50+
}
51+
if (loadContext?.diagram.example_inputs) {
52+
diagram.example_inputs = loadContext.diagram.example_inputs;
53+
}
4854
await saveState(diagram, {
4955
nodes: [...nodeManager.nodes],
5056
edges: [...edges],

0 commit comments

Comments
 (0)