Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842
  1. import { createSelectorHook, createState } from '@state-designer/react'
  2. import { updateFromCode } from 'lib/code/generate'
  3. import { createShape, getShapeUtils } from 'lib/shape-utils'
  4. import vec from 'utils/vec'
  5. import inputs from './inputs'
  6. import history from './history'
  7. import storage from './storage'
  8. import clipboard from './clipboard'
  9. import * as Sessions from './sessions'
  10. import commands from './commands'
  11. import {
  12. getChildren,
  13. getCommonBounds,
  14. getCurrent,
  15. getCurrentCamera,
  16. getPage,
  17. getSelectedBounds,
  18. getSelectedShapes,
  19. getShape,
  20. screenToWorld,
  21. setZoomCSS,
  22. rotateBounds,
  23. getBoundsCenter,
  24. getDocumentBranch,
  25. getCameraZoom,
  26. getSelectedIds,
  27. setSelectedIds,
  28. getPageState,
  29. getShapes,
  30. setToArray,
  31. } from 'utils/utils'
  32. import {
  33. Data,
  34. PointerInfo,
  35. Shape,
  36. ShapeType,
  37. Corner,
  38. Edge,
  39. CodeControl,
  40. MoveType,
  41. ShapeStyles,
  42. DistributeType,
  43. AlignType,
  44. StretchType,
  45. DashStyle,
  46. SizeStyle,
  47. ColorStyle,
  48. FontSize,
  49. } from 'types'
  50. import session from './session'
  51. import { pointInBounds } from 'utils/bounds'
  52. const initialData: Data = {
  53. isReadOnly: false,
  54. settings: {
  55. fontSize: 13,
  56. isDarkMode: false,
  57. isCodeOpen: false,
  58. isStyleOpen: false,
  59. isToolLocked: false,
  60. isPenLocked: false,
  61. nudgeDistanceLarge: 10,
  62. nudgeDistanceSmall: 1,
  63. },
  64. currentStyle: {
  65. size: SizeStyle.Medium,
  66. color: ColorStyle.Black,
  67. dash: DashStyle.Solid,
  68. fontSize: FontSize.Medium,
  69. isFilled: false,
  70. },
  71. activeTool: 'select',
  72. brush: undefined,
  73. boundsRotation: 0,
  74. pointedId: null,
  75. hoveredId: null,
  76. editingId: null,
  77. currentPageId: 'page1',
  78. currentParentId: 'page1',
  79. currentCodeFileId: 'file0',
  80. codeControls: {},
  81. document: {
  82. id: '0001',
  83. name: 'My Document',
  84. pages: {
  85. page1: {
  86. id: 'page1',
  87. type: 'page',
  88. name: 'Page 1',
  89. childIndex: 0,
  90. shapes: {},
  91. },
  92. },
  93. code: {
  94. file0: {
  95. id: 'file0',
  96. name: 'index.ts',
  97. code: ``,
  98. },
  99. },
  100. },
  101. pageStates: {
  102. page1: {
  103. id: 'page1',
  104. selectedIds: new Set([]),
  105. camera: {
  106. point: [0, 0],
  107. zoom: 1,
  108. },
  109. },
  110. },
  111. }
  112. const state = createState({
  113. data: initialData,
  114. on: {
  115. UNMOUNTED: { to: 'loading' },
  116. },
  117. initial: 'loading',
  118. states: {
  119. loading: {
  120. on: {
  121. MOUNTED: {
  122. do: 'restoreSavedData',
  123. to: 'ready',
  124. },
  125. },
  126. },
  127. ready: {
  128. onEnter: {
  129. wait: 0.01,
  130. if: 'hasSelection',
  131. do: 'zoomCameraToSelectionActual',
  132. else: ['zoomCameraToFit', 'zoomCameraToActual'],
  133. },
  134. on: {
  135. COPIED: { if: 'hasSelection', do: 'copyToClipboard' },
  136. PASTED: { do: 'pasteFromClipboard' },
  137. PASTED_SHAPES_FROM_CLIPBOARD: 'pasteShapesFromClipboard',
  138. LOADED_FONTS: 'resetShapes',
  139. TOGGLED_SHAPE_LOCK: { if: 'hasSelection', do: 'lockSelection' },
  140. TOGGLED_SHAPE_HIDE: { if: 'hasSelection', do: 'hideSelection' },
  141. TOGGLED_SHAPE_ASPECT_LOCK: {
  142. if: 'hasSelection',
  143. do: 'aspectLockSelection',
  144. },
  145. TOGGLED_CODE_PANEL_OPEN: 'toggleCodePanel',
  146. TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel',
  147. POINTED_CANVAS: ['closeStylePanel', 'clearCurrentParentId'],
  148. CHANGED_STYLE: ['updateStyles', 'applyStylesToSelection'],
  149. USED_PEN_DEVICE: 'enablePenLock',
  150. DISABLED_PEN_LOCK: 'disablePenLock',
  151. CLEARED_PAGE: {
  152. if: 'hasSelection',
  153. do: 'deleteSelection',
  154. else: ['selectAll', 'deleteSelection'],
  155. },
  156. SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
  157. CHANGED_PAGE: 'changePage',
  158. CREATED_PAGE: ['clearSelectedIds', 'createPage'],
  159. DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
  160. LOADED_FROM_FILE: ['loadDocumentFromJson', 'resetHistory'],
  161. PANNED_CAMERA: 'panCamera',
  162. SELECTED_SELECT_TOOL: { to: 'selecting' },
  163. SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
  164. SELECTED_ARROW_TOOL: { unless: 'isReadOnly', to: 'arrow' },
  165. SELECTED_DOT_TOOL: { unless: 'isReadOnly', to: 'dot' },
  166. SELECTED_CIRCLE_TOOL: { unless: 'isReadOnly', to: 'circle' },
  167. SELECTED_ELLIPSE_TOOL: { unless: 'isReadOnly', to: 'ellipse' },
  168. SELECTED_RAY_TOOL: { unless: 'isReadOnly', to: 'ray' },
  169. SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
  170. SELECTED_POLYLINE_TOOL: { unless: 'isReadOnly', to: 'polyline' },
  171. SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
  172. SELECTED_TEXT_TOOL: { unless: 'isReadOnly', to: 'text' },
  173. },
  174. initial: 'selecting',
  175. states: {
  176. selecting: {
  177. onEnter: 'setActiveToolSelect',
  178. on: {
  179. UNDO: 'undo',
  180. REDO: 'redo',
  181. SAVED: 'forceSave',
  182. LOADED_FROM_FILE_STSTEM: 'loadFromFileSystem',
  183. SAVED_TO_FILESYSTEM: 'saveToFileSystem',
  184. SAVED_AS_TO_FILESYSTEM: 'saveAsToFileSystem',
  185. SAVED_CODE: 'saveCode',
  186. DELETED: 'deleteSelection',
  187. INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
  188. DECREASED_CODE_FONT_SIZE: 'decreaseCodeFontSize',
  189. CHANGED_CODE_CONTROL: 'updateControls',
  190. GENERATED_FROM_CODE: ['setCodeControls', 'setGeneratedShapes'],
  191. TOGGLED_TOOL_LOCK: 'toggleToolLock',
  192. MOVED_TO_PAGE: {
  193. if: 'hasSelection',
  194. do: ['moveSelectionToPage', 'zoomCameraToSelectionActual'],
  195. },
  196. MOVED: { if: 'hasSelection', do: 'moveSelection' },
  197. DUPLICATED: { if: 'hasSelection', do: 'duplicateSelection' },
  198. ROTATED_CCW: { if: 'hasSelection', do: 'rotateSelectionCcw' },
  199. ALIGNED: { if: 'hasMultipleSelection', do: 'alignSelection' },
  200. STRETCHED: { if: 'hasMultipleSelection', do: 'stretchSelection' },
  201. DISTRIBUTED: {
  202. if: 'hasMultipleSelection',
  203. do: 'distributeSelection',
  204. },
  205. GROUPED: { if: 'hasMultipleSelection', do: 'groupSelection' },
  206. UNGROUPED: {
  207. if: ['hasSelection', 'selectionIncludesGroups'],
  208. do: 'ungroupSelection',
  209. },
  210. NUDGED: { do: 'nudgeSelection' },
  211. ZOOMED_CAMERA: {
  212. do: 'zoomCamera',
  213. },
  214. ZOOMED_TO_ACTUAL: {
  215. if: 'hasSelection',
  216. do: 'zoomCameraToSelectionActual',
  217. else: 'zoomCameraToActual',
  218. },
  219. ZOOMED_TO_SELECTION: {
  220. if: 'hasSelection',
  221. do: 'zoomCameraToSelection',
  222. },
  223. ZOOMED_TO_FIT: ['zoomCameraToFit', 'zoomCameraToActual'],
  224. ZOOMED_IN: 'zoomIn',
  225. ZOOMED_OUT: 'zoomOut',
  226. RESET_CAMERA: 'resetCamera',
  227. },
  228. initial: 'notPointing',
  229. states: {
  230. notPointing: {
  231. on: {
  232. CANCELLED: 'clearSelectedIds',
  233. STARTED_PINCHING: { to: 'pinching' },
  234. POINTED_CANVAS: { to: 'brushSelecting' },
  235. POINTED_BOUNDS: [
  236. {
  237. if: 'isPressingMetaKey',
  238. to: 'brushSelecting',
  239. },
  240. { to: 'pointingBounds' },
  241. ],
  242. POINTED_BOUNDS_HANDLE: {
  243. if: 'isPointingRotationHandle',
  244. to: 'rotatingSelection',
  245. else: { to: 'transformingSelection' },
  246. },
  247. STARTED_EDITING_SHAPE: {
  248. get: 'firstSelectedShape',
  249. if: ['hasSingleSelection', 'canEditSelectedShape'],
  250. do: 'setEditingId',
  251. to: 'editingShape',
  252. },
  253. DOUBLE_POINTED_BOUNDS_HANDLE: {
  254. if: 'hasSingleSelection',
  255. do: 'resetShapeBounds',
  256. },
  257. POINTED_HANDLE: { to: 'translatingHandles' },
  258. MOVED_OVER_SHAPE: {
  259. if: 'pointHitsShape',
  260. then: {
  261. unless: 'shapeIsHovered',
  262. do: 'setHoveredId',
  263. },
  264. else: {
  265. if: 'shapeIsHovered',
  266. do: 'clearHoveredId',
  267. },
  268. },
  269. UNHOVERED_SHAPE: 'clearHoveredId',
  270. DOUBLE_POINTED_SHAPE: [
  271. 'setPointedId',
  272. {
  273. if: 'isPointedShapeSelected',
  274. then: {
  275. get: 'firstSelectedShape',
  276. if: 'canEditSelectedShape',
  277. do: 'setEditingId',
  278. to: 'editingShape',
  279. },
  280. },
  281. {
  282. unless: 'isPressingShiftKey',
  283. do: [
  284. 'setDrilledPointedId',
  285. 'clearSelectedIds',
  286. 'pushPointedIdToSelectedIds',
  287. ],
  288. to: 'pointingBounds',
  289. },
  290. ],
  291. POINTED_SHAPE: [
  292. {
  293. if: 'isPressingMetaKey',
  294. to: 'brushSelecting',
  295. },
  296. 'setPointedId',
  297. {
  298. if: 'pointInSelectionBounds',
  299. to: 'pointingBounds',
  300. },
  301. {
  302. unless: 'isPointedShapeSelected',
  303. then: {
  304. if: 'isPressingShiftKey',
  305. do: ['pushPointedIdToSelectedIds', 'clearPointedId'],
  306. else: ['clearSelectedIds', 'pushPointedIdToSelectedIds'],
  307. },
  308. },
  309. {
  310. to: 'pointingBounds',
  311. },
  312. ],
  313. RIGHT_POINTED: [
  314. {
  315. if: 'isPointingCanvas',
  316. do: 'clearSelectedIds',
  317. else: {
  318. if: 'isPointingShape',
  319. then: [
  320. 'setPointedId',
  321. {
  322. unless: 'isPointedShapeSelected',
  323. do: [
  324. 'clearSelectedIds',
  325. 'pushPointedIdToSelectedIds',
  326. ],
  327. },
  328. ],
  329. },
  330. },
  331. ],
  332. },
  333. },
  334. pointingBounds: {
  335. on: {
  336. STOPPED_POINTING_BOUNDS: [],
  337. STOPPED_POINTING: [
  338. {
  339. if: 'isPointingBounds',
  340. do: 'clearSelectedIds',
  341. },
  342. {
  343. if: 'isPressingShiftKey',
  344. then: [
  345. {
  346. if: 'isPointedShapeSelected',
  347. do: 'pullPointedIdFromSelectedIds',
  348. },
  349. ],
  350. else: {
  351. unless: 'isPointingBounds',
  352. do: ['clearSelectedIds', 'pushPointedIdToSelectedIds'],
  353. },
  354. },
  355. { to: 'notPointing' },
  356. ],
  357. MOVED_POINTER: {
  358. unless: 'isReadOnly',
  359. if: 'distanceImpliesDrag',
  360. to: 'translatingSelection',
  361. },
  362. },
  363. },
  364. rotatingSelection: {
  365. onEnter: 'startRotateSession',
  366. onExit: ['completeSession', 'clearBoundsRotation'],
  367. on: {
  368. MOVED_POINTER: 'updateRotateSession',
  369. PANNED_CAMERA: 'updateRotateSession',
  370. PRESSED_SHIFT_KEY: 'keyUpdateRotateSession',
  371. RELEASED_SHIFT_KEY: 'keyUpdateRotateSession',
  372. STOPPED_POINTING: { to: 'selecting' },
  373. CANCELLED: { do: 'cancelSession', to: 'selecting' },
  374. },
  375. },
  376. transformingSelection: {
  377. onEnter: 'startTransformSession',
  378. onExit: 'completeSession',
  379. on: {
  380. // MOVED_POINTER: 'updateTransformSession', using hacks.fastTransform
  381. PANNED_CAMERA: 'updateTransformSession',
  382. PRESSED_SHIFT_KEY: 'keyUpdateTransformSession',
  383. RELEASED_SHIFT_KEY: 'keyUpdateTransformSession',
  384. STOPPED_POINTING: { to: 'selecting' },
  385. CANCELLED: { do: 'cancelSession', to: 'selecting' },
  386. },
  387. },
  388. translatingSelection: {
  389. onEnter: 'startTranslateSession',
  390. onExit: 'completeSession',
  391. on: {
  392. MOVED_POINTER: 'updateTranslateSession',
  393. PANNED_CAMERA: 'updateTranslateSession',
  394. PRESSED_SHIFT_KEY: 'keyUpdateTranslateSession',
  395. RELEASED_SHIFT_KEY: 'keyUpdateTranslateSession',
  396. PRESSED_ALT_KEY: 'keyUpdateTranslateSession',
  397. RELEASED_ALT_KEY: 'keyUpdateTranslateSession',
  398. STOPPED_POINTING: { to: 'selecting' },
  399. CANCELLED: { do: 'cancelSession', to: 'selecting' },
  400. },
  401. },
  402. translatingHandles: {
  403. onEnter: 'startHandleSession',
  404. onExit: 'completeSession',
  405. on: {
  406. MOVED_POINTER: 'updateHandleSession',
  407. PANNED_CAMERA: 'updateHandleSession',
  408. PRESSED_SHIFT_KEY: 'keyUpdateHandleSession',
  409. RELEASED_SHIFT_KEY: 'keyUpdateHandleSession',
  410. STOPPED_POINTING: { to: 'selecting' },
  411. CANCELLED: { do: 'cancelSession', to: 'selecting' },
  412. },
  413. },
  414. brushSelecting: {
  415. onExit: 'completeSession',
  416. onEnter: [
  417. {
  418. unless: ['isPressingMetaKey', 'isPressingShiftKey'],
  419. do: 'clearSelectedIds',
  420. },
  421. 'clearBoundsRotation',
  422. 'startBrushSession',
  423. ],
  424. on: {
  425. // MOVED_POINTER: 'updateBrushSession', using hacks.fastBrushSelect
  426. PANNED_CAMERA: 'updateBrushSession',
  427. STOPPED_POINTING: { to: 'selecting' },
  428. STARTED_PINCHING: { to: 'pinching' },
  429. CANCELLED: { do: 'cancelSession', to: 'selecting' },
  430. },
  431. },
  432. },
  433. },
  434. editingShape: {
  435. onEnter: 'startEditSession',
  436. onExit: ['completeSession', 'clearEditingId'],
  437. on: {
  438. EDITED_SHAPE: { do: 'updateEditSession' },
  439. BLURRED_EDITING_SHAPE: { to: 'selecting' },
  440. CANCELLED: [
  441. {
  442. get: 'editingShape',
  443. if: 'shouldDeleteShape',
  444. do: 'breakSession',
  445. else: 'cancelSession',
  446. },
  447. { to: 'selecting' },
  448. ],
  449. },
  450. },
  451. pinching: {
  452. on: {
  453. // PINCHED: { do: 'pinchCamera' }, using hacks.fastPinchCamera
  454. },
  455. initial: 'selectPinching',
  456. onExit: { secretlyDo: 'updateZoomCSS' },
  457. states: {
  458. selectPinching: {
  459. on: {
  460. STOPPED_PINCHING: { to: 'selecting' },
  461. },
  462. },
  463. toolPinching: {
  464. on: {
  465. STOPPED_PINCHING: { to: 'usingTool.previous' },
  466. },
  467. },
  468. },
  469. },
  470. usingTool: {
  471. initial: 'draw',
  472. onEnter: 'clearSelectedIds',
  473. on: {
  474. STARTED_PINCHING: {
  475. do: 'breakSession',
  476. to: 'pinching.toolPinching',
  477. },
  478. TOGGLED_TOOL_LOCK: 'toggleToolLock',
  479. },
  480. states: {
  481. draw: {
  482. onEnter: 'setActiveToolDraw',
  483. initial: 'creating',
  484. states: {
  485. creating: {
  486. on: {
  487. CANCELLED: { to: 'selecting' },
  488. POINTED_SHAPE: {
  489. get: 'newDraw',
  490. do: 'createShape',
  491. to: 'draw.editing',
  492. },
  493. POINTED_CANVAS: {
  494. get: 'newDraw',
  495. do: 'createShape',
  496. to: 'draw.editing',
  497. },
  498. UNDO: 'undo',
  499. REDO: 'redo',
  500. SAVED: 'forceSave',
  501. LOADED_FROM_FILE_STSTEM: 'loadFromFileSystem',
  502. SAVED_TO_FILESYSTEM: 'saveToFileSystem',
  503. SAVED_AS_TO_FILESYSTEM: 'saveAsToFileSystem',
  504. },
  505. },
  506. editing: {
  507. onEnter: 'startDrawSession',
  508. onExit: 'completeSession',
  509. on: {
  510. CANCELLED: {
  511. do: 'breakSession',
  512. to: 'selecting',
  513. },
  514. STOPPED_POINTING: {
  515. do: 'completeSession',
  516. to: 'draw.creating',
  517. },
  518. PRESSED_SHIFT: 'keyUpdateDrawSession',
  519. RELEASED_SHIFT: 'keyUpdateDrawSession',
  520. // MOVED_POINTER: 'updateDrawSession',
  521. PANNED_CAMERA: 'updateDrawSession',
  522. },
  523. },
  524. },
  525. },
  526. dot: {
  527. onEnter: 'setActiveToolDot',
  528. initial: 'creating',
  529. states: {
  530. creating: {
  531. on: {
  532. CANCELLED: { to: 'selecting' },
  533. POINTED_SHAPE: {
  534. get: 'newDot',
  535. do: 'createShape',
  536. to: 'dot.editing',
  537. },
  538. POINTED_CANVAS: {
  539. get: 'newDot',
  540. do: 'createShape',
  541. to: 'dot.editing',
  542. },
  543. },
  544. },
  545. editing: {
  546. on: {
  547. STOPPED_POINTING: [
  548. 'completeSession',
  549. {
  550. if: 'isToolLocked',
  551. to: 'dot.creating',
  552. else: { to: 'selecting' },
  553. },
  554. ],
  555. CANCELLED: {
  556. do: 'breakSession',
  557. to: 'selecting',
  558. },
  559. },
  560. initial: 'inactive',
  561. states: {
  562. inactive: {
  563. on: {
  564. MOVED_POINTER: {
  565. if: 'distanceImpliesDrag',
  566. to: 'dot.editing.active',
  567. },
  568. },
  569. },
  570. active: {
  571. onExit: 'completeSession',
  572. onEnter: 'startTranslateSession',
  573. on: {
  574. MOVED_POINTER: 'updateTranslateSession',
  575. PANNED_CAMERA: 'updateTranslateSession',
  576. },
  577. },
  578. },
  579. },
  580. },
  581. },
  582. arrow: {
  583. onEnter: 'setActiveToolArrow',
  584. initial: 'creating',
  585. states: {
  586. creating: {
  587. on: {
  588. CANCELLED: { to: 'selecting' },
  589. POINTED_SHAPE: {
  590. get: 'newArrow',
  591. do: 'createShape',
  592. to: 'arrow.editing',
  593. },
  594. POINTED_CANVAS: {
  595. get: 'newArrow',
  596. do: 'createShape',
  597. to: 'arrow.editing',
  598. },
  599. UNDO: { do: 'undo' },
  600. REDO: { do: 'redo' },
  601. },
  602. },
  603. editing: {
  604. onExit: 'completeSession',
  605. onEnter: 'startArrowSession',
  606. on: {
  607. STOPPED_POINTING: [
  608. 'completeSession',
  609. {
  610. if: 'isToolLocked',
  611. to: 'arrow.creating',
  612. else: { to: 'selecting' },
  613. },
  614. ],
  615. CANCELLED: {
  616. do: 'breakSession',
  617. if: 'isToolLocked',
  618. to: 'arrow.creating',
  619. else: { to: 'selecting' },
  620. },
  621. PRESSED_SHIFT: 'keyUpdateArrowSession',
  622. RELEASED_SHIFT: 'keyUpdateArrowSession',
  623. MOVED_POINTER: 'updateArrowSession',
  624. PANNED_CAMERA: 'updateArrowSession',
  625. },
  626. },
  627. },
  628. },
  629. circle: {
  630. onEnter: 'setActiveToolCircle',
  631. initial: 'creating',
  632. states: {
  633. creating: {
  634. on: {
  635. CANCELLED: { to: 'selecting' },
  636. POINTED_SHAPE: {
  637. to: 'circle.editing',
  638. },
  639. POINTED_CANVAS: {
  640. to: 'circle.editing',
  641. },
  642. },
  643. },
  644. editing: {
  645. on: {
  646. STOPPED_POINTING: { to: 'selecting' },
  647. CANCELLED: { to: 'selecting' },
  648. MOVED_POINTER: {
  649. if: 'distanceImpliesDrag',
  650. then: {
  651. get: 'newCircle',
  652. do: 'createShape',
  653. to: 'drawingShape.bounds',
  654. },
  655. },
  656. },
  657. },
  658. },
  659. },
  660. ellipse: {
  661. onEnter: 'setActiveToolEllipse',
  662. initial: 'creating',
  663. states: {
  664. creating: {
  665. on: {
  666. CANCELLED: { to: 'selecting' },
  667. POINTED_CANVAS: {
  668. to: 'ellipse.editing',
  669. },
  670. },
  671. },
  672. editing: {
  673. on: {
  674. STOPPED_POINTING: { to: 'selecting' },
  675. CANCELLED: { to: 'selecting' },
  676. MOVED_POINTER: {
  677. if: 'distanceImpliesDrag',
  678. then: {
  679. get: 'newEllipse',
  680. do: 'createShape',
  681. to: 'drawingShape.bounds',
  682. },
  683. },
  684. },
  685. },
  686. },
  687. },
  688. rectangle: {
  689. onEnter: 'setActiveToolRectangle',
  690. initial: 'creating',
  691. states: {
  692. creating: {
  693. on: {
  694. CANCELLED: { to: 'selecting' },
  695. POINTED_SHAPE: {
  696. to: 'rectangle.editing',
  697. },
  698. POINTED_CANVAS: {
  699. to: 'rectangle.editing',
  700. },
  701. },
  702. },
  703. editing: {
  704. on: {
  705. STOPPED_POINTING: { to: 'selecting' },
  706. CANCELLED: { to: 'selecting' },
  707. MOVED_POINTER: {
  708. if: 'distanceImpliesDrag',
  709. then: {
  710. get: 'newRectangle',
  711. do: 'createShape',
  712. to: 'drawingShape.bounds',
  713. },
  714. },
  715. },
  716. },
  717. },
  718. },
  719. text: {
  720. onEnter: 'setActiveToolText',
  721. initial: 'creating',
  722. states: {
  723. creating: {
  724. on: {
  725. CANCELLED: { to: 'selecting' },
  726. POINTED_SHAPE: [
  727. {
  728. get: 'newText',
  729. do: 'createShape',
  730. },
  731. {
  732. get: 'firstSelectedShape',
  733. if: 'canEditSelectedShape',
  734. do: 'setEditingId',
  735. to: 'editingShape',
  736. },
  737. ],
  738. POINTED_CANVAS: [
  739. {
  740. get: 'newText',
  741. do: 'createShape',
  742. to: 'editingShape',
  743. },
  744. ],
  745. },
  746. },
  747. },
  748. },
  749. ray: {
  750. onEnter: 'setActiveToolRay',
  751. initial: 'creating',
  752. states: {
  753. creating: {
  754. on: {
  755. CANCELLED: { to: 'selecting' },
  756. POINTED_SHAPE: {
  757. get: 'newRay',
  758. do: 'createShape',
  759. to: 'ray.editing',
  760. },
  761. POINTED_CANVAS: {
  762. get: 'newRay',
  763. do: 'createShape',
  764. to: 'ray.editing',
  765. },
  766. },
  767. },
  768. editing: {
  769. on: {
  770. STOPPED_POINTING: { to: 'selecting' },
  771. CANCELLED: { to: 'selecting' },
  772. MOVED_POINTER: {
  773. if: 'distanceImpliesDrag',
  774. to: 'drawingShape.direction',
  775. },
  776. },
  777. },
  778. },
  779. },
  780. line: {
  781. onEnter: 'setActiveToolLine',
  782. initial: 'creating',
  783. states: {
  784. creating: {
  785. on: {
  786. CANCELLED: { to: 'selecting' },
  787. POINTED_SHAPE: {
  788. get: 'newLine',
  789. do: 'createShape',
  790. to: 'line.editing',
  791. },
  792. POINTED_CANVAS: {
  793. get: 'newLine',
  794. do: 'createShape',
  795. to: 'line.editing',
  796. },
  797. },
  798. },
  799. editing: {
  800. on: {
  801. STOPPED_POINTING: { to: 'selecting' },
  802. CANCELLED: { to: 'selecting' },
  803. MOVED_POINTER: {
  804. if: 'distanceImpliesDrag',
  805. to: 'drawingShape.direction',
  806. },
  807. },
  808. },
  809. },
  810. },
  811. polyline: {
  812. onEnter: 'setActiveToolPolyline',
  813. },
  814. },
  815. },
  816. drawingShape: {
  817. onExit: 'completeSession',
  818. on: {
  819. STOPPED_POINTING: [
  820. 'completeSession',
  821. {
  822. if: 'isToolLocked',
  823. to: 'usingTool.previous',
  824. else: { to: 'selecting' },
  825. },
  826. ],
  827. CANCELLED: {
  828. do: 'breakSession',
  829. to: 'selecting',
  830. },
  831. },
  832. initial: 'drawingShapeBounds',
  833. states: {
  834. bounds: {
  835. onEnter: 'startDrawTransformSession',
  836. on: {
  837. MOVED_POINTER: 'updateTransformSession',
  838. PANNED_CAMERA: 'updateTransformSession',
  839. },
  840. },
  841. direction: {
  842. onEnter: 'startDirectionSession',
  843. onExit: 'completeSession',
  844. on: {
  845. MOVED_POINTER: 'updateDirectionSession',
  846. PANNED_CAMERA: 'updateDirectionSession',
  847. },
  848. },
  849. },
  850. },
  851. },
  852. },
  853. },
  854. results: {
  855. newDot() {
  856. return ShapeType.Dot
  857. },
  858. newRay() {
  859. return ShapeType.Ray
  860. },
  861. newLine() {
  862. return ShapeType.Line
  863. },
  864. newText() {
  865. return ShapeType.Text
  866. },
  867. newDraw() {
  868. return ShapeType.Draw
  869. },
  870. newArrow() {
  871. return ShapeType.Arrow
  872. },
  873. newCircle() {
  874. return ShapeType.Circle
  875. },
  876. newEllipse() {
  877. return ShapeType.Ellipse
  878. },
  879. newRectangle() {
  880. return ShapeType.Rectangle
  881. },
  882. firstSelectedShape(data) {
  883. return getSelectedShapes(data)[0]
  884. },
  885. editingShape(data) {
  886. return getShape(data, data.editingId)
  887. },
  888. },
  889. conditions: {
  890. shouldDeleteShape(data, payload, shape: Shape) {
  891. return getShapeUtils(shape).shouldDelete(shape)
  892. },
  893. isPointingCanvas(data, payload: PointerInfo) {
  894. return payload.target === 'canvas'
  895. },
  896. isPointingBounds(data, payload: PointerInfo) {
  897. return getSelectedIds(data).size > 0 && payload.target === 'bounds'
  898. },
  899. isPointingShape(data, payload: PointerInfo) {
  900. return (
  901. payload.target &&
  902. payload.target !== 'canvas' &&
  903. payload.target !== 'bounds'
  904. )
  905. },
  906. isReadOnly(data) {
  907. return data.isReadOnly
  908. },
  909. canEditSelectedShape(data, payload, result: Shape) {
  910. return getShapeUtils(result).canEdit
  911. },
  912. distanceImpliesDrag(data, payload: PointerInfo) {
  913. return vec.dist2(payload.origin, payload.point) > 8
  914. },
  915. hasPointedTarget(data, payload: PointerInfo) {
  916. return payload.target !== undefined
  917. },
  918. isPointedShapeSelected(data) {
  919. return getSelectedIds(data).has(data.pointedId)
  920. },
  921. isPressingShiftKey(data, payload: PointerInfo) {
  922. return payload.shiftKey
  923. },
  924. isPressingMetaKey(data, payload: PointerInfo) {
  925. return payload.metaKey
  926. },
  927. shapeIsHovered(data, payload: { target: string }) {
  928. return data.hoveredId === payload.target
  929. },
  930. pointInSelectionBounds(data, payload: PointerInfo) {
  931. if (getSelectedIds(data).size === 0) return false
  932. return pointInBounds(
  933. screenToWorld(payload.point, data),
  934. getSelectionBounds(data)
  935. )
  936. },
  937. pointHitsShape(data, payload: PointerInfo) {
  938. const shape = getShape(data, payload.target)
  939. return getShapeUtils(shape).hitTest(
  940. shape,
  941. screenToWorld(payload.point, data)
  942. )
  943. },
  944. isPointingRotationHandle(
  945. data,
  946. payload: { target: Edge | Corner | 'rotate' }
  947. ) {
  948. return payload.target === 'rotate'
  949. },
  950. hasSelection(data) {
  951. return getSelectedIds(data).size > 0
  952. },
  953. hasSingleSelection(data) {
  954. return getSelectedIds(data).size === 1
  955. },
  956. hasMultipleSelection(data) {
  957. return getSelectedIds(data).size > 1
  958. },
  959. isToolLocked(data) {
  960. return data.settings.isToolLocked
  961. },
  962. isPenLocked(data) {
  963. return data.settings.isPenLocked
  964. },
  965. hasOnlyOnePage(data) {
  966. return Object.keys(data.document.pages).length === 1
  967. },
  968. selectionIncludesGroups(data) {
  969. return getSelectedShapes(data).some(
  970. (shape) => shape.type === ShapeType.Group
  971. )
  972. },
  973. },
  974. actions: {
  975. /* ---------------------- Pages --------------------- */
  976. changePage(data, payload: { id: string }) {
  977. commands.changePage(data, payload.id)
  978. },
  979. createPage(data) {
  980. commands.createPage(data, true)
  981. },
  982. deletePage(data, payload: { id: string }) {
  983. commands.deletePage(data, payload.id)
  984. },
  985. /* --------------------- Shapes --------------------- */
  986. resetShapes(data) {
  987. const page = getPage(data)
  988. Object.values(page.shapes).forEach((shape) => {
  989. page.shapes[shape.id] = { ...shape }
  990. })
  991. },
  992. createShape(data, payload, type: ShapeType) {
  993. const shape = createShape(type, {
  994. parentId: data.currentPageId,
  995. point: vec.round(screenToWorld(payload.point, data)),
  996. style: getCurrent(data.currentStyle),
  997. })
  998. const siblings = getChildren(data, shape.parentId)
  999. const childIndex = siblings.length
  1000. ? siblings[siblings.length - 1].childIndex + 1
  1001. : 1
  1002. data.editingId = shape.id
  1003. getShapeUtils(shape).setProperty(shape, 'childIndex', childIndex)
  1004. getPage(data).shapes[shape.id] = shape
  1005. setSelectedIds(data, [shape.id])
  1006. },
  1007. /* -------------------- Sessions -------------------- */
  1008. // Shared
  1009. breakSession(data) {
  1010. session.current?.cancel(data)
  1011. session.clear()
  1012. history.disable()
  1013. commands.deleteSelected(data)
  1014. history.enable()
  1015. },
  1016. cancelSession(data) {
  1017. session.current?.cancel(data)
  1018. session.clear()
  1019. },
  1020. completeSession(data) {
  1021. session.current?.complete(data)
  1022. session.clear()
  1023. },
  1024. // Editing
  1025. startEditSession(data) {
  1026. session.current = new Sessions.EditSession(data)
  1027. },
  1028. updateEditSession(data, payload: { change: Partial<Shape> }) {
  1029. session.current.update(data, payload.change)
  1030. },
  1031. // Brushing
  1032. startBrushSession(data, payload: PointerInfo) {
  1033. session.current = new Sessions.BrushSession(
  1034. data,
  1035. screenToWorld(payload.point, data)
  1036. )
  1037. },
  1038. updateBrushSession(data, payload: PointerInfo) {
  1039. session.current.update(data, screenToWorld(payload.point, data))
  1040. },
  1041. // Rotating
  1042. startRotateSession(data, payload: PointerInfo) {
  1043. session.current = new Sessions.RotateSession(
  1044. data,
  1045. screenToWorld(payload.point, data)
  1046. )
  1047. },
  1048. keyUpdateRotateSession(data, payload: PointerInfo) {
  1049. session.current.update(
  1050. data,
  1051. screenToWorld(inputs.pointer.point, data),
  1052. payload.shiftKey
  1053. )
  1054. },
  1055. updateRotateSession(data, payload: PointerInfo) {
  1056. session.current.update(
  1057. data,
  1058. screenToWorld(payload.point, data),
  1059. payload.shiftKey
  1060. )
  1061. },
  1062. // Dragging / Translating
  1063. startTranslateSession(data) {
  1064. session.current = new Sessions.TranslateSession(
  1065. data,
  1066. screenToWorld(inputs.pointer.origin, data)
  1067. )
  1068. },
  1069. keyUpdateTranslateSession(
  1070. data,
  1071. payload: { shiftKey: boolean; altKey: boolean }
  1072. ) {
  1073. session.current.update(
  1074. data,
  1075. screenToWorld(inputs.pointer.point, data),
  1076. payload.shiftKey,
  1077. payload.altKey
  1078. )
  1079. },
  1080. updateTranslateSession(data, payload: PointerInfo) {
  1081. session.current.update(
  1082. data,
  1083. screenToWorld(payload.point, data),
  1084. payload.shiftKey,
  1085. payload.altKey
  1086. )
  1087. },
  1088. // Dragging Handle
  1089. startHandleSession(data, payload: PointerInfo) {
  1090. const shapeId = Array.from(getSelectedIds(data).values())[0]
  1091. const handleId = payload.target
  1092. session.current = new Sessions.HandleSession(
  1093. data,
  1094. shapeId,
  1095. handleId,
  1096. screenToWorld(inputs.pointer.origin, data)
  1097. )
  1098. },
  1099. keyUpdateHandleSession(
  1100. data,
  1101. payload: { shiftKey: boolean; altKey: boolean }
  1102. ) {
  1103. session.current.update(
  1104. data,
  1105. screenToWorld(inputs.pointer.point, data),
  1106. payload.shiftKey,
  1107. payload.altKey
  1108. )
  1109. },
  1110. updateHandleSession(data, payload: PointerInfo) {
  1111. session.current.update(
  1112. data,
  1113. screenToWorld(payload.point, data),
  1114. payload.shiftKey,
  1115. payload.altKey
  1116. )
  1117. },
  1118. // Transforming
  1119. startTransformSession(
  1120. data,
  1121. payload: PointerInfo & { target: Corner | Edge }
  1122. ) {
  1123. const point = screenToWorld(inputs.pointer.origin, data)
  1124. session.current =
  1125. getSelectedIds(data).size === 1
  1126. ? new Sessions.TransformSingleSession(data, payload.target, point)
  1127. : new Sessions.TransformSession(data, payload.target, point)
  1128. },
  1129. startDrawTransformSession(data, payload: PointerInfo) {
  1130. session.current = new Sessions.TransformSingleSession(
  1131. data,
  1132. Corner.BottomRight,
  1133. screenToWorld(payload.point, data),
  1134. true
  1135. )
  1136. },
  1137. keyUpdateTransformSession(data, payload: PointerInfo) {
  1138. session.current.update(
  1139. data,
  1140. screenToWorld(inputs.pointer.point, data),
  1141. payload.shiftKey,
  1142. payload.altKey
  1143. )
  1144. },
  1145. updateTransformSession(data, payload: PointerInfo) {
  1146. session.current.update(
  1147. data,
  1148. screenToWorld(payload.point, data),
  1149. payload.shiftKey,
  1150. payload.altKey
  1151. )
  1152. },
  1153. // Direction
  1154. startDirectionSession(data, payload: PointerInfo) {
  1155. session.current = new Sessions.DirectionSession(
  1156. data,
  1157. screenToWorld(inputs.pointer.origin, data)
  1158. )
  1159. },
  1160. updateDirectionSession(data, payload: PointerInfo) {
  1161. session.current.update(data, screenToWorld(payload.point, data))
  1162. },
  1163. // Drawing
  1164. startDrawSession(data, payload: PointerInfo) {
  1165. const id = Array.from(getSelectedIds(data).values())[0]
  1166. session.current = new Sessions.DrawSession(
  1167. data,
  1168. id,
  1169. screenToWorld(inputs.pointer.origin, data),
  1170. payload.shiftKey
  1171. )
  1172. },
  1173. keyUpdateDrawSession(data, payload: PointerInfo) {
  1174. session.current.update(
  1175. data,
  1176. screenToWorld(inputs.pointer.point, data),
  1177. payload.pressure,
  1178. payload.shiftKey
  1179. )
  1180. },
  1181. updateDrawSession(data, payload: PointerInfo) {
  1182. session.current.update(
  1183. data,
  1184. screenToWorld(payload.point, data),
  1185. payload.pressure,
  1186. payload.shiftKey
  1187. )
  1188. },
  1189. // Arrow
  1190. startArrowSession(data, payload: PointerInfo) {
  1191. const id = Array.from(getSelectedIds(data).values())[0]
  1192. session.current = new Sessions.ArrowSession(
  1193. data,
  1194. id,
  1195. screenToWorld(inputs.pointer.origin, data),
  1196. payload.shiftKey
  1197. )
  1198. },
  1199. keyUpdateArrowSession(data, payload: PointerInfo) {
  1200. session.current.update(
  1201. data,
  1202. screenToWorld(inputs.pointer.point, data),
  1203. payload.shiftKey
  1204. )
  1205. },
  1206. updateArrowSession(data, payload: PointerInfo) {
  1207. session.current.update(
  1208. data,
  1209. screenToWorld(payload.point, data),
  1210. payload.shiftKey
  1211. )
  1212. },
  1213. // Nudges
  1214. nudgeSelection(data, payload: { delta: number[]; shiftKey: boolean }) {
  1215. commands.nudge(
  1216. data,
  1217. vec.mul(
  1218. payload.delta,
  1219. payload.shiftKey
  1220. ? data.settings.nudgeDistanceLarge
  1221. : data.settings.nudgeDistanceSmall
  1222. )
  1223. )
  1224. },
  1225. /* -------------------- Selection ------------------- */
  1226. selectAll(data) {
  1227. const selectedIds = getSelectedIds(data)
  1228. const page = getPage(data)
  1229. selectedIds.clear()
  1230. for (let id in page.shapes) {
  1231. if (page.shapes[id].parentId === data.currentPageId) {
  1232. selectedIds.add(id)
  1233. }
  1234. }
  1235. },
  1236. setHoveredId(data, payload: PointerInfo) {
  1237. data.hoveredId = payload.target
  1238. },
  1239. clearHoveredId(data) {
  1240. data.hoveredId = undefined
  1241. },
  1242. setPointedId(data, payload: PointerInfo) {
  1243. data.pointedId = getPointedId(data, payload.target)
  1244. data.currentParentId = getParentId(data, data.pointedId)
  1245. },
  1246. setDrilledPointedId(data, payload: PointerInfo) {
  1247. data.pointedId = getDrilledPointedId(data, payload.target)
  1248. data.currentParentId = getParentId(data, data.pointedId)
  1249. },
  1250. clearCurrentParentId(data) {
  1251. data.currentParentId = data.currentPageId
  1252. data.pointedId = undefined
  1253. },
  1254. clearPointedId(data) {
  1255. data.pointedId = undefined
  1256. },
  1257. clearSelectedIds(data) {
  1258. setSelectedIds(data, [])
  1259. },
  1260. pullPointedIdFromSelectedIds(data) {
  1261. const { pointedId } = data
  1262. const selectedIds = getSelectedIds(data)
  1263. selectedIds.delete(pointedId)
  1264. },
  1265. pushPointedIdToSelectedIds(data) {
  1266. getSelectedIds(data).add(data.pointedId)
  1267. },
  1268. moveSelection(data, payload: { type: MoveType }) {
  1269. commands.move(data, payload.type)
  1270. },
  1271. moveSelectionToPage(data, payload: { id: string }) {
  1272. commands.moveToPage(data, payload.id)
  1273. },
  1274. alignSelection(data, payload: { type: AlignType }) {
  1275. commands.align(data, payload.type)
  1276. },
  1277. stretchSelection(data, payload: { type: StretchType }) {
  1278. commands.stretch(data, payload.type)
  1279. },
  1280. distributeSelection(data, payload: { type: DistributeType }) {
  1281. commands.distribute(data, payload.type)
  1282. },
  1283. duplicateSelection(data) {
  1284. commands.duplicate(data)
  1285. },
  1286. lockSelection(data) {
  1287. commands.toggle(data, 'isLocked')
  1288. },
  1289. hideSelection(data) {
  1290. commands.toggle(data, 'isHidden')
  1291. },
  1292. aspectLockSelection(data) {
  1293. commands.toggle(data, 'isAspectRatioLocked')
  1294. },
  1295. deleteSelection(data) {
  1296. commands.deleteSelected(data)
  1297. },
  1298. rotateSelectionCcw(data) {
  1299. commands.rotateCcw(data)
  1300. },
  1301. groupSelection(data) {
  1302. commands.group(data)
  1303. },
  1304. ungroupSelection(data) {
  1305. commands.ungroup(data)
  1306. },
  1307. resetShapeBounds(data) {
  1308. commands.resetBounds(data)
  1309. },
  1310. /* --------------------- Editing -------------------- */
  1311. setEditingId(data) {
  1312. const selectedShape = getSelectedShapes(data)[0]
  1313. if (getShapeUtils(selectedShape).canEdit) {
  1314. data.editingId = selectedShape.id
  1315. }
  1316. getPageState(data).selectedIds = new Set([selectedShape.id])
  1317. },
  1318. clearEditingId(data) {
  1319. data.editingId = null
  1320. },
  1321. /* ---------------------- Tool ---------------------- */
  1322. setActiveTool(data, payload: { tool: ShapeType | 'select' }) {
  1323. data.activeTool = payload.tool
  1324. },
  1325. setActiveToolSelect(data) {
  1326. data.activeTool = 'select'
  1327. },
  1328. setActiveToolDraw(data) {
  1329. data.activeTool = ShapeType.Draw
  1330. },
  1331. setActiveToolRectangle(data) {
  1332. data.activeTool = ShapeType.Rectangle
  1333. },
  1334. setActiveToolEllipse(data) {
  1335. data.activeTool = ShapeType.Ellipse
  1336. },
  1337. setActiveToolArrow(data) {
  1338. data.activeTool = ShapeType.Arrow
  1339. },
  1340. setActiveToolDot(data) {
  1341. data.activeTool = ShapeType.Dot
  1342. },
  1343. setActiveToolPolyline(data) {
  1344. data.activeTool = ShapeType.Polyline
  1345. },
  1346. setActiveToolRay(data) {
  1347. data.activeTool = ShapeType.Ray
  1348. },
  1349. setActiveToolCircle(data) {
  1350. data.activeTool = ShapeType.Circle
  1351. },
  1352. setActiveToolLine(data) {
  1353. data.activeTool = ShapeType.Line
  1354. },
  1355. setActiveToolText(data) {
  1356. data.activeTool = ShapeType.Text
  1357. },
  1358. /* --------------------- Camera --------------------- */
  1359. zoomIn(data) {
  1360. const camera = getCurrentCamera(data)
  1361. const i = Math.round((camera.zoom * 100) / 25)
  1362. const center = [window.innerWidth / 2, window.innerHeight / 2]
  1363. const p0 = screenToWorld(center, data)
  1364. camera.zoom = getCameraZoom((i + 1) * 0.25)
  1365. const p1 = screenToWorld(center, data)
  1366. camera.point = vec.add(camera.point, vec.sub(p1, p0))
  1367. setZoomCSS(camera.zoom)
  1368. },
  1369. zoomOut(data) {
  1370. const camera = getCurrentCamera(data)
  1371. const i = Math.round((camera.zoom * 100) / 25)
  1372. const center = [window.innerWidth / 2, window.innerHeight / 2]
  1373. const p0 = screenToWorld(center, data)
  1374. camera.zoom = getCameraZoom((i - 1) * 0.25)
  1375. const p1 = screenToWorld(center, data)
  1376. camera.point = vec.add(camera.point, vec.sub(p1, p0))
  1377. setZoomCSS(camera.zoom)
  1378. },
  1379. zoomCameraToActual(data) {
  1380. const camera = getCurrentCamera(data)
  1381. const center = [window.innerWidth / 2, window.innerHeight / 2]
  1382. const p0 = screenToWorld(center, data)
  1383. camera.zoom = 1
  1384. const p1 = screenToWorld(center, data)
  1385. camera.point = vec.add(camera.point, vec.sub(p1, p0))
  1386. setZoomCSS(camera.zoom)
  1387. },
  1388. zoomCameraToSelectionActual(data) {
  1389. const camera = getCurrentCamera(data)
  1390. const bounds = getSelectedBounds(data)
  1391. const mx = (window.innerWidth - bounds.width) / 2
  1392. const my = (window.innerHeight - bounds.height) / 2
  1393. camera.zoom = 1
  1394. camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my])
  1395. setZoomCSS(camera.zoom)
  1396. },
  1397. zoomCameraToSelection(data) {
  1398. const camera = getCurrentCamera(data)
  1399. const bounds = getSelectedBounds(data)
  1400. const zoom = getCameraZoom(
  1401. bounds.width > bounds.height
  1402. ? (window.innerWidth - 128) / bounds.width
  1403. : (window.innerHeight - 128) / bounds.height
  1404. )
  1405. const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
  1406. const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
  1407. camera.zoom = zoom
  1408. camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my])
  1409. setZoomCSS(camera.zoom)
  1410. },
  1411. zoomCameraToFit(data) {
  1412. const camera = getCurrentCamera(data)
  1413. const page = getPage(data)
  1414. const shapes = Object.values(page.shapes)
  1415. if (shapes.length === 0) {
  1416. return
  1417. }
  1418. const bounds = getCommonBounds(
  1419. ...Object.values(shapes).map((shape) =>
  1420. getShapeUtils(shape).getBounds(shape)
  1421. )
  1422. )
  1423. const zoom = getCameraZoom(
  1424. bounds.width > bounds.height
  1425. ? (window.innerWidth - 128) / bounds.width
  1426. : (window.innerHeight - 128) / bounds.height
  1427. )
  1428. const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
  1429. const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
  1430. camera.zoom = zoom
  1431. camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my])
  1432. setZoomCSS(camera.zoom)
  1433. },
  1434. zoomCamera(data, payload: { delta: number; point: number[] }) {
  1435. const camera = getCurrentCamera(data)
  1436. const next = camera.zoom - (payload.delta / 100) * camera.zoom
  1437. const p0 = screenToWorld(payload.point, data)
  1438. camera.zoom = getCameraZoom(next)
  1439. const p1 = screenToWorld(payload.point, data)
  1440. camera.point = vec.add(camera.point, vec.sub(p1, p0))
  1441. setZoomCSS(camera.zoom)
  1442. },
  1443. panCamera(data, payload: { delta: number[] }) {
  1444. const camera = getCurrentCamera(data)
  1445. camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom))
  1446. },
  1447. updateZoomCSS(data) {
  1448. const camera = getCurrentCamera(data)
  1449. setZoomCSS(camera.zoom)
  1450. },
  1451. pinchCamera(
  1452. data,
  1453. payload: {
  1454. delta: number[]
  1455. distanceDelta: number
  1456. angleDelta: number
  1457. point: number[]
  1458. }
  1459. ) {
  1460. const camera = getCurrentCamera(data)
  1461. camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom))
  1462. const next = camera.zoom - (payload.distanceDelta / 300) * camera.zoom
  1463. const p0 = screenToWorld(payload.point, data)
  1464. camera.zoom = getCameraZoom(next)
  1465. const p1 = screenToWorld(payload.point, data)
  1466. camera.point = vec.add(camera.point, vec.sub(p1, p0))
  1467. setZoomCSS(camera.zoom)
  1468. },
  1469. resetCamera(data) {
  1470. const camera = getCurrentCamera(data)
  1471. camera.zoom = 1
  1472. camera.point = [window.innerWidth / 2, window.innerHeight / 2]
  1473. document.documentElement.style.setProperty('--camera-zoom', '1')
  1474. },
  1475. /* ---------------------- History ---------------------- */
  1476. // History
  1477. popHistory() {
  1478. history.pop()
  1479. },
  1480. enableHistory() {
  1481. history.enable()
  1482. },
  1483. disableHistory() {
  1484. history.disable()
  1485. },
  1486. undo(data) {
  1487. history.undo(data)
  1488. },
  1489. redo(data) {
  1490. history.redo(data)
  1491. },
  1492. resetHistory(data) {
  1493. history.reset()
  1494. },
  1495. /* --------------------- Styles --------------------- */
  1496. toggleStylePanel(data) {
  1497. data.settings.isStyleOpen = !data.settings.isStyleOpen
  1498. },
  1499. closeStylePanel(data) {
  1500. data.settings.isStyleOpen = false
  1501. },
  1502. updateStyles(data, payload: Partial<ShapeStyles>) {
  1503. Object.assign(data.currentStyle, payload)
  1504. },
  1505. applyStylesToSelection(data, payload: Partial<ShapeStyles>) {
  1506. commands.style(data, payload)
  1507. },
  1508. /* ---------------------- Code ---------------------- */
  1509. closeCodePanel(data) {
  1510. data.settings.isCodeOpen = false
  1511. },
  1512. openCodePanel(data) {
  1513. data.settings.isCodeOpen = true
  1514. },
  1515. toggleCodePanel(data) {
  1516. data.settings.isCodeOpen = !data.settings.isCodeOpen
  1517. },
  1518. setGeneratedShapes(
  1519. data,
  1520. payload: { shapes: Shape[]; controls: CodeControl[] }
  1521. ) {
  1522. commands.generate(data, data.currentPageId, payload.shapes)
  1523. },
  1524. setCodeControls(data, payload: { controls: CodeControl[] }) {
  1525. data.codeControls = Object.fromEntries(
  1526. payload.controls.map((control) => [control.id, control])
  1527. )
  1528. },
  1529. increaseCodeFontSize(data) {
  1530. data.settings.fontSize++
  1531. },
  1532. decreaseCodeFontSize(data) {
  1533. data.settings.fontSize--
  1534. },
  1535. updateControls(data, payload: { [key: string]: any }) {
  1536. for (let key in payload) {
  1537. data.codeControls[key].value = payload[key]
  1538. }
  1539. history.disable()
  1540. setSelectedIds(data, [])
  1541. try {
  1542. const { shapes } = updateFromCode(
  1543. data,
  1544. data.document.code[data.currentCodeFileId].code
  1545. )
  1546. commands.generate(data, data.currentPageId, shapes)
  1547. } catch (e) {
  1548. console.error(e)
  1549. }
  1550. history.enable()
  1551. },
  1552. /* -------------------- Settings -------------------- */
  1553. enablePenLock(data) {
  1554. data.settings.isPenLocked = true
  1555. },
  1556. disablePenLock(data) {
  1557. data.settings.isPenLocked = false
  1558. },
  1559. toggleToolLock(data) {
  1560. data.settings.isToolLocked = !data.settings.isToolLocked
  1561. },
  1562. /* ---------------------- Data ---------------------- */
  1563. copyToClipboard(data) {
  1564. clipboard.copy(getSelectedShapes(data))
  1565. },
  1566. pasteFromClipboard(data) {
  1567. clipboard.paste()
  1568. },
  1569. pasteShapesFromClipboard(data, payload: { shapes: Shape[] }) {
  1570. commands.paste(data, payload.shapes)
  1571. },
  1572. restoreSavedData(data) {
  1573. storage.firstLoad(data)
  1574. },
  1575. saveToFileSystem(data) {
  1576. storage.saveToFileSystem(data)
  1577. },
  1578. saveAsToFileSystem(data) {
  1579. storage.saveAsToFileSystem(data)
  1580. },
  1581. loadFromFileSystem() {
  1582. storage.loadDocumentFromFilesystem()
  1583. },
  1584. loadDocumentFromJson(data, payload: { json: any }) {
  1585. storage.loadDocumentFromJson(data, payload.json)
  1586. },
  1587. forceSave(data) {
  1588. storage.saveToFileSystem(data)
  1589. },
  1590. savePage(data) {
  1591. storage.savePage(data)
  1592. },
  1593. loadPage(data) {
  1594. storage.loadPage(data)
  1595. },
  1596. saveCode(data, payload: { code: string }) {
  1597. data.document.code[data.currentCodeFileId].code = payload.code
  1598. storage.saveDocumentToLocalStorage(data)
  1599. },
  1600. clearBoundsRotation(data) {
  1601. data.boundsRotation = 0
  1602. },
  1603. },
  1604. values: {
  1605. selectedIds(data) {
  1606. return new Set(getSelectedIds(data))
  1607. },
  1608. selectedBounds(data) {
  1609. return getSelectionBounds(data)
  1610. },
  1611. currentShapes(data) {
  1612. const page = getPage(data)
  1613. return Object.values(page.shapes)
  1614. .filter((shape) => shape.parentId === page.id)
  1615. .sort((a, b) => a.childIndex - b.childIndex)
  1616. },
  1617. selectedStyle(data) {
  1618. const selectedIds = Array.from(getSelectedIds(data).values())
  1619. const { currentStyle } = data
  1620. if (selectedIds.length === 0) {
  1621. return currentStyle
  1622. }
  1623. const page = getPage(data)
  1624. const shapeStyles = selectedIds.map((id) => page.shapes[id].style)
  1625. const commonStyle: ShapeStyles = {} as ShapeStyles
  1626. const overrides = new Set<string>([])
  1627. for (const shapeStyle of shapeStyles) {
  1628. for (let key in currentStyle) {
  1629. if (overrides.has(key)) continue
  1630. if (commonStyle[key] === undefined) {
  1631. commonStyle[key] = shapeStyle[key]
  1632. } else {
  1633. if (commonStyle[key] === shapeStyle[key]) continue
  1634. commonStyle[key] = currentStyle[key]
  1635. overrides.add(key)
  1636. }
  1637. }
  1638. }
  1639. return commonStyle
  1640. },
  1641. },
  1642. })
  1643. export default state
  1644. export const useSelector = createSelectorHook(state)
  1645. function getParentId(data: Data, id: string) {
  1646. const shape = getPage(data).shapes[id]
  1647. return shape.parentId
  1648. }
  1649. function getPointedId(data: Data, id: string) {
  1650. const shape = getPage(data).shapes[id]
  1651. return shape.parentId === data.currentParentId ||
  1652. shape.parentId === data.currentPageId
  1653. ? id
  1654. : getPointedId(data, shape.parentId)
  1655. }
  1656. function getDrilledPointedId(data: Data, id: string) {
  1657. const shape = getPage(data).shapes[id]
  1658. return shape.parentId === data.currentPageId ||
  1659. shape.parentId === data.pointedId ||
  1660. shape.parentId === data.currentParentId
  1661. ? id
  1662. : getDrilledPointedId(data, shape.parentId)
  1663. }
  1664. function hasPointedIdInChildren(data: Data, id: string, pointedId: string) {
  1665. const shape = getPage(data).shapes[id]
  1666. if (shape.type !== ShapeType.Group) {
  1667. return false
  1668. }
  1669. if (shape.children.includes(pointedId)) {
  1670. return true
  1671. }
  1672. return shape.children.some((childId) =>
  1673. hasPointedIdInChildren(data, childId, pointedId)
  1674. )
  1675. }
  1676. function getSelectionBounds(data: Data) {
  1677. const selectedIds = getSelectedIds(data)
  1678. const page = getPage(data)
  1679. const shapes = Array.from(selectedIds.values())
  1680. .map((id) => page.shapes[id])
  1681. .filter(Boolean)
  1682. if (selectedIds.size === 0) return null
  1683. if (selectedIds.size === 1) {
  1684. if (!shapes[0]) {
  1685. console.error('Could not find that shape! Clearing selected IDs.')
  1686. setSelectedIds(data, [])
  1687. return null
  1688. }
  1689. const shape = shapes[0]
  1690. const shapeUtils = getShapeUtils(shape)
  1691. if (!shapeUtils.canTransform) return null
  1692. let bounds = shapeUtils.getBounds(shape)
  1693. let parentId = shape.parentId
  1694. while (parentId !== data.currentPageId) {
  1695. const parent = page.shapes[parentId]
  1696. bounds = rotateBounds(
  1697. bounds,
  1698. getBoundsCenter(getShapeUtils(parent).getBounds(parent)),
  1699. parent.rotation
  1700. )
  1701. bounds.rotation = parent.rotation
  1702. parentId = parent.parentId
  1703. }
  1704. return bounds
  1705. }
  1706. const uniqueSelectedShapeIds: string[] = Array.from(
  1707. new Set(
  1708. Array.from(selectedIds.values()).flatMap((id) =>
  1709. getDocumentBranch(data, id)
  1710. )
  1711. ).values()
  1712. )
  1713. const commonBounds = getCommonBounds(
  1714. ...uniqueSelectedShapeIds
  1715. .map((id) => page.shapes[id])
  1716. .filter((shape) => shape.type !== ShapeType.Group)
  1717. .map((shape) => getShapeUtils(shape).getRotatedBounds(shape))
  1718. )
  1719. return commonBounds
  1720. }
  1721. // state.enableLog(true)
  1722. // state.onUpdate((s) => console.log(s.log.filter((l) => l !== 'MOVED_POINTER')))