Skip to content

Commit 0a4645b

Browse files
authored
Merge pull request #82 from UW-Macrostrat/feedback-fix
Text Extractor Fix
2 parents f85c4ef + 08f066f commit 0a4645b

File tree

8 files changed

+223
-63
lines changed

8 files changed

+223
-63
lines changed

packages/feedback-components/src/extractions/index.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ export function buildHighlights(
2525
start: entity.indices[0],
2626
end: entity.indices[1],
2727
text: entity.name,
28-
backgroundColor: entity.type.color ?? "#ddd",
29-
tag: entity.type.name,
28+
backgroundColor: entity.type?.color ?? "rgb(107, 255, 91)",
29+
tag: entity.type?.name ?? "lith",
3030
id: entity.id,
3131
parents,
3232
});
@@ -140,15 +140,16 @@ export function EntityTag({
140140
matchComponent = null,
141141
}: EntityTagProps) {
142142
const { name, type, match } = data;
143+
143144
const className = classNames(
144145
{
145146
matched: match != null,
146-
type: data.type.name,
147+
type: data.type?.name ?? "lith",
147148
},
148149
"entity"
149150
);
150151

151-
const style = getTagStyle(type.color ?? "#aaaaaa", { highlighted, active });
152+
const style = getTagStyle(type?.color ?? "#aaaaaa", { highlighted, active });
152153

153154
let _matchLink = null;
154155
if (match != null && matchComponent != null) {
@@ -168,7 +169,7 @@ export function EntityTag({
168169
}
169170
},
170171
},
171-
[type.name, _matchLink]
172+
[type?.name, _matchLink]
172173
),
173174
]);
174175
}

