@macrostrat/feedback-components 1.1.3 → 1.1.5
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/CHANGELOG.md +10 -0
- package/dist/esm/feedback-components.03f08dc7.js +287 -0
- package/dist/esm/feedback-components.03f08dc7.js.map +1 -0
- package/dist/esm/feedback-components.06a79c6a.js +354 -0
- package/dist/esm/feedback-components.06a79c6a.js.map +1 -0
- package/dist/esm/{feedback-components.f577ebea.js → feedback-components.3f59f2a5.js} +14 -89
- package/dist/esm/feedback-components.3f59f2a5.js.map +1 -0
- package/dist/esm/{feedback-components.45d25912.js → feedback-components.4cbd249a.js} +5 -5
- package/dist/esm/{feedback-components.45d25912.js.map → feedback-components.4cbd249a.js.map} +1 -1
- package/dist/esm/{feedback-components.fb60c70d.css → feedback-components.5a8f0185.css} +29 -4
- package/dist/esm/feedback-components.5a8f0185.css.map +1 -0
- package/dist/esm/{feedback-components.fa1d3641.js → feedback-components.6cec1102.js} +84 -9
- package/dist/esm/feedback-components.6cec1102.js.map +1 -0
- package/dist/esm/{feedback-components.95dbe7d7.js → feedback-components.7c2fe400.js} +16 -1
- package/dist/esm/feedback-components.7c2fe400.js.map +1 -0
- package/dist/esm/{feedback-components.832b2eae.js → feedback-components.939a3a9f.js} +7 -7
- package/dist/esm/feedback-components.939a3a9f.js.map +1 -0
- package/dist/esm/feedback-components.e53837d9.js +248 -0
- package/dist/esm/feedback-components.e53837d9.js.map +1 -0
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/node/{feedback-components.f9abf0d6.js → feedback-components.3888aa2a.js} +2 -2
- package/dist/node/{feedback-components.f9abf0d6.js.map → feedback-components.3888aa2a.js.map} +1 -1
- package/dist/node/feedback-components.388de4ac.js +2 -0
- package/dist/node/feedback-components.388de4ac.js.map +1 -0
- package/dist/node/feedback-components.827f8d80.js +2 -0
- package/dist/node/feedback-components.827f8d80.js.map +1 -0
- package/dist/node/feedback-components.9e1d4e4c.js +2 -0
- package/dist/node/feedback-components.9e1d4e4c.js.map +1 -0
- package/dist/node/feedback-components.b8da3bce.js +2 -0
- package/dist/node/feedback-components.b8da3bce.js.map +1 -0
- package/dist/node/feedback-components.c31cf831.js +2 -0
- package/dist/node/feedback-components.c31cf831.js.map +1 -0
- package/dist/node/feedback-components.db4d0a14.css +2 -0
- package/dist/node/feedback-components.db4d0a14.css.map +1 -0
- package/dist/node/feedback-components.f91331e9.js +2 -0
- package/dist/node/feedback-components.f91331e9.js.map +1 -0
- package/dist/node/feedback-components.fc0395df.js +2 -0
- package/dist/node/feedback-components.fc0395df.js.map +1 -0
- package/dist/node/index.js +1 -1
- package/dist/node/index.js.map +1 -1
- package/package.json +2 -2
- package/src/feedback/edit-state.ts +99 -3
- package/src/feedback/feedback.module.sass +24 -4
- package/src/feedback/graph.ts +5 -2
- package/src/feedback/index.ts +39 -320
- package/src/feedback/matches.ts +279 -0
- package/src/feedback/text-visualizer.ts +34 -88
- package/src/feedback/typelist.ts +321 -0
- package/dist/esm/feedback-components.1e7da538.js +0 -587
- package/dist/esm/feedback-components.1e7da538.js.map +0 -1
- package/dist/esm/feedback-components.832b2eae.js.map +0 -1
- package/dist/esm/feedback-components.95dbe7d7.js.map +0 -1
- package/dist/esm/feedback-components.f577ebea.js.map +0 -1
- package/dist/esm/feedback-components.fa1d3641.js.map +0 -1
- package/dist/esm/feedback-components.fb60c70d.css.map +0 -1
- package/dist/node/feedback-components.25f1909a.js +0 -2
- package/dist/node/feedback-components.25f1909a.js.map +0 -1
- package/dist/node/feedback-components.4cd6b208.js +0 -2
- package/dist/node/feedback-components.4cd6b208.js.map +0 -1
- package/dist/node/feedback-components.9328e8ba.js +0 -2
- package/dist/node/feedback-components.9328e8ba.js.map +0 -1
- package/dist/node/feedback-components.b7946db4.js +0 -2
- package/dist/node/feedback-components.b7946db4.js.map +0 -1
- package/dist/node/feedback-components.c459cc27.js +0 -2
- package/dist/node/feedback-components.c459cc27.js.map +0 -1
- package/dist/node/feedback-components.c88cb37f.css +0 -2
- package/dist/node/feedback-components.c88cb37f.css.map +0 -1
|
@@ -17,6 +17,8 @@ interface TreeState {
|
|
|
17
17
|
lastInternalId: number;
|
|
18
18
|
isSelectingEntityType: boolean;
|
|
19
19
|
viewMode: ViewMode;
|
|
20
|
+
viewOnly: boolean;
|
|
21
|
+
matchMode: boolean;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
type TextRange = {
|
|
@@ -48,13 +50,18 @@ type TreeAction =
|
|
|
48
50
|
type: "update-entity-type";
|
|
49
51
|
payload: { id: number; name: string; description: string; color: string };
|
|
50
52
|
}
|
|
51
|
-
| { type: "select-range"; payload: { ids: number[] } }
|
|
53
|
+
| { type: "select-range"; payload: { ids: number[] } }
|
|
54
|
+
| { type: "add-match"; payload: { id: number; payload: any } }
|
|
55
|
+
| { type: "remove-match"; payload: { id: number } }
|
|
56
|
+
| { type: "toggle-match-mode" };
|
|
52
57
|
|
|
53
58
|
export type TreeDispatch = Dispatch<TreeAction>;
|
|
54
59
|
|
|
55
60
|
export function useUpdatableTree(
|
|
56
61
|
initialTree: TreeData[],
|
|
57
62
|
entityTypes: Map<number, EntityType>,
|
|
63
|
+
viewOnly: boolean,
|
|
64
|
+
matchMode: boolean,
|
|
58
65
|
): [TreeState, TreeDispatch] {
|
|
59
66
|
// Get the first entity type
|
|
60
67
|
// issue: grabs second entity instead of selected one
|
|
@@ -69,6 +76,8 @@ export function useUpdatableTree(
|
|
|
69
76
|
lastInternalId: 0,
|
|
70
77
|
isSelectingEntityType: false,
|
|
71
78
|
viewMode: ViewMode.Tree,
|
|
79
|
+
viewOnly,
|
|
80
|
+
matchMode,
|
|
72
81
|
});
|
|
73
82
|
}
|
|
74
83
|
|
|
@@ -83,6 +92,14 @@ export function useTreeDispatch() {
|
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
function treeReducer(state: TreeState, action: TreeAction) {
|
|
95
|
+
if (action.type === "toggle-match-mode") {
|
|
96
|
+
return { ...state, matchMode: !state.matchMode };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (state.viewOnly) return viewMode(state, action);
|
|
100
|
+
|
|
101
|
+
if (state.matchMode) return matchMode(state, action);
|
|
102
|
+
|
|
86
103
|
switch (action.type) {
|
|
87
104
|
case "add-entity-type": {
|
|
88
105
|
// Add a new entity type to the map
|
|
@@ -246,7 +263,6 @@ function treeReducer(state: TreeState, action: TreeAction) {
|
|
|
246
263
|
|
|
247
264
|
case "delete-entity-type": {
|
|
248
265
|
// Remove the entity type from the map
|
|
249
|
-
console.log("Deleting entity type:", action.payload.id);
|
|
250
266
|
const { id } = action.payload;
|
|
251
267
|
const newEntityTypesMap = new Map(state.entityTypesMap);
|
|
252
268
|
const oldType = newEntityTypesMap.get(id);
|
|
@@ -431,7 +447,7 @@ export function treeToGraph(tree: TreeData[]): GraphData {
|
|
|
431
447
|
return { nodes, edges };
|
|
432
448
|
}
|
|
433
449
|
|
|
434
|
-
function findNodeById(tree, id) {
|
|
450
|
+
export function findNodeById(tree, id) {
|
|
435
451
|
for (const node of tree) {
|
|
436
452
|
if (node.id === id) {
|
|
437
453
|
return node;
|
|
@@ -477,3 +493,83 @@ function flattenAndSort(nodes) {
|
|
|
477
493
|
// sort by start
|
|
478
494
|
return result.sort((a, b) => a.indices[0] - b.indices[0]);
|
|
479
495
|
}
|
|
496
|
+
|
|
497
|
+
function matchMode(state, action) {
|
|
498
|
+
if (action.type === "select-node" || action.type === "toggle-node-selected") {
|
|
499
|
+
const { ids } = action.payload;
|
|
500
|
+
|
|
501
|
+
if (ids.length != 1) return state;
|
|
502
|
+
|
|
503
|
+
if (state.selectedNodes.length === 1) {
|
|
504
|
+
if (ids[0] === state.selectedNodes[0]) {
|
|
505
|
+
// If the selected node is the same as the current selection, deselect it
|
|
506
|
+
return { ...state, selectedNodes: [] };
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const type =
|
|
511
|
+
action.payload.ids.length > 0
|
|
512
|
+
? findNodeById(state.tree, ids[0])?.type
|
|
513
|
+
: null;
|
|
514
|
+
|
|
515
|
+
return { ...state, selectedNodes: ids, selectedEntityType: type };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (action.type === "add-match") {
|
|
519
|
+
const { id } = action.payload;
|
|
520
|
+
|
|
521
|
+
// Find the node path
|
|
522
|
+
const keyPath = findNode(state.tree, id);
|
|
523
|
+
if (!keyPath) {
|
|
524
|
+
console.warn(`Node with id ${id} not found`);
|
|
525
|
+
return state;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Build update spec to set the `match` property
|
|
529
|
+
const matchUpdateSpec = buildNestedSpec(keyPath, {
|
|
530
|
+
match: { $set: action.payload.payload },
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const updatedTree = update(state.tree, matchUpdateSpec);
|
|
534
|
+
|
|
535
|
+
return {
|
|
536
|
+
...state,
|
|
537
|
+
tree: updatedTree,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (action.type === "remove-match") {
|
|
542
|
+
const { id } = action.payload;
|
|
543
|
+
|
|
544
|
+
console.log("Removing match for node with id:", id);
|
|
545
|
+
|
|
546
|
+
// Find the node path
|
|
547
|
+
const keyPath = findNode(state.tree, id);
|
|
548
|
+
if (!keyPath) {
|
|
549
|
+
console.warn(`Node with id ${id} not found`);
|
|
550
|
+
return state;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Build update spec to unset the `match` property
|
|
554
|
+
const matchUpdateSpec = buildNestedSpec(keyPath, {
|
|
555
|
+
match: { $set: null },
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
const updatedTree = update(state.tree, matchUpdateSpec);
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
...state,
|
|
562
|
+
tree: updatedTree,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return state;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function viewMode(state, action) {
|
|
570
|
+
if (action.type === "set-view-mode") {
|
|
571
|
+
return { ...state, viewMode: action.payload };
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return state;
|
|
575
|
+
}
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
cursor: pointer
|
|
32
32
|
|
|
33
33
|
circle
|
|
34
|
-
cursor: pointer
|
|
35
34
|
border: 1px solid black
|
|
36
35
|
|
|
37
36
|
.selected
|
|
@@ -79,12 +78,14 @@ mark
|
|
|
79
78
|
width: auto !important
|
|
80
79
|
|
|
81
80
|
.highlight
|
|
82
|
-
cursor: pointer
|
|
83
81
|
padding: .2em 0
|
|
84
82
|
border-radius: .2em
|
|
85
83
|
position: relative
|
|
86
84
|
zIndex: 10
|
|
87
85
|
|
|
86
|
+
.clickable
|
|
87
|
+
cursor: pointer
|
|
88
|
+
|
|
88
89
|
.feedback-text-wrapper
|
|
89
90
|
position: relative
|
|
90
91
|
z-index: 0
|
|
@@ -115,7 +116,7 @@ mark
|
|
|
115
116
|
align-items: center
|
|
116
117
|
|
|
117
118
|
.add-type-overlay
|
|
118
|
-
background-color: var(--
|
|
119
|
+
background-color: var(--tertiary-background)
|
|
119
120
|
padding: .5em 1em
|
|
120
121
|
display: flex
|
|
121
122
|
flex-direction: column
|
|
@@ -146,4 +147,23 @@ mark
|
|
|
146
147
|
.node-label
|
|
147
148
|
fill: var(--text-emphasized-color)
|
|
148
149
|
fontSize: 10px
|
|
149
|
-
pointerEvents: none
|
|
150
|
+
pointerEvents: none
|
|
151
|
+
|
|
152
|
+
.match-item
|
|
153
|
+
color: var(--text-emphasized-color)
|
|
154
|
+
padding: .1em .2em
|
|
155
|
+
border-radius: .2em
|
|
156
|
+
margin-bottom: 4px
|
|
157
|
+
cursor: pointer
|
|
158
|
+
|
|
159
|
+
.match-label
|
|
160
|
+
margin: 0
|
|
161
|
+
color: var(--text-color)
|
|
162
|
+
|
|
163
|
+
.match-container
|
|
164
|
+
display: flex
|
|
165
|
+
justify-content: space-between
|
|
166
|
+
align-items: center
|
|
167
|
+
|
|
168
|
+
.close-btn
|
|
169
|
+
cursor: pointer
|
package/src/feedback/graph.ts
CHANGED
|
@@ -24,10 +24,11 @@ export function GraphView(props: {
|
|
|
24
24
|
height: number;
|
|
25
25
|
dispatch: (action: any) => void;
|
|
26
26
|
selectedNodes: number[];
|
|
27
|
+
viewOnly?: boolean;
|
|
27
28
|
}) {
|
|
28
29
|
// A graph view with react-flow
|
|
29
30
|
// Get positions of nodes using force simulation
|
|
30
|
-
const { tree, width, height, dispatch, selectedNodes } = props;
|
|
31
|
+
const { tree, width, height, dispatch, selectedNodes, viewOnly } = props;
|
|
31
32
|
|
|
32
33
|
const [nodes, setNodes] = useState<SimulationNodeDatum[]>(null);
|
|
33
34
|
const [links, setLinks] = useState<SimulationLinkDatum[]>(null);
|
|
@@ -134,7 +135,9 @@ export function GraphView(props: {
|
|
|
134
135
|
});
|
|
135
136
|
}
|
|
136
137
|
},
|
|
137
|
-
className: active
|
|
138
|
+
className: active
|
|
139
|
+
? "selected"
|
|
140
|
+
: "" + (viewOnly ? "" : " clickable"),
|
|
138
141
|
stroke,
|
|
139
142
|
strokeWidth: 2,
|
|
140
143
|
}),
|
package/src/feedback/index.ts
CHANGED
|
@@ -18,26 +18,20 @@ import {
|
|
|
18
18
|
ButtonGroup,
|
|
19
19
|
Card,
|
|
20
20
|
SegmentedControl,
|
|
21
|
-
Icon,
|
|
22
|
-
Popover,
|
|
23
21
|
Divider,
|
|
24
|
-
Overlay2,
|
|
25
22
|
} from "@blueprintjs/core";
|
|
26
23
|
import { OmniboxSelector } from "./type-selector";
|
|
27
24
|
import {
|
|
28
25
|
CancelButton,
|
|
29
|
-
DataField,
|
|
30
26
|
ErrorBoundary,
|
|
31
|
-
FlexBox,
|
|
32
27
|
FlexRow,
|
|
33
28
|
SaveButton,
|
|
34
29
|
} from "@macrostrat/ui-components";
|
|
35
30
|
import useElementDimensions from "use-element-dimensions";
|
|
36
31
|
import { GraphView } from "./graph";
|
|
37
|
-
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import { Switch } from "@blueprintjs/core";
|
|
32
|
+
|
|
33
|
+
import { Matches } from "./matches";
|
|
34
|
+
import { TypeList } from "./typelist";
|
|
41
35
|
|
|
42
36
|
export type { GraphData } from "./edit-state";
|
|
43
37
|
export { treeToGraph } from "./edit-state";
|
|
@@ -62,13 +56,19 @@ export function FeedbackComponent({
|
|
|
62
56
|
onSave,
|
|
63
57
|
allowOverlap,
|
|
64
58
|
matchLinks,
|
|
59
|
+
view = false,
|
|
65
60
|
}) {
|
|
61
|
+
const [viewOnly, setViewOnly] = useState(view);
|
|
62
|
+
const [match, setMatchLinks] = useState(matchLinks);
|
|
63
|
+
const matchMode = match !== null;
|
|
64
|
+
|
|
66
65
|
// Get the input arguments
|
|
67
66
|
const [state, dispatch] = useUpdatableTree(
|
|
68
67
|
entities.map(processEntity) as any,
|
|
69
68
|
entityTypes,
|
|
69
|
+
viewOnly,
|
|
70
|
+
matchMode,
|
|
70
71
|
);
|
|
71
|
-
const [match, setMatchLinks] = useState(matchLinks || {});
|
|
72
72
|
|
|
73
73
|
const {
|
|
74
74
|
selectedNodes,
|
|
@@ -84,6 +84,18 @@ export function FeedbackComponent({
|
|
|
84
84
|
h(
|
|
85
85
|
"div.feedback-container",
|
|
86
86
|
h(TreeDispatchContext.Provider, { value: dispatch }, [
|
|
87
|
+
h.if(!view)(SegmentedControl, {
|
|
88
|
+
options: [
|
|
89
|
+
{ label: "View", value: "view" },
|
|
90
|
+
{ label: "Edit", value: "edit" },
|
|
91
|
+
],
|
|
92
|
+
value: viewOnly ? "view" : "edit",
|
|
93
|
+
small: true,
|
|
94
|
+
onValueChange() {
|
|
95
|
+
setViewOnly(!viewOnly);
|
|
96
|
+
},
|
|
97
|
+
role: "toolbar",
|
|
98
|
+
}),
|
|
87
99
|
h(
|
|
88
100
|
ErrorBoundary,
|
|
89
101
|
{
|
|
@@ -98,6 +110,7 @@ export function FeedbackComponent({
|
|
|
98
110
|
selectedNodes,
|
|
99
111
|
allowOverlap,
|
|
100
112
|
matchLinks: match,
|
|
113
|
+
viewOnly,
|
|
101
114
|
}),
|
|
102
115
|
),
|
|
103
116
|
h(
|
|
@@ -139,6 +152,7 @@ export function FeedbackComponent({
|
|
|
139
152
|
height,
|
|
140
153
|
dispatch,
|
|
141
154
|
selectedNodes,
|
|
155
|
+
viewOnly,
|
|
142
156
|
}),
|
|
143
157
|
],
|
|
144
158
|
),
|
|
@@ -146,7 +160,7 @@ export function FeedbackComponent({
|
|
|
146
160
|
),
|
|
147
161
|
h(Card, { className: "control-panel" }, [
|
|
148
162
|
h("div.control-content", [
|
|
149
|
-
h(
|
|
163
|
+
h.if(!viewOnly)(
|
|
150
164
|
ButtonGroup,
|
|
151
165
|
{
|
|
152
166
|
vertical: true,
|
|
@@ -155,13 +169,6 @@ export function FeedbackComponent({
|
|
|
155
169
|
alignText: "left",
|
|
156
170
|
},
|
|
157
171
|
[
|
|
158
|
-
h.if(matchLinks)(Switch, {
|
|
159
|
-
label: "Show matches",
|
|
160
|
-
checked: match !== null,
|
|
161
|
-
onChange: (e) => {
|
|
162
|
-
setMatchLinks(match === null ? matchLinks || {} : null);
|
|
163
|
-
},
|
|
164
|
-
}),
|
|
165
172
|
h(
|
|
166
173
|
CancelButton,
|
|
167
174
|
{
|
|
@@ -185,7 +192,15 @@ export function FeedbackComponent({
|
|
|
185
192
|
),
|
|
186
193
|
],
|
|
187
194
|
),
|
|
188
|
-
h(
|
|
195
|
+
h.if(!viewOnly)(Matches, {
|
|
196
|
+
match,
|
|
197
|
+
setMatchLinks,
|
|
198
|
+
matchLinks,
|
|
199
|
+
selectedNodes,
|
|
200
|
+
tree,
|
|
201
|
+
dispatch,
|
|
202
|
+
}),
|
|
203
|
+
h.if(!viewOnly)(Divider),
|
|
189
204
|
h(EntityTypeSelector, {
|
|
190
205
|
entityTypes: entityTypesMap,
|
|
191
206
|
selected: selectedEntityType,
|
|
@@ -201,6 +216,8 @@ export function FeedbackComponent({
|
|
|
201
216
|
type: "toggle-entity-type-selector",
|
|
202
217
|
payload: isOpen,
|
|
203
218
|
}),
|
|
219
|
+
viewOnly,
|
|
220
|
+
matchMode,
|
|
204
221
|
}),
|
|
205
222
|
]),
|
|
206
223
|
]),
|
|
@@ -227,6 +244,8 @@ function EntityTypeSelector({
|
|
|
227
244
|
tree,
|
|
228
245
|
dispatch,
|
|
229
246
|
selectedNodes = [],
|
|
247
|
+
viewOnly,
|
|
248
|
+
matchMode,
|
|
230
249
|
}) {
|
|
231
250
|
// Show all entity types when selected is null
|
|
232
251
|
const _selected = selected != null ? selected : undefined;
|
|
@@ -247,6 +266,7 @@ function EntityTypeSelector({
|
|
|
247
266
|
dispatch,
|
|
248
267
|
selectedNodes,
|
|
249
268
|
tree,
|
|
269
|
+
viewOnly: viewOnly || matchMode,
|
|
250
270
|
}),
|
|
251
271
|
h(OmniboxSelector, {
|
|
252
272
|
isOpen,
|
|
@@ -386,304 +406,3 @@ function ManagedSelectionTree(props) {
|
|
|
386
406
|
}),
|
|
387
407
|
);
|
|
388
408
|
}
|
|
389
|
-
|
|
390
|
-
function TypeList({ types, selected, dispatch, selectedNodes, tree }) {
|
|
391
|
-
const [selectedType, setSelectedType] = useState(null);
|
|
392
|
-
const isSelectedNodes = selectedNodes.length > 0;
|
|
393
|
-
const darkMode = useInDarkMode();
|
|
394
|
-
const luminance = darkMode ? 0.9 : 0.4;
|
|
395
|
-
|
|
396
|
-
return h("div.type-list-container", [
|
|
397
|
-
h(
|
|
398
|
-
"div.type-list-header",
|
|
399
|
-
isSelectedNodes && !selectedType
|
|
400
|
-
? "Change selected nodes to:"
|
|
401
|
-
: "Entity Types",
|
|
402
|
-
),
|
|
403
|
-
h(
|
|
404
|
-
"div.type-list",
|
|
405
|
-
Array.from(types.values()).map((type) =>
|
|
406
|
-
h(TypeTag, {
|
|
407
|
-
type,
|
|
408
|
-
luminance,
|
|
409
|
-
selectedType,
|
|
410
|
-
setSelectedType,
|
|
411
|
-
dispatch,
|
|
412
|
-
tree,
|
|
413
|
-
selectedNodes,
|
|
414
|
-
selected,
|
|
415
|
-
isSelectedNodes,
|
|
416
|
-
}),
|
|
417
|
-
),
|
|
418
|
-
),
|
|
419
|
-
h(AddType, { dispatch }),
|
|
420
|
-
]);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function collectMatchingIds(tree, id) {
|
|
424
|
-
const ids = [];
|
|
425
|
-
|
|
426
|
-
function traverse(node) {
|
|
427
|
-
if (node.type.id === id) {
|
|
428
|
-
ids.push(node.id);
|
|
429
|
-
}
|
|
430
|
-
if (Array.isArray(node.children)) {
|
|
431
|
-
node.children.forEach(traverse);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
tree.forEach(traverse);
|
|
436
|
-
return ids;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function AddType({ dispatch }) {
|
|
440
|
-
const [overlayOpen, setOverlayOpen] = useState(false);
|
|
441
|
-
|
|
442
|
-
const saveHandler = (payload) => {
|
|
443
|
-
dispatch({
|
|
444
|
-
type: "add-entity-type",
|
|
445
|
-
payload,
|
|
446
|
-
});
|
|
447
|
-
setOverlayOpen(false);
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
return h("div.add-type-container", [
|
|
451
|
-
h("div.add-type", { onClick: () => setOverlayOpen(true) }, [
|
|
452
|
-
h("p.add-type-text", "Add new type"),
|
|
453
|
-
h(Icon, { icon: "plus" }),
|
|
454
|
-
]),
|
|
455
|
-
h(TypeOverlay, {
|
|
456
|
-
setOverlayOpen,
|
|
457
|
-
overlayOpen,
|
|
458
|
-
title: "Add New Type",
|
|
459
|
-
saveHandler,
|
|
460
|
-
}),
|
|
461
|
-
]);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function EditType({ dispatch, type }) {
|
|
465
|
-
const [editorOpen, setEditorOpen] = useState(false);
|
|
466
|
-
|
|
467
|
-
const saveHandler = (payload) => {
|
|
468
|
-
dispatch({
|
|
469
|
-
type: "update-entity-type",
|
|
470
|
-
payload,
|
|
471
|
-
});
|
|
472
|
-
setEditorOpen(false);
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
return h("div.edit-type", [
|
|
476
|
-
h(Icon, {
|
|
477
|
-
icon: "edit",
|
|
478
|
-
className: "edit-icon",
|
|
479
|
-
onClick: (e) => {
|
|
480
|
-
e.stopPropagation();
|
|
481
|
-
setEditorOpen(true);
|
|
482
|
-
},
|
|
483
|
-
}),
|
|
484
|
-
h(TypeOverlay, {
|
|
485
|
-
setOverlayOpen: setEditorOpen,
|
|
486
|
-
overlayOpen: editorOpen,
|
|
487
|
-
originalType: type,
|
|
488
|
-
title: "Edit Type",
|
|
489
|
-
saveHandler,
|
|
490
|
-
}),
|
|
491
|
-
]);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
function TypeOverlay({
|
|
495
|
-
setOverlayOpen,
|
|
496
|
-
overlayOpen,
|
|
497
|
-
originalType,
|
|
498
|
-
title,
|
|
499
|
-
saveHandler,
|
|
500
|
-
}) {
|
|
501
|
-
const { name, description, color, id } = originalType || {};
|
|
502
|
-
|
|
503
|
-
const [nameInput, setNameInput] = useState(name || "");
|
|
504
|
-
const [descriptionInput, setDescriptionInput] = useState(description || "");
|
|
505
|
-
const [colorInput, setColorInput] = useState(color || "#fff");
|
|
506
|
-
|
|
507
|
-
return h(
|
|
508
|
-
Overlay2,
|
|
509
|
-
{
|
|
510
|
-
isOpen: overlayOpen,
|
|
511
|
-
},
|
|
512
|
-
h(
|
|
513
|
-
"div.overlay-container",
|
|
514
|
-
h("div.add-type-overlay", [
|
|
515
|
-
h("h2.title", [
|
|
516
|
-
title,
|
|
517
|
-
h(Icon, {
|
|
518
|
-
icon: "cross",
|
|
519
|
-
className: "close-icon",
|
|
520
|
-
onClick: () => {
|
|
521
|
-
setOverlayOpen(false);
|
|
522
|
-
},
|
|
523
|
-
style: { cursor: "pointer", color: "red" },
|
|
524
|
-
}),
|
|
525
|
-
]),
|
|
526
|
-
h("div.form-group", [
|
|
527
|
-
h("div.text-inputs", [
|
|
528
|
-
h("div.form-field.name", [
|
|
529
|
-
h("p.label", "Name"),
|
|
530
|
-
h("input", {
|
|
531
|
-
type: "text",
|
|
532
|
-
placeholder: "Enter type name",
|
|
533
|
-
onChange: (e) => setNameInput(e.target.value),
|
|
534
|
-
value: nameInput,
|
|
535
|
-
}),
|
|
536
|
-
]),
|
|
537
|
-
h("div.form-field.form-description", [
|
|
538
|
-
h("p.label", "Description"),
|
|
539
|
-
h("input", {
|
|
540
|
-
type: "text",
|
|
541
|
-
placeholder: "Enter type description",
|
|
542
|
-
onChange: (e) => setDescriptionInput(e.target.value),
|
|
543
|
-
value: descriptionInput,
|
|
544
|
-
}),
|
|
545
|
-
]),
|
|
546
|
-
]),
|
|
547
|
-
h("div.form-field.color", [
|
|
548
|
-
h("p.label", "Color"),
|
|
549
|
-
h(ColorPicker, {
|
|
550
|
-
value: colorInput,
|
|
551
|
-
onChange: (color) => setColorInput(color),
|
|
552
|
-
style: { width: "100%" },
|
|
553
|
-
}),
|
|
554
|
-
]),
|
|
555
|
-
]),
|
|
556
|
-
h(
|
|
557
|
-
SaveButton,
|
|
558
|
-
{
|
|
559
|
-
className: "save-btn",
|
|
560
|
-
small: true,
|
|
561
|
-
onClick: () =>
|
|
562
|
-
saveHandler({
|
|
563
|
-
name: nameInput,
|
|
564
|
-
description: descriptionInput,
|
|
565
|
-
color: colorInput,
|
|
566
|
-
id,
|
|
567
|
-
}),
|
|
568
|
-
},
|
|
569
|
-
"Save changes",
|
|
570
|
-
),
|
|
571
|
-
]),
|
|
572
|
-
),
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function TypeTag({
|
|
577
|
-
type,
|
|
578
|
-
luminance,
|
|
579
|
-
selectedType,
|
|
580
|
-
setSelectedType,
|
|
581
|
-
dispatch,
|
|
582
|
-
tree,
|
|
583
|
-
selectedNodes,
|
|
584
|
-
selected,
|
|
585
|
-
isSelectedNodes,
|
|
586
|
-
}) {
|
|
587
|
-
const { color, name, id, description } = type;
|
|
588
|
-
const darkMode = useInDarkMode();
|
|
589
|
-
const isSelected = id === selected?.id && selectedNodes.length > 0;
|
|
590
|
-
|
|
591
|
-
const style = getTagStyle(color, {
|
|
592
|
-
active: isSelected,
|
|
593
|
-
highlighted: selectedNodes.length === 0,
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
const payload = {
|
|
597
|
-
id,
|
|
598
|
-
name,
|
|
599
|
-
color,
|
|
600
|
-
description,
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
const ids = collectMatchingIds(tree, id);
|
|
604
|
-
|
|
605
|
-
const handleTagClick = () => {
|
|
606
|
-
if (!isSelectedNodes && selectedType === null) {
|
|
607
|
-
if (ids.length > 0) {
|
|
608
|
-
setSelectedType(type);
|
|
609
|
-
dispatch({ type: "toggle-node-selected", payload: { ids } });
|
|
610
|
-
}
|
|
611
|
-
} else if (isSelectedNodes && selectedType === null) {
|
|
612
|
-
if (id === selected?.id && selectedNodes.length > 0) {
|
|
613
|
-
dispatch({
|
|
614
|
-
type: "toggle-node-selected",
|
|
615
|
-
payload: { ids: selectedNodes },
|
|
616
|
-
});
|
|
617
|
-
} else {
|
|
618
|
-
dispatch({ type: "select-entity-type", payload });
|
|
619
|
-
}
|
|
620
|
-
} else if (isSelectedNodes && selectedType.id === id) {
|
|
621
|
-
setSelectedType(null);
|
|
622
|
-
dispatch({ type: "toggle-node-selected", payload: { ids } });
|
|
623
|
-
} else if (isSelectedNodes && selectedType.id !== id) {
|
|
624
|
-
if (ids.length > 0) {
|
|
625
|
-
setSelectedType(type);
|
|
626
|
-
const oldIds = collectMatchingIds(tree, selectedType.id);
|
|
627
|
-
|
|
628
|
-
dispatch({ type: "toggle-node-selected", payload: { ids: oldIds } });
|
|
629
|
-
dispatch({ type: "toggle-node-selected", payload: { ids } });
|
|
630
|
-
}
|
|
631
|
-
} else {
|
|
632
|
-
console.warn("Unexpected state in TypeTag click handler", {
|
|
633
|
-
isSelectedNodes,
|
|
634
|
-
selectedType,
|
|
635
|
-
selectedNodes,
|
|
636
|
-
ids,
|
|
637
|
-
id,
|
|
638
|
-
selected,
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
return h(
|
|
644
|
-
Popover,
|
|
645
|
-
{
|
|
646
|
-
autoFocus: false,
|
|
647
|
-
content: h("div.description", description || "No description available"),
|
|
648
|
-
interactionKind: "hover",
|
|
649
|
-
},
|
|
650
|
-
h(
|
|
651
|
-
"div.type-tag",
|
|
652
|
-
{
|
|
653
|
-
onClick: handleTagClick,
|
|
654
|
-
style: {
|
|
655
|
-
cursor:
|
|
656
|
-
ids.length > 0 || (isSelectedNodes && !selectedType)
|
|
657
|
-
? "pointer"
|
|
658
|
-
: "",
|
|
659
|
-
color: "black",
|
|
660
|
-
backgroundColor: style.backgroundColor,
|
|
661
|
-
border: isSelected
|
|
662
|
-
? `1px solid var(--text-emphasized-color)`
|
|
663
|
-
: `1px solid var(--background-color)`,
|
|
664
|
-
},
|
|
665
|
-
},
|
|
666
|
-
h("div.type-container", [
|
|
667
|
-
h("div.type-name", name),
|
|
668
|
-
h("div.icons", [
|
|
669
|
-
h(EditType, {
|
|
670
|
-
dispatch,
|
|
671
|
-
type,
|
|
672
|
-
}),
|
|
673
|
-
h(Icon, {
|
|
674
|
-
icon: "cross",
|
|
675
|
-
className: "delete-type-icon",
|
|
676
|
-
style: { color: "red", cursor: "pointer" },
|
|
677
|
-
onClick: (e) => {
|
|
678
|
-
e.stopPropagation();
|
|
679
|
-
dispatch({
|
|
680
|
-
type: "delete-entity-type",
|
|
681
|
-
payload: { id },
|
|
682
|
-
});
|
|
683
|
-
},
|
|
684
|
-
}),
|
|
685
|
-
]),
|
|
686
|
-
]),
|
|
687
|
-
),
|
|
688
|
-
);
|
|
689
|
-
}
|