hise-flow-graphs 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +32 -0
- package/README.md +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.LICENSE.txt +21 -0
- package/package.json +34 -0
- package/src/components/Certificates/CertificateGraph/NodeAttributes.jsx +32 -0
- package/src/components/Certificates/CertificateGraph/helpers.jsx +239 -0
- package/src/components/Certificates/CertificateGraph/index.jsx +54 -0
- package/src/components/Certificates/CertificateGraph/style.css +29 -0
- package/src/components/Certificates/EditCertificate/EditNode.jsx +46 -0
- package/src/components/Certificates/EditCertificate/EditNodeModal.jsx +131 -0
- package/src/components/Certificates/EditCertificate/MaskMetadataModal.jsx +299 -0
- package/src/components/Certificates/EditCertificate/helpers.js +58 -0
- package/src/components/Certificates/EditCertificate/index.jsx +65 -0
- package/src/components/Certificates/ReadCertificate/MetadataModal/helpers.jsx +22 -0
- package/src/components/Certificates/ReadCertificate/MetadataModal/index.jsx +169 -0
- package/src/components/Certificates/ReadCertificate/ReadNode.jsx +34 -0
- package/src/components/Certificates/ReadCertificate/helpers.js +39 -0
- package/src/components/Certificates/ReadCertificate/index.jsx +54 -0
- package/src/components/Certificates/ReviewCertificate/ReviewNode.jsx +38 -0
- package/src/components/Certificates/ReviewCertificate/ReviewNodes/AllNodesModal.jsx +63 -0
- package/src/components/Certificates/ReviewCertificate/ReviewNodes/SingleNodeModal.jsx +83 -0
- package/src/components/Certificates/ReviewCertificate/ReviewNodes/index.jsx +152 -0
- package/src/components/Certificates/ReviewCertificate/helpers.jsx +92 -0
- package/src/components/Certificates/ReviewCertificate/index.jsx +54 -0
- package/src/index.js +11 -0
- package/src/state/NodeContext.jsx +29 -0
- package/webpack.config.js +53 -0
- package/yarn-error.log +3721 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*! For license information please see main.js.LICENSE.txt */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @license React
|
|
5
|
+
* use-sync-external-store-shim.production.min.js
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
8
|
+
*
|
|
9
|
+
* This source code is licensed under the MIT license found in the
|
|
10
|
+
* LICENSE file in the root directory of this source tree.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @license React
|
|
15
|
+
* use-sync-external-store-shim/with-selector.production.min.js
|
|
16
|
+
*
|
|
17
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
18
|
+
*
|
|
19
|
+
* This source code is licensed under the MIT license found in the
|
|
20
|
+
* LICENSE file in the root directory of this source tree.
|
|
21
|
+
*/
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hise-flow-graphs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "This package manages ReactFlow graphs for HISE.",
|
|
5
|
+
"main": "dist/main.js",
|
|
6
|
+
"repository": "git@github.com:aifimmunology/hise-flow-graphs.git",
|
|
7
|
+
"author": "Allen Institute for Immunology",
|
|
8
|
+
"license": "ISC",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "webpack"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"bootstrap": "5.1.3",
|
|
14
|
+
"dagre": "^0.8.5",
|
|
15
|
+
"ramda": "0.27.1",
|
|
16
|
+
"reactflow": "^11.8.1"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@babel/core": "^7.22.10",
|
|
20
|
+
"@babel/preset-env": "^7.22.10",
|
|
21
|
+
"@babel/preset-react": "^7.22.5",
|
|
22
|
+
"babel-loader": "^9.1.3",
|
|
23
|
+
"css-loader": "^6.8.1",
|
|
24
|
+
"eslint": "^8.47.0",
|
|
25
|
+
"eslint-plugin-react": "^7.33.1",
|
|
26
|
+
"style-loader": "^3.3.3",
|
|
27
|
+
"webpack": "^5.88.2",
|
|
28
|
+
"webpack-cli": "^5.1.4"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"react": "18.2.0",
|
|
32
|
+
"react-dom": "18.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { getNodeDescription, getNodeLabel } from '../CertificateGraph/helpers';
|
|
3
|
+
|
|
4
|
+
export const Description = (props) => {
|
|
5
|
+
const nodeDescription = getNodeDescription(props.label, props.description);
|
|
6
|
+
|
|
7
|
+
if (!Object.keys(props.description).length || !nodeDescription) return null;
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className="text-center">
|
|
11
|
+
{ nodeDescription }
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const Label = (props) => {
|
|
17
|
+
const label = getNodeLabel(props.label, props.description);
|
|
18
|
+
|
|
19
|
+
if (!props.link) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="label flex-fill">
|
|
22
|
+
{ label }
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<a href={`#${props.link}`} target="_blank" rel="noreferrer">
|
|
29
|
+
{ label }
|
|
30
|
+
</a>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import dagre from 'dagre';
|
|
3
|
+
|
|
4
|
+
export const getEdges = (edges) => (
|
|
5
|
+
Object.entries(edges || {})
|
|
6
|
+
.reduce((all, [source, targets]) => (
|
|
7
|
+
[
|
|
8
|
+
...all,
|
|
9
|
+
...targets.map((target) => ({
|
|
10
|
+
id: `${source}-${target}`,
|
|
11
|
+
source,
|
|
12
|
+
target,
|
|
13
|
+
type: 'smoothstep',
|
|
14
|
+
animated: true,
|
|
15
|
+
})),
|
|
16
|
+
]
|
|
17
|
+
), [])
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const nodeWidth = 200;
|
|
21
|
+
const nodeHeight = 75;
|
|
22
|
+
|
|
23
|
+
export const getLayoutedElements = (nodes, edges, direction = 'LR') => {
|
|
24
|
+
const dagreGraph = new dagre.graphlib.Graph();
|
|
25
|
+
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
|
26
|
+
|
|
27
|
+
const isHorizontal = direction === 'LR';
|
|
28
|
+
|
|
29
|
+
dagreGraph.setGraph({ rankdir: direction });
|
|
30
|
+
|
|
31
|
+
nodes.forEach((node) => {
|
|
32
|
+
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
edges.forEach((edge) => {
|
|
36
|
+
dagreGraph.setEdge(edge.source, edge.target);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
dagre.layout(dagreGraph);
|
|
40
|
+
|
|
41
|
+
nodes.forEach((node) => {
|
|
42
|
+
const nodeWithPosition = dagreGraph.node(node.id);
|
|
43
|
+
|
|
44
|
+
node.targetPosition = isHorizontal ? 'left' : 'top';
|
|
45
|
+
node.sourcePosition = isHorizontal ? 'right' : 'bottom';
|
|
46
|
+
|
|
47
|
+
// We are shifting the dagre node position (anchor=center center) to the top left
|
|
48
|
+
// so it matches the React Flow node anchor point (top left).
|
|
49
|
+
node.position = {
|
|
50
|
+
x: nodeWithPosition.x - nodeWidth / 2,
|
|
51
|
+
y: nodeWithPosition.y - nodeHeight / 2,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return node;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return { nodes, edges };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const getStyle = (type) => {
|
|
61
|
+
const style = {
|
|
62
|
+
'background-color': 'white',
|
|
63
|
+
'border-radius': '3px',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
switch (type) {
|
|
67
|
+
case 'FileSet':
|
|
68
|
+
case 'Visualization':
|
|
69
|
+
style['background-color'] = 'yellow';
|
|
70
|
+
style.border = '4px solid';
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case 'IngestReceipt':
|
|
74
|
+
style.border = '2px solid #EE7733';
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
case 'File':
|
|
78
|
+
style.border = '2px solid #33BBEE';
|
|
79
|
+
break;
|
|
80
|
+
|
|
81
|
+
case 'IDE':
|
|
82
|
+
style.border = '2px solid #882255';
|
|
83
|
+
break;
|
|
84
|
+
|
|
85
|
+
case 'Process':
|
|
86
|
+
style.border = '2px solid #009988';
|
|
87
|
+
break;
|
|
88
|
+
|
|
89
|
+
case 'Trace':
|
|
90
|
+
style.border = '2px solid #999933';
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
case 'Sample':
|
|
94
|
+
case 'Subject':
|
|
95
|
+
case 'Cohort':
|
|
96
|
+
style.border = '2px solid #BBBBBB';
|
|
97
|
+
break;
|
|
98
|
+
|
|
99
|
+
default:
|
|
100
|
+
style.border = '2px solid #eee';
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return style;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const getSchemes = (vert, labels, schemes) => {
|
|
108
|
+
const nodeSchemes = {};
|
|
109
|
+
|
|
110
|
+
if (!labels || !schemes) return nodeSchemes;
|
|
111
|
+
|
|
112
|
+
const schemeLabels = labels[vert];
|
|
113
|
+
|
|
114
|
+
Object.entries(schemeLabels).forEach(([key, val]) => {
|
|
115
|
+
if (key === 'Survey Data Scheme Key') {
|
|
116
|
+
nodeSchemes.surveyData = schemes[val];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (key === 'EMR Scheme Key') {
|
|
120
|
+
nodeSchemes.emrData = schemes[val];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (key === 'Lab Data Scheme Key') {
|
|
124
|
+
nodeSchemes.labData = schemes[val];
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return nodeSchemes;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const getNodeLabel = (type, desc) => {
|
|
132
|
+
switch (type) {
|
|
133
|
+
case 'FileSet':
|
|
134
|
+
case 'Visualization':
|
|
135
|
+
return (
|
|
136
|
+
<React.Fragment>
|
|
137
|
+
{ type }
|
|
138
|
+
<br />
|
|
139
|
+
(
|
|
140
|
+
{ desc.Title }
|
|
141
|
+
)
|
|
142
|
+
</React.Fragment>
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
case 'IngestReceipt':
|
|
146
|
+
return 'Data Ingested';
|
|
147
|
+
|
|
148
|
+
case 'Process':
|
|
149
|
+
case 'File':
|
|
150
|
+
case 'Subject':
|
|
151
|
+
case 'IDE':
|
|
152
|
+
case 'Trace':
|
|
153
|
+
case 'Sample':
|
|
154
|
+
case 'Cohort':
|
|
155
|
+
return type;
|
|
156
|
+
|
|
157
|
+
default:
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const getNodeDescription = (type, desc) => {
|
|
163
|
+
switch (type) {
|
|
164
|
+
case 'FileSet':
|
|
165
|
+
case 'Visualization':
|
|
166
|
+
return desc.Description;
|
|
167
|
+
|
|
168
|
+
case 'File':
|
|
169
|
+
return desc['File Type'];
|
|
170
|
+
|
|
171
|
+
case 'IDE':
|
|
172
|
+
case 'IngestReceipt':
|
|
173
|
+
case 'Process':
|
|
174
|
+
return desc.Type;
|
|
175
|
+
|
|
176
|
+
case 'Trace':
|
|
177
|
+
case 'Cohort':
|
|
178
|
+
return desc.Description;
|
|
179
|
+
|
|
180
|
+
case 'Sample':
|
|
181
|
+
return `${desc['Visit Name']} (${desc['Visit Details']})`;
|
|
182
|
+
|
|
183
|
+
// case 'Subject':
|
|
184
|
+
|
|
185
|
+
default:
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const getLabelLink = (type, guid, isExecutable) => {
|
|
191
|
+
if (isExecutable === 'false') return null;
|
|
192
|
+
|
|
193
|
+
switch (type) {
|
|
194
|
+
case 'Visualization':
|
|
195
|
+
return `/collaboration/visualization/${guid}`;
|
|
196
|
+
|
|
197
|
+
case 'IngestReceipt':
|
|
198
|
+
return `/ingest-receipts/${guid}`;
|
|
199
|
+
|
|
200
|
+
case 'File':
|
|
201
|
+
return `/files/${guid}`;
|
|
202
|
+
|
|
203
|
+
case 'Process':
|
|
204
|
+
return `/processes/${guid}`;
|
|
205
|
+
|
|
206
|
+
case 'Sample':
|
|
207
|
+
return `/samples/${guid}`;
|
|
208
|
+
|
|
209
|
+
case 'Subject':
|
|
210
|
+
return `/subjects/${guid}`;
|
|
211
|
+
|
|
212
|
+
case 'Cohort':
|
|
213
|
+
return `/cohorts/${guid}`;
|
|
214
|
+
|
|
215
|
+
default:
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export const getKey = (key, scheme, tab) => {
|
|
221
|
+
if (!scheme) return key;
|
|
222
|
+
if (tab === 'demographicsData') return `${key[0].toUpperCase()}${key.split(/(?=[A-Z])/).join(' ').slice(1)}`;
|
|
223
|
+
|
|
224
|
+
const definition = scheme[key];
|
|
225
|
+
if (!definition) return key;
|
|
226
|
+
if (definition.friendlyName) return definition.friendlyName;
|
|
227
|
+
|
|
228
|
+
return key;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export const getValue = (key, value, scheme, tab) => {
|
|
232
|
+
if (!scheme) return value;
|
|
233
|
+
|
|
234
|
+
const definition = scheme[key];
|
|
235
|
+
if (!definition || tab === 'demographicsData') return value;
|
|
236
|
+
if (definition.units) return `${value} ${definition.units}`;
|
|
237
|
+
|
|
238
|
+
return value;
|
|
239
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import dagre from 'dagre';
|
|
3
|
+
import ReactFlow, {
|
|
4
|
+
Controls,
|
|
5
|
+
Background,
|
|
6
|
+
MiniMap,
|
|
7
|
+
useNodesState,
|
|
8
|
+
useEdgesState,
|
|
9
|
+
ConnectionLineType,
|
|
10
|
+
} from 'reactflow';
|
|
11
|
+
import ReadNode from '../ReadCertificate/ReadNode';
|
|
12
|
+
import EditNode from '../EditCertificate/EditNode';
|
|
13
|
+
import ReviewNode from '../ReviewCertificate/ReviewNode';
|
|
14
|
+
import 'reactflow/dist/style.css';
|
|
15
|
+
import './style.css';
|
|
16
|
+
|
|
17
|
+
const nodeTypes = {
|
|
18
|
+
readNode: ReadNode,
|
|
19
|
+
editNode: EditNode,
|
|
20
|
+
reviewNode: ReviewNode,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const dagreGraph = new dagre.graphlib.Graph();
|
|
24
|
+
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
|
25
|
+
|
|
26
|
+
const CertificateGraph = (props) => {
|
|
27
|
+
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
|
28
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
setNodes(props.nodes);
|
|
32
|
+
setEdges(props.edges);
|
|
33
|
+
}, [props]);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="layoutflow">
|
|
37
|
+
<ReactFlow
|
|
38
|
+
nodes={nodes}
|
|
39
|
+
edges={edges}
|
|
40
|
+
onNodesChange={onNodesChange}
|
|
41
|
+
onEdgesChange={onEdgesChange}
|
|
42
|
+
connectionLineType={ConnectionLineType.SmoothStep}
|
|
43
|
+
nodeTypes={nodeTypes}
|
|
44
|
+
fitView
|
|
45
|
+
>
|
|
46
|
+
<Controls />
|
|
47
|
+
<Background />
|
|
48
|
+
<MiniMap />
|
|
49
|
+
</ReactFlow>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default CertificateGraph;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.layoutflow {
|
|
2
|
+
flex-grow: 1;
|
|
3
|
+
position: relative;
|
|
4
|
+
height: 100%;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.layoutflow .controls {
|
|
8
|
+
position: absolute;
|
|
9
|
+
right: 10px;
|
|
10
|
+
top: 10px;
|
|
11
|
+
z-index: 10;
|
|
12
|
+
font-size: 12px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.layoutflow .controls button:first-child {
|
|
16
|
+
margin-right: 10px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* reactflow styling */
|
|
20
|
+
|
|
21
|
+
.react-flow__node {
|
|
22
|
+
text-transform: uppercase;
|
|
23
|
+
letter-spacing: 0.2em;
|
|
24
|
+
|
|
25
|
+
.label {
|
|
26
|
+
font-weight: 800;
|
|
27
|
+
text-align: center;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Handle, Position } from 'reactflow';
|
|
3
|
+
import { MaskMetadataButton } from './MaskMetadataModal';
|
|
4
|
+
import { Description, Label } from '../CertificateGraph/NodeAttributes';
|
|
5
|
+
import { EditNodeModalBtn } from './EditNodeModal';
|
|
6
|
+
import { getLabelLink } from '../CertificateGraph/helpers';
|
|
7
|
+
|
|
8
|
+
const EditNode = ({ data }) => {
|
|
9
|
+
const { vert, description, metadata, isExecutable } = data;
|
|
10
|
+
|
|
11
|
+
const [label, guid] = data.vert.split('/');
|
|
12
|
+
const link = getLabelLink(label, guid, isExecutable);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
<Handle
|
|
17
|
+
type="target"
|
|
18
|
+
position={Position.Left}
|
|
19
|
+
/>
|
|
20
|
+
|
|
21
|
+
<div className="p-2" style={{ width: '200px' }}>
|
|
22
|
+
<div className="label">
|
|
23
|
+
<Label link={link} label={label} description={description} />
|
|
24
|
+
|
|
25
|
+
<EditNodeModalBtn
|
|
26
|
+
node={data}
|
|
27
|
+
/>
|
|
28
|
+
|
|
29
|
+
<MaskMetadataButton
|
|
30
|
+
show={metadata[vert]}
|
|
31
|
+
node={data}
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<Description label={label} description={description} />
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<Handle
|
|
39
|
+
type="source"
|
|
40
|
+
position={Position.Right}
|
|
41
|
+
/>
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default EditNode;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Button, ConfirmationModal, ResourceEditor, MessageStripe } from 'hise-components';
|
|
3
|
+
import { useNode } from '../../../state/NodeContext';
|
|
4
|
+
import { getFormControls } from './helpers';
|
|
5
|
+
|
|
6
|
+
export const EditNodeModalBtn = ({ node }) => {
|
|
7
|
+
const { setNode } = useNode();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Button
|
|
11
|
+
id="edit-btn"
|
|
12
|
+
key="edit-btn"
|
|
13
|
+
color="link"
|
|
14
|
+
className="text-info px-1 pt-0 ml-2"
|
|
15
|
+
icon="fas fa-edit"
|
|
16
|
+
noText
|
|
17
|
+
onClick={() => {
|
|
18
|
+
node.toggleEditModal();
|
|
19
|
+
setNode(node);
|
|
20
|
+
}}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const EditNodeModal = ({
|
|
26
|
+
isOpen,
|
|
27
|
+
loading,
|
|
28
|
+
toggle,
|
|
29
|
+
handleUpdate,
|
|
30
|
+
refresh,
|
|
31
|
+
}) => {
|
|
32
|
+
const { node } = useNode();
|
|
33
|
+
|
|
34
|
+
const {
|
|
35
|
+
link, vert, description, customLabels, metadata,
|
|
36
|
+
} = node;
|
|
37
|
+
|
|
38
|
+
const [error, setError] = useState('');
|
|
39
|
+
|
|
40
|
+
const [form, setForm] = useState({});
|
|
41
|
+
const clear = () => setForm({});
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (description) setForm(description);
|
|
45
|
+
}, [isOpen, node, description]);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<ConfirmationModal
|
|
49
|
+
scrollable
|
|
50
|
+
isOpen={isOpen}
|
|
51
|
+
onClose={toggle}
|
|
52
|
+
headerContent="Edit Node Label"
|
|
53
|
+
noSubmit
|
|
54
|
+
noCancel
|
|
55
|
+
content={(
|
|
56
|
+
<>
|
|
57
|
+
{ link && (
|
|
58
|
+
<Button
|
|
59
|
+
to={link}
|
|
60
|
+
color="link"
|
|
61
|
+
icon="fas fa-long-arrow-alt-right"
|
|
62
|
+
target="_blank"
|
|
63
|
+
>
|
|
64
|
+
{ vert }
|
|
65
|
+
</Button>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{ (error && !loading) && (
|
|
69
|
+
<MessageStripe message={error} color="danger" dismissible={false} />
|
|
70
|
+
)}
|
|
71
|
+
|
|
72
|
+
<ResourceEditor
|
|
73
|
+
className=""
|
|
74
|
+
onChange={(l) => setForm(l)}
|
|
75
|
+
editedResource={form}
|
|
76
|
+
controls={getFormControls(form)}
|
|
77
|
+
/>
|
|
78
|
+
</>
|
|
79
|
+
)}
|
|
80
|
+
customFooter={(
|
|
81
|
+
<div className="float-right">
|
|
82
|
+
{ loading && (
|
|
83
|
+
<div className="spinner-grow spinner-grow-sm text-warning" role="status" />
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
<Button
|
|
87
|
+
id="cancel-btn"
|
|
88
|
+
icon="fas fa-times"
|
|
89
|
+
className="mx-2"
|
|
90
|
+
noText
|
|
91
|
+
onClick={() => {
|
|
92
|
+
toggle();
|
|
93
|
+
clear();
|
|
94
|
+
setError('');
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
<Button
|
|
99
|
+
id="ok-btn"
|
|
100
|
+
icon="fas fa-check"
|
|
101
|
+
noText
|
|
102
|
+
color="primary"
|
|
103
|
+
onClick={() => {
|
|
104
|
+
const body = {
|
|
105
|
+
metadata,
|
|
106
|
+
customLabels: {
|
|
107
|
+
...customLabels,
|
|
108
|
+
[vert]: form,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
handleUpdate(body)
|
|
113
|
+
.then(([r, d]) => {
|
|
114
|
+
if (r.ok) {
|
|
115
|
+
toggle();
|
|
116
|
+
clear();
|
|
117
|
+
refresh();
|
|
118
|
+
setError('');
|
|
119
|
+
} else {
|
|
120
|
+
setError(d.Message);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
/>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export default EditNodeModal;
|