Skip to content

Commit 9aa6951

Browse files
authored
Merge pull request #116 from UW-Macrostrat/feedback
Feedback update
2 parents de78ae9 + ae16227 commit 9aa6951

File tree

8 files changed

+296
-160
lines changed

8 files changed

+296
-160
lines changed

packages/feedback-components/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ 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] - 2025-07-01
8+
9+
- new text component, following same clicking rules as tree
10+
- Entity panel updates
11+
- Entity types are editable, deletable, and addable
12+
713
## [1.1.0] - 2025-06-25
814

915
Major updates by David Sklar to improve usability

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

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ type TreeAction =
4747
| {
4848
type: "update-entity-type";
4949
payload: { id: number; name: string; description: string; color: string };
50-
};
50+
}
51+
| { type: "select-range"; payload: { ids: number[] } };
5152

5253
export type TreeDispatch = Dispatch<TreeAction>;
5354

@@ -134,6 +135,27 @@ function treeReducer(state: TreeState, action: TreeAction) {
134135
selectedEntityType: updatedType,
135136
};
136137
}
138+
case "select-range":
139+
// Select a range of nodes by their IDs
140+
const payloadIds = action.payload.ids;
141+
const node1 = payloadIds[0];
142+
const node2 = payloadIds[1];
143+
144+
// make list of nodes in order
145+
const allNodes = flattenAndSort(state.tree);
146+
147+
// select all nodes between node1 and node2
148+
const startIndex = allNodes.findIndex((node) => node.id === node1);
149+
const endIndex = allNodes.findIndex((node) => node.id === node2);
150+
151+
const selectedNodes = allNodes.slice(startIndex, endIndex + 1);
152+
153+
console.log("Selecting range:", selectedNodes);
154+
return {
155+
...state,
156+
selectedNodes: selectedNodes.map((node) => node.id),
157+
};
158+
137159
case "move-node":
138160
// For each node in the tree, if the node is in the dragIds, remove it from the tree and collect it
139161
const [newTree, removedNodes] = removeNodes(
@@ -183,8 +205,6 @@ function treeReducer(state: TreeState, action: TreeAction) {
183205
? findNodeById(state.tree, ids[0])?.type
184206
: null;
185207

186-
console.log("Selecting nodes:", ids, "Type:", type);
187-
188208
return { ...state, selectedNodes: ids, selectedEntityType: type };
189209
// otherwise fall through to toggle-node-selected for a single ID
190210
case "toggle-node-selected":
@@ -439,3 +459,21 @@ function updateNodeType(node, oldType, defaultType) {
439459
: [],
440460
};
441461
}
462+
463+
function flattenAndSort(nodes) {
464+
const result = [];
465+
466+
function traverse(nodeList) {
467+
for (const node of nodeList) {
468+
result.push(node);
469+
if (Array.isArray(node.children) && node.children.length > 0) {
470+
traverse(node.children);
471+
}
472+
}
473+
}
474+
475+
traverse(nodes);
476+
477+
// sort by start
478+
return result.sort((a, b) => a.indices[0] - b.indices[0]);
479+
}

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

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
:root
2+
--text-line-height: 3em
3+
--main-extra-width: 200px
4+
5+
.page-wrapper
6+
display: flex
7+
flex-direction: row
8+
position: relative
9+
gap: 2em
10+
align-items: flex-start // makes control-content lose stick
11+
12+
.feedback-container
13+
flex: 4
14+
15+
.control-panel
16+
flex: 1
17+
height: auto
18+
19+
.control-content
20+
position: sticky
21+
top: 0
22+
123
.feedback-component
224
position: relative
325
width: 800px
@@ -21,14 +43,7 @@ circle
2143
.entity-panel
2244
position: relative
2345
max-height: 600px
24-
25-
.control-panel
26-
position: absolute
27-
top: 1em
28-
right: 1em
29-
padding: 0.2em 0.5em
30-
31-
.entity-panel
46+
width: calc(100% - 2em)
3247
flex: 1
3348
min-height: 100px
3449
padding: 1em
@@ -40,7 +55,6 @@ circle
4055
.selection-tree
4156
margin: -1em 0
4257
padding: 1em 0
43-
width: 70% !important
4458

4559
.type-list
4660
display: grid
@@ -75,6 +89,7 @@ mark
7589
position: relative
7690
z-index: 0
7791
overflow: visible
92+
line-height: var(--text-line-height)
7893

7994
.type-container
8095
display: flex
@@ -126,4 +141,9 @@ mark
126141

127142
.icons
128143
display: flex
129-
gap: .25em
144+
gap: .25em
145+
146+
.node-label
147+
fill: var(--text-emphasized-color)
148+
fontSize: 10px
149+
pointerEvents: none

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

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { TreeData } from "./types";
22
import { treeToGraph } from "./edit-state";
3-
import h from "@macrostrat/hyper";
4-
3+
import styles from "./feedback.module.sass";
4+
import hyper from "@macrostrat/hyper";
55
import {
66
forceSimulation,
77
SimulationNodeDatum,
@@ -12,10 +12,12 @@ import {
1212
forceCollide,
1313
} from "d3-force";
1414
import { useEffect, useState } from "react";
15-
import { Spinner, Popover } from "@blueprintjs/core";
15+
import { Spinner, Switch } from "@blueprintjs/core";
1616
import { ErrorBoundary } from "@macrostrat/ui-components";
1717
import { getTagStyle } from "../extractions";
1818

19+
const h = hyper.styled(styles);
20+
1921
export function GraphView(props: {
2022
tree: TreeData[];
2123
width: number;
@@ -29,6 +31,7 @@ export function GraphView(props: {
2931

3032
const [nodes, setNodes] = useState<SimulationNodeDatum[]>(null);
3133
const [links, setLinks] = useState<SimulationLinkDatum[]>(null);
34+
const [showLabels, setShowLabels] = useState(false);
3235

3336
useEffect(() => {
3437
const { nodes, edges } = treeToGraph(tree);
@@ -78,6 +81,12 @@ export function GraphView(props: {
7881
description: "An error occurred while rendering the graph view.",
7982
},
8083
h("div.graph-view", { style: { width, height } }, [
84+
h(Switch, {
85+
className: "show-labels-switch",
86+
label: "Show Labels",
87+
checked: showLabels,
88+
onChange: (e) => setShowLabels(e.target.checked),
89+
}),
8190
h("svg", { width, height }, [
8291
h(
8392
"g.links",
@@ -99,12 +108,11 @@ export function GraphView(props: {
99108
const highlighted = isHighlighted(d.id, selectedNodes, nodes);
100109
const style = getTagStyle(d.color, { highlighted, active });
101110

102-
return h(
103-
"circle",
104-
{
111+
return h("g", [
112+
h("circle", {
105113
cx: d.x,
106114
cy: d.y,
107-
r: 5,
115+
r: 8,
108116
fill: style.backgroundColor || "blue",
109117
onClick: (e) => {
110118
e.stopPropagation();
@@ -116,9 +124,18 @@ export function GraphView(props: {
116124
className: active ? "selected" : "",
117125
stroke,
118126
strokeWidth: 2,
119-
},
120-
h("title", d.name || `Node ${d.id}`),
121-
);
127+
}),
128+
h.if(showLabels)(
129+
"text",
130+
{
131+
x: d.x + 10,
132+
y: d.y + 4,
133+
className: "node-label",
134+
},
135+
d.name || `Node ${d.id}`,
136+
),
137+
h.if(!showLabels)("title", d.name || `Node ${d.id}`),
138+
]);
122139
}),
123140
),
124141
]),

0 commit comments

Comments
 (0)