返回 presentation-ai
snake-static.tsx
1 "use client";
2
3 import { SlateElement, type SlateElementProps } from "platejs/static";
4
5 import { cn } from "@/lib/utils";
6 import { getDiagramFitFrameStyle, useDiagramFitScale } from "../diagram-fit";
7 import { getSmartLayoutStepColor } from "../smart-layout-gradient";
8 import {
9 buildSnakeArrowPath,
10 getSnakeArrowBaselineY,
11 getSnakeArrowEndX,
12 getSnakeArrowStartX,
13 getSvgWidth,
14 SNAKE_COL_WIDTH,
15 SNAKE_EDGE_PADDING_X,
16 SNAKE_END_ARROW_LENGTH,
17 SNAKE_GRID_ROWS,
18 SNAKE_LAYOUT_HEIGHT_PX,
19 SNAKE_START_DOT_GAP,
20 SNAKE_START_DOT_RADIUS,
21 SNAKE_SVG_HEIGHT,
22 } from "../snake-shared";
23 import {
24 getStaticDiagramJustifyClass,
25 type StaticDiagramElement,
26 } from "./diagram-static-utils";
27
28 export default function SnakeStatic(props: SlateElementProps) {
29 const element = props.element as StaticDiagramElement;
30 const total = element.children?.length || 1;
31 const svgWidth = getSvgWidth(total);
32 const layoutWidth = svgWidth + SNAKE_EDGE_PADDING_X * 2;
33 const startBaselineY = getSnakeArrowBaselineY(0);
34 const endBaselineY = getSnakeArrowBaselineY(total - 1);
35 const { containerRef, fitStyle, frameStyle, layoutRef } =
36 useDiagramFitScale<HTMLDivElement>(layoutWidth, SNAKE_LAYOUT_HEIGHT_PX);
37
38 return (
39 <SlateElement {...props} className="my-4">
40 <div
41 ref={containerRef}
42 className={cn(
43 "w-full overflow-visible",
44 getStaticDiagramJustifyClass(element.alignment),
45 )}
46 >
47 <div
48 style={{
49 ...frameStyle,
50 ...getDiagramFitFrameStyle(element.alignment),
51 }}
52 >
53 <div
54 ref={layoutRef}
55 className="relative overflow-visible"
56 style={{
57 ...fitStyle,
58 height: SNAKE_LAYOUT_HEIGHT_PX,
59 }}
60 >
61 {/* SVG decorations: alternating arrow lanes */}
62 <svg
63 className="pointer-events-none absolute inset-0 h-full w-full overflow-visible"
64 viewBox={`${-SNAKE_EDGE_PADDING_X} 0 ${layoutWidth} ${SNAKE_SVG_HEIGHT}`}
65 preserveAspectRatio="none"
66 aria-hidden="true"
67 data-decor="true"
68 >
69 <defs>
70 <marker
71 id="presentation-snake-arrow-static"
72 markerHeight="10"
73 markerWidth="10"
74 orient="auto"
75 refX="8"
76 refY="5"
77 viewBox="0 0 10 10"
78 >
79 <path
80 d="M 1 1 L 9 5 L 1 9"
81 fill="none"
82 stroke="context-stroke"
83 strokeLinecap="round"
84 strokeLinejoin="round"
85 strokeWidth="1.4"
86 />
87 </marker>
88 </defs>
89
90 {/* Start dot */}
91 <circle
92 cx={
93 getSnakeArrowStartX(0) -
94 SNAKE_START_DOT_GAP -
95 SNAKE_START_DOT_RADIUS
96 }
97 cy={startBaselineY}
98 r={SNAKE_START_DOT_RADIUS}
99 fill={getSmartLayoutStepColor(0, total)}
100 />
101 <line
102 x1={
103 getSnakeArrowStartX(0) -
104 SNAKE_START_DOT_GAP -
105 SNAKE_START_DOT_RADIUS
106 }
107 y1={startBaselineY}
108 x2={getSnakeArrowStartX(0)}
109 y2={startBaselineY}
110 stroke={getSmartLayoutStepColor(0, total)}
111 strokeLinecap="round"
112 strokeWidth="3"
113 />
114
115 {Array.from({ length: total }).map((_, index) => {
116 const isLast = index === total - 1;
117
118 return (
119 <path
120 key={`snake-arrow-${index}`}
121 d={buildSnakeArrowPath(index)}
122 fill="none"
123 markerEnd={
124 isLast
125 ? undefined
126 : "url(#presentation-snake-arrow-static)"
127 }
128 stroke={getSmartLayoutStepColor(index, total)}
129 strokeLinecap="round"
130 strokeWidth="3"
131 />
132 );
133 })}
134
135 {/* End arrow */}
136 {total > 0 && (
137 <line
138 x1={getSnakeArrowEndX(total - 1)}
139 y1={endBaselineY}
140 x2={getSnakeArrowEndX(total - 1) + SNAKE_END_ARROW_LENGTH}
141 y2={endBaselineY}
142 stroke={getSmartLayoutStepColor(total - 1, total)}
143 strokeWidth="3"
144 strokeLinecap="round"
145 markerEnd="url(#presentation-snake-arrow-static)"
146 />
147 )}
148 </svg>
149
150 {/* CSS Grid for text items */}
151 <div
152 className="absolute top-0 bottom-0"
153 style={{
154 display: "grid",
155 gridTemplateColumns: `repeat(${total}, ${SNAKE_COL_WIDTH}px)`,
156 gridTemplateRows: `repeat(${SNAKE_GRID_ROWS}, 1fr)`,
157 left: SNAKE_EDGE_PADDING_X,
158 width: svgWidth,
159 }}
160 >
161 {props.children}
162 </div>
163 </div>
164 </div>
165 </div>
166 </SlateElement>
167 );
168 }
169
169 lines Plain Text