@gravity-ui/page-constructor 8.16.0 → 8.18.0
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/build/cjs/components/VideoButton/VideoButton.css +2 -2
- package/build/cjs/constructor-items.d.ts +1 -1
- package/build/cjs/demo/ComponentMap/ComponentMap.css +55 -0
- package/build/cjs/demo/ComponentMap/ComponentMap.d.ts +6 -0
- package/build/cjs/demo/ComponentMap/ComponentMap.js +286 -0
- package/build/cjs/demo/ComponentMap/ComponentMap.js.map +1 -0
- package/build/cjs/demo/ComponentMap/draw.d.ts +23 -0
- package/build/cjs/demo/ComponentMap/draw.js +134 -0
- package/build/cjs/demo/ComponentMap/draw.js.map +1 -0
- package/build/cjs/demo/ComponentMap/edgeGeometry.d.ts +20 -0
- package/build/cjs/demo/ComponentMap/edgeGeometry.js +146 -0
- package/build/cjs/demo/ComponentMap/edgeGeometry.js.map +1 -0
- package/build/cjs/demo/ComponentMap/graph.d.ts +9 -0
- package/build/cjs/demo/ComponentMap/graph.js +68 -0
- package/build/cjs/demo/ComponentMap/graph.js.map +1 -0
- package/build/cjs/demo/ComponentMap/layout.d.ts +12 -0
- package/build/cjs/demo/ComponentMap/layout.js +149 -0
- package/build/cjs/demo/ComponentMap/layout.js.map +1 -0
- package/build/cjs/demo/ComponentMap/model.d.ts +55 -0
- package/build/cjs/demo/ComponentMap/model.js +3 -0
- package/build/cjs/demo/ComponentMap/model.js.map +1 -0
- package/build/cjs/demo/ComponentMap/viewport.d.ts +7 -0
- package/build/cjs/demo/ComponentMap/viewport.js +41 -0
- package/build/cjs/demo/ComponentMap/viewport.js.map +1 -0
- package/build/cjs/demo/HeroAndMedia.stories.js +12 -23
- package/build/cjs/demo/HeroAndMedia.stories.js.map +1 -1
- package/build/cjs/demo/TokenizationHeader.stories.d.ts +13 -0
- package/build/cjs/demo/TokenizationHeader.stories.js +47 -0
- package/build/cjs/demo/TokenizationHeader.stories.js.map +1 -0
- package/build/cjs/demo/TokenizationHero.stories.d.ts +9 -0
- package/build/cjs/demo/TokenizationHero.stories.js +20 -0
- package/build/cjs/demo/TokenizationHero.stories.js.map +1 -0
- package/build/cjs/demo/tokenizationLanding.d.ts +2 -0
- package/build/cjs/demo/tokenizationLanding.js +21 -0
- package/build/cjs/demo/tokenizationLanding.js.map +1 -0
- package/build/cjs/models/constructor-items/sub-blocks.d.ts +3 -0
- package/build/cjs/models/constructor-items/sub-blocks.js.map +1 -1
- package/build/cjs/sub-blocks/LayoutItem/LayoutItem.css +15 -0
- package/build/cjs/sub-blocks/LayoutItem/LayoutItem.d.ts +1 -1
- package/build/cjs/sub-blocks/LayoutItem/LayoutItem.js +12 -2
- package/build/cjs/sub-blocks/LayoutItem/LayoutItem.js.map +1 -1
- package/build/cjs/sub-blocks/LayoutItem/schema.d.ts +10 -0
- package/build/cjs/sub-blocks/LayoutItem/schema.js +10 -0
- package/build/cjs/sub-blocks/LayoutItem/schema.js.map +1 -1
- package/build/docs/sub-blocks/LayoutItem.md +18 -13
- package/build/esm/components/VideoButton/VideoButton.css +2 -2
- package/build/esm/constructor-items.d.ts +1 -1
- package/build/esm/demo/ComponentMap/ComponentMap.css +55 -0
- package/build/esm/demo/ComponentMap/ComponentMap.d.ts +7 -0
- package/build/esm/demo/ComponentMap/ComponentMap.js +283 -0
- package/build/esm/demo/ComponentMap/ComponentMap.js.map +1 -0
- package/build/esm/demo/ComponentMap/draw.d.ts +23 -0
- package/build/esm/demo/ComponentMap/draw.js +130 -0
- package/build/esm/demo/ComponentMap/draw.js.map +1 -0
- package/build/esm/demo/ComponentMap/edgeGeometry.d.ts +20 -0
- package/build/esm/demo/ComponentMap/edgeGeometry.js +136 -0
- package/build/esm/demo/ComponentMap/edgeGeometry.js.map +1 -0
- package/build/esm/demo/ComponentMap/graph.d.ts +9 -0
- package/build/esm/demo/ComponentMap/graph.js +62 -0
- package/build/esm/demo/ComponentMap/graph.js.map +1 -0
- package/build/esm/demo/ComponentMap/layout.d.ts +12 -0
- package/build/esm/demo/ComponentMap/layout.js +144 -0
- package/build/esm/demo/ComponentMap/layout.js.map +1 -0
- package/build/esm/demo/ComponentMap/model.d.ts +55 -0
- package/build/esm/demo/ComponentMap/model.js +2 -0
- package/build/esm/demo/ComponentMap/model.js.map +1 -0
- package/build/esm/demo/ComponentMap/viewport.d.ts +7 -0
- package/build/esm/demo/ComponentMap/viewport.js +34 -0
- package/build/esm/demo/ComponentMap/viewport.js.map +1 -0
- package/build/esm/demo/HeroAndMedia.stories.js +1 -12
- package/build/esm/demo/HeroAndMedia.stories.js.map +1 -1
- package/build/esm/demo/TokenizationHeader.stories.d.ts +13 -0
- package/build/esm/demo/TokenizationHeader.stories.js +43 -0
- package/build/esm/demo/TokenizationHeader.stories.js.map +1 -0
- package/build/esm/demo/TokenizationHero.stories.d.ts +9 -0
- package/build/esm/demo/TokenizationHero.stories.js +16 -0
- package/build/esm/demo/TokenizationHero.stories.js.map +1 -0
- package/build/esm/demo/tokenizationLanding.d.ts +2 -0
- package/build/esm/demo/tokenizationLanding.js +16 -0
- package/build/esm/demo/tokenizationLanding.js.map +1 -0
- package/build/esm/models/constructor-items/sub-blocks.d.ts +3 -0
- package/build/esm/models/constructor-items/sub-blocks.js.map +1 -1
- package/build/esm/sub-blocks/LayoutItem/LayoutItem.css +15 -0
- package/build/esm/sub-blocks/LayoutItem/LayoutItem.d.ts +1 -1
- package/build/esm/sub-blocks/LayoutItem/LayoutItem.js +14 -4
- package/build/esm/sub-blocks/LayoutItem/LayoutItem.js.map +1 -1
- package/build/esm/sub-blocks/LayoutItem/schema.d.ts +10 -0
- package/build/esm/sub-blocks/LayoutItem/schema.js +10 -0
- package/build/esm/sub-blocks/LayoutItem/schema.js.map +1 -1
- package/package.json +2 -1
- package/schema/index.js +1 -1
- package/server/models/constructor-items/sub-blocks.d.ts +3 -0
- package/widget/index.js +1 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { getEdgeKey } from "./graph.js";
|
|
2
|
+
export const ARROW_SIZE = 12;
|
|
3
|
+
export const EDGE_CURVE_SEGMENTS = 24;
|
|
4
|
+
export const EDGE_HIT_TOLERANCE = 6;
|
|
5
|
+
export const SAME_GROUP_ARC_MIN_OUTSET = 64;
|
|
6
|
+
export const SAME_GROUP_ARC_SPAN_FACTOR = 0.25;
|
|
7
|
+
const distance = (left, right) => Math.hypot(left.x - right.x, left.y - right.y);
|
|
8
|
+
const getNodeCenter = (node) => ({
|
|
9
|
+
x: node.rect.x + node.rect.width / 2,
|
|
10
|
+
y: node.rect.y + node.rect.height / 2,
|
|
11
|
+
});
|
|
12
|
+
export function getSameGroupArcOutset(source, target) {
|
|
13
|
+
const sourceCenter = getNodeCenter(source);
|
|
14
|
+
const targetCenter = getNodeCenter(target);
|
|
15
|
+
return Math.max(SAME_GROUP_ARC_MIN_OUTSET, Math.abs(targetCenter.y - sourceCenter.y) * SAME_GROUP_ARC_SPAN_FACTOR);
|
|
16
|
+
}
|
|
17
|
+
function distanceToSegment(point, start, end) {
|
|
18
|
+
const deltaX = end.x - start.x;
|
|
19
|
+
const deltaY = end.y - start.y;
|
|
20
|
+
const lengthSquared = deltaX * deltaX + deltaY * deltaY;
|
|
21
|
+
if (lengthSquared === 0) {
|
|
22
|
+
return distance(point, start);
|
|
23
|
+
}
|
|
24
|
+
const ratio = Math.max(0, Math.min(1, ((point.x - start.x) * deltaX + (point.y - start.y) * deltaY) / lengthSquared));
|
|
25
|
+
return distance(point, { x: start.x + ratio * deltaX, y: start.y + ratio * deltaY });
|
|
26
|
+
}
|
|
27
|
+
function triangleSign(point, first, second) {
|
|
28
|
+
return ((point.x - second.x) * (first.y - second.y) - (first.x - second.x) * (point.y - second.y));
|
|
29
|
+
}
|
|
30
|
+
function isInsideTriangle(point, [first, second, third]) {
|
|
31
|
+
const firstSign = triangleSign(point, first, second);
|
|
32
|
+
const secondSign = triangleSign(point, second, third);
|
|
33
|
+
const thirdSign = triangleSign(point, third, first);
|
|
34
|
+
const hasNegative = firstSign < 0 || secondSign < 0 || thirdSign < 0;
|
|
35
|
+
const hasPositive = firstSign > 0 || secondSign > 0 || thirdSign > 0;
|
|
36
|
+
return !(hasNegative && hasPositive);
|
|
37
|
+
}
|
|
38
|
+
export function getEdgeGeometry(source, target) {
|
|
39
|
+
const sourceCenter = getNodeCenter(source);
|
|
40
|
+
const targetCenter = getNodeCenter(target);
|
|
41
|
+
let start;
|
|
42
|
+
let end;
|
|
43
|
+
let firstControl;
|
|
44
|
+
let secondControl;
|
|
45
|
+
if (source.kind === target.kind) {
|
|
46
|
+
start = { x: source.rect.x + source.rect.width, y: sourceCenter.y };
|
|
47
|
+
end = { x: target.rect.x + target.rect.width, y: targetCenter.y };
|
|
48
|
+
const arcX = Math.max(start.x, end.x) + getSameGroupArcOutset(source, target);
|
|
49
|
+
firstControl = { x: arcX, y: start.y };
|
|
50
|
+
secondControl = { x: arcX, y: end.y };
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const direction = targetCenter.x >= sourceCenter.x ? 1 : -1;
|
|
54
|
+
start = {
|
|
55
|
+
x: sourceCenter.x + (source.rect.width / 2) * direction,
|
|
56
|
+
y: sourceCenter.y,
|
|
57
|
+
};
|
|
58
|
+
end = {
|
|
59
|
+
x: targetCenter.x - (target.rect.width / 2) * direction,
|
|
60
|
+
y: targetCenter.y,
|
|
61
|
+
};
|
|
62
|
+
const controlX = (start.x + end.x) / 2;
|
|
63
|
+
firstControl = { x: controlX, y: start.y };
|
|
64
|
+
secondControl = { x: controlX, y: end.y };
|
|
65
|
+
}
|
|
66
|
+
const angle = Math.atan2(end.y - secondControl.y, end.x - secondControl.x);
|
|
67
|
+
const arrowhead = [
|
|
68
|
+
end,
|
|
69
|
+
{
|
|
70
|
+
x: end.x - ARROW_SIZE * Math.cos(angle - Math.PI / 6),
|
|
71
|
+
y: end.y - ARROW_SIZE * Math.sin(angle - Math.PI / 6),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
x: end.x - ARROW_SIZE * Math.cos(angle + Math.PI / 6),
|
|
75
|
+
y: end.y - ARROW_SIZE * Math.sin(angle + Math.PI / 6),
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
return { start, end, firstControl, secondControl, arrowhead };
|
|
79
|
+
}
|
|
80
|
+
export function getCubicBezierPoint(geometry, ratio) {
|
|
81
|
+
const inverse = 1 - ratio;
|
|
82
|
+
return {
|
|
83
|
+
x: inverse ** 3 * geometry.start.x +
|
|
84
|
+
3 * inverse ** 2 * ratio * geometry.firstControl.x +
|
|
85
|
+
3 * inverse * ratio ** 2 * geometry.secondControl.x +
|
|
86
|
+
ratio ** 3 * geometry.end.x,
|
|
87
|
+
y: inverse ** 3 * geometry.start.y +
|
|
88
|
+
3 * inverse ** 2 * ratio * geometry.firstControl.y +
|
|
89
|
+
3 * inverse * ratio ** 2 * geometry.secondControl.y +
|
|
90
|
+
ratio ** 3 * geometry.end.y,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export function getEdgeDistance(point, geometry) {
|
|
94
|
+
if (isInsideTriangle(point, geometry.arrowhead)) {
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
let minimum = Number.POSITIVE_INFINITY;
|
|
98
|
+
let previous = geometry.start;
|
|
99
|
+
for (let index = 1; index <= EDGE_CURVE_SEGMENTS; index += 1) {
|
|
100
|
+
const current = getCubicBezierPoint(geometry, index / EDGE_CURVE_SEGMENTS);
|
|
101
|
+
minimum = Math.min(minimum, distanceToSegment(point, previous, current));
|
|
102
|
+
previous = current;
|
|
103
|
+
}
|
|
104
|
+
for (let index = 0; index < geometry.arrowhead.length; index += 1) {
|
|
105
|
+
minimum = Math.min(minimum, distanceToSegment(point, geometry.arrowhead[index], geometry.arrowhead[(index + 1) % geometry.arrowhead.length]));
|
|
106
|
+
}
|
|
107
|
+
return minimum;
|
|
108
|
+
}
|
|
109
|
+
export const getEdgeHitTolerance = (scale) => EDGE_HIT_TOLERANCE / Math.max(scale, Number.EPSILON);
|
|
110
|
+
export function orderEdgesForInteraction(edges, hoveredEdgeKey, selectedEdgeKeys) {
|
|
111
|
+
const priority = (edge) => {
|
|
112
|
+
const key = getEdgeKey(edge.source.id, edge.target.id);
|
|
113
|
+
if (selectedEdgeKeys.has(key)) {
|
|
114
|
+
return 2;
|
|
115
|
+
}
|
|
116
|
+
return key === hoveredEdgeKey ? 1 : 0;
|
|
117
|
+
};
|
|
118
|
+
return edges
|
|
119
|
+
.map((edge, index) => ({ edge, index, priority: priority(edge) }))
|
|
120
|
+
.sort((left, right) => left.priority - right.priority || left.index - right.index)
|
|
121
|
+
.map(({ edge }) => edge);
|
|
122
|
+
}
|
|
123
|
+
export function hitTestEdge(edges, point, tolerance) {
|
|
124
|
+
let closest;
|
|
125
|
+
let closestDistance = Number.POSITIVE_INFINITY;
|
|
126
|
+
for (let index = edges.length - 1; index >= 0; index -= 1) {
|
|
127
|
+
const edge = edges[index];
|
|
128
|
+
const edgeDistance = getEdgeDistance(point, getEdgeGeometry(edge.source, edge.target));
|
|
129
|
+
if (edgeDistance <= tolerance && (!closest || edgeDistance < closestDistance)) {
|
|
130
|
+
closest = edge;
|
|
131
|
+
closestDistance = edgeDistance;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return closest;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=edgeGeometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edgeGeometry.js","sourceRoot":"../../../../src","sources":["demo/ComponentMap/edgeGeometry.ts"],"names":[],"mappings":"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","sourcesContent":["import {getEdgeKey} from './graph';\nimport type {LayoutEdge, LayoutNode, Point} from './model';\n\nexport const ARROW_SIZE = 12;\nexport const EDGE_CURVE_SEGMENTS = 24;\nexport const EDGE_HIT_TOLERANCE = 6;\nexport const SAME_GROUP_ARC_MIN_OUTSET = 64;\nexport const SAME_GROUP_ARC_SPAN_FACTOR = 0.25;\n\nexport interface EdgeGeometry {\n start: Point;\n end: Point;\n firstControl: Point;\n secondControl: Point;\n arrowhead: [Point, Point, Point];\n}\n\nconst distance = (left: Point, right: Point) => Math.hypot(left.x - right.x, left.y - right.y);\n\nconst getNodeCenter = (node: LayoutNode): Point => ({\n x: node.rect.x + node.rect.width / 2,\n y: node.rect.y + node.rect.height / 2,\n});\n\nexport function getSameGroupArcOutset(source: LayoutNode, target: LayoutNode): number {\n const sourceCenter = getNodeCenter(source);\n const targetCenter = getNodeCenter(target);\n return Math.max(\n SAME_GROUP_ARC_MIN_OUTSET,\n Math.abs(targetCenter.y - sourceCenter.y) * SAME_GROUP_ARC_SPAN_FACTOR,\n );\n}\n\nfunction distanceToSegment(point: Point, start: Point, end: Point) {\n const deltaX = end.x - start.x;\n const deltaY = end.y - start.y;\n const lengthSquared = deltaX * deltaX + deltaY * deltaY;\n if (lengthSquared === 0) {\n return distance(point, start);\n }\n const ratio = Math.max(\n 0,\n Math.min(1, ((point.x - start.x) * deltaX + (point.y - start.y) * deltaY) / lengthSquared),\n );\n return distance(point, {x: start.x + ratio * deltaX, y: start.y + ratio * deltaY});\n}\n\nfunction triangleSign(point: Point, first: Point, second: Point) {\n return (\n (point.x - second.x) * (first.y - second.y) - (first.x - second.x) * (point.y - second.y)\n );\n}\n\nfunction isInsideTriangle(point: Point, [first, second, third]: [Point, Point, Point]) {\n const firstSign = triangleSign(point, first, second);\n const secondSign = triangleSign(point, second, third);\n const thirdSign = triangleSign(point, third, first);\n const hasNegative = firstSign < 0 || secondSign < 0 || thirdSign < 0;\n const hasPositive = firstSign > 0 || secondSign > 0 || thirdSign > 0;\n return !(hasNegative && hasPositive);\n}\n\nexport function getEdgeGeometry(source: LayoutNode, target: LayoutNode): EdgeGeometry {\n const sourceCenter = getNodeCenter(source);\n const targetCenter = getNodeCenter(target);\n let start: Point;\n let end: Point;\n let firstControl: Point;\n let secondControl: Point;\n\n if (source.kind === target.kind) {\n start = {x: source.rect.x + source.rect.width, y: sourceCenter.y};\n end = {x: target.rect.x + target.rect.width, y: targetCenter.y};\n const arcX = Math.max(start.x, end.x) + getSameGroupArcOutset(source, target);\n firstControl = {x: arcX, y: start.y};\n secondControl = {x: arcX, y: end.y};\n } else {\n const direction = targetCenter.x >= sourceCenter.x ? 1 : -1;\n start = {\n x: sourceCenter.x + (source.rect.width / 2) * direction,\n y: sourceCenter.y,\n };\n end = {\n x: targetCenter.x - (target.rect.width / 2) * direction,\n y: targetCenter.y,\n };\n const controlX = (start.x + end.x) / 2;\n firstControl = {x: controlX, y: start.y};\n secondControl = {x: controlX, y: end.y};\n }\n const angle = Math.atan2(end.y - secondControl.y, end.x - secondControl.x);\n const arrowhead: [Point, Point, Point] = [\n end,\n {\n x: end.x - ARROW_SIZE * Math.cos(angle - Math.PI / 6),\n y: end.y - ARROW_SIZE * Math.sin(angle - Math.PI / 6),\n },\n {\n x: end.x - ARROW_SIZE * Math.cos(angle + Math.PI / 6),\n y: end.y - ARROW_SIZE * Math.sin(angle + Math.PI / 6),\n },\n ];\n\n return {start, end, firstControl, secondControl, arrowhead};\n}\n\nexport function getCubicBezierPoint(geometry: EdgeGeometry, ratio: number): Point {\n const inverse = 1 - ratio;\n return {\n x:\n inverse ** 3 * geometry.start.x +\n 3 * inverse ** 2 * ratio * geometry.firstControl.x +\n 3 * inverse * ratio ** 2 * geometry.secondControl.x +\n ratio ** 3 * geometry.end.x,\n y:\n inverse ** 3 * geometry.start.y +\n 3 * inverse ** 2 * ratio * geometry.firstControl.y +\n 3 * inverse * ratio ** 2 * geometry.secondControl.y +\n ratio ** 3 * geometry.end.y,\n };\n}\n\nexport function getEdgeDistance(point: Point, geometry: EdgeGeometry): number {\n if (isInsideTriangle(point, geometry.arrowhead)) {\n return 0;\n }\n\n let minimum = Number.POSITIVE_INFINITY;\n let previous = geometry.start;\n for (let index = 1; index <= EDGE_CURVE_SEGMENTS; index += 1) {\n const current = getCubicBezierPoint(geometry, index / EDGE_CURVE_SEGMENTS);\n minimum = Math.min(minimum, distanceToSegment(point, previous, current));\n previous = current;\n }\n\n for (let index = 0; index < geometry.arrowhead.length; index += 1) {\n minimum = Math.min(\n minimum,\n distanceToSegment(\n point,\n geometry.arrowhead[index],\n geometry.arrowhead[(index + 1) % geometry.arrowhead.length],\n ),\n );\n }\n return minimum;\n}\n\nexport const getEdgeHitTolerance = (scale: number) =>\n EDGE_HIT_TOLERANCE / Math.max(scale, Number.EPSILON);\n\nexport function orderEdgesForInteraction(\n edges: LayoutEdge[],\n hoveredEdgeKey: string | undefined,\n selectedEdgeKeys: ReadonlySet<string>,\n): LayoutEdge[] {\n const priority = (edge: LayoutEdge) => {\n const key = getEdgeKey(edge.source.id, edge.target.id);\n if (selectedEdgeKeys.has(key)) {\n return 2;\n }\n return key === hoveredEdgeKey ? 1 : 0;\n };\n\n return edges\n .map((edge, index) => ({edge, index, priority: priority(edge)}))\n .sort((left, right) => left.priority - right.priority || left.index - right.index)\n .map(({edge}) => edge);\n}\n\nexport function hitTestEdge(\n edges: LayoutEdge[],\n point: Point,\n tolerance: number,\n): LayoutEdge | undefined {\n let closest: LayoutEdge | undefined;\n let closestDistance = Number.POSITIVE_INFINITY;\n for (let index = edges.length - 1; index >= 0; index -= 1) {\n const edge = edges[index];\n const edgeDistance = getEdgeDistance(point, getEdgeGeometry(edge.source, edge.target));\n if (edgeDistance <= tolerance && (!closest || edgeDistance < closestDistance)) {\n closest = edge;\n closestDistance = edgeDistance;\n }\n }\n return closest;\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ComponentMapData, ComponentMapEmphasis } from "./model.js";
|
|
2
|
+
export declare const getEdgeKey: (source: string, target: string) => string;
|
|
3
|
+
interface ComponentMapEmphasisRequest {
|
|
4
|
+
selectedNodeId?: string;
|
|
5
|
+
searchQuery?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function getRelatedNodeIds(data: ComponentMapData, selectedId: string): Set<string>;
|
|
8
|
+
export declare function getEmphasis(data: ComponentMapData, { selectedNodeId, searchQuery }?: ComponentMapEmphasisRequest): ComponentMapEmphasis;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export const getEdgeKey = (source, target) => `${source}\0${target}`;
|
|
2
|
+
const normalizeSearchValue = (value) => value.trim().toLowerCase();
|
|
3
|
+
function createAdjacency(data) {
|
|
4
|
+
const incoming = new Map();
|
|
5
|
+
const outgoing = new Map();
|
|
6
|
+
for (const { source, target } of data.edges) {
|
|
7
|
+
const outgoingTargets = outgoing.get(source) ?? [];
|
|
8
|
+
outgoingTargets.push(target);
|
|
9
|
+
outgoing.set(source, outgoingTargets);
|
|
10
|
+
const incomingSources = incoming.get(target) ?? [];
|
|
11
|
+
incomingSources.push(source);
|
|
12
|
+
incoming.set(target, incomingSources);
|
|
13
|
+
}
|
|
14
|
+
return { incoming, outgoing };
|
|
15
|
+
}
|
|
16
|
+
function visit(start, adjacency, result) {
|
|
17
|
+
const queue = [start];
|
|
18
|
+
while (queue.length) {
|
|
19
|
+
const current = queue.shift();
|
|
20
|
+
for (const next of adjacency.get(current) ?? []) {
|
|
21
|
+
if (!result.has(next)) {
|
|
22
|
+
result.add(next);
|
|
23
|
+
queue.push(next);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function getRelatedNodeIds(data, selectedId) {
|
|
29
|
+
if (!data.nodes.some(({ id }) => id === selectedId)) {
|
|
30
|
+
return new Set();
|
|
31
|
+
}
|
|
32
|
+
const { incoming, outgoing } = createAdjacency(data);
|
|
33
|
+
const ancestorIds = new Set([selectedId]);
|
|
34
|
+
const descendantIds = new Set([selectedId]);
|
|
35
|
+
visit(selectedId, incoming, ancestorIds);
|
|
36
|
+
visit(selectedId, outgoing, descendantIds);
|
|
37
|
+
return new Set([...ancestorIds, ...descendantIds]);
|
|
38
|
+
}
|
|
39
|
+
export function getEmphasis(data, { selectedNodeId, searchQuery = '' } = {}) {
|
|
40
|
+
const normalizedQuery = normalizeSearchValue(searchQuery);
|
|
41
|
+
if (normalizedQuery) {
|
|
42
|
+
return {
|
|
43
|
+
mode: 'search',
|
|
44
|
+
activeNodeIds: new Set(data.nodes
|
|
45
|
+
.filter(({ label }) => normalizeSearchValue(label).includes(normalizedQuery))
|
|
46
|
+
.map(({ id }) => id)),
|
|
47
|
+
activeEdgeIds: new Set(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (!selectedNodeId) {
|
|
51
|
+
return { mode: 'none', activeNodeIds: new Set(), activeEdgeIds: new Set() };
|
|
52
|
+
}
|
|
53
|
+
const activeNodeIds = getRelatedNodeIds(data, selectedNodeId);
|
|
54
|
+
return {
|
|
55
|
+
mode: 'selection',
|
|
56
|
+
activeNodeIds,
|
|
57
|
+
activeEdgeIds: new Set(data.edges
|
|
58
|
+
.filter(({ source, target }) => activeNodeIds.has(source) && activeNodeIds.has(target))
|
|
59
|
+
.map(({ source, target }) => getEdgeKey(source, target))),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"../../../../src","sources":["demo/ComponentMap/graph.ts"],"names":[],"mappings":"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","sourcesContent":["import type {ComponentMapData, ComponentMapEmphasis} from './model';\n\nexport const getEdgeKey = (source: string, target: string) => `${source}\\0${target}`;\n\ninterface ComponentMapEmphasisRequest {\n selectedNodeId?: string;\n searchQuery?: string;\n}\n\nconst normalizeSearchValue = (value: string) => value.trim().toLowerCase();\n\nfunction createAdjacency(data: ComponentMapData) {\n const incoming = new Map<string, string[]>();\n const outgoing = new Map<string, string[]>();\n\n for (const {source, target} of data.edges) {\n const outgoingTargets = outgoing.get(source) ?? [];\n outgoingTargets.push(target);\n outgoing.set(source, outgoingTargets);\n\n const incomingSources = incoming.get(target) ?? [];\n incomingSources.push(source);\n incoming.set(target, incomingSources);\n }\n\n return {incoming, outgoing};\n}\n\nfunction visit(start: string, adjacency: Map<string, string[]>, result: Set<string>) {\n const queue = [start];\n while (queue.length) {\n const current = queue.shift() as string;\n for (const next of adjacency.get(current) ?? []) {\n if (!result.has(next)) {\n result.add(next);\n queue.push(next);\n }\n }\n }\n}\n\nexport function getRelatedNodeIds(data: ComponentMapData, selectedId: string): Set<string> {\n if (!data.nodes.some(({id}) => id === selectedId)) {\n return new Set();\n }\n\n const {incoming, outgoing} = createAdjacency(data);\n const ancestorIds = new Set([selectedId]);\n const descendantIds = new Set([selectedId]);\n\n visit(selectedId, incoming, ancestorIds);\n visit(selectedId, outgoing, descendantIds);\n\n return new Set([...ancestorIds, ...descendantIds]);\n}\n\nexport function getEmphasis(\n data: ComponentMapData,\n {selectedNodeId, searchQuery = ''}: ComponentMapEmphasisRequest = {},\n): ComponentMapEmphasis {\n const normalizedQuery = normalizeSearchValue(searchQuery);\n if (normalizedQuery) {\n return {\n mode: 'search',\n activeNodeIds: new Set(\n data.nodes\n .filter(({label}) => normalizeSearchValue(label).includes(normalizedQuery))\n .map(({id}) => id),\n ),\n activeEdgeIds: new Set(),\n };\n }\n\n if (!selectedNodeId) {\n return {mode: 'none', activeNodeIds: new Set(), activeEdgeIds: new Set()};\n }\n\n const activeNodeIds = getRelatedNodeIds(data, selectedNodeId);\n return {\n mode: 'selection',\n activeNodeIds,\n activeEdgeIds: new Set(\n data.edges\n .filter(\n ({source, target}) => activeNodeIds.has(source) && activeNodeIds.has(target),\n )\n .map(({source, target}) => getEdgeKey(source, target)),\n ),\n };\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ComponentMapData, ComponentMapLayout, LayoutNode, Point } from "./model.js";
|
|
2
|
+
export declare const NODE_MIN_WIDTH = 208;
|
|
3
|
+
export declare const NODE_HEIGHT = 48;
|
|
4
|
+
export declare const NODE_LABEL_FONT = "600 13px sans-serif";
|
|
5
|
+
export declare const NODE_LABEL_PADDING = 16;
|
|
6
|
+
export declare const VERTICAL_GAP = 32;
|
|
7
|
+
export declare const GROUP_GAP = 112;
|
|
8
|
+
export declare const BAND_HEADER_HEIGHT = 36;
|
|
9
|
+
export declare const WORLD_PADDING = 56;
|
|
10
|
+
export declare const KIND_ORDER: readonly ["block", "sub-block", "component"];
|
|
11
|
+
export declare function layoutComponentMap(data: ComponentMapData): ComponentMapLayout;
|
|
12
|
+
export declare function hitTest(layoutOrNodes: ComponentMapLayout | LayoutNode[], point: Point): LayoutNode | undefined;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { ARROW_SIZE, getSameGroupArcOutset } from "./edgeGeometry.js";
|
|
2
|
+
export const NODE_MIN_WIDTH = 208;
|
|
3
|
+
export const NODE_HEIGHT = 48;
|
|
4
|
+
export const NODE_LABEL_FONT = '600 13px sans-serif';
|
|
5
|
+
export const NODE_LABEL_PADDING = 16;
|
|
6
|
+
export const VERTICAL_GAP = 32;
|
|
7
|
+
export const GROUP_GAP = 112;
|
|
8
|
+
export const BAND_HEADER_HEIGHT = 36;
|
|
9
|
+
export const WORLD_PADDING = 56;
|
|
10
|
+
export const KIND_ORDER = ['block', 'sub-block', 'component'];
|
|
11
|
+
const BAND_LABELS = {
|
|
12
|
+
block: 'Blocks',
|
|
13
|
+
'sub-block': 'Sub-blocks',
|
|
14
|
+
component: 'Components',
|
|
15
|
+
};
|
|
16
|
+
function sortByLabel(nodes) {
|
|
17
|
+
return [...nodes].sort((left, right) => left.label.localeCompare(right.label));
|
|
18
|
+
}
|
|
19
|
+
function createLabelMeasurer() {
|
|
20
|
+
if (typeof CanvasRenderingContext2D !== 'undefined' && typeof document !== 'undefined') {
|
|
21
|
+
try {
|
|
22
|
+
const context = document.createElement('canvas').getContext('2d');
|
|
23
|
+
if (context) {
|
|
24
|
+
context.font = NODE_LABEL_FONT;
|
|
25
|
+
return (label) => context.measureText(label).width;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Use deterministic metrics when Canvas is unavailable (for example during SSR).
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return (label) => Array.from(label).length * 7;
|
|
33
|
+
}
|
|
34
|
+
const getNodesHeight = (count) => count === 0 ? 0 : count * NODE_HEIGHT + (count - 1) * VERTICAL_GAP;
|
|
35
|
+
function measureGroup(kind, nodes, measureLabel) {
|
|
36
|
+
const sortedNodes = sortByLabel(nodes);
|
|
37
|
+
const nodeWidth = Math.max(NODE_MIN_WIDTH, ...sortedNodes.map(({ label }) => measureLabel(label) + NODE_LABEL_PADDING * 2));
|
|
38
|
+
return {
|
|
39
|
+
kind,
|
|
40
|
+
label: BAND_LABELS[kind],
|
|
41
|
+
nodes: sortedNodes,
|
|
42
|
+
nodeWidth,
|
|
43
|
+
visibleHeight: BAND_HEADER_HEIGHT + getNodesHeight(sortedNodes.length),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function createBand(group, x, y) {
|
|
47
|
+
return {
|
|
48
|
+
kind: group.kind,
|
|
49
|
+
label: group.label,
|
|
50
|
+
bounds: {
|
|
51
|
+
x,
|
|
52
|
+
y,
|
|
53
|
+
width: group.nodeWidth,
|
|
54
|
+
height: group.visibleHeight,
|
|
55
|
+
},
|
|
56
|
+
nodes: group.nodes.map((node, index) => ({
|
|
57
|
+
...node,
|
|
58
|
+
rect: {
|
|
59
|
+
x,
|
|
60
|
+
y: y + BAND_HEADER_HEIGHT + index * (NODE_HEIGHT + VERTICAL_GAP),
|
|
61
|
+
width: group.nodeWidth,
|
|
62
|
+
height: NODE_HEIGHT,
|
|
63
|
+
},
|
|
64
|
+
})),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function resolveEdges(edges, nodeById) {
|
|
68
|
+
return edges.map(({ source, target }) => {
|
|
69
|
+
const sourceNode = nodeById.get(source);
|
|
70
|
+
const targetNode = nodeById.get(target);
|
|
71
|
+
if (!sourceNode || !targetNode) {
|
|
72
|
+
throw new Error(`Component map edge references unknown node: ${source} -> ${target}`);
|
|
73
|
+
}
|
|
74
|
+
return { source: sourceNode, target: targetNode };
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export function layoutComponentMap(data) {
|
|
78
|
+
const nodesByKind = new Map(KIND_ORDER.map((kind) => [kind, []]));
|
|
79
|
+
for (const node of data.nodes) {
|
|
80
|
+
nodesByKind.get(node.kind)?.push(node);
|
|
81
|
+
}
|
|
82
|
+
const measureLabel = createLabelMeasurer();
|
|
83
|
+
const measuredGroups = KIND_ORDER.map((kind) => measureGroup(kind, nodesByKind.get(kind) ?? [], measureLabel));
|
|
84
|
+
const maximumGroupHeight = Math.max(...measuredGroups.map(({ visibleHeight }) => visibleHeight));
|
|
85
|
+
const commonCenterY = WORLD_PADDING + maximumGroupHeight / 2;
|
|
86
|
+
const provisionalBands = measuredGroups.map((group) => createBand(group, 0, commonCenterY - group.visibleHeight / 2));
|
|
87
|
+
const provisionalNodes = provisionalBands.flatMap(({ nodes }) => nodes);
|
|
88
|
+
const provisionalNodeById = new Map(provisionalNodes.map((node) => [node.id, node]));
|
|
89
|
+
const provisionalEdges = resolveEdges(data.edges, provisionalNodeById);
|
|
90
|
+
const corridorByKind = new Map(KIND_ORDER.map((kind) => [kind, 0]));
|
|
91
|
+
for (const edge of provisionalEdges) {
|
|
92
|
+
if (edge.source.kind === edge.target.kind) {
|
|
93
|
+
corridorByKind.set(edge.source.kind, Math.max(corridorByKind.get(edge.source.kind) ?? 0, getSameGroupArcOutset(edge.source, edge.target) + ARROW_SIZE));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
let nextGroupX = WORLD_PADDING;
|
|
97
|
+
const bands = provisionalBands.map((band, index) => {
|
|
98
|
+
const x = nextGroupX;
|
|
99
|
+
const placedBand = {
|
|
100
|
+
...band,
|
|
101
|
+
bounds: { ...band.bounds, x },
|
|
102
|
+
nodes: band.nodes.map((node) => ({
|
|
103
|
+
...node,
|
|
104
|
+
rect: { ...node.rect, x },
|
|
105
|
+
})),
|
|
106
|
+
};
|
|
107
|
+
nextGroupX =
|
|
108
|
+
x +
|
|
109
|
+
band.bounds.width +
|
|
110
|
+
(corridorByKind.get(band.kind) ?? 0) +
|
|
111
|
+
(index === provisionalBands.length - 1 ? 0 : GROUP_GAP);
|
|
112
|
+
return placedBand;
|
|
113
|
+
});
|
|
114
|
+
const nodes = bands.flatMap((band) => band.nodes);
|
|
115
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
116
|
+
const edges = resolveEdges(data.edges, nodeById);
|
|
117
|
+
const rightmostBand = bands[bands.length - 1];
|
|
118
|
+
const rightmost = rightmostBand.bounds.x +
|
|
119
|
+
rightmostBand.bounds.width +
|
|
120
|
+
(corridorByKind.get(rightmostBand.kind) ?? 0);
|
|
121
|
+
return {
|
|
122
|
+
bands,
|
|
123
|
+
nodes,
|
|
124
|
+
edges,
|
|
125
|
+
bounds: {
|
|
126
|
+
x: 0,
|
|
127
|
+
y: 0,
|
|
128
|
+
width: rightmost + WORLD_PADDING,
|
|
129
|
+
height: maximumGroupHeight + WORLD_PADDING * 2,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export function hitTest(layoutOrNodes, point) {
|
|
134
|
+
const nodes = Array.isArray(layoutOrNodes) ? layoutOrNodes : layoutOrNodes.nodes;
|
|
135
|
+
for (let index = nodes.length - 1; index >= 0; index -= 1) {
|
|
136
|
+
const node = nodes[index];
|
|
137
|
+
const { x, y, width, height } = node.rect;
|
|
138
|
+
if (point.x >= x && point.x <= x + width && point.y >= y && point.y <= y + height) {
|
|
139
|
+
return node;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"../../../../src","sources":["demo/ComponentMap/layout.ts"],"names":[],"mappings":"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","sourcesContent":["import {ARROW_SIZE, getSameGroupArcOutset} from './edgeGeometry';\nimport type {\n ComponentMapData,\n ComponentMapEdge,\n ComponentMapLayout,\n ComponentMapNode,\n ComponentMapNodeKind,\n LayoutBand,\n LayoutEdge,\n LayoutNode,\n Point,\n} from './model';\n\nexport const NODE_MIN_WIDTH = 208;\nexport const NODE_HEIGHT = 48;\nexport const NODE_LABEL_FONT = '600 13px sans-serif';\nexport const NODE_LABEL_PADDING = 16;\nexport const VERTICAL_GAP = 32;\nexport const GROUP_GAP = 112;\nexport const BAND_HEADER_HEIGHT = 36;\nexport const WORLD_PADDING = 56;\nexport const KIND_ORDER = ['block', 'sub-block', 'component'] as const;\n\nconst BAND_LABELS: Record<ComponentMapNodeKind, string> = {\n block: 'Blocks',\n 'sub-block': 'Sub-blocks',\n component: 'Components',\n};\n\nfunction sortByLabel(nodes: ComponentMapNode[]) {\n return [...nodes].sort((left, right) => left.label.localeCompare(right.label));\n}\n\nfunction createLabelMeasurer() {\n if (typeof CanvasRenderingContext2D !== 'undefined' && typeof document !== 'undefined') {\n try {\n const context = document.createElement('canvas').getContext('2d');\n if (context) {\n context.font = NODE_LABEL_FONT;\n return (label: string) => context.measureText(label).width;\n }\n } catch {\n // Use deterministic metrics when Canvas is unavailable (for example during SSR).\n }\n }\n\n return (label: string) => Array.from(label).length * 7;\n}\n\ninterface MeasuredGroup {\n kind: ComponentMapNodeKind;\n label: string;\n nodes: ComponentMapNode[];\n nodeWidth: number;\n visibleHeight: number;\n}\n\nconst getNodesHeight = (count: number) =>\n count === 0 ? 0 : count * NODE_HEIGHT + (count - 1) * VERTICAL_GAP;\n\nfunction measureGroup(\n kind: ComponentMapNodeKind,\n nodes: ComponentMapNode[],\n measureLabel: (label: string) => number,\n): MeasuredGroup {\n const sortedNodes = sortByLabel(nodes);\n const nodeWidth = Math.max(\n NODE_MIN_WIDTH,\n ...sortedNodes.map(({label}) => measureLabel(label) + NODE_LABEL_PADDING * 2),\n );\n return {\n kind,\n label: BAND_LABELS[kind],\n nodes: sortedNodes,\n nodeWidth,\n visibleHeight: BAND_HEADER_HEIGHT + getNodesHeight(sortedNodes.length),\n };\n}\n\nfunction createBand(group: MeasuredGroup, x: number, y: number): LayoutBand {\n return {\n kind: group.kind,\n label: group.label,\n bounds: {\n x,\n y,\n width: group.nodeWidth,\n height: group.visibleHeight,\n },\n nodes: group.nodes.map((node, index) => ({\n ...node,\n rect: {\n x,\n y: y + BAND_HEADER_HEIGHT + index * (NODE_HEIGHT + VERTICAL_GAP),\n width: group.nodeWidth,\n height: NODE_HEIGHT,\n },\n })),\n };\n}\n\nfunction resolveEdges(\n edges: ComponentMapEdge[],\n nodeById: ReadonlyMap<string, LayoutNode>,\n): LayoutEdge[] {\n return edges.map(({source, target}) => {\n const sourceNode = nodeById.get(source);\n const targetNode = nodeById.get(target);\n if (!sourceNode || !targetNode) {\n throw new Error(`Component map edge references unknown node: ${source} -> ${target}`);\n }\n return {source: sourceNode, target: targetNode};\n });\n}\n\nexport function layoutComponentMap(data: ComponentMapData): ComponentMapLayout {\n const nodesByKind = new Map<ComponentMapNodeKind, ComponentMapNode[]>(\n KIND_ORDER.map((kind) => [kind, []]),\n );\n for (const node of data.nodes) {\n nodesByKind.get(node.kind)?.push(node);\n }\n\n const measureLabel = createLabelMeasurer();\n const measuredGroups = KIND_ORDER.map((kind) =>\n measureGroup(kind, nodesByKind.get(kind) ?? [], measureLabel),\n );\n const maximumGroupHeight = Math.max(...measuredGroups.map(({visibleHeight}) => visibleHeight));\n const commonCenterY = WORLD_PADDING + maximumGroupHeight / 2;\n const provisionalBands = measuredGroups.map((group) =>\n createBand(group, 0, commonCenterY - group.visibleHeight / 2),\n );\n const provisionalNodes = provisionalBands.flatMap(({nodes}) => nodes);\n const provisionalNodeById = new Map(provisionalNodes.map((node) => [node.id, node]));\n const provisionalEdges = resolveEdges(data.edges, provisionalNodeById);\n const corridorByKind = new Map<ComponentMapNodeKind, number>(\n KIND_ORDER.map((kind) => [kind, 0]),\n );\n for (const edge of provisionalEdges) {\n if (edge.source.kind === edge.target.kind) {\n corridorByKind.set(\n edge.source.kind,\n Math.max(\n corridorByKind.get(edge.source.kind) ?? 0,\n getSameGroupArcOutset(edge.source, edge.target) + ARROW_SIZE,\n ),\n );\n }\n }\n\n let nextGroupX = WORLD_PADDING;\n const bands = provisionalBands.map((band, index) => {\n const x = nextGroupX;\n const placedBand: LayoutBand = {\n ...band,\n bounds: {...band.bounds, x},\n nodes: band.nodes.map((node) => ({\n ...node,\n rect: {...node.rect, x},\n })),\n };\n nextGroupX =\n x +\n band.bounds.width +\n (corridorByKind.get(band.kind) ?? 0) +\n (index === provisionalBands.length - 1 ? 0 : GROUP_GAP);\n return placedBand;\n });\n const nodes = bands.flatMap((band) => band.nodes);\n const nodeById = new Map(nodes.map((node) => [node.id, node]));\n const edges = resolveEdges(data.edges, nodeById);\n const rightmostBand = bands[bands.length - 1];\n const rightmost =\n rightmostBand.bounds.x +\n rightmostBand.bounds.width +\n (corridorByKind.get(rightmostBand.kind) ?? 0);\n\n return {\n bands,\n nodes,\n edges,\n bounds: {\n x: 0,\n y: 0,\n width: rightmost + WORLD_PADDING,\n height: maximumGroupHeight + WORLD_PADDING * 2,\n },\n };\n}\n\nexport function hitTest(\n layoutOrNodes: ComponentMapLayout | LayoutNode[],\n point: Point,\n): LayoutNode | undefined {\n const nodes = Array.isArray(layoutOrNodes) ? layoutOrNodes : layoutOrNodes.nodes;\n\n for (let index = nodes.length - 1; index >= 0; index -= 1) {\n const node = nodes[index];\n const {x, y, width, height} = node.rect;\n if (point.x >= x && point.x <= x + width && point.y >= y && point.y <= y + height) {\n return node;\n }\n }\n\n return undefined;\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export type ComponentMapNodeKind = 'block' | 'sub-block' | 'component';
|
|
2
|
+
export interface ComponentMapNode {
|
|
3
|
+
id: string;
|
|
4
|
+
kind: ComponentMapNodeKind;
|
|
5
|
+
exportName: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ComponentMapEdge {
|
|
9
|
+
source: string;
|
|
10
|
+
target: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ComponentMapData {
|
|
13
|
+
nodes: ComponentMapNode[];
|
|
14
|
+
edges: ComponentMapEdge[];
|
|
15
|
+
}
|
|
16
|
+
export interface Point {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
}
|
|
20
|
+
export interface Rect extends Point {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
}
|
|
24
|
+
export interface LayoutNode extends ComponentMapNode {
|
|
25
|
+
rect: Rect;
|
|
26
|
+
}
|
|
27
|
+
export interface LayoutBand {
|
|
28
|
+
kind: ComponentMapNodeKind;
|
|
29
|
+
label: string;
|
|
30
|
+
bounds: Rect;
|
|
31
|
+
nodes: LayoutNode[];
|
|
32
|
+
}
|
|
33
|
+
export interface LayoutEdge {
|
|
34
|
+
source: LayoutNode;
|
|
35
|
+
target: LayoutNode;
|
|
36
|
+
}
|
|
37
|
+
export interface ComponentMapLayout {
|
|
38
|
+
nodes: LayoutNode[];
|
|
39
|
+
bands: LayoutBand[];
|
|
40
|
+
edges: LayoutEdge[];
|
|
41
|
+
bounds: Rect;
|
|
42
|
+
}
|
|
43
|
+
export interface ViewportSize {
|
|
44
|
+
width: number;
|
|
45
|
+
height: number;
|
|
46
|
+
}
|
|
47
|
+
export interface ViewportTransform extends Point {
|
|
48
|
+
scale: number;
|
|
49
|
+
}
|
|
50
|
+
export type ComponentMapEmphasisMode = 'none' | 'selection' | 'search';
|
|
51
|
+
export interface ComponentMapEmphasis {
|
|
52
|
+
mode: ComponentMapEmphasisMode;
|
|
53
|
+
activeNodeIds: Set<string>;
|
|
54
|
+
activeEdgeIds: Set<string>;
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"../../../../src","sources":["demo/ComponentMap/model.ts"],"names":[],"mappings":"","sourcesContent":["export type ComponentMapNodeKind = 'block' | 'sub-block' | 'component';\n\nexport interface ComponentMapNode {\n id: string;\n kind: ComponentMapNodeKind;\n exportName: string;\n label: string;\n}\n\nexport interface ComponentMapEdge {\n source: string;\n target: string;\n}\n\nexport interface ComponentMapData {\n nodes: ComponentMapNode[];\n edges: ComponentMapEdge[];\n}\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface Rect extends Point {\n width: number;\n height: number;\n}\n\nexport interface LayoutNode extends ComponentMapNode {\n rect: Rect;\n}\n\nexport interface LayoutBand {\n kind: ComponentMapNodeKind;\n label: string;\n bounds: Rect;\n nodes: LayoutNode[];\n}\n\nexport interface LayoutEdge {\n source: LayoutNode;\n target: LayoutNode;\n}\n\nexport interface ComponentMapLayout {\n nodes: LayoutNode[];\n bands: LayoutBand[];\n edges: LayoutEdge[];\n bounds: Rect;\n}\n\nexport interface ViewportSize {\n width: number;\n height: number;\n}\n\nexport interface ViewportTransform extends Point {\n scale: number;\n}\n\nexport type ComponentMapEmphasisMode = 'none' | 'selection' | 'search';\n\nexport interface ComponentMapEmphasis {\n mode: ComponentMapEmphasisMode;\n activeNodeIds: Set<string>;\n activeEdgeIds: Set<string>;\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Point, Rect, ViewportSize, ViewportTransform } from "./model.js";
|
|
2
|
+
export declare const VIEWPORT_PADDING = 24;
|
|
3
|
+
export declare const MINIMUM_VIEWPORT_SCALE = 0.01;
|
|
4
|
+
export declare const worldToScreen: (point: Point, transform: ViewportTransform) => Point;
|
|
5
|
+
export declare const screenToWorld: (point: Point, transform: ViewportTransform) => Point;
|
|
6
|
+
export declare function fitToViewport(bounds: Rect, viewport: ViewportSize, padding?: number): ViewportTransform;
|
|
7
|
+
export declare function zoomAtPoint(transform: ViewportTransform, factor: number, point: Point, minScale: number, maxScale: number): ViewportTransform;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const VIEWPORT_PADDING = 24;
|
|
2
|
+
export const MINIMUM_VIEWPORT_SCALE = 0.01;
|
|
3
|
+
export const worldToScreen = (point, transform) => ({
|
|
4
|
+
x: point.x * transform.scale + transform.x,
|
|
5
|
+
y: point.y * transform.scale + transform.y,
|
|
6
|
+
});
|
|
7
|
+
export const screenToWorld = (point, transform) => ({
|
|
8
|
+
x: (point.x - transform.x) / transform.scale,
|
|
9
|
+
y: (point.y - transform.y) / transform.scale,
|
|
10
|
+
});
|
|
11
|
+
export function fitToViewport(bounds, viewport, padding = VIEWPORT_PADDING) {
|
|
12
|
+
const availableWidth = Math.max(0, viewport.width - padding * 2);
|
|
13
|
+
const availableHeight = Math.max(0, viewport.height - padding * 2);
|
|
14
|
+
const widthScale = bounds.width > 0 ? availableWidth / bounds.width : 1;
|
|
15
|
+
const heightScale = bounds.height > 0 ? availableHeight / bounds.height : 1;
|
|
16
|
+
// React postpones fitting until it has a positive canvas size. Keep this pure fallback
|
|
17
|
+
// invertible as well for callers that receive an empty or padding-sized viewport.
|
|
18
|
+
const scale = Math.max(MINIMUM_VIEWPORT_SCALE, Math.min(1, widthScale, heightScale));
|
|
19
|
+
return {
|
|
20
|
+
scale,
|
|
21
|
+
x: viewport.width / 2 - (bounds.x + bounds.width / 2) * scale,
|
|
22
|
+
y: viewport.height / 2 - (bounds.y + bounds.height / 2) * scale,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function zoomAtPoint(transform, factor, point, minScale, maxScale) {
|
|
26
|
+
const world = screenToWorld(point, transform);
|
|
27
|
+
const scale = Math.min(maxScale, Math.max(minScale, transform.scale * factor));
|
|
28
|
+
return {
|
|
29
|
+
scale,
|
|
30
|
+
x: point.x - world.x * scale,
|
|
31
|
+
y: point.y - world.y * scale,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=viewport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewport.js","sourceRoot":"../../../../src","sources":["demo/ComponentMap/viewport.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AAE3C,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAY,EAAE,SAA4B,EAAS,EAAE,CAAC,CAAC;IACjF,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAC1C,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;CAC7C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAY,EAAE,SAA4B,EAAS,EAAE,CAAC,CAAC;IACjF,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK;IAC5C,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK;CAC/C,CAAC,CAAC;AAEH,MAAM,UAAU,aAAa,CACzB,MAAY,EACZ,QAAsB,EACtB,OAAO,GAAG,gBAAgB;IAE1B,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC;IACjE,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC;IACnE,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,uFAAuF;IACvF,kFAAkF;IAClF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,sBAAsB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;IAErF,OAAO;QACH,KAAK;QACL,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,KAAK;QAC7D,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,KAAK;KAClE,CAAC;AACN,CAAC;AAED,MAAM,UAAU,WAAW,CACvB,SAA4B,EAC5B,MAAc,EACd,KAAY,EACZ,QAAgB,EAChB,QAAgB;IAEhB,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC;IAE/E,OAAO;QACH,KAAK;QACL,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK;QAC5B,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK;KAC/B,CAAC;AACN,CAAC","sourcesContent":["import type {Point, Rect, ViewportSize, ViewportTransform} from './model';\n\nexport const VIEWPORT_PADDING = 24;\nexport const MINIMUM_VIEWPORT_SCALE = 0.01;\n\nexport const worldToScreen = (point: Point, transform: ViewportTransform): Point => ({\n x: point.x * transform.scale + transform.x,\n y: point.y * transform.scale + transform.y,\n});\n\nexport const screenToWorld = (point: Point, transform: ViewportTransform): Point => ({\n x: (point.x - transform.x) / transform.scale,\n y: (point.y - transform.y) / transform.scale,\n});\n\nexport function fitToViewport(\n bounds: Rect,\n viewport: ViewportSize,\n padding = VIEWPORT_PADDING,\n): ViewportTransform {\n const availableWidth = Math.max(0, viewport.width - padding * 2);\n const availableHeight = Math.max(0, viewport.height - padding * 2);\n const widthScale = bounds.width > 0 ? availableWidth / bounds.width : 1;\n const heightScale = bounds.height > 0 ? availableHeight / bounds.height : 1;\n // React postpones fitting until it has a positive canvas size. Keep this pure fallback\n // invertible as well for callers that receive an empty or padding-sized viewport.\n const scale = Math.max(MINIMUM_VIEWPORT_SCALE, Math.min(1, widthScale, heightScale));\n\n return {\n scale,\n x: viewport.width / 2 - (bounds.x + bounds.width / 2) * scale,\n y: viewport.height / 2 - (bounds.y + bounds.height / 2) * scale,\n };\n}\n\nexport function zoomAtPoint(\n transform: ViewportTransform,\n factor: number,\n point: Point,\n minScale: number,\n maxScale: number,\n): ViewportTransform {\n const world = screenToWorld(point, transform);\n const scale = Math.min(maxScale, Math.max(minScale, transform.scale * factor));\n\n return {\n scale,\n x: point.x - world.x * scale,\n y: point.y - world.y * scale,\n };\n}\n"]}
|
|
@@ -3,26 +3,15 @@ import { blockTransform } from "../../.storybook/utils.js";
|
|
|
3
3
|
import { PageConstructor } from "../containers/PageConstructor/index.js";
|
|
4
4
|
import bannerData from "../blocks/Banner/__stories__/data.json";
|
|
5
5
|
import companiesData from "../blocks/Companies/__stories__/data.json";
|
|
6
|
-
import headerData from "../blocks/Header/__stories__/data.json";
|
|
7
6
|
import infoData from "../blocks/Info/__stories__/data.json";
|
|
8
7
|
import mediaData from "../blocks/Media/__stories__/data.json";
|
|
9
8
|
import navData from "../navigation/__stories__/data.json";
|
|
10
9
|
export default {
|
|
11
|
-
title: 'Lab/Tokenization/Blocks/
|
|
10
|
+
title: 'Lab/Tokenization/Blocks/MediaAndPromo',
|
|
12
11
|
component: PageConstructor,
|
|
13
12
|
};
|
|
14
13
|
const Template = ({ navigation, custom = {}, }) => (_jsx(PageConstructor, { navigation: navigation, custom: custom, content: {
|
|
15
14
|
blocks: [
|
|
16
|
-
// header-block: default with action buttons
|
|
17
|
-
blockTransform(headerData.default),
|
|
18
|
-
// header-block: with breadcrumbs + light background
|
|
19
|
-
blockTransform(headerData.breadcrumbs[0]),
|
|
20
|
-
// header-block: with breadcrumbs + dark theme
|
|
21
|
-
blockTransform(headerData.breadcrumbs[1]),
|
|
22
|
-
// header-block: with image
|
|
23
|
-
blockTransform(headerData.image),
|
|
24
|
-
// header-block: with background image and color (media variant)
|
|
25
|
-
blockTransform({ type: 'header-block', ...headerData.media.image }),
|
|
26
15
|
// banner-block: light theme with themed image
|
|
27
16
|
blockTransform(bannerData.default.content),
|
|
28
17
|
// banner-block: forced dark theme
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HeroAndMedia.stories.js","sourceRoot":"../../../src","sources":["demo/HeroAndMedia.stories.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAC,cAAc,EAAC,kCAA+B;AACtD,OAAO,EAAC,eAAe,EAAC,+CAAsC;AAG9D,OAAO,UAAU,+CAA+C;AAChE,OAAO,aAAa,kDAAkD;AACtE,OAAO,
|
|
1
|
+
{"version":3,"file":"HeroAndMedia.stories.js","sourceRoot":"../../../src","sources":["demo/HeroAndMedia.stories.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAC,cAAc,EAAC,kCAA+B;AACtD,OAAO,EAAC,eAAe,EAAC,+CAAsC;AAG9D,OAAO,UAAU,+CAA+C;AAChE,OAAO,aAAa,kDAAkD;AACtE,OAAO,QAAQ,6CAA6C;AAC5D,OAAO,SAAS,8CAA8C;AAC9D,OAAO,OAAO,4CAA4C;AAE1D,eAAe;IACX,KAAK,EAAE,uCAAuC;IAC9C,SAAS,EAAE,eAAe;CACrB,CAAC;AAEV,MAAM,QAAQ,GAAiE,CAAC,EAC5E,UAAU,EACV,MAAM,GAAG,EAAE,GACd,EAAE,EAAE,CAAC,CACF,KAAC,eAAe,IACZ,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,EACd,OAAO,EACH;QACI,MAAM,EAAE;YACJ,8CAA8C;YAC9C,cAAc,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC;YAC1C,kCAAkC;YAClC,cAAc,CAAC,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC;YAE5C,kCAAkC;YAClC,cAAc,CAAC,SAAS,CAAC,OAAO,CAAC;YACjC,4BAA4B;YAC5B,cAAc,CAAC,SAAS,CAAC,WAAW,CAAC;YACrC,6BAA6B;YAC7B,cAAc,CAAC,EAAC,IAAI,EAAE,aAAa,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,OAAO,EAAC,CAAC;YACjE,mCAAmC;YACnC,cAAc,CAAC,EAAC,IAAI,EAAE,aAAa,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,kBAAkB,EAAC,CAAC;YAC5E,kCAAkC;YAClC,cAAc,CAAC,EAAC,IAAI,EAAE,aAAa,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,gBAAgB,EAAC,CAAC;YAE1E,8BAA8B;YAC9B,cAAc,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC;YAC7C,oCAAoC;YACpC,cAAc,CAAC,aAAa,CAAC,eAAe,CAAC,OAAO,CAAC;YAErD,mCAAmC;YACnC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC;YAChC,gDAAgD;YAChD,cAAc,CAAC,EAAC,IAAI,EAAE,YAAY,EAAE,GAAG,QAAQ,CAAC,KAAK,EAAC,CAAC;SAC1D;KACW,GAEtB,CACL,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACzC,OAAO,CAAC,IAAI,GAAG;IACX,UAAU,EAAE,OAAO,CAAC,UAA4B;CACnD,CAAC","sourcesContent":["import {Meta, StoryFn} from '@storybook/react';\n\nimport {blockTransform} from '../../.storybook/utils';\nimport {PageConstructor} from '../containers/PageConstructor';\nimport {CustomConfig, NavigationData, PageContent} from '../models';\n\nimport bannerData from '../blocks/Banner/__stories__/data.json';\nimport companiesData from '../blocks/Companies/__stories__/data.json';\nimport infoData from '../blocks/Info/__stories__/data.json';\nimport mediaData from '../blocks/Media/__stories__/data.json';\nimport navData from '../navigation/__stories__/data.json';\n\nexport default {\n title: 'Lab/Tokenization/Blocks/MediaAndPromo',\n component: PageConstructor,\n} as Meta;\n\nconst Template: StoryFn<{navigation: NavigationData; custom?: CustomConfig}> = ({\n navigation,\n custom = {},\n}) => (\n <PageConstructor\n navigation={navigation}\n custom={custom}\n content={\n {\n blocks: [\n // banner-block: light theme with themed image\n blockTransform(bannerData.default.content),\n // banner-block: forced dark theme\n blockTransform(bannerData.darkTheme.content),\n\n // media-block: default with image\n blockTransform(mediaData.default),\n // media-block: image slider\n blockTransform(mediaData.imageSlider),\n // media-block: youtube embed\n blockTransform({type: 'media-block', ...mediaData.video.youtube}),\n // media-block: video with controls\n blockTransform({type: 'media-block', ...mediaData.video.staticWithControls}),\n // media-block: video with preview\n blockTransform({type: 'media-block', ...mediaData.video.videoWithPreview}),\n\n // companies-block: title only\n blockTransform(companiesData.default.content),\n // companies-block: with description\n blockTransform(companiesData.withDescription.content),\n\n // info-block: dark theme (default)\n blockTransform(infoData.default),\n // info-block: light theme with background color\n blockTransform({type: 'info-block', ...infoData.light}),\n ],\n } as PageContent\n }\n />\n);\n\nexport const Default = Template.bind({});\nDefault.args = {\n navigation: navData.navigation as NavigationData,\n};\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Meta } from '@storybook/react';
|
|
2
|
+
import { HeaderBlockModel, NavigationData } from "../models/index.js";
|
|
3
|
+
interface StoryArgs {
|
|
4
|
+
headerBlock: HeaderBlockModel;
|
|
5
|
+
navigation: NavigationData;
|
|
6
|
+
}
|
|
7
|
+
declare const _default: Meta;
|
|
8
|
+
export default _default;
|
|
9
|
+
export declare const Default: import("@storybook/core/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, StoryArgs>;
|
|
10
|
+
export declare const BreadcrumbsLight: import("@storybook/core/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, StoryArgs>;
|
|
11
|
+
export declare const BreadcrumbsDark: import("@storybook/core/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, StoryArgs>;
|
|
12
|
+
export declare const Image: import("@storybook/core/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, StoryArgs>;
|
|
13
|
+
export declare const BackgroundMedia: import("@storybook/core/csf").AnnotatedStoryFn<import("@storybook/react").ReactRenderer, StoryArgs>;
|