@truedat/lm 8.4.7 → 8.4.8
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/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/lm",
|
|
3
|
-
"version": "8.4.
|
|
3
|
+
"version": "8.4.8",
|
|
4
4
|
"description": "Truedat Link Manager",
|
|
5
|
-
"sideEffects":
|
|
5
|
+
"sideEffects": [
|
|
6
|
+
"**/*.css",
|
|
7
|
+
"**/*.less"
|
|
8
|
+
],
|
|
6
9
|
"module": "src/index.js",
|
|
7
10
|
"files": [
|
|
8
11
|
"src",
|
|
@@ -51,7 +54,7 @@
|
|
|
51
54
|
"@testing-library/jest-dom": "^6.6.3",
|
|
52
55
|
"@testing-library/react": "^16.3.0",
|
|
53
56
|
"@testing-library/user-event": "^14.6.1",
|
|
54
|
-
"@truedat/test": "8.4.
|
|
57
|
+
"@truedat/test": "8.4.8",
|
|
55
58
|
"identity-obj-proxy": "^3.0.0",
|
|
56
59
|
"jest": "^29.7.0",
|
|
57
60
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -71,11 +74,9 @@
|
|
|
71
74
|
"react-csv": "^2.2.2",
|
|
72
75
|
"react-dom": "^19.1.0",
|
|
73
76
|
"react-dropzone": "^14.3.8",
|
|
74
|
-
"react-graph-vis": "1.0.7",
|
|
75
77
|
"react-hook-form": "^7.56.4",
|
|
76
78
|
"react-intl": "^7.1.11",
|
|
77
79
|
"react-moment": "^1.1.3",
|
|
78
|
-
"react-rangeslider": "^2.2.0",
|
|
79
80
|
"react-redux": "^9.2.0",
|
|
80
81
|
"react-router": "^7.6.0",
|
|
81
82
|
"redux": "^5.0.1",
|
|
@@ -85,5 +86,5 @@
|
|
|
85
86
|
"semantic-ui-react": "^3.0.0-beta.2",
|
|
86
87
|
"swr": "^2.3.3"
|
|
87
88
|
},
|
|
88
|
-
"gitHead": "
|
|
89
|
+
"gitHead": "7829e377f78c3cfc66e449f5dc31a8b03c0f5f00"
|
|
89
90
|
}
|
|
@@ -1,53 +1,97 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
|
-
import {
|
|
2
|
+
import { useMemo } from "react";
|
|
3
3
|
import { useIntl } from "react-intl";
|
|
4
4
|
import PropTypes from "prop-types";
|
|
5
5
|
import { Graph } from "@truedat/core/components";
|
|
6
|
+
import { EMPTY_EDGE_TYPE } from "../services/edgeColorPalette";
|
|
6
7
|
|
|
7
|
-
export const RelationGraph = ({ navigate, currentId, relationsGraph }) => {
|
|
8
|
+
export const RelationGraph = ({ navigate, currentId, relationsGraph, colorMap }) => {
|
|
8
9
|
const { formatMessage } = useIntl();
|
|
9
10
|
|
|
10
|
-
const nodes =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
11
|
+
const nodes = useMemo(
|
|
12
|
+
() =>
|
|
13
|
+
_.flow(
|
|
14
|
+
_.get("nodes"),
|
|
15
|
+
_.map(({ id, name: label, resource_id }) => ({
|
|
16
|
+
id,
|
|
17
|
+
type: "concept",
|
|
18
|
+
data: { label, isActive: currentId === id },
|
|
19
|
+
resource_id,
|
|
20
|
+
}))
|
|
21
|
+
)(relationsGraph),
|
|
22
|
+
[relationsGraph, currentId]
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const edges = useMemo(
|
|
26
|
+
() =>
|
|
27
|
+
_.flow(
|
|
28
|
+
_.get("edges"),
|
|
29
|
+
_.map(({ id: id, source_id: source, target_id: target, tags }) => {
|
|
30
|
+
const primaryType = _.path([0, "value", "type"])(tags) || EMPTY_EDGE_TYPE;
|
|
31
|
+
const label = _.isEmpty(tags)
|
|
32
|
+
? formatMessage({
|
|
33
|
+
id: `source.${EMPTY_EDGE_TYPE}`,
|
|
34
|
+
defaultMessage: "Empty",
|
|
35
|
+
})
|
|
36
|
+
: _.flow(
|
|
37
|
+
_.map((tag) =>
|
|
38
|
+
formatMessage({
|
|
39
|
+
id: `source.${_.prop("value.type")(tag)}`,
|
|
40
|
+
defaultMessage: _.prop("value.type")(tag),
|
|
41
|
+
})
|
|
42
|
+
),
|
|
43
|
+
_.join("\n")
|
|
44
|
+
)(tags);
|
|
45
|
+
const color = primaryType && colorMap ? colorMap.get(primaryType) : undefined;
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
id,
|
|
49
|
+
source,
|
|
50
|
+
target,
|
|
51
|
+
type: "colored",
|
|
52
|
+
data: { label, primaryType },
|
|
53
|
+
...(color && {
|
|
54
|
+
style: { stroke: color, strokeWidth: 1.5 },
|
|
55
|
+
markerEnd: {
|
|
56
|
+
type: "arrowclosed",
|
|
57
|
+
width: 12,
|
|
58
|
+
height: 12,
|
|
59
|
+
color,
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
})
|
|
64
|
+
)(relationsGraph),
|
|
65
|
+
[relationsGraph, formatMessage, colorMap]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const safeEdges = useMemo(() => {
|
|
69
|
+
const safeNodeIds = new Set(_.map("id", nodes));
|
|
70
|
+
return _.filter(
|
|
71
|
+
({ source, target }) => safeNodeIds.has(source) && safeNodeIds.has(target),
|
|
72
|
+
edges
|
|
73
|
+
);
|
|
74
|
+
}, [nodes, edges]);
|
|
75
|
+
|
|
76
|
+
const graphData = useMemo(
|
|
77
|
+
() => ({
|
|
78
|
+
nodes,
|
|
79
|
+
edges: safeEdges,
|
|
80
|
+
}),
|
|
81
|
+
[nodes, safeEdges]
|
|
82
|
+
);
|
|
44
83
|
|
|
45
84
|
const onClick = (_, { resource_id }) => {
|
|
46
85
|
if (navigate) navigate({ resource_id });
|
|
47
86
|
};
|
|
48
87
|
|
|
49
88
|
return !_.isEmpty(relationsGraph) ? (
|
|
50
|
-
<Graph
|
|
89
|
+
<Graph
|
|
90
|
+
nodes={graphData.nodes}
|
|
91
|
+
edges={graphData.edges}
|
|
92
|
+
onNodeClick={onClick}
|
|
93
|
+
rootNodeId={currentId}
|
|
94
|
+
/>
|
|
51
95
|
) : null;
|
|
52
96
|
};
|
|
53
97
|
|
|
@@ -55,6 +99,7 @@ RelationGraph.propTypes = {
|
|
|
55
99
|
navigate: PropTypes.func,
|
|
56
100
|
currentId: PropTypes.string,
|
|
57
101
|
relationsGraph: PropTypes.object,
|
|
102
|
+
colorMap: PropTypes.instanceOf(Map),
|
|
58
103
|
};
|
|
59
104
|
|
|
60
105
|
export default RelationGraph;
|
|
@@ -1,25 +1,71 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, useCallback } from "react";
|
|
1
2
|
import _ from "lodash/fp";
|
|
2
3
|
import PropTypes from "prop-types";
|
|
3
|
-
import
|
|
4
|
-
import Slider from "react-rangeslider";
|
|
5
|
-
import "react-rangeslider/lib/index.css";
|
|
6
|
-
import { FormattedMessage } from "react-intl";
|
|
4
|
+
import "../styles/relationGraph.less";
|
|
7
5
|
|
|
8
6
|
export const RelationGraphDepth = ({ onChange, depth, maxDepth }) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
7
|
+
const [localDepth, setLocalDepth] = useState(depth);
|
|
8
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
9
|
+
const sliderRef = useRef(null);
|
|
10
|
+
const latestDepthRef = useRef(depth);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
setLocalDepth(depth);
|
|
14
|
+
latestDepthRef.current = depth;
|
|
15
|
+
}, [depth]);
|
|
16
|
+
|
|
17
|
+
const updateDepthFromY = useCallback(
|
|
18
|
+
(clientY) => {
|
|
19
|
+
if (!sliderRef.current || !maxDepth) return;
|
|
20
|
+
const rect = sliderRef.current.getBoundingClientRect();
|
|
21
|
+
const relativeY = Math.max(0, Math.min(1, (rect.bottom - clientY) / rect.height));
|
|
22
|
+
const newDepth = Math.round(relativeY * maxDepth);
|
|
23
|
+
setLocalDepth(newDepth);
|
|
24
|
+
latestDepthRef.current = newDepth;
|
|
25
|
+
},
|
|
26
|
+
[maxDepth]
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const handleMouseMove = (e) => { if (isDragging) updateDepthFromY(e.clientY); };
|
|
31
|
+
const handleTouchMove = (e) => { if (isDragging) updateDepthFromY(e.touches[0].clientY); };
|
|
32
|
+
const handleEnd = () => {
|
|
33
|
+
setIsDragging(false);
|
|
34
|
+
if (onChange) onChange(latestDepthRef.current);
|
|
35
|
+
};
|
|
36
|
+
if (isDragging) {
|
|
37
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
38
|
+
window.addEventListener("mouseup", handleEnd);
|
|
39
|
+
window.addEventListener("touchmove", handleTouchMove);
|
|
40
|
+
window.addEventListener("touchend", handleEnd);
|
|
41
|
+
}
|
|
42
|
+
return () => {
|
|
43
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
44
|
+
window.removeEventListener("mouseup", handleEnd);
|
|
45
|
+
window.removeEventListener("touchmove", handleTouchMove);
|
|
46
|
+
window.removeEventListener("touchend", handleEnd);
|
|
47
|
+
};
|
|
48
|
+
}, [isDragging, updateDepthFromY, onChange]);
|
|
49
|
+
|
|
50
|
+
if (_.isUndefined(maxDepth)) return null;
|
|
51
|
+
|
|
52
|
+
const percentage = maxDepth > 0 ? (localDepth / maxDepth) * 100 : 0;
|
|
53
|
+
const label = `${localDepth}`;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className={`graph-depth-v${maxDepth === 0 ? " disabled" : ""}`}>
|
|
57
|
+
<span className="graph-depth-v-label">{label}</span>
|
|
58
|
+
<div
|
|
59
|
+
ref={sliderRef}
|
|
60
|
+
onMouseDown={(e) => { setIsDragging(true); updateDepthFromY(e.clientY); }}
|
|
61
|
+
onTouchStart={(e) => { setIsDragging(true); updateDepthFromY(e.touches[0].clientY); }}
|
|
62
|
+
className="graph-depth-v-track"
|
|
63
|
+
style={{ "--td-depth-pct": `${percentage}%` }}
|
|
64
|
+
>
|
|
65
|
+
<div className="graph-depth-v-fill" />
|
|
66
|
+
<div className={`graph-depth-v-thumb${isDragging ? " dragging" : ""}`} />
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
23
69
|
);
|
|
24
70
|
};
|
|
25
71
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildColorMap,
|
|
3
|
+
EDGE_COLOR_PALETTE,
|
|
4
|
+
EMPTY_EDGE_COLOR,
|
|
5
|
+
EMPTY_EDGE_TYPE,
|
|
6
|
+
} from "../edgeColorPalette";
|
|
7
|
+
|
|
8
|
+
describe("buildColorMap", () => {
|
|
9
|
+
it("returns the empty edge color for empty edges", () => {
|
|
10
|
+
expect(buildColorMap([])).toEqual(
|
|
11
|
+
new Map([[EMPTY_EDGE_TYPE, EMPTY_EDGE_COLOR]])
|
|
12
|
+
);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("assigns the first palette color to the first distinct type", () => {
|
|
16
|
+
const edges = [{ tags: [{ value: { type: "Contains" } }] }];
|
|
17
|
+
const map = buildColorMap(edges);
|
|
18
|
+
expect(map.get("Contains")).toBe(EDGE_COLOR_PALETTE[0]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("assigns different colors to different types", () => {
|
|
22
|
+
const edges = [
|
|
23
|
+
{ tags: [{ value: { type: "Contains" } }, { value: { type: "Uses" } }] },
|
|
24
|
+
];
|
|
25
|
+
const map = buildColorMap(edges);
|
|
26
|
+
expect(map.get("Contains")).not.toBe(map.get("Uses"));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("assigns the same color to the same type across multiple edges", () => {
|
|
30
|
+
const edges = [
|
|
31
|
+
{ tags: [{ value: { type: "Contains" } }] },
|
|
32
|
+
{ tags: [{ value: { type: "Contains" } }] },
|
|
33
|
+
];
|
|
34
|
+
const map = buildColorMap(edges);
|
|
35
|
+
expect(map.size).toBe(2);
|
|
36
|
+
expect(map.get(EMPTY_EDGE_TYPE)).toBe(EMPTY_EDGE_COLOR);
|
|
37
|
+
expect(map.get("Contains")).toBe(EDGE_COLOR_PALETTE[0]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("wraps palette when there are more types than palette entries", () => {
|
|
41
|
+
const edges = EDGE_COLOR_PALETTE.map((_, i) => ({
|
|
42
|
+
tags: [{ value: { type: `type${i}` } }],
|
|
43
|
+
}));
|
|
44
|
+
// Add one more type beyond palette length
|
|
45
|
+
edges.push({ tags: [{ value: { type: "overflow" } }] });
|
|
46
|
+
const map = buildColorMap(edges);
|
|
47
|
+
expect(map.get("overflow")).toBe(EDGE_COLOR_PALETTE[0]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("skips tags with missing value.type", () => {
|
|
51
|
+
const edges = [
|
|
52
|
+
{ tags: [{ value: {} }, { value: { type: "Contains" } }] },
|
|
53
|
+
];
|
|
54
|
+
const map = buildColorMap(edges);
|
|
55
|
+
expect(map.size).toBe(2);
|
|
56
|
+
expect(map.get(EMPTY_EDGE_TYPE)).toBe(EMPTY_EDGE_COLOR);
|
|
57
|
+
expect(map.get("Contains")).toBe(EDGE_COLOR_PALETTE[0]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("handles edges with no tags array", () => {
|
|
61
|
+
const edges = [{ tags: undefined }, { tags: [] }];
|
|
62
|
+
expect(() => buildColorMap(edges)).not.toThrow();
|
|
63
|
+
expect(buildColorMap(edges)).toEqual(
|
|
64
|
+
new Map([[EMPTY_EDGE_TYPE, EMPTY_EDGE_COLOR]])
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const EDGE_COLOR_PALETTE = [
|
|
2
|
+
"#4a90d9",
|
|
3
|
+
"#27ae60",
|
|
4
|
+
"#8e44ad",
|
|
5
|
+
"#c0392b",
|
|
6
|
+
"#16a085",
|
|
7
|
+
"#2c3e50",
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export const EMPTY_EDGE_COLOR = "#5f6672";
|
|
11
|
+
export const EMPTY_EDGE_TYPE = "_empty";
|
|
12
|
+
|
|
13
|
+
export const buildColorMap = (edges = []) => {
|
|
14
|
+
const map = new Map([[EMPTY_EDGE_TYPE, EMPTY_EDGE_COLOR]]);
|
|
15
|
+
let paletteIndex = 0;
|
|
16
|
+
|
|
17
|
+
edges.forEach(({ tags = [] }) => {
|
|
18
|
+
(tags || []).forEach(({ value }) => {
|
|
19
|
+
const type = value?.type;
|
|
20
|
+
if (type && !map.has(type)) {
|
|
21
|
+
map.set(type, EDGE_COLOR_PALETTE[paletteIndex % EDGE_COLOR_PALETTE.length]);
|
|
22
|
+
paletteIndex += 1;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
return map;
|
|
27
|
+
};
|
|
@@ -1,31 +1,177 @@
|
|
|
1
1
|
.graph-depth {
|
|
2
|
-
position: absolute
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
position: absolute;
|
|
3
|
+
left: 12px;
|
|
4
|
+
bottom: 12px;
|
|
5
|
+
z-index: 2;
|
|
6
|
+
width: 170px;
|
|
7
|
+
margin: 0;
|
|
8
|
+
min-width: 170px;
|
|
9
|
+
padding: 0;
|
|
10
|
+
border: 0;
|
|
11
|
+
box-shadow: none;
|
|
12
|
+
background: transparent;
|
|
13
|
+
|
|
14
|
+
.graph-depth-header {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: flex-start;
|
|
18
|
+
gap: 8px;
|
|
19
|
+
margin-bottom: 4px;
|
|
20
|
+
line-height: 1;
|
|
21
|
+
|
|
22
|
+
.graph-depth-title {
|
|
23
|
+
font-weight: 600;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.graph-depth-value {
|
|
27
|
+
min-width: 12px;
|
|
28
|
+
font-weight: bold;
|
|
29
|
+
color: var(--td-graph-accent);
|
|
30
|
+
}
|
|
10
31
|
}
|
|
11
32
|
|
|
12
33
|
&.disabled {
|
|
13
34
|
pointer-events: none;
|
|
14
|
-
opacity: 0.
|
|
35
|
+
opacity: 0.6;
|
|
36
|
+
|
|
37
|
+
.graph-depth-step,
|
|
38
|
+
.graph-depth-range {
|
|
39
|
+
accent-color: var(--td-graph-depth-disabled);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.graph-depth-step {
|
|
43
|
+
background-color: var(--td-graph-depth-disabled);
|
|
44
|
+
border-color: var(--td-graph-depth-disabled);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.graph-depth-slider {
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: 6px;
|
|
52
|
+
|
|
53
|
+
.graph-depth-step {
|
|
54
|
+
width: 16px;
|
|
55
|
+
height: 16px;
|
|
56
|
+
padding: 0;
|
|
57
|
+
border-radius: 50%;
|
|
58
|
+
border: 1px solid var(--td-graph-accent);
|
|
59
|
+
background: #fff;
|
|
60
|
+
color: var(--td-graph-accent);
|
|
61
|
+
font-size: 11px;
|
|
62
|
+
line-height: 14px;
|
|
63
|
+
text-align: center;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.graph-depth-step:disabled {
|
|
68
|
+
border-color: var(--td-graph-depth-disabled);
|
|
69
|
+
color: var(--td-graph-depth-disabled);
|
|
70
|
+
cursor: default;
|
|
71
|
+
}
|
|
15
72
|
|
|
16
|
-
.
|
|
17
|
-
|
|
73
|
+
.graph-depth-range {
|
|
74
|
+
flex: 1;
|
|
75
|
+
height: 18px;
|
|
76
|
+
margin: 0;
|
|
77
|
+
accent-color: var(--td-graph-accent);
|
|
78
|
+
cursor: pointer;
|
|
18
79
|
}
|
|
19
80
|
}
|
|
20
81
|
|
|
21
|
-
.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
82
|
+
.graph-depth-labels {
|
|
83
|
+
display: flex;
|
|
84
|
+
justify-content: space-between;
|
|
85
|
+
font-size: 11px;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
margin-top: 2px;
|
|
88
|
+
color: #666;
|
|
89
|
+
|
|
90
|
+
span {
|
|
91
|
+
min-width: 12px;
|
|
92
|
+
text-align: center;
|
|
93
|
+
user-select: none;
|
|
25
94
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.relation-graph-wrapper {
|
|
99
|
+
position: relative;
|
|
100
|
+
width: 100%;
|
|
101
|
+
margin: 0;
|
|
102
|
+
overflow: hidden;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.graph-depth-v-container {
|
|
106
|
+
position: absolute;
|
|
107
|
+
top: 12px;
|
|
108
|
+
right: 20px;
|
|
109
|
+
z-index: 2;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.graph-depth-v {
|
|
113
|
+
width: 32px;
|
|
114
|
+
display: flex;
|
|
115
|
+
flex-direction: column;
|
|
116
|
+
align-items: center;
|
|
117
|
+
gap: 4px;
|
|
118
|
+
padding: 4px 8px 2px;
|
|
119
|
+
background: rgba(255, 255, 255, 0.65);
|
|
120
|
+
backdrop-filter: blur(4px);
|
|
121
|
+
border-radius: 12px;
|
|
122
|
+
user-select: none;
|
|
123
|
+
|
|
124
|
+
&.disabled {
|
|
125
|
+
pointer-events: none;
|
|
126
|
+
opacity: 0.5;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.graph-depth-v-label {
|
|
130
|
+
font-size: 16px;
|
|
131
|
+
font-weight: 700;
|
|
132
|
+
color: var(--td-graph-accent);
|
|
133
|
+
line-height: 1;
|
|
134
|
+
margin-top: -4px;
|
|
135
|
+
margin-bottom: 8px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.graph-depth-v-track {
|
|
139
|
+
position: relative;
|
|
140
|
+
width: 4px;
|
|
141
|
+
height: 88px;
|
|
142
|
+
background: var(--td-graph-depth-inactive);
|
|
143
|
+
border-radius: 2px;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
|
|
146
|
+
.graph-depth-v-fill {
|
|
147
|
+
position: absolute;
|
|
148
|
+
bottom: 0;
|
|
149
|
+
left: 0;
|
|
150
|
+
width: 100%;
|
|
151
|
+
height: var(--td-depth-pct, 0%);
|
|
152
|
+
background: var(--td-graph-accent);
|
|
153
|
+
border-radius: 2px;
|
|
154
|
+
transition: height 0.2s ease-out;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.graph-depth-v-thumb {
|
|
158
|
+
position: absolute;
|
|
159
|
+
left: 50%;
|
|
160
|
+
bottom: calc(var(--td-depth-pct, 0%) - 7px);
|
|
161
|
+
transform: translateX(-50%);
|
|
162
|
+
width: 14px;
|
|
163
|
+
height: 14px;
|
|
164
|
+
background: #ffffff;
|
|
165
|
+
border: 2px solid var(--td-graph-accent);
|
|
166
|
+
border-radius: 50%;
|
|
167
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
|
|
168
|
+
cursor: grab;
|
|
169
|
+
transition: bottom 0.1s ease-out, transform 0.1s ease-out;
|
|
170
|
+
|
|
171
|
+
&.dragging {
|
|
172
|
+
transform: translateX(-50%) scale(1.2);
|
|
173
|
+
cursor: grabbing;
|
|
174
|
+
}
|
|
29
175
|
}
|
|
30
176
|
}
|
|
31
|
-
}
|
|
177
|
+
}
|