Skip to content

Commit 75795fe

Browse files
authored
[DAPS-1408] Provenance Visual Management (2/2) (#1419)
* [DAPS-14xx] Base * [DAPS-14xx] Add styling and tooltips * [DAPS-14xx] Add node and label customization * [DAPS-14xx] Add editor modal on right-click. Draggable model * [DAPS-14xx] Revert * [DAPS-14xx] Seperate files, update styles * [DAPS-14xx] simplify feat * [DAPS-14xx] Expand mocha tests, prettier * [DAPS-14xx] Update checkout version * [DAPS-14xx] Add todo * [DAPS-14xx] Update tests, consts, fix tooltip * [DAPS-14xx] Base: [DAPS-14xx] Address collapse, expand, hide * [DAPS-14xx] THEME, address comments * [DAPS-14xx] Zoom * [DAPS-14xx] JSDOC
1 parent b369e1e commit 75795fe

File tree

15 files changed

+2650
-333
lines changed

15 files changed

+2650
-333
lines changed

.github/workflows/unit-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ jobs:
55
runs-on: ubuntu-24.04
66
if: ${{ always() }}
77
steps:
8-
- uses: actions/checkout@v2
8+
- uses: actions/checkout@v4
99
- name: Update ubuntu
1010
run: sudo apt-get update
1111
- name: Install software-properties-common

web/package.json.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"sanitize-html": "^2.11.0"
1919
},
2020
"scripts": {
21-
"test": "mocha"
21+
"test": "mocha test/**/*.test.js"
2222
},
2323
"repository": {
2424
"type": "git",

web/static/components/provenance/assets/arrow-markers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ function defineArrowMarkerComp(a_svg) {
2929
}
3030

3131
// New version marker at 'end'
32-
function defineArrowMarkerNewVer(a_svg, a_name) {
32+
function defineArrowMarkerNewVer(a_svg) {
3333
a_svg
3434
.append("defs")
3535
.append("marker")
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import { DEFAULTS } from "./state.js";
2+
3+
function showCustomizationModal(node, x, y, currentCustomizationNode) {
4+
const modal = document.getElementById("customization-modal");
5+
if (!modal) return;
6+
7+
// Set the current node being customized
8+
currentCustomizationNode = node;
9+
10+
// Save original values for reverting if cancelled
11+
if (window.saveOriginalValues) {
12+
window.saveOriginalValues(node);
13+
}
14+
15+
const nodeColorInput = document.getElementById("node-color-input");
16+
// Helper function to convert RGB to hex format
17+
const rgbToHex = (rgb) => {
18+
return "#" + rgb.map((x) => parseInt(x).toString(16).padStart(2, "0")).join("");
19+
};
20+
21+
// Helper function to get the default color from CSS
22+
const getDefaultColor = (node) => {
23+
const nodeElement = d3.select(`[id="${node.id}"] circle.obj`).node();
24+
if (!nodeElement) {
25+
return DEFAULTS.NODE_COLOR;
26+
}
27+
28+
const computedStyle = window.getComputedStyle(nodeElement);
29+
const fillColor = computedStyle.fill;
30+
31+
if (fillColor && fillColor !== "none") {
32+
if (fillColor.startsWith("rgb")) {
33+
const rgb = fillColor.match(/\d+/g);
34+
return rgb && rgb.length === 3 ? rgbToHex(rgb) : DEFAULTS.NODE_COLOR;
35+
}
36+
return fillColor;
37+
}
38+
39+
return DEFAULTS.NODE_COLOR;
40+
};
41+
42+
// Get the actual current node color
43+
nodeColorInput.value = node.nodeColor || getDefaultColor(node);
44+
45+
const labelSizeSlider = document.getElementById("label-size-slider");
46+
const labelSizeValue = labelSizeSlider.nextElementSibling;
47+
labelSizeSlider.value = node.labelSize || DEFAULTS.LABEL_SIZE;
48+
labelSizeValue.textContent = `${labelSizeSlider.value}px`;
49+
50+
const labelColorInput = document.getElementById("label-color-input");
51+
labelColorInput.value = node.labelColor || DEFAULTS.LABEL_COLOR;
52+
53+
const anchorCheckbox = document.getElementById("anchor-checkbox");
54+
anchorCheckbox.checked = node.anchored || false;
55+
56+
// Position and show modal
57+
modal.style.left = `${x}px`;
58+
modal.style.top = `${y}px`;
59+
modal.style.display = "block";
60+
61+
// Return the current node for reference
62+
return currentCustomizationNode;
63+
}
64+
65+
/**
66+
* Makes a modal element draggable by its header
67+
* Optimized to only attach document listeners during drag operations
68+
* @param {HTMLElement} modal - The modal element to make draggable
69+
*/
70+
function makeModalDraggable(modal) {
71+
let offsetX, offsetY;
72+
const header = modal.querySelector(".modal-header") || modal;
73+
74+
// Handle mouse movement during drag
75+
function handleMouseMove(e) {
76+
modal.style.left = `${e.clientX - offsetX}px`;
77+
modal.style.top = `${e.clientY - offsetY}px`;
78+
}
79+
80+
// Handle end of drag operation
81+
function handleMouseUp() {
82+
// Remove event listeners when dragging ends to improve performance
83+
document.removeEventListener("mousemove", handleMouseMove);
84+
document.removeEventListener("mouseup", handleMouseUp);
85+
}
86+
87+
// Start dragging when mousedown on header
88+
header.addEventListener("mousedown", function (e) {
89+
// Calculate initial offset
90+
offsetX = e.clientX - modal.offsetLeft;
91+
offsetY = e.clientY - modal.offsetTop;
92+
93+
// Add event listeners for dragging only when needed
94+
document.addEventListener("mousemove", handleMouseMove);
95+
document.addEventListener("mouseup", handleMouseUp);
96+
97+
e.preventDefault();
98+
});
99+
}
100+
101+
function createCustomizationModal() {
102+
// Remove existing modal if it exists
103+
const existingModal = document.getElementById("customization-modal");
104+
if (existingModal) {
105+
document.body.removeChild(existingModal);
106+
}
107+
108+
const modal = document.createElement("div");
109+
modal.id = "customization-modal";
110+
modal.className = "customization-modal";
111+
modal.style.display = "none";
112+
113+
// Add a draggable header
114+
const modalHeader = document.createElement("div");
115+
modalHeader.className = "modal-header";
116+
117+
// Title in the draggable header
118+
const title = document.createElement("h3");
119+
title.textContent = "Customize Node & Label";
120+
modalHeader.appendChild(title);
121+
modal.appendChild(modalHeader);
122+
123+
// Node customization section
124+
const nodeSection = document.createElement("div");
125+
nodeSection.className = "section";
126+
127+
// Node color picker
128+
const nodeColorLabel = document.createElement("label");
129+
nodeColorLabel.textContent = "Node Color";
130+
nodeSection.appendChild(nodeColorLabel);
131+
132+
const nodeColorRow = document.createElement("div");
133+
nodeColorRow.className = "control-row";
134+
135+
const nodeColorInput = document.createElement("input");
136+
nodeColorInput.type = "color";
137+
nodeColorInput.id = "node-color-input";
138+
nodeColorInput.value = DEFAULTS.NODE_COLOR;
139+
140+
nodeColorRow.appendChild(nodeColorInput);
141+
nodeSection.appendChild(nodeColorRow);
142+
143+
modal.appendChild(nodeSection);
144+
145+
// Label customization section
146+
const labelSection = document.createElement("div");
147+
labelSection.className = "section";
148+
149+
const labelSizeLabel = document.createElement("label");
150+
labelSizeLabel.textContent = "Label Size";
151+
labelSection.appendChild(labelSizeLabel);
152+
153+
const labelSizeRow = document.createElement("div");
154+
labelSizeRow.className = "control-row";
155+
156+
const labelSizeSlider = document.createElement("input");
157+
labelSizeSlider.type = "range";
158+
labelSizeSlider.min = "8";
159+
labelSizeSlider.max = "24";
160+
labelSizeSlider.value = DEFAULTS.LABEL_SIZE;
161+
labelSizeSlider.id = "label-size-slider";
162+
163+
const labelSizeValue = document.createElement("span");
164+
labelSizeValue.className = "value";
165+
labelSizeValue.textContent = `${DEFAULTS.LABEL_SIZE}px`;
166+
167+
labelSizeRow.appendChild(labelSizeSlider);
168+
labelSizeRow.appendChild(labelSizeValue);
169+
labelSection.appendChild(labelSizeRow);
170+
171+
// Label color picker
172+
const labelColorLabel = document.createElement("label");
173+
labelColorLabel.textContent = "Label Color";
174+
labelSection.appendChild(labelColorLabel);
175+
176+
const labelColorRow = document.createElement("div");
177+
labelColorRow.className = "control-row";
178+
179+
const labelColorInput = document.createElement("input");
180+
labelColorInput.type = "color";
181+
labelColorInput.id = "label-color-input";
182+
labelColorInput.value = DEFAULTS.LABEL_COLOR; // Default text color
183+
184+
labelColorRow.appendChild(labelColorInput);
185+
labelSection.appendChild(labelColorRow);
186+
187+
modal.appendChild(labelSection);
188+
189+
// Anchor controls
190+
const anchorSection = document.createElement("div");
191+
anchorSection.className = "section";
192+
193+
const anchorRow = document.createElement("div");
194+
anchorRow.className = "control-row checkbox-row";
195+
196+
const anchorCheckbox = document.createElement("input");
197+
anchorCheckbox.type = "checkbox";
198+
anchorCheckbox.id = "anchor-checkbox";
199+
200+
const anchorLabel = document.createElement("label");
201+
anchorLabel.htmlFor = "anchor-checkbox";
202+
anchorLabel.textContent = "Anchor Node";
203+
anchorLabel.classList.add("inline-label");
204+
205+
anchorRow.appendChild(anchorCheckbox);
206+
anchorRow.appendChild(anchorLabel);
207+
anchorSection.appendChild(anchorRow);
208+
209+
modal.appendChild(anchorSection);
210+
211+
// Buttons
212+
const buttonsDiv = document.createElement("div");
213+
buttonsDiv.className = "buttons";
214+
215+
const applyButton = document.createElement("button");
216+
applyButton.textContent = "Apply";
217+
applyButton.className = "primary";
218+
applyButton.id = "apply-customization";
219+
220+
const closeButton = document.createElement("button");
221+
closeButton.textContent = "Close";
222+
closeButton.id = "close-customization";
223+
224+
buttonsDiv.appendChild(closeButton);
225+
buttonsDiv.appendChild(applyButton);
226+
227+
modal.appendChild(buttonsDiv);
228+
229+
document.body.appendChild(modal);
230+
231+
// Make the modal draggable
232+
makeModalDraggable(modal);
233+
234+
return modal;
235+
}
236+
237+
export { showCustomizationModal, createCustomizationModal };

0 commit comments

Comments
 (0)