Skip to content

Commit e636fdd

Browse files
authored
Merge pull request #144 from UW-Macrostrat/feedback
Feedback update
2 parents eb96db8 + e77178a commit e636fdd

File tree

9 files changed

+323
-236
lines changed

9 files changed

+323
-236
lines changed

packages/feedback-components/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format
44
is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this
55
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [unreleased]
8+
9+
- Simplified match logic
10+
711
## [1.1.4] - 2025-07-16
812

913
- Add and remove match feature added

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

Lines changed: 96 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ interface TreeState {
1717
lastInternalId: number;
1818
isSelectingEntityType: boolean;
1919
viewMode: ViewMode;
20+
viewOnly: boolean;
21+
matchMode: boolean;
2022
}
2123

2224
type TextRange = {
@@ -50,13 +52,16 @@ type TreeAction =
5052
}
5153
| { type: "select-range"; payload: { ids: number[] } }
5254
| { type: "add-match"; payload: { id: number; payload: any } }
53-
| { type: "remove-match"; payload: { id: number } };
55+
| { type: "remove-match"; payload: { id: number } }
56+
| { type: "toggle-match-mode" };
5457

5558
export type TreeDispatch = Dispatch<TreeAction>;
5659

5760
export function useUpdatableTree(
5861
initialTree: TreeData[],
5962
entityTypes: Map<number, EntityType>,
63+
viewOnly: boolean,
64+
matchMode: boolean,
6065
): [TreeState, TreeDispatch] {
6166
// Get the first entity type
6267
// issue: grabs second entity instead of selected one
@@ -71,6 +76,8 @@ export function useUpdatableTree(
7176
lastInternalId: 0,
7277
isSelectingEntityType: false,
7378
viewMode: ViewMode.Tree,
79+
viewOnly,
80+
matchMode,
7481
});
7582
}
7683

@@ -85,6 +92,14 @@ export function useTreeDispatch() {
8592
}
8693

