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

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