@truedat/core 7.0.5 → 7.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/core",
3
- "version": "7.0.5",
3
+ "version": "7.0.6",
4
4
  "description": "Truedat Web Core",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -36,7 +36,7 @@
36
36
  "@testing-library/react": "^12.0.0",
37
37
  "@testing-library/react-hooks": "^8.0.1",
38
38
  "@testing-library/user-event": "^13.2.1",
39
- "@truedat/test": "7.0.5",
39
+ "@truedat/test": "7.0.6",
40
40
  "babel-jest": "^28.1.0",
41
41
  "babel-plugin-dynamic-import-node": "^2.3.3",
42
42
  "babel-plugin-lodash": "^3.3.4",
@@ -118,5 +118,5 @@
118
118
  "react-dom": ">= 16.8.6 < 17",
119
119
  "semantic-ui-react": ">= 2.0.3 < 2.2"
120
120
  },
121
- "gitHead": "d76d7852ddb993ed53f0266ec17858119e731bbf"
121
+ "gitHead": "00bc7f5ff0c4a073987b4ed538f9547a12d2d0ad"
122
122
  }
@@ -9,12 +9,17 @@ import { DOMAINS_QUERY } from "../api/queries";
9
9
  import TreeSelector from "./TreeSelector";
10
10
 
11
11
  export const DomainSelector = ({
12
+ allNodesOpen,
12
13
  action,
14
+ className,
13
15
  multiple,
14
16
  domainActions,
15
17
  domainIds,
18
+ notDropdown,
19
+ onNodeClick,
16
20
  onLoad,
17
21
  value,
22
+ ascendants,
18
23
  ...props
19
24
  }) => {
20
25
  const { formatMessage } = useIntl();
@@ -39,6 +44,10 @@ export const DomainSelector = ({
39
44
 
40
45
  return (
41
46
  <TreeSelector
47
+ allNodesOpen={allNodesOpen}
48
+ className={className}
49
+ notDropdown={notDropdown}
50
+ onChange={onNodeClick}
42
51
  options={options}
43
52
  placeholder={formatMessage({
44
53
  id: multiple
@@ -47,17 +56,23 @@ export const DomainSelector = ({
47
56
  })}
48
57
  value={multiple ? _.map(_.toString)(value) : _.toString(value)}
49
58
  multiple={multiple}
59
+ ascendants={ascendants}
50
60
  {...props}
51
61
  />
52
62
  );
53
63
  };
54
64
 
55
65
  DomainSelector.propTypes = {
66
+ allNodesOpen: PropTypes.bool,
56
67
  action: PropTypes.string,
68
+ className: PropTypes.string,
57
69
  multiple: PropTypes.bool,
58
70
  domainActions: PropTypes.array,
59
71
  domainIds: PropTypes.array,
72
+ notDropdown: PropTypes.bool,
73
+ onNodeClick: PropTypes.func,
60
74
  onLoad: PropTypes.func,
75
+ ascendants: PropTypes.array,
61
76
  value: PropTypes.oneOfType([
62
77
  PropTypes.array,
63
78
  PropTypes.string,
@@ -13,6 +13,7 @@ export const DropdownMenuItem = ({
13
13
  name,
14
14
  level,
15
15
  disabled = false,
16
+ className,
16
17
  }) => {
17
18
  const handleOpen = (e) => {
18
19
  e && e.preventDefault();
@@ -27,7 +28,11 @@ export const DropdownMenuItem = ({
27
28
  };
28
29
 
29
30
  return (
30
- <Dropdown.Item onClick={handleClick} selected={!check && selected}>
31
+ <Dropdown.Item
32
+ onClick={handleClick}
33
+ selected={!check && selected}
34
+ className={className}
35
+ >
31
36
  <div style={{ marginLeft: `${8 * level}px` }}>
32
37
  {canOpen ? (
33
38
  <Icon name={open ? "minus" : "plus"} onClick={handleOpen} />
@@ -47,6 +52,7 @@ DropdownMenuItem.propTypes = {
47
52
  id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
48
53
  canOpen: PropTypes.bool,
49
54
  check: PropTypes.bool,
55
+ className: PropTypes.string,
50
56
  onOpen: PropTypes.func,
51
57
  onClick: PropTypes.func,
52
58
  selected: PropTypes.bool,
@@ -0,0 +1,316 @@
1
+ import React, { useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import { useIntl } from "react-intl";
4
+ import { Icon, Grid, Label, Input, Form } from "semantic-ui-react";
5
+ import DescriptionInput from "./DescriptionInput";
6
+
7
+ const ListHierarchyItem = ({
8
+ node,
9
+ hierarchy,
10
+ onChange,
11
+ addNode,
12
+ removeNode,
13
+ isEditionMode,
14
+ }) => {
15
+ const { formatMessage } = useIntl();
16
+ const { id, name, error, editionStatus, description } = node;
17
+ const [expanded, setExpanded] = useState(false);
18
+ const [mouseOver, setMouseOver] = useState(false);
19
+ const generation = hierarchy.nodes.filter((n) => n.parentId == id);
20
+ const hasChildren = !generation.length == 0;
21
+
22
+ return (
23
+ <li
24
+ key={id}
25
+ onMouseOver={() => setMouseOver(true)}
26
+ onMouseOut={() => setMouseOver(false)}
27
+ >
28
+ <Icon
29
+ className="li_trash"
30
+ size="large"
31
+ name="cut"
32
+ title={formatMessage({ id: "hierarchy.delete_node" })}
33
+ style={{
34
+ cursor: "pointer",
35
+ visibility:
36
+ isEditionMode && !hasChildren && mouseOver ? "visible" : "hidden",
37
+ }}
38
+ onClick={() => removeNode(id)}
39
+ />
40
+
41
+ <div
42
+ style={{
43
+ display: "flex",
44
+ flexDirection: "row",
45
+ alignItems: "self-start",
46
+ }}
47
+ >
48
+ <div>
49
+ <Input
50
+ labelPosition="left"
51
+ type="text"
52
+ placeholder={formatMessage({ id: "hierarchy.node" })}
53
+ value={name == null ? "" : name}
54
+ error={error}
55
+ {...(!isEditionMode && {
56
+ "data-tooltip": name == null ? "" : name,
57
+ "data-position": "top left",
58
+ })}
59
+ className={isEditionMode ? "" : "hierarchyNode"}
60
+ disabled={!isEditionMode}
61
+ onChange={(_e, { value }) =>
62
+ onChange(id, { name: value, description: description })
63
+ }
64
+ >
65
+ {!hasChildren ? (
66
+ isEditionMode && mouseOver && name && name != "" ? (
67
+ <Label
68
+ basic
69
+ title={formatMessage({ id: "hierarchy.add_child" })}
70
+ onClick={() => {
71
+ addNode(id, null);
72
+ setExpanded(true);
73
+ }}
74
+ >
75
+ <Icon
76
+ fitted
77
+ name="times circle outline"
78
+ style={{ transform: "rotate(45deg)", cursor: "pointer" }}
79
+ color={
80
+ editionStatus == "dirty"
81
+ ? "orange"
82
+ : editionStatus == "added"
83
+ ? "green"
84
+ : null
85
+ }
86
+ />
87
+ </Label>
88
+ ) : (
89
+ <Label basic>
90
+ <Icon
91
+ fitted
92
+ name="circle outline"
93
+ color={
94
+ editionStatus == "dirty"
95
+ ? "orange"
96
+ : editionStatus == "added"
97
+ ? "green"
98
+ : null
99
+ }
100
+ />
101
+ </Label>
102
+ )
103
+ ) : (
104
+ <Label
105
+ basic
106
+ title={
107
+ expanded
108
+ ? formatMessage({ id: "hierarchy.collapse_node" })
109
+ : formatMessage({ id: "hierarchy.expand_node" })
110
+ }
111
+ >
112
+ <Icon
113
+ fitted
114
+ onClick={() => setExpanded(!expanded)}
115
+ style={{ cursor: "pointer" }}
116
+ color={
117
+ editionStatus == "dirty"
118
+ ? "orange"
119
+ : editionStatus == "added"
120
+ ? "green"
121
+ : null
122
+ }
123
+ name={expanded ? "dot circle outline" : "circle"}
124
+ />
125
+ </Label>
126
+ )}
127
+ <input />
128
+ </Input>
129
+ </div>
130
+
131
+ <div className="description_wrapper">
132
+ <DescriptionInput
133
+ disabled={!isEditionMode}
134
+ value={description}
135
+ onChange={(_e, { value }) =>
136
+ onChange(id, { name: name, description: value })
137
+ }
138
+ />
139
+ </div>
140
+ </div>
141
+ {generation.length == 0 || !expanded ? null : (
142
+ <ListHierarchy
143
+ hierarchy={hierarchy}
144
+ parentId={id}
145
+ onItemChange={onChange}
146
+ addNode={addNode}
147
+ removeNode={removeNode}
148
+ isEditionMode={isEditionMode}
149
+ />
150
+ )}
151
+ </li>
152
+ );
153
+ };
154
+
155
+ ListHierarchyItem.propTypes = {
156
+ node: PropTypes.object,
157
+ hierarchy: PropTypes.object,
158
+ onChange: PropTypes.func,
159
+ addNode: PropTypes.func,
160
+ removeNode: PropTypes.func,
161
+ isEditionMode: PropTypes.bool,
162
+ };
163
+
164
+ const ListHierarchy = ({
165
+ hierarchy,
166
+ parentId = null,
167
+ onItemChange,
168
+ addNode,
169
+ removeNode,
170
+ isEditionMode,
171
+ }) => {
172
+ const { formatMessage } = useIntl();
173
+ const [newNodeName, setNewNodeName] = useState();
174
+ const generation = hierarchy.nodes.filter(
175
+ (node) => node.parentId == parentId
176
+ );
177
+ const addNodeTo = (id = null) => (
178
+ <Form
179
+ onSubmit={() => {
180
+ addNode(id, newNodeName);
181
+ setNewNodeName("");
182
+ }}
183
+ >
184
+ <Input
185
+ labelPosition={newNodeName && newNodeName !== "" ? "right" : null}
186
+ onChange={(_e, { value }) => setNewNodeName(value)}
187
+ placeholder={formatMessage({ id: "hierarchy.new_node" })}
188
+ value={newNodeName}
189
+ >
190
+ <input />
191
+
192
+ {!newNodeName || newNodeName == "" ? null : (
193
+ <Label
194
+ style={{ cursor: "pointer" }}
195
+ onClick={() => {
196
+ addNode(id, newNodeName);
197
+ setNewNodeName("");
198
+ }}
199
+ >
200
+ {formatMessage({ id: "hierarchy.add" })}
201
+ </Label>
202
+ )}
203
+ </Input>
204
+ </Form>
205
+ );
206
+
207
+ return generation.length == 0 ? (
208
+ isEditionMode ? (
209
+ <ul className="wtree expanded">
210
+ <li>{addNodeTo()}</li>
211
+ </ul>
212
+ ) : null
213
+ ) : (
214
+ <ul className="wtree expanded">
215
+ {generation.map((node) => {
216
+ return (
217
+ <ListHierarchyItem
218
+ key={node.id}
219
+ node={node}
220
+ hierarchy={hierarchy}
221
+ onChange={onItemChange}
222
+ addNode={addNode}
223
+ removeNode={removeNode}
224
+ isEditionMode={isEditionMode}
225
+ />
226
+ );
227
+ })}
228
+ {isEditionMode ? (
229
+ <li className="add_node" key={generation.length}>
230
+ <div>{addNodeTo(parentId)}</div>
231
+ </li>
232
+ ) : null}
233
+ </ul>
234
+ );
235
+ };
236
+
237
+ ListHierarchy.propTypes = {
238
+ hierarchy: PropTypes.object,
239
+ parentId: PropTypes.number,
240
+ onItemChange: PropTypes.func,
241
+ addNode: PropTypes.func,
242
+ removeNode: PropTypes.func,
243
+ isEditionMode: PropTypes.bool,
244
+ };
245
+
246
+ const Hierarchy = ({
247
+ addNode,
248
+ hierarchy,
249
+ parentId = null,
250
+ updateMetadata,
251
+ updateNode,
252
+ removeNode,
253
+ isEditionMode,
254
+ }) => {
255
+ const { formatMessage } = useIntl();
256
+ return (
257
+ <Grid.Column>
258
+ <Grid.Row>
259
+ <div
260
+ style={{
261
+ display: "flex",
262
+ flexDirection: "row",
263
+ alignItems: "self-start",
264
+ }}
265
+ >
266
+ <Input
267
+ type="text"
268
+ placeholder={formatMessage({ id: "hierarchy.name" })}
269
+ disabled={!isEditionMode}
270
+ {...(!isEditionMode && {
271
+ "data-tooltip": hierarchy.name == null ? "" : hierarchy.name,
272
+ "data-position": "top left",
273
+ })}
274
+ style={isEditionMode ? {} : { opacity: 1 }}
275
+ value={hierarchy.name}
276
+ onChange={(_e, { value }) =>
277
+ updateMetadata({
278
+ name: value,
279
+ description: hierarchy.description,
280
+ })
281
+ }
282
+ />
283
+ <div className="description_wrapper">
284
+ <DescriptionInput
285
+ disabled={!isEditionMode}
286
+ value={hierarchy.description}
287
+ onChange={(_e, { value }) =>
288
+ updateMetadata({ name: hierarchy.name, description: value })
289
+ }
290
+ />
291
+ </div>
292
+ </div>
293
+ </Grid.Row>
294
+ <ListHierarchy
295
+ hierarchy={hierarchy}
296
+ parentId={parentId}
297
+ onItemChange={updateNode}
298
+ addNode={addNode}
299
+ removeNode={removeNode}
300
+ isEditionMode={isEditionMode}
301
+ />
302
+ </Grid.Column>
303
+ );
304
+ };
305
+
306
+ Hierarchy.propTypes = {
307
+ addNode: PropTypes.func,
308
+ hierarchy: PropTypes.object,
309
+ parentId: PropTypes.number,
310
+ updateMetadata: PropTypes.func,
311
+ updateNode: PropTypes.func,
312
+ removeNode: PropTypes.func,
313
+ isEditionMode: PropTypes.bool,
314
+ };
315
+
316
+ export default Hierarchy;
@@ -0,0 +1,65 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { useHistory } from "react-router-dom";
5
+ import { linkTo } from "@truedat/core/routes";
6
+ import { Grid, Header, Icon, Segment } from "semantic-ui-react";
7
+ import { FormattedMessage } from "react-intl";
8
+ import { ascendants } from "../services/tree";
9
+ import DomainSelector from "./DomainSelector";
10
+ import NodeOpenActions from "./NodeOpenActions";
11
+
12
+ const HierarchyNodeFinder = ({ nodes = [], idSelectedNode }) => {
13
+ const history = useHistory();
14
+ const [allOpen, setAllOpen] = useState(false);
15
+
16
+ const handleToggleAllNodes = () => {
17
+ setAllOpen((prev) => !prev);
18
+ };
19
+
20
+ const handleNodeClick = (e, { value }) => {
21
+ if (value) history.push(linkTo.DOMAIN_CONCEPTS({ id: value }));
22
+ };
23
+
24
+ return (
25
+ <Segment>
26
+ <Grid>
27
+ <Grid.Row>
28
+ <Grid.Column width={16}>
29
+ {!_.isEmpty(nodes) ? (
30
+ <>
31
+ <NodeOpenActions
32
+ onClick={handleToggleAllNodes}
33
+ allNodesOpen={allOpen}
34
+ className="nodeOpenActions"
35
+ />
36
+ <DomainSelector
37
+ notDropdown
38
+ value={idSelectedNode}
39
+ className="hierarchyNodeFinderTreeSelector"
40
+ onNodeClick={handleNodeClick}
41
+ ascendants={ascendants(idSelectedNode, nodes)}
42
+ allNodesOpen={allOpen}
43
+ />
44
+ </>
45
+ ) : (
46
+ <Header as="h4">
47
+ <Icon name="cube" />
48
+ <Header.Content>
49
+ <FormattedMessage id="domains.notExist" />
50
+ </Header.Content>
51
+ </Header>
52
+ )}
53
+ </Grid.Column>
54
+ </Grid.Row>
55
+ </Grid>
56
+ </Segment>
57
+ );
58
+ };
59
+
60
+ HierarchyNodeFinder.propTypes = {
61
+ nodes: PropTypes.array,
62
+ idSelectedNode: PropTypes.number,
63
+ };
64
+
65
+ export default HierarchyNodeFinder;
@@ -7,6 +7,21 @@ import { accentInsensitivePathOrder } from "../services/sort";
7
7
  import { stratify, flatten } from "../services/tree";
8
8
  import TreeSelector from "./TreeSelector";
9
9
 
10
+ const maybeMapKeys = _.map((option) => {
11
+ if (_.has("children")(option)) {
12
+ return {
13
+ ...option,
14
+ id: _.has("key")(option) ? option.key : option.id,
15
+ children: maybeMapKeys(option.children),
16
+ };
17
+ } else {
18
+ return {
19
+ ...option,
20
+ id: _.has("key")(option) ? option.key : option.id,
21
+ };
22
+ }
23
+ });
24
+
10
25
  const HierarchySelector = ({
11
26
  multiple,
12
27
  hierarchy,
@@ -25,7 +40,8 @@ const HierarchySelector = ({
25
40
  _.propOr([], "nodes"),
26
41
  _.sortBy(accentInsensitivePathOrder("name")),
27
42
  stratify({}),
28
- flatten
43
+ flatten,
44
+ maybeMapKeys
29
45
  )(data);
30
46
 
31
47
  return (
@@ -37,7 +53,7 @@ const HierarchySelector = ({
37
53
  ? "hierarchy.multiple.placeholder"
38
54
  : "hierarchy.selector.placeholder",
39
55
  })}
40
- value={value}
56
+ value={multiple ? _.map(_.toString)(value) : _.toString(value)}
41
57
  multiple={multiple}
42
58
  {...props}
43
59
  />
@@ -0,0 +1,30 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { useIntl } from "react-intl";
4
+ import PropTypes from "prop-types";
5
+ import { Button } from "semantic-ui-react";
6
+
7
+ export const NodeOpenActions = ({ allNodesOpen, className, onClick }) => {
8
+ const { formatMessage } = useIntl();
9
+ return (
10
+ <Button
11
+ floated="right"
12
+ className={className}
13
+ secondary
14
+ onClick={onClick}
15
+ data-tooltip={formatMessage({
16
+ id: allNodesOpen ? "actions.close.all" : "actions.open.all",
17
+ })}
18
+ data-position="bottom right"
19
+ icon={allNodesOpen ? "minus" : "plus"}
20
+ />
21
+ );
22
+ };
23
+
24
+ NodeOpenActions.propTypes = {
25
+ allNodesOpen: PropTypes.bool,
26
+ className: PropTypes.string,
27
+ onClick: PropTypes.func,
28
+ };
29
+
30
+ export default NodeOpenActions;
@@ -1,6 +1,5 @@
1
1
  import _ from "lodash/fp";
2
2
  import React, { useState } from "react";
3
- import { useParams } from "react-router-dom";
4
3
  import PropTypes from "prop-types";
5
4
  import { Card, Header, Input, Message, Grid } from "semantic-ui-react";
6
5
  import { useIntl } from "react-intl";
@@ -29,8 +28,7 @@ const filterData = (filter, data) => {
29
28
  return _.filter(matchesFilter(filter))(data);
30
29
  };
31
30
 
32
- export const ResourceMembers = ({ type }) => {
33
- const { id } = useParams();
31
+ export const ResourceMembers = ({ type, id }) => {
34
32
  const {
35
33
  data,
36
34
  loading,
@@ -100,6 +98,7 @@ export const ResourceMembers = ({ type }) => {
100
98
 
101
99
  ResourceMembers.propTypes = {
102
100
  type: PropTypes.string,
101
+ id: PropTypes.number,
103
102
  };
104
103
 
105
104
  export default ResourceMembers;