8794
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+
88103
switch (action.type) {
89104
case "add-entity-type": {
90105
// Add a new entity type to the map
@@ -264,54 +279,6 @@ function treeReducer(state: TreeState, action: TreeAction) {
264279
};
265280
}
266281

267-
case "add-match": {
268-
const { id } = action.payload;
269-
270-
// Find the node path
271-
const keyPath = findNode(state.tree, id);
272-
if (!keyPath) {
273-
console.warn(`Node with id ${id} not found`);
274-
return state;
275-
}
276-
277-
// Build update spec to set the `match` property
278-
const matchUpdateSpec = buildNestedSpec(keyPath, {
279-
match: { $set: action.payload.payload },
280-
});
281-
282-
const updatedTree = update(state.tree, matchUpdateSpec);
283-
284-
return {
285-
...state,
286-
tree: updatedTree,
287-
};
288-
}
289-
290-
case "remove-match": {
291-
const { id } = action.payload;
292-
293-
console.log("Removing match for node with id:", id);
294-
295-
// Find the node path
296-
const keyPath = findNode(state.tree, id);
297-
if (!keyPath) {
298-
console.warn(`Node with id ${id} not found`);
299-
return state;
300-
}
301-
302-
// Build update spec to unset the `match` property
303-
const matchUpdateSpec = buildNestedSpec(keyPath, {
304-
match: { $set: null },
305-
});
306-
307-
const updatedTree = update(state.tree, matchUpdateSpec);
308-
309-
return {
310-
...state,
311-
tree: updatedTree,
312-
};
313-
}
314-
315282
/** Entity type selection */
316283
case "toggle-entity-type-selector":
317284
return {
@@ -526,3 +493,83 @@ function flattenAndSort(nodes) {
526493
// sort by start
527494
return result.sort((a, b) => a.indices[0] - b.indices[0]);
528495
}
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+
}

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
cursor: pointer
3232

3333
circle
34-
cursor: pointer
3534
border: 1px solid black
3635

3736
.selected
@@ -79,12 +78,14 @@ mark
7978
width: auto !important
8079

8180
.highlight
82-
cursor: pointer
8381
padding: .2em 0
8482
border-radius: .2em
8583
position: relative
8684
zIndex: 10
8785

86+
.clickable
87+
cursor: pointer
88+
8889
.feedback-text-wrapper
8990
position: relative
9091
z-index: 0
@@ -115,7 +116,7 @@ mark
115116
align-items: center
116117

117118
.add-type-overlay
118-
background-color: var(--secondary-color)
119+
background-color: var(--tertiary-background)
119120
padding: .5em 1em
120121
display: flex
121122
flex-direction: column
@@ -149,17 +150,20 @@ mark
149150
pointerEvents: none
150151

151152
.match-item
152-
background-color: var(--background-color)
153153
color: var(--text-emphasized-color)
154154
padding: .1em .2em
155155
border-radius: .2em
156156
margin-bottom: 4px
157157
cursor: pointer
158+
159+
.match-label
160+
margin: 0
161+
color: var(--text-color)
158162

159163
.match-container
160164
display: flex
161165
justify-content: space-between
162166
align-items: center
163167

164168
.close-btn
165-
cursor: pointer
169+
cursor: pointer

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ export function GraphView(props: {
2424
height: number;
2525
dispatch: (action: any) => void;
2626
selectedNodes: number[];
27+
viewOnly?: boolean;
2728
}) {
2829
// A graph view with react-flow
2930
// Get positions of nodes using force simulation
30-
const { tree, width, height, dispatch, selectedNodes } = props;
31+
const { tree, width, height, dispatch, selectedNodes, viewOnly } = props;
3132

3233
const [nodes, setNodes] = useState<SimulationNodeDatum[]>(null);
3334
const [links, setLinks] = useState<SimulationLinkDatum[]>(null);
@@ -134,7 +135,9 @@ export function GraphView(props: {
134135
});
135136
}
136137
},
137-
className: active ? "selected" : "",
138+
className: active
139+
? "selected"
140+
: "" + (viewOnly ? "" : " clickable"),
138141
stroke,
139142
strokeWidth: 2,
140143
}),

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,19 @@ export function FeedbackComponent({
5656
onSave,
5757
allowOverlap,
5858
matchLinks,
59+
view = false,
5960
}) {
61+
const [viewOnly, setViewOnly] = useState(view);
62+
const [match, setMatchLinks] = useState(matchLinks);
63+
const matchMode = match !== null;
64+
6065
// Get the input arguments
6166
const [state, dispatch] = useUpdatableTree(
6267
entities.map(processEntity) as any,
6368
entityTypes,
69+
viewOnly,
70+
matchMode,
6471
);
65-
const [match, setMatchLinks] = useState(matchLinks || {});
6672

6773
const {
6874
selectedNodes,
@@ -78,6 +84,18 @@ export function FeedbackComponent({
7884
h(
7985
"div.feedback-container",
8086
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+
}),
8199
h(
82100
ErrorBoundary,
83101
{
@@ -92,6 +110,7 @@ export function FeedbackComponent({
92110
selectedNodes,
93111
allowOverlap,
94112
matchLinks: match,
113+
viewOnly,
95114
}),
96115
),
97116
h(
@@ -133,14 +152,15 @@ export function FeedbackComponent({
133152
height,
134153
dispatch,
135154
selectedNodes,
155+
viewOnly,
136156
}),
137157
],
138158
),
139159
]),
140160
),
141161
h(Card, { className: "control-panel" }, [
142162
h("div.control-content", [
143-
h(
163+
h.if(!viewOnly)(
144164
ButtonGroup,
145165
{
146166
vertical: true,
@@ -172,15 +192,15 @@ export function FeedbackComponent({
172192
),
173193
],
174194
),
175-
h(Matches, {
195+
h.if(!viewOnly)(Matches, {
176196
match,
177197
setMatchLinks,
178198
matchLinks,
179199
selectedNodes,
180200
tree,
181201
dispatch,
182202
}),
183-
h(Divider),
203+
h.if(!viewOnly)(Divider),
184204
h(EntityTypeSelector, {
185205
entityTypes: entityTypesMap,
186206
selected: selectedEntityType,
@@ -196,6 +216,8 @@ export function FeedbackComponent({
196216
type: "toggle-entity-type-selector",
197217
payload: isOpen,
198218
}),
219+
viewOnly,
220+
matchMode,
199221
}),
200222
]),
201223
]),
@@ -222,6 +244,8 @@ function EntityTypeSelector({
222244
tree,
223245
dispatch,
224246
selectedNodes = [],
247+
viewOnly,
248+
matchMode,
225249
}) {
226250
// Show all entity types when selected is null
227251
const _selected = selected != null ? selected : undefined;
@@ -242,6 +266,7 @@ function EntityTypeSelector({
242266
dispatch,
243267
selectedNodes,
244268
tree,
269+
viewOnly: viewOnly || matchMode,
245270
}),
246271
h(OmniboxSelector, {
247272
isOpen,

0 commit comments

Comments
 (0)