Skip to content

Commit 7a1cbfd

Browse files
committed
sidebar with tabs added
1 parent dbea4ea commit 7a1cbfd

File tree

5 files changed

+630
-26
lines changed

5 files changed

+630
-26
lines changed

frontend/packages/volto-workflow-manager/src/actions/workflow.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
ASSIGN_WORKFLOW,
88
UPDATE_WORKFLOW_SECURITY,
99
VALIDATE_WORKFLOW,
10+
SELECT_WORKFLOW_ITEM,
11+
CLEAR_WORKFLOW_SELECTION,
1012
CLEAR_VALIDATION,
1113
} from '../constants';
1214

@@ -112,3 +114,19 @@ export function clearValidation() {
112114
type: CLEAR_VALIDATION,
113115
};
114116
}
117+
118+
export function selectWorkflowItem(item: {
119+
kind: 'state' | 'transition';
120+
id: string;
121+
}) {
122+
return {
123+
type: SELECT_WORKFLOW_ITEM,
124+
payload: item,
125+
};
126+
}
127+
128+
export function clearWorkflowSelection() {
129+
return {
130+
type: CLEAR_WORKFLOW_SELECTION,
131+
};
132+
}

frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { useCallback, useState, useEffect } from 'react';
2+
import { useDispatch } from 'react-redux';
3+
import { selectWorkflowItem } from '../../actions';
24
import {
35
ReactFlow,
46
ReactFlowProvider,
@@ -8,6 +10,7 @@ import {
810
reconnectEdge,
911
useEdgesState,
1012
useNodesState,
13+
type Node,
1114
type Edge,
1215
type FitViewOptions,
1316
type OnReconnect,
@@ -16,9 +19,10 @@ import {
1619
} from '@xyflow/react';
1720
import CustomEdge from './Edges/CustomEdge';
1821
import CustomNode from './Nodes/CustomNode';
19-
import type { Workflow, EdgeData } from '../../types/graph';
22+
import type { Workflow, EdgeData, NodeData } from '../../types/graph';
2023
import '@xyflow/react/dist/style.css';
2124
import CreateTransition from '../Transitions/CreateTransition';
25+
import { setSidebarTab } from '@plone/volto/actions/sidebar/sidebar';
2226

2327
const fitViewOptions: FitViewOptions = { padding: 0.2 };
2428
const defaultEdgeOptions: DefaultEdgeOptions = {
@@ -33,6 +37,7 @@ interface WorkflowGraphProps {
3337
}
3438

3539
const WorkflowGraphInner: React.FC<WorkflowGraphProps> = ({ workflow }) => {
40+
const dispatch = useDispatch();
3641
const [nodes, setNodes, onNodesChange] = useNodesState([]);
3742
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
3843
const [isCreateTransitionOpen, setCreateTransitionOpen] = useState(false);
@@ -46,7 +51,7 @@ const WorkflowGraphInner: React.FC<WorkflowGraphProps> = ({ workflow }) => {
4651

4752
const nodeCount = workflow.states.length;
4853
const radius = Math.max(200, nodeCount * 40);
49-
const newNodes = workflow.states.map((state, idx) => {
54+
const newNodes: Node<NodeData>[] = workflow.states.map((state, idx) => {
5055
const angle = (idx * 2 * Math.PI) / nodeCount;
5156
return {
5257
id: state.id,
@@ -114,6 +119,27 @@ const WorkflowGraphInner: React.FC<WorkflowGraphProps> = ({ workflow }) => {
114119
[setEdges],
115120
);
116121

122+
const onNodeClick = useCallback(
123+
(_event: React.MouseEvent, node: Node<NodeData>) => {
124+
const selected = { kind: 'state' as const, id: node.id };
125+
dispatch(selectWorkflowItem(selected));
126+
dispatch(setSidebarTab(1)); // Switch to "States" tab
127+
},
128+
[dispatch],
129+
);
130+
131+
const onEdgeClick = useCallback(
132+
(_event: React.MouseEvent, edge: Edge<EdgeData>) => {
133+
const transitionId = edge.data?.transitionId;
134+
if (transitionId) {
135+
const selected = { kind: 'transition' as const, id: transitionId };
136+
dispatch(selectWorkflowItem(selected));
137+
dispatch(setSidebarTab(2)); // Switch to "Transitions" tab
138+
}
139+
},
140+
[dispatch],
141+
);
142+
117143
return (
118144
<>
119145
<div style={{ width: '100%', height: '100%' }}>
@@ -124,6 +150,8 @@ const WorkflowGraphInner: React.FC<WorkflowGraphProps> = ({ workflow }) => {
124150
onEdgesChange={onEdgesChange}
125151
onConnect={onConnect}
126152
onReconnect={onReconnect}
153+
onNodeClick={onNodeClick}
154+
onEdgeClick={onEdgeClick}
127155
edgeTypes={edgeTypes}
128156
nodeTypes={nodeTypes}
129157
fitView
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React, { useState, useEffect, useCallback } from 'react';
2+
import { View, ProgressCircle } from '@adobe/react-spectrum';
3+
import Form from '@plone/volto/components/manage/Form/Form';
4+
5+
interface PropertiesFormProps {
6+
schema: any;
7+
item: { [key: string]: any } | null | undefined;
8+
onDataChange: (payload: any | null) => void;
9+
isDisabled: boolean;
10+
}
11+
12+
const PropertiesForm: React.FC<PropertiesFormProps> = ({
13+
schema,
14+
item,
15+
onDataChange,
16+
isDisabled,
17+
}) => {
18+
const [formData, setFormData] = useState<{ [key: string]: any } | null>(null);
19+
20+
useEffect(() => {
21+
if (item) {
22+
setFormData({
23+
title: item.title || '',
24+
description: item.description || '',
25+
});
26+
}
27+
}, [item]);
28+
29+
useEffect(() => {
30+
if (!formData || !item) return;
31+
32+
const hasUnsavedChanges = !(
33+
formData.title === item.title && formData.description === item.description
34+
);
35+
36+
if (hasUnsavedChanges) {
37+
const payload = item.id ? { id: item.id, ...formData } : formData;
38+
onDataChange(payload);
39+
} else {
40+
onDataChange(null);
41+
}
42+
}, [formData, onDataChange, item]);
43+
44+
const handleFormChange = useCallback(
45+
(newFormData: { [key: string]: any }) => {
46+
setFormData(newFormData);
47+
},
48+
[],
49+
);
50+
51+
if (!item || !formData) {
52+
return <ProgressCircle isIndeterminate aria-label="Loading item..." />;
53+
}
54+
55+
return (
56+
<View padding="size-200">
57+
<Form
58+
schema={schema}
59+
formData={formData}
60+
onChangeFormData={handleFormChange}
61+
editable={!isDisabled}
62+
hideActions
63+
/>
64+
</View>
65+
);
66+
};
67+
68+
export default PropertiesForm;

0 commit comments

Comments
 (0)