@truedat/lm 8.4.7 → 8.4.9
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 +7 -6
- package/src/components/ConceptSuggestionLinkForm.js +1 -1
- package/src/components/RelationGraph.js +82 -37
- package/src/components/RelationGraphDepth.js +64 -18
- package/src/components/__tests__/ConceptSuggestionLinkForm.spec.js +1 -1
- package/src/messages/en.js +4 -0
- package/src/messages/es.js +4 -0
- package/src/services/__tests__/edgeColorPalette.spec.js +67 -0
- package/src/services/edgeColorPalette.js +27 -0
- package/src/styles/relationGraph.less +165 -19
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/lm",
|
|
3
|
-
"version": "8.4.
|
|
3
|
+
"version": "8.4.9",
|
|
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.9",
|
|
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": "9fd4bd2126a33342009194c8ae1829cd3c617a48"
|
|
89
90
|
}
|
|
@@ -14,7 +14,7 @@ import TagTypeDropdownSelector from "./TagTypeDropdownSelector";
|
|
|
14
14
|
import RelationTagsLoader from "./RelationTagsLoader";
|
|
15
15
|
|
|
16
16
|
const StructureSuggestions = React.lazy(
|
|
17
|
-
() => import("@truedat/
|
|
17
|
+
() => import("@truedat/ai/components/StructureSuggestions")
|
|
18
18
|
);
|
|
19
19
|
|
|
20
20
|
const selectTagOptions = makeTagOptionsSelector("data_field");
|
|
@@ -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
|
|
|
@@ -27,7 +27,7 @@ jest.mock("@truedat/core/selectors", () => ({
|
|
|
27
27
|
}));
|
|
28
28
|
|
|
29
29
|
// Mock components
|
|
30
|
-
jest.mock("@truedat/
|
|
30
|
+
jest.mock("@truedat/ai/components/StructureSuggestions", () =>
|
|
31
31
|
jest.fn((props) => {
|
|
32
32
|
// Include a button to simulate structure selection
|
|
33
33
|
return (
|
package/src/messages/en.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
"implementations.relation.new.header": "Link to Concept",
|
|
3
|
+
"links.actions.suggest": "Suggest Link",
|
|
4
|
+
"links.suggest.prompt.label": "Prompt",
|
|
5
|
+
"links.suggest.prompt.placeholder": "Additional prompt to help find related structures (optional)",
|
|
6
|
+
"links.suggest.submit": "Search suggestions",
|
|
3
7
|
"implementations.linked_concepts.empty": "No linked business concepts found",
|
|
4
8
|
"links.actions.create": "Add Link",
|
|
5
9
|
"relations.actions.create": "Add relation",
|
package/src/messages/es.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
"implementations.linked_concepts.empty": "No hay conceptos relacionados",
|
|
3
|
+
"links.actions.suggest": "Sugerir vínculo",
|
|
4
|
+
"links.suggest.prompt.label": "Prompt",
|
|
5
|
+
"links.suggest.prompt.placeholder": "Contexto adicional para encontrar estructuras relacionadas (opcional)",
|
|
6
|
+
"links.suggest.submit": "Buscar sugerencias",
|
|
3
7
|
"implementations.relation.new.header":
|
|
4
8
|
"Vas a vincular esta implementación a un término del glosario",
|
|
5
9
|
"links.actions.create": "Añadir vinculación",
|
|
@@ -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
|
+
}
|