Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. /* -------------------------------------------------- */
  2. /* Client State */
  3. /* -------------------------------------------------- */
  4. export interface Data {
  5. isReadOnly: boolean
  6. settings: {
  7. fontSize: number
  8. isDarkMode: boolean
  9. isCodeOpen: boolean
  10. isStyleOpen: boolean
  11. nudgeDistanceSmall: number
  12. nudgeDistanceLarge: number
  13. isToolLocked: boolean
  14. isPenLocked: boolean
  15. }
  16. currentStyle: ShapeStyles
  17. activeTool: ShapeType | 'select'
  18. brush?: Bounds
  19. boundsRotation: number
  20. pointedId?: string
  21. hoveredId?: string
  22. editingId?: string
  23. currentPageId: string
  24. currentParentId: string
  25. currentCodeFileId: string
  26. codeControls: Record<string, CodeControl>
  27. document: TLDocument
  28. pageStates: Record<string, PageState>
  29. }
  30. /* -------------------------------------------------- */
  31. /* Document */
  32. /* -------------------------------------------------- */
  33. export interface TLDocument {
  34. id: string
  35. name: string
  36. pages: Record<string, Page>
  37. code: Record<string, CodeFile>
  38. }
  39. export interface Page {
  40. id: string
  41. type: 'page'
  42. childIndex: number
  43. name: string
  44. shapes: Record<string, Shape>
  45. }
  46. export interface PageState {
  47. id: string
  48. selectedIds: Set<string>
  49. camera: {
  50. point: number[]
  51. zoom: number
  52. }
  53. }
  54. /* ----------------- Start Copy Here ---------------- */
  55. export enum ShapeType {
  56. Dot = 'dot',
  57. Ellipse = 'ellipse',
  58. Line = 'line',
  59. Ray = 'ray',
  60. Polyline = 'polyline',
  61. Rectangle = 'rectangle',
  62. Draw = 'draw',
  63. Arrow = 'arrow',
  64. Text = 'text',
  65. Group = 'group',
  66. }
  67. export enum ColorStyle {
  68. White = 'White',
  69. LightGray = 'LightGray',
  70. Gray = 'Gray',
  71. Black = 'Black',
  72. Green = 'Green',
  73. Cyan = 'Cyan',
  74. Blue = 'Blue',
  75. Indigo = 'Indigo',
  76. Violet = 'Violet',
  77. Red = 'Red',
  78. Orange = 'Orange',
  79. Yellow = 'Yellow',
  80. }
  81. export enum SizeStyle {
  82. Small = 'Small',
  83. Medium = 'Medium',
  84. Large = 'Large',
  85. }
  86. export enum DashStyle {
  87. Solid = 'Solid',
  88. Dashed = 'Dashed',
  89. Dotted = 'Dotted',
  90. }
  91. export enum FontSize {
  92. Small = 'Small',
  93. Medium = 'Medium',
  94. Large = 'Large',
  95. ExtraLarge = 'ExtraLarge',
  96. }
  97. export type ShapeStyles = {
  98. color: ColorStyle
  99. size: SizeStyle
  100. dash: DashStyle
  101. isFilled: boolean
  102. }
  103. export interface BaseShape {
  104. id: string
  105. type: ShapeType
  106. parentId: string
  107. childIndex: number
  108. isGenerated: boolean
  109. name: string
  110. point: number[]
  111. style: ShapeStyles
  112. rotation: number
  113. children?: string[]
  114. bindings?: Record<string, ShapeBinding>
  115. handles?: Record<string, ShapeHandle>
  116. isLocked: boolean
  117. isHidden: boolean
  118. isAspectRatioLocked: boolean
  119. }
  120. export interface DotShape extends BaseShape {
  121. type: ShapeType.Dot
  122. }
  123. export interface EllipseShape extends BaseShape {
  124. type: ShapeType.Ellipse
  125. radiusX: number
  126. radiusY: number
  127. }
  128. export interface LineShape extends BaseShape {
  129. type: ShapeType.Line
  130. direction: number[]
  131. }
  132. export interface RayShape extends BaseShape {
  133. type: ShapeType.Ray
  134. direction: number[]
  135. }
  136. export interface PolylineShape extends BaseShape {
  137. type: ShapeType.Polyline
  138. points: number[][]
  139. }
  140. export interface RectangleShape extends BaseShape {
  141. type: ShapeType.Rectangle
  142. size: number[]
  143. radius: number
  144. }
  145. export interface DrawShape extends BaseShape {
  146. type: ShapeType.Draw
  147. points: number[][]
  148. }
  149. export interface ArrowShape extends BaseShape {
  150. type: ShapeType.Arrow
  151. handles: Record<string, ShapeHandle>
  152. bend: number
  153. decorations?: {
  154. start: Decoration
  155. end: Decoration
  156. middle: Decoration
  157. }
  158. }
  159. export interface TextShape extends BaseShape {
  160. type: ShapeType.Text
  161. text: string
  162. scale: number
  163. }
  164. export interface GroupShape extends BaseShape {
  165. type: ShapeType.Group
  166. children: string[]
  167. size: number[]
  168. }
  169. // type DeepPartial<T> = {
  170. // [P in keyof T]?: DeepPartial<T[P]>
  171. // }
  172. export type ShapeProps<T extends Shape> = {
  173. [P in keyof T]?: P extends 'style' ? Partial<T[P]> : T[P]
  174. }
  175. export type MutableShape =
  176. | DotShape
  177. | EllipseShape
  178. | LineShape
  179. | RayShape
  180. | PolylineShape
  181. | DrawShape
  182. | RectangleShape
  183. | ArrowShape
  184. | TextShape
  185. | GroupShape
  186. export interface Shapes {
  187. [ShapeType.Dot]: Readonly<DotShape>
  188. [ShapeType.Ellipse]: Readonly<EllipseShape>
  189. [ShapeType.Line]: Readonly<LineShape>
  190. [ShapeType.Ray]: Readonly<RayShape>
  191. [ShapeType.Polyline]: Readonly<PolylineShape>
  192. [ShapeType.Draw]: Readonly<DrawShape>
  193. [ShapeType.Rectangle]: Readonly<RectangleShape>
  194. [ShapeType.Arrow]: Readonly<ArrowShape>
  195. [ShapeType.Text]: Readonly<TextShape>
  196. [ShapeType.Group]: Readonly<GroupShape>
  197. }
  198. export type Shape = Readonly<MutableShape>
  199. export type ShapeByType<T extends ShapeType> = Shapes[T]
  200. export enum Decoration {
  201. Arrow = 'Arrow',
  202. }
  203. export interface ShapeBinding {
  204. id: string
  205. index: number
  206. point: number[]
  207. }
  208. export interface ShapeHandle {
  209. id: string
  210. index: number
  211. point: number[]
  212. }
  213. export interface CodeFile {
  214. id: string
  215. name: string
  216. code: string
  217. }
  218. export interface CodeError {
  219. message: string
  220. line: number
  221. column: number
  222. }
  223. export interface CodeResult {
  224. shapes: Shape[]
  225. controls: CodeControl[]
  226. error: CodeError
  227. }
  228. /* -------------------------------------------------- */
  229. /* Editor UI */
  230. /* -------------------------------------------------- */
  231. export interface PointerInfo {
  232. target: string
  233. pointerId: number
  234. origin: number[]
  235. point: number[]
  236. pressure: number
  237. shiftKey: boolean
  238. ctrlKey: boolean
  239. metaKey: boolean
  240. altKey: boolean
  241. }
  242. export enum Edge {
  243. Top = 'top_edge',
  244. Right = 'right_edge',
  245. Bottom = 'bottom_edge',
  246. Left = 'left_edge',
  247. }
  248. export enum Corner {
  249. TopLeft = 'top_left_corner',
  250. TopRight = 'top_right_corner',
  251. BottomRight = 'bottom_right_corner',
  252. BottomLeft = 'bottom_left_corner',
  253. }
  254. export interface Bounds {
  255. minX: number
  256. minY: number
  257. maxX: number
  258. maxY: number
  259. width: number
  260. height: number
  261. rotation?: number
  262. }
  263. export interface RotatedBounds extends Bounds {
  264. rotation: number
  265. }
  266. export interface ShapeBounds extends Bounds {
  267. id: string
  268. }
  269. export interface PointSnapshot extends Bounds {
  270. nx: number
  271. nmx: number
  272. ny: number
  273. nmy: number
  274. }
  275. export interface BoundsSnapshot extends PointSnapshot {
  276. nw: number
  277. nh: number
  278. }
  279. export type Difference<A, B> = A extends B ? never : A
  280. export type ShapeSpecificProps<T extends Shape> = Pick<
  281. T,
  282. Difference<keyof T, keyof BaseShape>
  283. >
  284. export type ShapeIndicatorProps<T extends Shape> = ShapeSpecificProps<T>
  285. export type ShapeUtil<K extends Shape> = {
  286. create(props: Partial<K>): K
  287. getBounds(shape: K): Bounds
  288. hitTest(shape: K, test: number[]): boolean
  289. hitTestBounds(shape: K, bounds: Bounds): boolean
  290. rotate(shape: K): K
  291. translate(shape: K, delta: number[]): K
  292. scale(shape: K, scale: number): K
  293. stretch(shape: K, scaleX: number, scaleY: number): K
  294. render(shape: K): JSX.Element
  295. }
  296. export enum MoveType {
  297. Backward,
  298. Forward,
  299. ToFront,
  300. ToBack,
  301. }
  302. export enum AlignType {
  303. Top,
  304. CenterVertical,
  305. Bottom,
  306. Left,
  307. CenterHorizontal,
  308. Right,
  309. }
  310. export enum StretchType {
  311. Horizontal,
  312. Vertical,
  313. }
  314. export enum DistributeType {
  315. Horizontal,
  316. Vertical,
  317. }
  318. export interface BezierCurveSegment {
  319. start: number[]
  320. tangentStart: number[]
  321. normalStart: number[]
  322. pressureStart: number
  323. end: number[]
  324. tangentEnd: number[]
  325. normalEnd: number[]
  326. pressureEnd: number
  327. }
  328. /* -------------------------------------------------- */
  329. /* Code Editor */
  330. /* -------------------------------------------------- */
  331. export enum ControlType {
  332. Number = 'number',
  333. Vector = 'vector',
  334. Text = 'text',
  335. Select = 'select',
  336. }
  337. export interface BaseCodeControl {
  338. id: string
  339. type: ControlType
  340. label: string
  341. }
  342. export interface NumberCodeControl extends BaseCodeControl {
  343. type: ControlType.Number
  344. value: number
  345. min?: number
  346. max?: number
  347. step?: number
  348. format?: (value: number) => number
  349. }
  350. export interface VectorCodeControl extends BaseCodeControl {
  351. type: ControlType.Vector
  352. value: number[]
  353. min?: number
  354. max?: number
  355. step?: number
  356. isNormalized?: boolean
  357. format?: (value: number[]) => number[]
  358. }
  359. export interface TextCodeControl extends BaseCodeControl {
  360. type: ControlType.Text
  361. value: string
  362. format?: (value: string) => string
  363. }
  364. export interface SelectCodeControl<T extends string = ''>
  365. extends BaseCodeControl {
  366. type: ControlType.Select
  367. value: T
  368. options: T[]
  369. format?: (string: T) => string
  370. }
  371. export type CodeControl =
  372. | NumberCodeControl
  373. | VectorCodeControl
  374. | TextCodeControl
  375. export type PropsOfType<T extends Record<string, unknown>> = {
  376. [K in keyof T]: T[K] extends boolean ? K : never
  377. }[keyof T]
  378. export type Mutable<T extends Shape> = { -readonly [K in keyof T]: T[K] }
  379. export interface ShapeUtility<K extends Shape> {
  380. // A cache for the computed bounds of this kind of shape.
  381. boundsCache: WeakMap<K, Bounds>
  382. // Whether to show transform controls when this shape is selected.
  383. canTransform: boolean
  384. // Whether the shape's aspect ratio can change.
  385. canChangeAspectRatio: boolean
  386. // Whether the shape's style can be filled.
  387. canStyleFill: boolean
  388. // Whether the shape may be edited in an editing mode
  389. canEdit: boolean
  390. // Whether the shape is a foreign object.
  391. isForeignObject: boolean
  392. // Whether the shape can contain other shapes.
  393. isParent: boolean
  394. // Whether the shape is only shown when on hovered.
  395. isShy: boolean
  396. // Create a new shape.
  397. create(props: Partial<K>): K
  398. // Update a shape's styles
  399. applyStyles(
  400. this: ShapeUtility<K>,
  401. shape: Mutable<K>,
  402. style: Partial<ShapeStyles>
  403. ): ShapeUtility<K>
  404. translateBy(
  405. this: ShapeUtility<K>,
  406. shape: Mutable<K>,
  407. point: number[]
  408. ): ShapeUtility<K>
  409. translateTo(
  410. this: ShapeUtility<K>,
  411. shape: Mutable<K>,
  412. point: number[]
  413. ): ShapeUtility<K>
  414. rotateBy(
  415. this: ShapeUtility<K>,
  416. shape: Mutable<K>,
  417. rotation: number
  418. ): ShapeUtility<K>
  419. rotateTo(
  420. this: ShapeUtility<K>,
  421. shape: Mutable<K>,
  422. rotation: number,
  423. delta: number
  424. ): ShapeUtility<K>
  425. // Transform to fit a new bounding box when more than one shape is selected.
  426. transform(
  427. this: ShapeUtility<K>,
  428. shape: Mutable<K>,
  429. bounds: Bounds,
  430. info: {
  431. type: Edge | Corner
  432. initialShape: K
  433. scaleX: number
  434. scaleY: number
  435. transformOrigin: number[]
  436. }
  437. ): ShapeUtility<K>
  438. // Transform a single shape to fit a new bounding box.
  439. transformSingle(
  440. this: ShapeUtility<K>,
  441. shape: Mutable<K>,
  442. bounds: Bounds,
  443. info: {
  444. type: Edge | Corner
  445. initialShape: K
  446. scaleX: number
  447. scaleY: number
  448. transformOrigin: number[]
  449. }
  450. ): ShapeUtility<K>
  451. setProperty<P extends keyof K>(
  452. this: ShapeUtility<K>,
  453. shape: Mutable<K>,
  454. prop: P,
  455. value: K[P]
  456. ): ShapeUtility<K>
  457. // Respond when any child of this shape changes.
  458. onChildrenChange(
  459. this: ShapeUtility<K>,
  460. shape: Mutable<K>,
  461. children: Shape[]
  462. ): ShapeUtility<K>
  463. // Respond when a user moves one of the shape's bound elements.
  464. onBindingChange(
  465. this: ShapeUtility<K>,
  466. shape: Mutable<K>,
  467. bindings: Record<string, ShapeBinding>
  468. ): ShapeUtility<K>
  469. // Respond when a user moves one of the shape's handles.
  470. onHandleChange(
  471. this: ShapeUtility<K>,
  472. shape: Mutable<K>,
  473. handle: Partial<K['handles']>
  474. ): ShapeUtility<K>
  475. onDoublePointHandle(
  476. this: ShapeUtility<K>,
  477. shape: Mutable<K>,
  478. handle: keyof K['handles'],
  479. info: PointerInfo
  480. ): ShapeUtility<K>
  481. // Respond when a user double clicks the shape's bounds.
  482. onBoundsReset(this: ShapeUtility<K>, shape: Mutable<K>): ShapeUtility<K>
  483. // Respond when a user double clicks the center of the shape.
  484. onDoubleFocus(this: ShapeUtility<K>, shape: Mutable<K>): ShapeUtility<K>
  485. // Clean up changes when a session ends.
  486. onSessionComplete(this: ShapeUtility<K>, shape: Mutable<K>): ShapeUtility<K>
  487. // Render a shape to JSX.
  488. render(
  489. this: ShapeUtility<K>,
  490. shape: K,
  491. info: {
  492. isEditing: boolean
  493. ref?: React.MutableRefObject<HTMLTextAreaElement>
  494. }
  495. ): JSX.Element
  496. invalidate(this: ShapeUtility<K>, shape: K): ShapeUtility<K>
  497. // Get the bounds of the a shape.
  498. getBounds(this: ShapeUtility<K>, shape: K): Bounds
  499. // Get the routated bounds of the a shape.
  500. getRotatedBounds(this: ShapeUtility<K>, shape: K): Bounds
  501. // Get the center of the shape
  502. getCenter(this: ShapeUtility<K>, shape: K): number[]
  503. // Test whether a point lies within a shape.
  504. hitTest(this: ShapeUtility<K>, shape: K, test: number[]): boolean
  505. // Test whether bounds collide with or contain a shape.
  506. hitTestBounds(this: ShapeUtility<K>, shape: K, bounds: Bounds): boolean
  507. // Get whether the shape should delete
  508. shouldDelete(this: ShapeUtility<K>, shape: K): boolean
  509. // Get whether the shape should render
  510. shouldRender(this: ShapeUtility<K>, shape: K, previous: K): boolean
  511. }