packages/feedback-components/src/feedback/edit-state.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export function useUpdatableTree(
4747
entityTypes: Map<number, EntityType>
4848
): [TreeState, TreeDispatch] {
4949
// Get the first entity type
50+
// issue: grabs second entity instead of selected one
5051
const type = entityTypes.values().next().value;
5152

5253
return useReducer(treeReducer, {
@@ -72,7 +73,6 @@ export function useTreeDispatch() {
7273
}
7374

7475
function treeReducer(state: TreeState, action: TreeAction) {
75-
console.log(action);
7676
switch (action.type) {
7777
case "move-node":
7878
// For each node in the tree, if the node is in the dragIds, remove it from the tree and collect it
@@ -117,7 +117,12 @@ function treeReducer(state: TreeState, action: TreeAction) {
117117
};
118118
case "select-node":
119119
const { ids } = action.payload;
120-
return { ...state, selectedNodes: ids };
120+
121+
const type = action.payload.ids.length > 0
122+
? findNodeById(state.tree, ids[0])?.type
123+
: null;
124+
125+
return { ...state, selectedNodes: ids, selectedEntityType: type };
121126
// otherwise fall through to toggle-node-selected for a single ID
122127
case "toggle-node-selected":
123128
const nodesToAdd = action.payload.ids.filter(
@@ -309,3 +314,16 @@ export function treeToGraph(tree: TreeData[]): GraphData {
309314

310315
return { nodes, edges };
311316
}
317+
318+
function findNodeById(tree, id) {
319+
for (const node of tree) {
320+
if (node.id === id) {
321+
return node;
322+
}
323+
if (node.children) {
324+
const found = findNodeById(node.children, id);
325+
if (found) return found;
326+
}
327+
}
328+
return null;
329+
}

packages/feedback-components/src/feedback/feedback.module.sass

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,9 @@
3535
.selection-tree
3636
margin: -1em 0
3737
padding: 1em 0
38+
width: 80% !important
39+
40+
.type-list
41+
display: flex
42+
flex-direction: column
43+
gap: .2em

packages/feedback-components/src/feedback/index.ts

Lines changed: 141 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import {
1313
useUpdatableTree,
1414
ViewMode,
1515
} from "./edit-state";
16-
import { useCallback, useEffect, useRef } from "react";
17-
import { ButtonGroup, Card, SegmentedControl } from "@blueprintjs/core";
16+
import { useCallback, useEffect, useRef, useState } from "react";
17+
import { ButtonGroup, Card, SegmentedControl, Tag } from "@blueprintjs/core";
1818
import { OmniboxSelector } from "./type-selector";
1919
import {
2020
CancelButton,
@@ -25,6 +25,8 @@ import {
2525
} from "@macrostrat/ui-components";
2626
import useElementDimensions from "use-element-dimensions";
2727
import { GraphView } from "./graph";
28+
import { useInDarkMode } from "@macrostrat/ui-components";
29+
import { asChromaColor } from "@macrostrat/color-utils";
2830

2931
export type { GraphData } from "./edit-state";
3032
export { treeToGraph } from "./edit-state";
@@ -49,7 +51,6 @@ export function FeedbackComponent({
4951
onSave,
5052
}) {
5153
// Get the input arguments
52-
5354
const [state, dispatch] = useUpdatableTree(
5455
entities.map(processEntity) as any,
5556
entityTypes
@@ -128,6 +129,9 @@ export function FeedbackComponent({
128129
onChange(payload) {
129130
dispatch({ type: "select-entity-type", payload });
130131
},
132+
dispatch,
133+
tree,
134+
selectedNodes,
131135
isOpen: isSelectingEntityType,
132136
setOpen: (isOpen: boolean) =>
133137
dispatch({
@@ -171,34 +175,68 @@ function EntityTypeSelector({
171175
isOpen,
172176
setOpen,
173177
onChange,
178+
tree,
179+
dispatch,
180+
selectedNodes = [],
174181
}) {
175182
// Show all entity types when selected is null
176183
const _selected = selected != null ? selected : undefined;
177-
return h(DataField, { label: "Entity type", inline: true }, [
184+
const [inputValue, setInputValue] = useState("");
185+
const types = Array.from(entityTypes.values());
186+
187+
const items = inputValue !== "" ? types.filter((d) =>
188+
d.name.toLowerCase().includes(inputValue.toLowerCase())
189+
) : types;
190+
191+
return h('div.entity-type-selector', [
192+
/*
178193
h(
179194
"code.bp5-code",
180195
{
181196
onClick() {
182197
setOpen((d) => !d);
183198
},
184199
},
185-
selected.name
200+
selected?.name ?? "None"
186201
),
202+
*/
203+
h('p', "Entity Type:"),
204+
h(TypeList, { types: entityTypes, selected: _selected, dispatch, tree, selectedNodes }),
187205
h(OmniboxSelector, {
188206
isOpen,
189-
items: Array.from(entityTypes.values()),
207+
items,
190208
selectedItem: _selected,
191209
onSelectItem(item) {
192210
setOpen(false);
193211
onChange(item);
194212
},
213+
onQueryChange(query) {
214+
setInputValue(query);
215+
},
195216
onClose() {
196217
setOpen(false);
197218
},
198219
}),
199220
]);
200221
}
201222

223+
function countNodes(tree) {
224+
if (!tree) return 0;
225+
let count = 0;
226+
227+
function recurse(nodes) {
228+
for (const node of nodes) {
229+
count++;
230+
if (node.children && Array.isArray(node.children)) {
231+
recurse(node.children);
232+
}
233+
}
234+
}
235+
236+
recurse(tree);
237+
return count;
238+
}
239+
202240
function ManagedSelectionTree(props) {
203241
const {
204242
selectedNodes,
@@ -207,65 +245,128 @@ function ManagedSelectionTree(props) {
207245
height,
208246
width,
209247
matchComponent,
210-
...rest
211248
} = props;
212249

213250
const ref = useRef<TreeApi<TreeData>>();
251+
// Use a ref to track clicks (won't cause rerender)
252+
const clickedRef = useRef(false);
214253

215254
const _Node = useCallback(
216255
(props) => h(Node, { ...props, matchComponent }),
217256
[matchComponent]
218257
);
219258

259+
// Update Tree selection when selectedNodes change
220260
useEffect(() => {
221261
if (ref.current == null) return;
222-
// Check if selection matches current
262+
223263
const selection = new Set(selectedNodes.map((d) => d.toString()));
224264
const currentSelection = ref.current.selectedIds;
225265
if (setsAreTheSame(selection, currentSelection)) return;
226-
// If the selection is the same, do nothing
227266

228-
// Set selection
229267
ref.current.setSelection({
230268
ids: selectedNodes.map((d) => d.toString()),
231269
anchor: null,
232270
mostRecent: null,
233271
});
234272
}, [selectedNodes]);
235273

236-
return h(Tree, {
237-
className: "selection-tree",
238-
height,
239-
width,
240-
ref,
241-
data: tree,
242-
onMove({ dragIds, parentId, index }) {
243-
dispatch({
244-
type: "move-node",
245-
payload: {
246-
dragIds: dragIds.map((d) => parseInt(d)),
247-
parentId: parentId ? parseInt(parentId) : null,
248-
index,
249-
},
250-
});
251-
},
252-
onDelete({ ids }) {
253-
dispatch({
254-
type: "delete-node",
255-
payload: { ids: ids.map((d) => parseInt(d)) },
256-
});
257-
},
258-
onSelect(nodes) {
274+
// Mark clicked when user clicks inside the tree container
275+
function handleClick() {
276+
clickedRef.current = true;
277+
}
278+
279+
const handleSelect = useCallback(
280+
(nodes) => {
281+
if (!clickedRef.current) return;
282+
clickedRef.current = false;
283+
259284
let ids = nodes.map((d) => parseInt(d.id));
260-
if (ids.length == 1 && ids[0] == selectedNodes[0]) {
261-
// Deselect
285+
if (ids.length === 1 && ids[0] === selectedNodes[0]) {
262286
ids = [];
263287
}
288+
264289
dispatch({ type: "select-node", payload: { ids } });
265290
},
266-
children: _Node,
267-
idAccessor(d: TreeData) {
268-
return d.id.toString();
269-
},
270-
});
291+
[selectedNodes, dispatch]
292+
);
293+
294+
return h(
295+
"div.selection-tree-wrapper",
296+
{ onPointerDown: handleClick },
297+
h(Tree, {
298+
className: "selection-tree",
299+
height,
300+
width,
301+
ref,
302+
data: tree,
303+
onMove({ dragIds, parentId, index }) {
304+
dispatch({
305+
type: "move-node",
306+
payload: {
307+
dragIds: dragIds.map((d) => parseInt(d)),
308+
parentId: parentId ? parseInt(parentId) : null,
309+
index,
310+
},
311+
});
312+
},
313+
onDelete({ ids }) {
314+
dispatch({
315+
type: "delete-node",
316+
payload: { ids: ids.map((d) => parseInt(d)) },
317+
});
318+
},
319+
onSelect: handleSelect,
320+
children: _Node,
321+
idAccessor(d) {
322+
return d.id.toString();
323+
},
324+
})
325+
);
326+
}
327+
328+
function TypeList({ types, selected, dispatch, tree, selectedNodes }) {
329+
return h(
330+
"div.type-list",
331+
Array.from(types.values()).map((type) => {
332+
const { color, name, id } = type;
333+
const darkMode = useInDarkMode();
334+
const luminance = darkMode ? 0.9 : 0.4;
335+
const chromaColor = asChromaColor(color ?? "#000000")
336+
const ids = collectMatchingIds(tree, name);
337+
const alreadySelected = ids.length === selectedNodes.length && ids.every((v, i) => v === selectedNodes[i])
338+
339+
return h(
340+
Tag,
341+
{
342+
className: "type-tag",
343+
style: {
344+
color: chromaColor?.luminance(luminance).hex(),
345+
backgroundColor: chromaColor?.luminance(1 - luminance).hex(),
346+
border: id === selected?.id ? `1px solid white` : `1px solid black`,
347+
},
348+
onClick: () => {
349+
dispatch({ type: "select-node", payload: { ids: alreadySelected ? [] : ids } });
350+
}
351+
},
352+
name
353+
);
354+
})
355+
);
356+
}
357+
358+
function collectMatchingIds(tree, name) {
359+
const ids = [];
360+
361+
function traverse(node) {
362+
if (node.term_type === name) {
363+
ids.push(node.id);
364+
}
365+
if (Array.isArray(node.children)) {
366+
node.children.forEach(traverse);
367+
}
368+
}
369+
370+
tree.forEach(traverse);
371+
return ids;
271372
}

packages/feedback-components/src/feedback/node.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ function Node({ node, style, dragHandle, tree, matchComponent }: any) {
4747

4848
const dispatch = useTreeDispatch();
4949

50+
// console.log("Node render", node.data, highlighted, active);
51+
52+
if(!node.data?.type) {
53+
node.data.type = { name: "lith", color: "rgb(107, 255, 91)" };
54+
}
55+
5056
return h(
5157
"div.node",
5258
{ style, ref: dragHandle },

0 commit comments

Comments
 (0)