Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

state.ts 69KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418
  1. import { createSelectorHook, createState } from '@state-designer/react'
  2. import { updateFromCode } from './code/generate'
  3. import { createShape, getShapeUtils } from './shape-utils'
  4. import * as Sessions from './sessions'
  5. import inputs from './inputs'
  6. import history from './history'
  7. import storage from './storage'
  8. import session from './session'
  9. import clipboard from './clipboard'
  10. import commands from './commands'
  11. import { dark, light } from 'styles'
  12. import {
  13. vec,
  14. getCommonBounds,
  15. rotateBounds,
  16. getBoundsCenter,
  17. deepClone,
  18. pointInBounds,
  19. uniqueId,
  20. boundsContain,
  21. boundsCollide,
  22. } from 'utils'
  23. import tld from '../utils/tld'
  24. import {
  25. Data,
  26. PointerInfo,
  27. Shape,
  28. ShapeType,
  29. Corner,
  30. Edge,
  31. CodeControl,
  32. MoveType,
  33. ShapeStyles,
  34. DistributeType,
  35. AlignType,
  36. StretchType,
  37. DashStyle,
  38. SizeStyle,
  39. ColorStyle,
  40. ShapeTreeNode,
  41. } from 'types'
  42. import { getFontSize } from './shape-styles'
  43. import logger from './logger'
  44. const initialData: Data = {
  45. isReadOnly: false,
  46. settings: {
  47. fontSize: 13,
  48. isTestMode: false,
  49. isDarkMode: true,
  50. isCodeOpen: false,
  51. isDebugMode: false,
  52. isDebugOpen: false,
  53. isStyleOpen: false,
  54. isToolLocked: false,
  55. isPenLocked: false,
  56. nudgeDistanceLarge: 10,
  57. nudgeDistanceSmall: 1,
  58. },
  59. currentStyle: {
  60. size: SizeStyle.Medium,
  61. color: ColorStyle.Black,
  62. dash: DashStyle.Draw,
  63. isFilled: false,
  64. },
  65. brush: undefined,
  66. activeTool: 'select',
  67. pointedId: null,
  68. hoveredId: null,
  69. editingId: null,
  70. boundsRotation: 0,
  71. currentPageId: 'page1',
  72. currentParentId: 'page1',
  73. currentCodeFileId: 'file0',
  74. codeControls: {},
  75. document: {
  76. id: '0001',
  77. name: 'My Document',
  78. pages: {
  79. page1: {
  80. id: 'page1',
  81. type: 'page',
  82. name: 'Page 1',
  83. childIndex: 0,
  84. shapes: {},
  85. },
  86. },
  87. code: {
  88. file0: {
  89. id: 'file0',
  90. name: 'index.ts',
  91. code: `
  92. const draw = new Draw({
  93. points: [
  94. ...Utils.getPointsBetween([0, 0], [20, 50]),
  95. ...Utils.getPointsBetween([20, 50], [100, 20], 3),
  96. ...Utils.getPointsBetween([100, 20], [100, 100], 10),
  97. [100, 100],
  98. ],
  99. })
  100. const rectangle = new Rectangle({
  101. point: [200, 0],
  102. style: {
  103. color: ColorStyle.Blue,
  104. },
  105. })
  106. const ellipse = new Ellipse({
  107. point: [400, 0],
  108. })
  109. const arrow = new Arrow({
  110. start: [600, 0],
  111. end: [700, 100],
  112. })
  113. const radius = 1000
  114. const count = 100
  115. const center = [350, 50]
  116. for (let i = 0; i < count; i++) {
  117. const point = Vec.rotWith(
  118. Vec.add(center, [radius, 0]),
  119. center,
  120. (Math.PI * 2 * i) / count
  121. )
  122. const dot = new Dot({
  123. point,
  124. })
  125. }
  126. `,
  127. },
  128. },
  129. },
  130. pageStates: {
  131. page1: {
  132. id: 'page1',
  133. selectedIds: [],
  134. camera: {
  135. point: [0, 0],
  136. zoom: 1,
  137. },
  138. },
  139. },
  140. }
  141. const state = createState({
  142. data: initialData,
  143. onEnter: 'applyTheme',
  144. on: {
  145. TOGGLED_DEBUG_PANEL: 'toggleDebugPanel',
  146. TOGGLED_DEBUG_MODE: 'toggleDebugMode',
  147. TOGGLED_TEST_MODE: 'toggleTestMode',
  148. TOGGLED_LOGGER: 'toggleLogger',
  149. COPIED_DEBUG_LOG: 'copyDebugLog',
  150. LOADED_FROM_SNAPSHOT: {
  151. unless: 'isInSession',
  152. do: ['loadDocumentFromJson', 'resetHistory'],
  153. },
  154. },
  155. initial: 'loading',
  156. states: {
  157. loading: {
  158. on: {
  159. MOUNTED: [
  160. 'resetHistory',
  161. 'resetStorage',
  162. 'restoredPreviousDocument',
  163. { to: 'ready' },
  164. ],
  165. },
  166. },
  167. ready: {
  168. onEnter: [
  169. 'applyTheme',
  170. {
  171. wait: 0.01,
  172. if: 'hasSelection',
  173. do: 'zoomCameraToSelectionActual',
  174. else: ['zoomCameraToActual'],
  175. },
  176. ],
  177. on: {
  178. UNMOUNTED: {
  179. do: ['saveDocumentState', 'resetDocumentState'],
  180. to: 'loading',
  181. },
  182. RESIZED_WINDOW: 'resetPageState',
  183. RESET_DOCUMENT_STATE: [
  184. 'resetHistory',
  185. 'resetDocumentState',
  186. { to: 'selecting' },
  187. ],
  188. TOGGLED_READ_ONLY: 'toggleReadOnly',
  189. LOADED_FONTS: 'resetShapes',
  190. USED_PEN_DEVICE: 'enablePenLock',
  191. DISABLED_PEN_LOCK: 'disablePenLock',
  192. TOGGLED_CODE_PANEL_OPEN: ['toggleCodePanel', 'saveAppState'],
  193. TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel',
  194. PANNED_CAMERA: {
  195. ifAny: ['isSimulating', 'isTestMode'],
  196. do: 'panCamera',
  197. },
  198. POINTED_CANVAS: ['closeStylePanel', 'clearCurrentParentId'],
  199. COPIED_STATE_TO_CLIPBOARD: 'copyStateToClipboard',
  200. COPIED: { if: 'hasSelection', do: 'copyToClipboard' },
  201. PASTED: {
  202. unlessAny: ['isReadOnly', 'isInSession'],
  203. do: 'pasteFromClipboard',
  204. },
  205. PASTED_SHAPES_FROM_CLIPBOARD: {
  206. unlessAny: ['isReadOnly', 'isInSession'],
  207. do: 'pasteShapesFromClipboard',
  208. },
  209. TOGGLED_DARK_MODE: {
  210. do: ['toggleDarkMode', 'applyTheme'],
  211. },
  212. TOGGLED_SHAPE_LOCK: {
  213. unlessAny: ['isReadOnly', 'isInSession'],
  214. if: 'hasSelection',
  215. do: 'lockSelection',
  216. },
  217. TOGGLED_SHAPE_HIDE: {
  218. unlessAny: ['isReadOnly', 'isInSession'],
  219. if: 'hasSelection',
  220. do: 'hideSelection',
  221. },
  222. TOGGLED_SHAPE_ASPECT_LOCK: {
  223. unlessAny: ['isReadOnly', 'isInSession'],
  224. if: 'hasSelection',
  225. do: 'aspectLockSelection',
  226. },
  227. CHANGED_STYLE: {
  228. unlessAny: ['isReadOnly', 'isInSession'],
  229. do: ['updateStyles', 'applyStylesToSelection'],
  230. },
  231. FORCE_CLEARED_PAGE: ['selectAll', 'deleteSelection'],
  232. CLEARED_PAGE: {
  233. unlessAny: ['isReadOnly', 'isInSession'],
  234. if: 'hasSelection',
  235. do: 'deleteSelection',
  236. else: ['selectAll', 'deleteSelection'],
  237. },
  238. CREATED_PAGE: {
  239. unless: ['isReadOnly', 'isInSession'],
  240. do: ['clearSelectedIds', 'createPage'],
  241. },
  242. DELETED_PAGE: {
  243. unlessAny: ['isReadOnly', 'isInSession', 'hasOnlyOnePage'],
  244. do: 'deletePage',
  245. },
  246. SELECTED_SELECT_TOOL: {
  247. unless: 'isInSession',
  248. to: 'selecting',
  249. },
  250. SELECTED_DRAW_TOOL: {
  251. unlessAny: ['isReadOnly', 'isInSession'],
  252. to: 'draw',
  253. },
  254. SELECTED_ARROW_TOOL: {
  255. unless: ['isReadOnly', 'isInSession'],
  256. to: 'arrow',
  257. },
  258. SELECTED_DOT_TOOL: {
  259. unless: ['isReadOnly', 'isInSession'],
  260. to: 'dot',
  261. },
  262. SELECTED_ELLIPSE_TOOL: {
  263. unless: ['isReadOnly', 'isInSession'],
  264. to: 'ellipse',
  265. },
  266. SELECTED_RAY_TOOL: {
  267. unless: ['isReadOnly', 'isInSession'],
  268. to: 'ray',
  269. },
  270. SELECTED_LINE_TOOL: {
  271. unless: ['isReadOnly', 'isInSession'],
  272. to: 'line',
  273. },
  274. SELECTED_POLYLINE_TOOL: {
  275. unless: ['isReadOnly', 'isInSession'],
  276. to: 'polyline',
  277. },
  278. SELECTED_RECTANGLE_TOOL: {
  279. unless: ['isReadOnly', 'isInSession'],
  280. to: 'rectangle',
  281. },
  282. SELECTED_TEXT_TOOL: {
  283. unless: ['isReadOnly', 'isInSession'],
  284. to: 'text',
  285. },
  286. GENERATED_FROM_CODE: {
  287. unless: ['isReadOnly', 'isInSession'],
  288. do: ['setCodeControls', 'setGeneratedShapes'],
  289. },
  290. UNDO: {
  291. unless: ['isReadOnly', 'isInSession'],
  292. do: 'undo',
  293. },
  294. REDO: {
  295. unless: ['isReadOnly', 'isInSession'],
  296. do: 'redo',
  297. },
  298. SAVED: {
  299. unlessAny: ['isInSession', 'isReadOnly'],
  300. do: ['saveDocumentState', 'saveToFileSystem'],
  301. },
  302. LOADED_FROM_FILE: {
  303. unless: 'isInSession',
  304. do: ['loadDocumentFromJson', 'resetHistory'],
  305. },
  306. DESELECTED_ALL: {
  307. unless: 'isInSession',
  308. do: 'deselectAll',
  309. to: 'selecting',
  310. },
  311. SELECTED_ALL: {
  312. unless: 'isInSession',
  313. do: 'selectAll',
  314. to: 'selecting',
  315. },
  316. CHANGED_PAGE: {
  317. unless: 'isInSession',
  318. do: 'changePage',
  319. },
  320. ZOOMED_TO_ACTUAL: {
  321. if: 'hasSelection',
  322. do: 'zoomCameraToSelectionActual',
  323. else: 'zoomCameraToActual',
  324. },
  325. ZOOMED_CAMERA: 'zoomCamera',
  326. INCREASED_CODE_FONT_SIZE: 'increaseCodeFontSize',
  327. DECREASED_CODE_FONT_SIZE: 'decreaseCodeFontSize',
  328. CHANGED_CODE_CONTROL: { to: 'updatingControls' },
  329. TOGGLED_TOOL_LOCK: 'toggleToolLock',
  330. ZOOMED_TO_SELECTION: {
  331. if: 'hasSelection',
  332. do: 'zoomCameraToSelection',
  333. else: 'zoomCameraToFit',
  334. },
  335. ZOOMED_TO_CONTENT: 'zoomCameraToContent',
  336. ZOOMED_TO_FIT: 'zoomCameraToFit',
  337. ZOOMED_IN: 'zoomIn',
  338. ZOOMED_OUT: 'zoomOut',
  339. RESET_CAMERA: 'resetCamera',
  340. COPIED_TO_SVG: 'copyToSvg',
  341. LOADED_FROM_FILE_STSTEM: 'loadFromFileSystem',
  342. SAVED_AS_TO_FILESYSTEM: ['saveDocumentState', 'saveAsToFileSystem'],
  343. SAVED_TO_FILESYSTEM: [
  344. 'saveDocumentState',
  345. {
  346. unless: 'isReadOnly',
  347. then: {
  348. if: 'isReadOnly',
  349. do: 'saveAsToFileSystem',
  350. else: 'saveToFileSystem',
  351. },
  352. },
  353. ],
  354. },
  355. initial: 'selecting',
  356. states: {
  357. selecting: {
  358. onEnter: ['setActiveToolSelect', 'clearInputs'],
  359. on: {
  360. KEYBOARD_PANNED_CAMERA: {
  361. ifAny: ['isSimulating', 'isTestMode'],
  362. do: 'panCamera',
  363. },
  364. STARTED_PINCHING: {
  365. unless: 'isInSession',
  366. to: 'pinching.selectPinching',
  367. },
  368. DELETED: {
  369. unless: 'isReadOnly',
  370. do: 'deleteSelection',
  371. },
  372. SAVED_CODE: {
  373. unless: 'isReadOnly',
  374. do: 'saveCode',
  375. },
  376. MOVED_TO_PAGE: {
  377. unless: 'isReadOnly',
  378. if: 'hasSelection',
  379. do: ['moveSelectionToPage', 'zoomCameraToSelectionActual'],
  380. },
  381. MOVED: {
  382. unless: 'isReadOnly',
  383. if: 'hasSelection',
  384. do: 'moveSelection',
  385. },
  386. DUPLICATED: {
  387. unless: 'isReadOnly',
  388. if: 'hasSelection',
  389. do: 'duplicateSelection',
  390. },
  391. ROTATED_CCW: {
  392. unless: 'isReadOnly',
  393. if: 'hasSelection',
  394. do: 'rotateSelectionCcw',
  395. },
  396. ALIGNED: {
  397. unless: 'isReadOnly',
  398. if: 'hasMultipleSelection',
  399. do: 'alignSelection',
  400. },
  401. STRETCHED: {
  402. unless: 'isReadOnly',
  403. if: 'hasMultipleSelection',
  404. do: 'stretchSelection',
  405. },
  406. DISTRIBUTED: {
  407. unless: 'isReadOnly',
  408. if: 'hasMultipleSelection',
  409. do: 'distributeSelection',
  410. },
  411. GROUPED: {
  412. unless: 'isReadOnly',
  413. if: 'hasMultipleSelection',
  414. do: 'groupSelection',
  415. },
  416. UNGROUPED: {
  417. unless: 'isReadOnly',
  418. if: ['hasSelection', 'selectionIncludesGroups'],
  419. do: 'ungroupSelection',
  420. },
  421. MOVED_OVER_SHAPE: {
  422. if: 'pointHitsShape',
  423. then: {
  424. unless: 'shapeIsHovered',
  425. do: 'setHoveredId',
  426. },
  427. else: {
  428. if: 'shapeIsHovered',
  429. do: 'clearHoveredId',
  430. },
  431. },
  432. UNHOVERED_SHAPE: 'clearHoveredId',
  433. NUDGED: 'nudgeSelection',
  434. },
  435. initial: 'notPointing',
  436. states: {
  437. notPointing: {
  438. onEnter: 'clearPointedId',
  439. on: {
  440. CANCELLED: {
  441. if: 'hasCurrentParentShape',
  442. do: ['selectCurrentParentId', 'raiseCurrentParentId'],
  443. else: 'clearSelectedIds',
  444. },
  445. POINTED_CANVAS: {
  446. to: 'brushSelecting',
  447. do: 'setCurrentParentIdToPage',
  448. },
  449. DOUBLE_POINTED_CANVAS: [
  450. {
  451. get: 'newText',
  452. do: 'createShape',
  453. },
  454. {
  455. get: 'firstSelectedShape',
  456. if: 'canEditSelectedShape',
  457. do: 'setEditingId',
  458. to: 'editingShape',
  459. },
  460. ],
  461. POINTED_BOUNDS: [
  462. {
  463. if: 'isPressingMetaKey',
  464. to: 'brushSelecting',
  465. },
  466. { to: 'pointingBounds' },
  467. ],
  468. POINTED_BOUNDS_HANDLE: {
  469. unless: 'isReadOnly',
  470. if: 'isPointingRotationHandle',
  471. to: 'rotatingSelection',
  472. else: { to: 'transformingSelection' },
  473. },
  474. STARTED_EDITING_SHAPE: {
  475. unless: 'isReadOnly',
  476. get: 'firstSelectedShape',
  477. if: ['hasSingleSelection', 'canEditSelectedShape'],
  478. do: 'setEditingId',
  479. to: 'editingShape',
  480. },
  481. DOUBLE_POINTED_BOUNDS_HANDLE: {
  482. unless: 'isReadOnly',
  483. if: 'hasSingleSelection',
  484. do: 'resetShapeBounds',
  485. },
  486. POINTED_HANDLE: {
  487. unless: 'isReadOnly',
  488. to: 'translatingHandles',
  489. },
  490. POINTED_SHAPE: [
  491. {
  492. if: 'isPressingMetaKey',
  493. to: 'brushSelecting',
  494. },
  495. 'setPointedId',
  496. {
  497. if: 'pointInSelectionBounds',
  498. to: 'pointingBounds',
  499. },
  500. {
  501. unless: 'isPointedShapeSelected',
  502. then: {
  503. if: 'isPressingShiftKey',
  504. do: ['pushPointedIdToSelectedIds', 'clearPointedId'],
  505. else: ['clearSelectedIds', 'pushPointedIdToSelectedIds'],
  506. },
  507. },
  508. {
  509. to: 'pointingBounds',
  510. },
  511. ],
  512. DOUBLE_POINTED_SHAPE: [
  513. 'setPointedId',
  514. {
  515. if: 'isPointedShapeSelected',
  516. then: {
  517. get: 'firstSelectedShape',
  518. if: 'canEditSelectedShape',
  519. do: 'setEditingId',
  520. to: 'editingShape',
  521. },
  522. },
  523. {
  524. unless: 'isPressingShiftKey',
  525. do: [
  526. 'setCurrentParentId',
  527. 'clearSelectedIds',
  528. 'pushPointedIdToSelectedIds',
  529. ],
  530. to: 'pointingBounds',
  531. },
  532. ],
  533. RIGHT_POINTED: [
  534. {
  535. if: 'isPointingCanvas',
  536. do: 'clearSelectedIds',
  537. else: {
  538. if: 'isPointingShape',
  539. then: [
  540. 'setPointedId',
  541. {
  542. unless: 'isPointedShapeSelected',
  543. do: [
  544. 'clearSelectedIds',
  545. 'pushPointedIdToSelectedIds',
  546. ],
  547. },
  548. ],
  549. },
  550. },
  551. ],
  552. },
  553. },
  554. pointingBounds: {
  555. on: {
  556. CANCELLED: { to: 'notPointing' },
  557. STOPPED_POINTING_BOUNDS: [],
  558. STOPPED_POINTING: [
  559. {
  560. if: 'isPointingBounds',
  561. do: 'clearSelectedIds',
  562. },
  563. {
  564. if: 'isPressingShiftKey',
  565. then: {
  566. if: 'isPointedShapeSelected',
  567. do: 'pullPointedIdFromSelectedIds',
  568. },
  569. else: {
  570. if: 'isPointingShape',
  571. do: [
  572. 'clearSelectedIds',
  573. 'setPointedId',
  574. 'pushPointedIdToSelectedIds',
  575. ],
  576. },
  577. },
  578. { to: 'notPointing' },
  579. ],
  580. MOVED_POINTER: {
  581. unless: ['isReadOnly', 'isInSession'],
  582. if: 'distanceImpliesDrag',
  583. to: 'translatingSelection',
  584. },
  585. },
  586. },
  587. rotatingSelection: {
  588. onEnter: 'startRotateSession',
  589. onExit: ['completeSession', 'clearBoundsRotation'],
  590. on: {
  591. MOVED_POINTER: 'updateRotateSession',
  592. PANNED_CAMERA: 'updateRotateSession',
  593. PRESSED_SHIFT_KEY: 'keyUpdateRotateSession',
  594. RELEASED_SHIFT_KEY: 'keyUpdateRotateSession',
  595. STOPPED_POINTING: { to: 'selecting' },
  596. CANCELLED: { do: 'cancelSession', to: 'selecting' },
  597. },
  598. },
  599. transformingSelection: {
  600. onEnter: 'startTransformSession',
  601. onExit: 'completeSession',
  602. on: {
  603. MOVED_POINTER: {
  604. ifAny: ['isSimulating', 'isTestMode'],
  605. do: 'updateTransformSession',
  606. },
  607. PANNED_CAMERA: {
  608. ifAny: ['isSimulating', 'isTestMode'],
  609. do: 'updateTransformSession',
  610. },
  611. PRESSED_SHIFT_KEY: 'keyUpdateTransformSession',
  612. RELEASED_SHIFT_KEY: 'keyUpdateTransformSession',
  613. STOPPED_POINTING: { to: 'selecting' },
  614. CANCELLED: { do: 'cancelSession', to: 'selecting' },
  615. },
  616. },
  617. translatingSelection: {
  618. onEnter: 'startTranslateSession',
  619. onExit: 'completeSession',
  620. on: {
  621. STARTED_PINCHING: { to: 'pinching' },
  622. MOVED_POINTER: {
  623. ifAny: ['isSimulating', 'isTestMode'],
  624. do: 'updateTranslateSession',
  625. },
  626. PANNED_CAMERA: {
  627. ifAny: ['isSimulating', 'isTestMode'],
  628. do: 'updateTranslateSession',
  629. },
  630. PRESSED_SHIFT_KEY: 'keyUpdateTranslateSession',
  631. RELEASED_SHIFT_KEY: 'keyUpdateTranslateSession',
  632. PRESSED_ALT_KEY: 'keyUpdateTranslateSession',
  633. RELEASED_ALT_KEY: 'keyUpdateTranslateSession',
  634. STOPPED_POINTING: { to: 'selecting' },
  635. CANCELLED: { do: 'cancelSession', to: 'selecting' },
  636. },
  637. },
  638. translatingHandles: {
  639. onEnter: 'startHandleSession',
  640. onExit: 'completeSession',
  641. on: {
  642. MOVED_POINTER: 'updateHandleSession',
  643. PANNED_CAMERA: 'updateHandleSession',
  644. PRESSED_SHIFT_KEY: 'keyUpdateHandleSession',
  645. RELEASED_SHIFT_KEY: 'keyUpdateHandleSession',
  646. STOPPED_POINTING: { to: 'selecting' },
  647. DOUBLE_POINTED_HANDLE: {
  648. do: ['cancelSession', 'doublePointHandle'],
  649. to: 'selecting',
  650. },
  651. CANCELLED: { do: 'cancelSession', to: 'selecting' },
  652. },
  653. },
  654. brushSelecting: {
  655. onExit: 'completeSession',
  656. onEnter: [
  657. {
  658. unless: ['isPressingMetaKey', 'isPressingShiftKey'],
  659. do: 'clearSelectedIds',
  660. },
  661. 'clearBoundsRotation',
  662. 'startBrushSession',
  663. ],
  664. on: {
  665. MOVED_POINTER: {
  666. ifAny: ['isSimulating', 'isTestMode'],
  667. do: 'updateBrushSession',
  668. },
  669. PANNED_CAMERA: {
  670. ifAny: ['isSimulating', 'isTestMode'],
  671. do: 'updateBrushSession',
  672. },
  673. STOPPED_POINTING: { to: 'selecting' },
  674. STARTED_PINCHING: { to: 'pinching' },
  675. CANCELLED: { do: 'cancelSession', to: 'selecting' },
  676. },
  677. },
  678. updatingControls: {
  679. onEnter: 'updateControls',
  680. async: {
  681. await: 'getUpdatedShapes',
  682. onResolve: { do: 'updateGeneratedShapes', to: 'selecting' },
  683. },
  684. },
  685. },
  686. },
  687. editingShape: {
  688. onEnter: ['startEditSession', 'clearHoveredId'],
  689. onExit: ['completeSession', 'clearEditingId'],
  690. on: {
  691. EDITED_SHAPE: { do: 'updateEditSession' },
  692. POINTED_SHAPE: [
  693. {
  694. unless: 'isPointingEditingShape',
  695. if: 'isPointingTextShape',
  696. do: [
  697. 'completeSession',
  698. 'clearEditingId',
  699. 'setPointedId',
  700. 'clearSelectedIds',
  701. 'pushPointedIdToSelectedIds',
  702. 'setEditingId',
  703. 'startEditSession',
  704. ],
  705. },
  706. ],
  707. BLURRED_EDITING_SHAPE: [
  708. {
  709. unless: 'isEditingShape',
  710. get: 'editingShape',
  711. if: 'shouldDeleteShape',
  712. do: ['cancelSession', 'deleteSelection'],
  713. },
  714. { to: 'selecting' },
  715. ],
  716. POINTED_CANVAS: [
  717. {
  718. unless: 'isEditingShape',
  719. get: 'editingShape',
  720. if: 'shouldDeleteShape',
  721. do: ['cancelSession', 'deleteSelection'],
  722. },
  723. { to: 'selecting' },
  724. ],
  725. CANCELLED: [
  726. {
  727. unless: 'isEditingShape',
  728. get: 'editingShape',
  729. if: 'shouldDeleteShape',
  730. do: ['cancelSession', 'deleteSelection'],
  731. },
  732. { to: 'selecting' },
  733. ],
  734. },
  735. },
  736. pinching: {
  737. on: {
  738. PINCHED: { if: 'isTestMode', do: 'pinchCamera' },
  739. },
  740. initial: 'selectPinching',
  741. onExit: { secretlyDo: 'updateZoomCSS' },
  742. states: {
  743. selectPinching: {
  744. on: {
  745. STOPPED_PINCHING: { to: 'selecting' },
  746. },
  747. },
  748. toolPinching: {
  749. on: {
  750. STOPPED_PINCHING: { to: 'usingTool.previous' },
  751. },
  752. },
  753. },
  754. },
  755. usingTool: {
  756. initial: 'draw',
  757. onEnter: 'clearSelectedIds',
  758. on: {
  759. STARTED_PINCHING: {
  760. do: 'breakSession',
  761. to: 'pinching.toolPinching',
  762. },
  763. },
  764. states: {
  765. draw: {
  766. onEnter: 'setActiveToolDraw',
  767. initial: 'creating',
  768. states: {
  769. creating: {
  770. on: {
  771. CANCELLED: { to: 'selecting' },
  772. POINTED_SHAPE: {
  773. get: 'newDraw',
  774. do: 'createShape',
  775. to: 'draw.editing',
  776. },
  777. POINTED_CANVAS: {
  778. get: 'newDraw',
  779. do: 'createShape',
  780. to: 'draw.editing',
  781. },
  782. STOPPED_POINTING: {
  783. to: 'draw.creating',
  784. },
  785. },
  786. },
  787. editing: {
  788. onEnter: 'startDrawSession',
  789. onExit: 'completeSession',
  790. on: {
  791. CANCELLED: {
  792. do: 'breakSession',
  793. to: 'selecting',
  794. },
  795. STOPPED_POINTING: {
  796. do: 'completeSession',
  797. to: 'draw.creating',
  798. },
  799. PRESSED_SHIFT: 'keyUpdateDrawSession',
  800. RELEASED_SHIFT: 'keyUpdateDrawSession',
  801. PANNED_CAMERA: {
  802. ifAny: ['isSimulating', 'isTestMode'],
  803. do: 'updateDrawSession',
  804. },
  805. MOVED_POINTER: {
  806. ifAny: ['isSimulating', 'isTestMode'],
  807. do: 'updateDrawSession',
  808. },
  809. },
  810. },
  811. },
  812. },
  813. dot: {
  814. onEnter: 'setActiveToolDot',
  815. initial: 'creating',
  816. states: {
  817. creating: {
  818. on: {
  819. CANCELLED: { to: 'selecting' },
  820. POINTED_SHAPE: {
  821. get: 'newDot',
  822. do: 'createShape',
  823. to: 'dot.editing',
  824. },
  825. POINTED_CANVAS: {
  826. get: 'newDot',
  827. do: 'createShape',
  828. to: 'dot.editing',
  829. },
  830. },
  831. },
  832. editing: {
  833. on: {
  834. STOPPED_POINTING: [
  835. 'completeSession',
  836. {
  837. if: 'isToolLocked',
  838. to: 'dot.creating',
  839. else: {
  840. to: 'selecting',
  841. },
  842. },
  843. ],
  844. CANCELLED: {
  845. do: 'breakSession',
  846. to: 'selecting',
  847. },
  848. },
  849. initial: 'inactive',
  850. states: {
  851. inactive: {
  852. on: {
  853. MOVED_POINTER: {
  854. if: 'distanceImpliesDrag',
  855. to: 'dot.editing.active',
  856. },
  857. },
  858. },
  859. active: {
  860. onExit: 'completeSession',
  861. onEnter: 'startTranslateSession',
  862. on: {
  863. MOVED_POINTER: {
  864. ifAny: ['isSimulating', 'isTestMode'],
  865. do: 'updateTranslateSession',
  866. },
  867. PANNED_CAMERA: {
  868. ifAny: ['isSimulating', 'isTestMode'],
  869. do: 'updateTranslateSession',
  870. },
  871. },
  872. },
  873. },
  874. },
  875. },
  876. },
  877. arrow: {
  878. onEnter: 'setActiveToolArrow',
  879. initial: 'idle',
  880. states: {
  881. idle: {
  882. on: {
  883. CANCELLED: { to: 'selecting' },
  884. POINTED_SHAPE: {
  885. get: 'newArrow',
  886. do: 'createShape',
  887. to: 'arrow.editing',
  888. },
  889. POINTED_CANVAS: {
  890. get: 'newArrow',
  891. do: 'createShape',
  892. to: 'arrow.editing',
  893. },
  894. },
  895. },
  896. editing: {
  897. onExit: 'completeSession',
  898. onEnter: 'startArrowHandleSession',
  899. on: {
  900. STOPPED_POINTING: [
  901. 'completeSession',
  902. {
  903. if: 'isToolLocked',
  904. to: 'arrow.creating',
  905. else: { to: 'selecting' },
  906. },
  907. ],
  908. CANCELLED: {
  909. do: 'breakSession',
  910. if: 'isToolLocked',
  911. to: 'arrow.creating',
  912. else: { to: 'selecting' },
  913. },
  914. PRESSED_SHIFT: 'keyUpdateHandleSession',
  915. RELEASED_SHIFT: 'keyUpdateHandleSession',
  916. MOVED_POINTER: 'updateHandleSession',
  917. PANNED_CAMERA: 'updateHandleSession',
  918. },
  919. },
  920. },
  921. },
  922. ellipse: {
  923. onEnter: 'setActiveToolEllipse',
  924. initial: 'creating',
  925. states: {
  926. creating: {
  927. on: {
  928. CANCELLED: { to: 'selecting' },
  929. POINTED_CANVAS: {
  930. to: 'ellipse.editing',
  931. },
  932. },
  933. },
  934. editing: {
  935. on: {
  936. STOPPED_POINTING: { to: 'selecting' },
  937. CANCELLED: { to: 'selecting' },
  938. MOVED_POINTER: {
  939. if: 'distanceImpliesDrag',
  940. then: {
  941. get: 'newEllipse',
  942. do: 'createShape',
  943. to: 'drawingShape.bounds',
  944. },
  945. },
  946. },
  947. },
  948. },
  949. },
  950. rectangle: {
  951. onEnter: 'setActiveToolRectangle',
  952. initial: 'creating',
  953. states: {
  954. creating: {
  955. on: {
  956. CANCELLED: { to: 'selecting' },
  957. POINTED_SHAPE: {
  958. to: 'rectangle.editing',
  959. },
  960. POINTED_CANVAS: {
  961. to: 'rectangle.editing',
  962. },
  963. },
  964. },
  965. editing: {
  966. on: {
  967. STOPPED_POINTING: { to: 'selecting' },
  968. CANCELLED: { to: 'selecting' },
  969. MOVED_POINTER: {
  970. if: 'distanceImpliesDrag',
  971. then: {
  972. get: 'newRectangle',
  973. do: 'createShape',
  974. to: 'drawingShape.bounds',
  975. },
  976. },
  977. },
  978. },
  979. },
  980. },
  981. text: {
  982. onEnter: 'setActiveToolText',
  983. initial: 'creating',
  984. states: {
  985. creating: {
  986. on: {
  987. CANCELLED: { to: 'selecting' },
  988. POINTED_SHAPE: [
  989. {
  990. if: 'isPointingTextShape',
  991. unless: 'isPressingShiftKey',
  992. do: [
  993. 'clearSelectedIds',
  994. 'pushPointedIdToSelectedIds',
  995. 'setEditingId',
  996. ],
  997. to: 'editingShape',
  998. },
  999. {
  1000. get: 'newText',
  1001. do: 'createShape',
  1002. },
  1003. {
  1004. get: 'firstSelectedShape',
  1005. if: 'canEditSelectedShape',
  1006. do: 'setEditingId',
  1007. to: 'editingShape',
  1008. },
  1009. ],
  1010. POINTED_CANVAS: [
  1011. {
  1012. get: 'newText',
  1013. do: 'createShape',
  1014. },
  1015. {
  1016. get: 'firstSelectedShape',
  1017. if: 'canEditSelectedShape',
  1018. do: 'setEditingId',
  1019. to: 'editingShape',
  1020. },
  1021. ],
  1022. },
  1023. },
  1024. },
  1025. },
  1026. ray: {
  1027. onEnter: 'setActiveToolRay',
  1028. initial: 'creating',
  1029. states: {
  1030. creating: {
  1031. on: {
  1032. CANCELLED: { to: 'selecting' },
  1033. POINTED_SHAPE: {
  1034. get: 'newRay',
  1035. do: 'createShape',
  1036. to: 'ray.editing',
  1037. },
  1038. POINTED_CANVAS: {
  1039. get: 'newRay',
  1040. do: 'createShape',
  1041. to: 'ray.editing',
  1042. },
  1043. },
  1044. },
  1045. editing: {
  1046. on: {
  1047. STOPPED_POINTING: { to: 'selecting' },
  1048. CANCELLED: { to: 'selecting' },
  1049. MOVED_POINTER: {
  1050. if: 'distanceImpliesDrag',
  1051. to: 'drawingShape.direction',
  1052. },
  1053. },
  1054. },
  1055. },
  1056. },
  1057. line: {
  1058. onEnter: 'setActiveToolLine',
  1059. initial: 'creating',
  1060. states: {
  1061. creating: {
  1062. on: {
  1063. CANCELLED: { to: 'selecting' },
  1064. POINTED_SHAPE: {
  1065. get: 'newLine',
  1066. do: 'createShape',
  1067. to: 'line.editing',
  1068. },
  1069. POINTED_CANVAS: {
  1070. get: 'newLine',
  1071. do: 'createShape',
  1072. to: 'line.editing',
  1073. },
  1074. },
  1075. },
  1076. editing: {
  1077. on: {
  1078. STOPPED_POINTING: { to: 'selecting' },
  1079. CANCELLED: { to: 'selecting' },
  1080. MOVED_POINTER: {
  1081. if: 'distanceImpliesDrag',
  1082. to: 'drawingShape.direction',
  1083. },
  1084. },
  1085. },
  1086. },
  1087. },
  1088. polyline: {
  1089. onEnter: 'setActiveToolPolyline',
  1090. },
  1091. },
  1092. },
  1093. drawingShape: {
  1094. onExit: 'completeSession',
  1095. on: {
  1096. STOPPED_POINTING: [
  1097. 'completeSession',
  1098. {
  1099. if: 'isToolLocked',
  1100. to: 'usingTool.previous',
  1101. else: { to: 'selecting' },
  1102. },
  1103. ],
  1104. CANCELLED: {
  1105. do: 'breakSession',
  1106. to: 'selecting',
  1107. },
  1108. },
  1109. initial: 'bounds',
  1110. states: {
  1111. bounds: {
  1112. onEnter: 'startDrawTransformSession',
  1113. on: {
  1114. MOVED_POINTER: {
  1115. do: 'updateTransformSession',
  1116. },
  1117. PANNED_CAMERA: {
  1118. do: 'updateTransformSession',
  1119. },
  1120. },
  1121. },
  1122. direction: {
  1123. onEnter: 'startDirectionSession',
  1124. onExit: 'completeSession',
  1125. on: {
  1126. MOVED_POINTER: {
  1127. do: 'updateDirectionSession',
  1128. },
  1129. PANNED_CAMERA: {
  1130. do: 'updateDirectionSession',
  1131. },
  1132. },
  1133. },
  1134. },
  1135. },
  1136. },
  1137. },
  1138. },
  1139. results: {
  1140. newDot() {
  1141. return ShapeType.Dot
  1142. },
  1143. newRay() {
  1144. return ShapeType.Ray
  1145. },
  1146. newLine() {
  1147. return ShapeType.Line
  1148. },
  1149. newText() {
  1150. return ShapeType.Text
  1151. },
  1152. newDraw() {
  1153. return ShapeType.Draw
  1154. },
  1155. newArrow() {
  1156. return ShapeType.Arrow
  1157. },
  1158. newEllipse() {
  1159. return ShapeType.Ellipse
  1160. },
  1161. newRectangle() {
  1162. return ShapeType.Rectangle
  1163. },
  1164. firstSelectedShape(data) {
  1165. return tld.getSelectedShapes(data)[0]
  1166. },
  1167. editingShape(data) {
  1168. return tld.getShape(data, data.editingId)
  1169. },
  1170. },
  1171. conditions: {
  1172. isSimulating() {
  1173. return logger.isSimulating
  1174. },
  1175. isTestMode(data) {
  1176. return data.settings.isTestMode
  1177. },
  1178. isEditingShape(data, payload: { id: string }) {
  1179. return payload.id === data.editingId
  1180. },
  1181. shouldDeleteShape(data, payload, shape: Shape) {
  1182. return getShapeUtils(shape).shouldDelete(shape)
  1183. },
  1184. isPointingCanvas(data, payload: PointerInfo) {
  1185. return payload.target === 'canvas'
  1186. },
  1187. isPointingEditingShape(data, payload: { target: string }) {
  1188. return payload.target === data.editingId
  1189. },
  1190. isPointingTextShape(data, payload: { target: string }) {
  1191. return tld.getShape(data, payload.target)?.type === ShapeType.Text
  1192. },
  1193. isPointingBounds(data, payload: PointerInfo) {
  1194. return tld.getSelectedIds(data).length > 0 && payload.target === 'bounds'
  1195. },
  1196. isPointingShape(data, payload: PointerInfo) {
  1197. return (
  1198. payload.target &&
  1199. payload.target !== 'canvas' &&
  1200. payload.target !== 'bounds'
  1201. )
  1202. },
  1203. isReadOnly(data) {
  1204. return data.isReadOnly
  1205. },
  1206. isInSession() {
  1207. return session.isInSession
  1208. },
  1209. canEditSelectedShape(data, payload, result: Shape) {
  1210. return getShapeUtils(result).canEdit && !result.isLocked
  1211. },
  1212. distanceImpliesDrag(data, payload: PointerInfo) {
  1213. return vec.dist2(payload.origin, payload.point) > 8
  1214. },
  1215. hasPointedTarget(data, payload: PointerInfo) {
  1216. return payload.target !== undefined
  1217. },
  1218. isPointedShapeSelected(data) {
  1219. return tld.getSelectedIds(data).includes(data.pointedId)
  1220. },
  1221. isPressingShiftKey(data, payload: PointerInfo) {
  1222. return payload.shiftKey
  1223. },
  1224. isPressingMetaKey(data, payload: PointerInfo) {
  1225. return payload.metaKey
  1226. },
  1227. shapeIsHovered(data, payload: { target: string }) {
  1228. return data.hoveredId === payload.target
  1229. },
  1230. pointInSelectionBounds(data, payload: PointerInfo) {
  1231. const bounds = getSelectionBounds(data)
  1232. if (!bounds) return false
  1233. return pointInBounds(tld.screenToWorld(payload.point, data), bounds)
  1234. },
  1235. pointHitsShape(data, payload: PointerInfo) {
  1236. const shape = tld.getShape(data, payload.target)
  1237. return getShapeUtils(shape).hitTest(
  1238. shape,
  1239. tld.screenToWorld(payload.point, data)
  1240. )
  1241. },
  1242. hasPointedId(data, payload: PointerInfo) {
  1243. return tld.getShape(data, payload.target) !== undefined
  1244. },
  1245. isPointingRotationHandle(
  1246. _data,
  1247. payload: { target: Edge | Corner | 'rotate' }
  1248. ) {
  1249. return payload.target === 'rotate'
  1250. },
  1251. hasSelection(data) {
  1252. return tld.getSelectedIds(data).length > 0
  1253. },
  1254. hasSingleSelection(data) {
  1255. return tld.getSelectedIds(data).length === 1
  1256. },
  1257. hasMultipleSelection(data) {
  1258. return tld.getSelectedIds(data).length > 1
  1259. },
  1260. hasCurrentParentShape(data) {
  1261. return data.currentParentId !== data.currentPageId
  1262. },
  1263. isToolLocked(data) {
  1264. return data.settings.isToolLocked
  1265. },
  1266. isPenLocked(data) {
  1267. return data.settings.isPenLocked
  1268. },
  1269. hasOnlyOnePage(data) {
  1270. return Object.keys(data.document.pages).length === 1
  1271. },
  1272. selectionIncludesGroups(data) {
  1273. return tld
  1274. .getSelectedShapes(data)
  1275. .some((shape) => shape.type === ShapeType.Group)
  1276. },
  1277. },
  1278. actions: {
  1279. /* ---------------------- Debug --------------------- */
  1280. closeDebugPanel(data) {
  1281. data.settings.isDebugOpen = false
  1282. },
  1283. openDebugPanel(data) {
  1284. data.settings.isDebugOpen = true
  1285. },
  1286. toggleDebugMode(data) {
  1287. data.settings.isDebugMode = !data.settings.isDebugMode
  1288. },
  1289. toggleTestMode(data) {
  1290. data.settings.isTestMode = !data.settings.isTestMode
  1291. },
  1292. toggleDebugPanel(data) {
  1293. data.settings.isDebugOpen = !data.settings.isDebugOpen
  1294. },
  1295. toggleLogger(data) {
  1296. if (logger.isRunning) {
  1297. logger.stop(data)
  1298. } else {
  1299. logger.start(data)
  1300. }
  1301. },
  1302. copyDebugLog() {
  1303. logger.copyToJson()
  1304. },
  1305. // Networked Room
  1306. addRtShape(data, payload: { pageId: string; shape: Shape }) {
  1307. const { pageId, shape } = payload
  1308. // What if the page is in storage?
  1309. data.document.pages[pageId].shapes[shape.id] = shape
  1310. },
  1311. deleteRtShape(data, payload: { pageId: string; shapeId: string }) {
  1312. const { pageId, shapeId } = payload
  1313. // What if the page is in storage?
  1314. delete data.document[pageId].shapes[shapeId]
  1315. },
  1316. editRtShape(data, payload: { pageId: string; shape: Shape }) {
  1317. const { pageId, shape } = payload
  1318. // What if the page is in storage?
  1319. Object.assign(data.document[pageId].shapes[shape.id], shape)
  1320. },
  1321. clearRoom(data) {
  1322. data.room = undefined
  1323. },
  1324. resetStorage() {
  1325. storage.reset()
  1326. },
  1327. resetDocumentState(data, payload: { roomId?: string }) {
  1328. // Save the current document and app state.
  1329. storage.savePage(data)
  1330. storage.savePageState(data)
  1331. storage.saveAppStateToLocalStorage(data)
  1332. storage.saveDocumentToLocalStorage(data)
  1333. // Cancel all current sessions, reset history, etc..
  1334. session.cancel(data)
  1335. inputs.reset()
  1336. history.reset()
  1337. storage.reset()
  1338. // Populate a new app state.
  1339. const newDocumentId = payload?.roomId ? payload.roomId : uniqueId()
  1340. const newPageId = 'page1'
  1341. data.document.id = newDocumentId
  1342. data.pointedId = null
  1343. data.hoveredId = null
  1344. data.editingId = null
  1345. data.currentPageId = newPageId
  1346. data.currentParentId = newPageId
  1347. data.currentCodeFileId = 'file0'
  1348. data.codeControls = {}
  1349. data.document.pages = {
  1350. [newPageId]: {
  1351. id: newPageId,
  1352. name: 'Page 1',
  1353. type: 'page',
  1354. shapes: {},
  1355. childIndex: 1,
  1356. },
  1357. }
  1358. data.pageStates = {
  1359. [newPageId]: {
  1360. id: newPageId,
  1361. selectedIds: [],
  1362. camera: {
  1363. point: [0, 0],
  1364. zoom: 1,
  1365. },
  1366. },
  1367. }
  1368. // Save the new app state.
  1369. storage.savePage(data)
  1370. storage.savePageState(data)
  1371. storage.saveAppStateToLocalStorage(data)
  1372. storage.saveDocumentToLocalStorage(data)
  1373. },
  1374. resetPageState(data) {
  1375. const pageState = data.pageStates[data.currentPageId]
  1376. data.pageStates[data.currentPageId] = { ...pageState }
  1377. },
  1378. toggleReadOnly(data) {
  1379. data.isReadOnly = !data.isReadOnly
  1380. },
  1381. /* ---------------------- Pages --------------------- */
  1382. changePage(data, payload: { id: string }) {
  1383. commands.changePage(data, payload.id)
  1384. },
  1385. createPage(data) {
  1386. commands.createPage(data, true)
  1387. },
  1388. deletePage(data, payload: { id: string }) {
  1389. commands.deletePage(data, payload.id)
  1390. },
  1391. /* --------------------- Shapes --------------------- */
  1392. resetShapes(data) {
  1393. const page = tld.getPage(data)
  1394. Object.values(page.shapes).forEach((shape) => {
  1395. page.shapes[shape.id] = { ...shape }
  1396. })
  1397. },
  1398. createShape(data, payload, type: ShapeType) {
  1399. const style = deepClone(data.currentStyle)
  1400. let point = tld.screenToWorld(payload.point, data)
  1401. if (type === ShapeType.Text) {
  1402. point = vec.sub(point, vec.mul([0, 1], getFontSize(style.size) * 0.8))
  1403. }
  1404. const shape = createShape(type, {
  1405. id: uniqueId(),
  1406. parentId: data.currentPageId,
  1407. point: vec.round(point),
  1408. style: deepClone(data.currentStyle),
  1409. })
  1410. const siblings = tld.getChildren(data, shape.parentId)
  1411. const childIndex = siblings.length
  1412. ? siblings[siblings.length - 1].childIndex + 1
  1413. : 1
  1414. data.editingId = shape.id
  1415. getShapeUtils(shape).setProperty(shape, 'childIndex', childIndex)
  1416. tld.getPage(data).shapes[shape.id] = shape
  1417. tld.setSelectedIds(data, [shape.id])
  1418. },
  1419. /* -------------------- Sessions -------------------- */
  1420. // Shared
  1421. breakSession(data) {
  1422. session.cancel(data)
  1423. history.disable()
  1424. commands.deleteShapes(data, tld.getSelectedShapes(data))
  1425. history.enable()
  1426. },
  1427. cancelSession(data) {
  1428. session.cancel(data)
  1429. },
  1430. completeSession(data) {
  1431. session.complete(data)
  1432. },
  1433. // Editing
  1434. startEditSession(data) {
  1435. session.begin(new Sessions.EditSession(data))
  1436. },
  1437. updateEditSession(data, payload: { change: Partial<Shape> }) {
  1438. session.update<Sessions.EditSession>(data, payload.change)
  1439. },
  1440. // Brushing
  1441. startBrushSession(data, payload: PointerInfo) {
  1442. session.begin(
  1443. new Sessions.BrushSession(data, tld.screenToWorld(payload.point, data))
  1444. )
  1445. },
  1446. updateBrushSession(data, payload: PointerInfo) {
  1447. session.update<Sessions.BrushSession>(
  1448. data,
  1449. tld.screenToWorld(payload.point, data)
  1450. )
  1451. },
  1452. // Rotating
  1453. startRotateSession(data, payload: PointerInfo) {
  1454. session.begin(
  1455. new Sessions.RotateSession(data, tld.screenToWorld(payload.point, data))
  1456. )
  1457. },
  1458. keyUpdateRotateSession(data, payload: PointerInfo) {
  1459. session.update<Sessions.RotateSession>(
  1460. data,
  1461. tld.screenToWorld(inputs.pointer.point, data),
  1462. payload.shiftKey
  1463. )
  1464. },
  1465. updateRotateSession(data, payload: PointerInfo) {
  1466. session.update<Sessions.RotateSession>(
  1467. data,
  1468. tld.screenToWorld(payload.point, data),
  1469. payload.shiftKey
  1470. )
  1471. },
  1472. // Dragging / Translating
  1473. startTranslateSession(data) {
  1474. session.begin(
  1475. new Sessions.TranslateSession(
  1476. data,
  1477. tld.screenToWorld(inputs.pointer.origin, data)
  1478. )
  1479. )
  1480. },
  1481. keyUpdateTranslateSession(
  1482. data,
  1483. payload: { shiftKey: boolean; altKey: boolean }
  1484. ) {
  1485. session.update<Sessions.TranslateSession>(
  1486. data,
  1487. tld.screenToWorld(inputs.pointer.point, data),
  1488. payload.shiftKey,
  1489. payload.altKey
  1490. )
  1491. },
  1492. updateTranslateSession(data, payload: PointerInfo) {
  1493. session.update<Sessions.TranslateSession>(
  1494. data,
  1495. tld.screenToWorld(payload.point, data),
  1496. payload.shiftKey,
  1497. payload.altKey
  1498. )
  1499. },
  1500. // Handles
  1501. doublePointHandle(data, payload: PointerInfo) {
  1502. const id = tld.getSelectedIds(data)[0]
  1503. commands.doublePointHandle(data, id, payload)
  1504. },
  1505. // Dragging Handle
  1506. startHandleSession(data, payload: PointerInfo) {
  1507. const shapeId = Array.from(tld.getSelectedIds(data).values())[0]
  1508. const handleId = payload.target
  1509. session.begin(
  1510. new Sessions.HandleSession(
  1511. data,
  1512. shapeId,
  1513. handleId,
  1514. tld.screenToWorld(inputs.pointer.origin, data),
  1515. false
  1516. )
  1517. )
  1518. },
  1519. keyUpdateHandleSession(
  1520. data,
  1521. payload: { shiftKey: boolean; altKey: boolean; metaKey: boolean }
  1522. ) {
  1523. session.update<Sessions.HandleSession>(
  1524. data,
  1525. tld.screenToWorld(inputs.pointer.point, data),
  1526. payload.shiftKey,
  1527. payload.altKey,
  1528. payload.metaKey
  1529. )
  1530. },
  1531. updateHandleSession(data, payload: PointerInfo) {
  1532. session.update<Sessions.HandleSession>(
  1533. data,
  1534. tld.screenToWorld(payload.point, data),
  1535. payload.shiftKey,
  1536. payload.altKey,
  1537. payload.metaKey
  1538. )
  1539. },
  1540. // Transforming
  1541. startTransformSession(
  1542. data,
  1543. payload: PointerInfo & { target: Corner | Edge }
  1544. ) {
  1545. const point = tld.screenToWorld(inputs.pointer.origin, data)
  1546. session.begin(
  1547. tld.getSelectedIds(data).length === 1
  1548. ? new Sessions.TransformSingleSession(data, payload.target, point)
  1549. : new Sessions.TransformSession(data, payload.target, point)
  1550. )
  1551. },
  1552. startDrawTransformSession(data, payload: PointerInfo) {
  1553. session.begin(
  1554. new Sessions.TransformSingleSession(
  1555. data,
  1556. Corner.BottomRight,
  1557. tld.screenToWorld(payload.point, data),
  1558. true
  1559. )
  1560. )
  1561. },
  1562. keyUpdateTransformSession(data, payload: PointerInfo) {
  1563. session.update<Sessions.TransformSession>(
  1564. data,
  1565. tld.screenToWorld(inputs.pointer.point, data),
  1566. payload.shiftKey
  1567. )
  1568. },
  1569. updateTransformSession(data, payload: PointerInfo) {
  1570. session.update<Sessions.TransformSession>(
  1571. data,
  1572. tld.screenToWorld(payload.point, data),
  1573. payload.shiftKey
  1574. )
  1575. },
  1576. // Direction
  1577. startDirectionSession(data) {
  1578. session.begin(
  1579. new Sessions.DirectionSession(
  1580. data,
  1581. tld.screenToWorld(inputs.pointer.origin, data)
  1582. )
  1583. )
  1584. },
  1585. updateDirectionSession(data, payload: PointerInfo) {
  1586. session.update<Sessions.DirectionSession>(
  1587. data,
  1588. tld.screenToWorld(payload.point, data)
  1589. )
  1590. },
  1591. // Drawing
  1592. startDrawSession(data) {
  1593. const id = Array.from(tld.getSelectedIds(data).values())[0]
  1594. session.begin(
  1595. new Sessions.DrawSession(
  1596. data,
  1597. id,
  1598. tld.screenToWorld(inputs.pointer.origin, data)
  1599. )
  1600. )
  1601. },
  1602. keyUpdateDrawSession(data, payload: PointerInfo) {
  1603. session.update<Sessions.DrawSession>(
  1604. data,
  1605. tld.screenToWorld(inputs.pointer.point, data),
  1606. payload.pressure,
  1607. payload.shiftKey
  1608. )
  1609. },
  1610. updateDrawSession(data, payload: PointerInfo) {
  1611. session.update<Sessions.DrawSession>(
  1612. data,
  1613. tld.screenToWorld(payload.point, data),
  1614. payload.pressure,
  1615. payload.shiftKey
  1616. )
  1617. },
  1618. // Arrow
  1619. startArrowHandleSession(data) {
  1620. const shapeId = Array.from(tld.getSelectedIds(data).values())[0]
  1621. const handleId = 'end'
  1622. session.begin(
  1623. new Sessions.HandleSession(
  1624. data,
  1625. shapeId,
  1626. handleId,
  1627. tld.screenToWorld(inputs.pointer.origin, data),
  1628. true
  1629. )
  1630. )
  1631. },
  1632. /* -------------------- Selection ------------------- */
  1633. // Nudges
  1634. nudgeSelection(data, payload: { delta: number[]; shiftKey: boolean }) {
  1635. commands.nudge(
  1636. data,
  1637. vec.mul(
  1638. payload.delta,
  1639. payload.shiftKey
  1640. ? data.settings.nudgeDistanceLarge
  1641. : data.settings.nudgeDistanceSmall
  1642. )
  1643. )
  1644. },
  1645. clearInputs() {
  1646. inputs.clear()
  1647. },
  1648. deselectAll(data) {
  1649. tld.setSelectedIds(data, [])
  1650. },
  1651. selectAll(data) {
  1652. tld.setSelectedIds(
  1653. data,
  1654. tld
  1655. .getShapes(data)
  1656. .filter((shape) => shape.parentId === data.currentPageId)
  1657. .map((shape) => shape.id)
  1658. )
  1659. },
  1660. setHoveredId(data, payload: PointerInfo) {
  1661. data.hoveredId = payload.target
  1662. },
  1663. clearHoveredId(data) {
  1664. data.hoveredId = undefined
  1665. },
  1666. setPointedId(data, payload: PointerInfo) {
  1667. data.pointedId = getPointedId(data, payload.target)
  1668. data.currentParentId = getParentId(data, data.pointedId)
  1669. },
  1670. setCurrentParentId(data, payload: PointerInfo) {
  1671. data.pointedId = getDrilledPointedId(data, payload.target)
  1672. data.currentParentId = getParentId(data, data.pointedId)
  1673. },
  1674. raiseCurrentParentId(data) {
  1675. const currentParent = tld.getShape(data, data.currentParentId)
  1676. data.currentParentId =
  1677. currentParent.parentId === data.currentPageId
  1678. ? data.currentPageId
  1679. : currentParent.parentId
  1680. },
  1681. setCurrentParentIdToPage(data) {
  1682. data.currentParentId = data.currentPageId
  1683. },
  1684. selectCurrentParentId(data) {
  1685. tld.setSelectedIds(data, [data.currentParentId])
  1686. },
  1687. clearCurrentParentId(data) {
  1688. data.currentParentId = data.currentPageId
  1689. data.pointedId = undefined
  1690. },
  1691. clearPointedId(data) {
  1692. data.pointedId = undefined
  1693. },
  1694. clearSelectedIds(data) {
  1695. tld.setSelectedIds(data, [])
  1696. },
  1697. selectId(data, payload: PointerInfo) {
  1698. tld.setSelectedIds(data, [payload.target])
  1699. },
  1700. pullPointedIdFromSelectedIds(data) {
  1701. const { pointedId } = data
  1702. const selectedIds = tld.getSelectedIds(data)
  1703. selectedIds.splice(selectedIds.indexOf(pointedId), 1)
  1704. },
  1705. pushPointedIdToSelectedIds(data) {
  1706. tld.getSelectedIds(data).push(data.pointedId)
  1707. },
  1708. moveSelection(data, payload: { type: MoveType }) {
  1709. commands.move(data, payload.type)
  1710. },
  1711. moveSelectionToPage(data, payload: { id: string }) {
  1712. commands.moveToPage(data, payload.id)
  1713. },
  1714. alignSelection(data, payload: { type: AlignType }) {
  1715. commands.align(data, payload.type)
  1716. },
  1717. stretchSelection(data, payload: { type: StretchType }) {
  1718. commands.stretch(data, payload.type)
  1719. },
  1720. distributeSelection(data, payload: { type: DistributeType }) {
  1721. commands.distribute(data, payload.type)
  1722. },
  1723. duplicateSelection(data) {
  1724. commands.duplicate(data)
  1725. },
  1726. lockSelection(data) {
  1727. commands.toggle(data, 'isLocked')
  1728. },
  1729. hideSelection(data) {
  1730. commands.toggle(data, 'isHidden')
  1731. },
  1732. aspectLockSelection(data) {
  1733. commands.toggle(data, 'isAspectRatioLocked')
  1734. },
  1735. deleteSelection(data) {
  1736. commands.deleteShapes(data, tld.getSelectedShapes(data))
  1737. },
  1738. rotateSelectionCcw(data) {
  1739. commands.rotateCcw(data)
  1740. },
  1741. groupSelection(data) {
  1742. commands.group(data)
  1743. },
  1744. ungroupSelection(data) {
  1745. commands.ungroup(data)
  1746. },
  1747. resetShapeBounds(data) {
  1748. commands.resetBounds(data)
  1749. },
  1750. resetPage(data) {
  1751. data.document.pages[data.currentPageId].shapes = {}
  1752. },
  1753. /* --------------------- Editing -------------------- */
  1754. setEditingId(data) {
  1755. const selectedShape = tld.getSelectedShapes(data)[0]
  1756. if (getShapeUtils(selectedShape).canEdit) {
  1757. data.editingId = selectedShape.id
  1758. }
  1759. tld.getPageState(data).selectedIds = [selectedShape.id]
  1760. },
  1761. clearEditingId(data) {
  1762. data.editingId = null
  1763. },
  1764. /* ---------------------- Tool ---------------------- */
  1765. setActiveTool(data, payload: { tool: ShapeType | 'select' }) {
  1766. data.activeTool = payload.tool
  1767. },
  1768. setActiveToolSelect(data) {
  1769. data.activeTool = 'select'
  1770. },
  1771. setActiveToolDraw(data) {
  1772. data.activeTool = ShapeType.Draw
  1773. },
  1774. setActiveToolRectangle(data) {
  1775. data.activeTool = ShapeType.Rectangle
  1776. },
  1777. setActiveToolEllipse(data) {
  1778. data.activeTool = ShapeType.Ellipse
  1779. },
  1780. setActiveToolArrow(data) {
  1781. data.activeTool = ShapeType.Arrow
  1782. },
  1783. setActiveToolDot(data) {
  1784. data.activeTool = ShapeType.Dot
  1785. },
  1786. setActiveToolPolyline(data) {
  1787. data.activeTool = ShapeType.Polyline
  1788. },
  1789. setActiveToolRay(data) {
  1790. data.activeTool = ShapeType.Ray
  1791. },
  1792. setActiveToolLine(data) {
  1793. data.activeTool = ShapeType.Line
  1794. },
  1795. setActiveToolText(data) {
  1796. data.activeTool = ShapeType.Text
  1797. },
  1798. /* --------------------- Camera --------------------- */
  1799. zoomIn(data) {
  1800. const camera = tld.getCurrentCamera(data)
  1801. const i = Math.round((camera.zoom * 100) / 25)
  1802. const center = [window.innerWidth / 2, window.innerHeight / 2]
  1803. const p0 = tld.screenToWorld(center, data)
  1804. camera.zoom = tld.getCameraZoom((i + 1) * 0.25)
  1805. const p1 = tld.screenToWorld(center, data)
  1806. camera.point = vec.add(camera.point, vec.sub(p1, p0))
  1807. tld.setZoomCSS(camera.zoom)
  1808. },
  1809. zoomOut(data) {
  1810. const camera = tld.getCurrentCamera(data)
  1811. const i = Math.round((camera.zoom * 100) / 25)
  1812. const center = [window.innerWidth / 2, window.innerHeight / 2]
  1813. const p0 = tld.screenToWorld(center, data)
  1814. camera.zoom = tld.getCameraZoom((i - 1) * 0.25)
  1815. const p1 = tld.screenToWorld(center, data)
  1816. camera.point = vec.add(camera.point, vec.sub(p1, p0))
  1817. tld.setZoomCSS(camera.zoom)
  1818. },
  1819. zoomCameraToActual(data) {
  1820. const camera = tld.getCurrentCamera(data)
  1821. const center = [window.innerWidth / 2, window.innerHeight / 2]
  1822. const p0 = tld.screenToWorld(center, data)
  1823. camera.zoom = 1
  1824. const p1 = tld.screenToWorld(center, data)
  1825. camera.point = vec.add(camera.point, vec.sub(p1, p0))
  1826. tld.setZoomCSS(camera.zoom)
  1827. },
  1828. zoomCameraToSelectionActual(data) {
  1829. const camera = tld.getCurrentCamera(data)
  1830. const bounds = tld.getSelectedBounds(data)
  1831. const mx = (window.innerWidth - bounds.width) / 2
  1832. const my = (window.innerHeight - bounds.height) / 2
  1833. camera.zoom = 1
  1834. camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my])
  1835. tld.setZoomCSS(camera.zoom)
  1836. },
  1837. zoomCameraToSelection(data) {
  1838. const camera = tld.getCurrentCamera(data)
  1839. const bounds = tld.getSelectedBounds(data)
  1840. const zoom = tld.getCameraZoom(
  1841. bounds.width > bounds.height
  1842. ? (window.innerWidth - 128) / bounds.width
  1843. : (window.innerHeight - 128) / bounds.height
  1844. )
  1845. const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
  1846. const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
  1847. camera.zoom = zoom
  1848. camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my])
  1849. tld.setZoomCSS(camera.zoom)
  1850. },
  1851. zoomCameraToFit(data) {
  1852. const camera = tld.getCurrentCamera(data)
  1853. const page = tld.getPage(data)
  1854. const shapes = Object.values(page.shapes)
  1855. if (shapes.length === 0) {
  1856. return
  1857. }
  1858. const bounds = getCommonBounds(
  1859. ...Object.values(shapes).map((shape) =>
  1860. getShapeUtils(shape).getBounds(shape)
  1861. )
  1862. )
  1863. const zoom = tld.getCameraZoom(
  1864. bounds.width > bounds.height
  1865. ? (window.innerWidth - 128) / bounds.width
  1866. : (window.innerHeight - 128) / bounds.height
  1867. )
  1868. const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
  1869. const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
  1870. camera.zoom = zoom
  1871. camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my])
  1872. tld.setZoomCSS(camera.zoom)
  1873. },
  1874. zoomCameraToContent(data) {
  1875. const camera = tld.getCurrentCamera(data)
  1876. const page = tld.getPage(data)
  1877. const shapes = Object.values(page.shapes)
  1878. if (shapes.length === 0) {
  1879. return
  1880. }
  1881. const bounds = getCommonBounds(
  1882. ...Object.values(shapes).map((shape) =>
  1883. getShapeUtils(shape).getBounds(shape)
  1884. )
  1885. )
  1886. const { zoom } = camera
  1887. const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
  1888. const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
  1889. camera.point = vec.add([-bounds.minX, -bounds.minY], [mx, my])
  1890. },
  1891. zoomCamera(data, payload: { delta: number; point: number[] }) {
  1892. const camera = tld.getCurrentCamera(data)
  1893. const next = camera.zoom - (payload.delta / 100) * camera.zoom
  1894. const p0 = tld.screenToWorld(payload.point, data)
  1895. camera.zoom = tld.getCameraZoom(next)
  1896. const p1 = tld.screenToWorld(payload.point, data)
  1897. camera.point = vec.add(camera.point, vec.sub(p1, p0))
  1898. tld.setZoomCSS(camera.zoom)
  1899. },
  1900. panCamera(data, payload: { delta: number[] }) {
  1901. const camera = tld.getCurrentCamera(data)
  1902. camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom))
  1903. },
  1904. updateZoomCSS(data) {
  1905. const camera = tld.getCurrentCamera(data)
  1906. tld.setZoomCSS(camera.zoom)
  1907. },
  1908. pinchCamera(
  1909. data,
  1910. payload: {
  1911. delta: number[]
  1912. distanceDelta: number
  1913. angleDelta: number
  1914. point: number[]
  1915. }
  1916. ) {
  1917. // This is usually replaced with hacks.fastPinchCamera!
  1918. const camera = tld.getCurrentCamera(data)
  1919. camera.point = vec.sub(camera.point, vec.div(payload.delta, camera.zoom))
  1920. const next = camera.zoom - (payload.distanceDelta / 300) * camera.zoom
  1921. const p0 = tld.screenToWorld(payload.point, data)
  1922. camera.zoom = tld.getCameraZoom(next)
  1923. const p1 = tld.screenToWorld(payload.point, data)
  1924. camera.point = vec.add(camera.point, vec.sub(p1, p0))
  1925. tld.setZoomCSS(camera.zoom)
  1926. },
  1927. resetCamera(data) {
  1928. const camera = tld.getCurrentCamera(data)
  1929. camera.zoom = 1
  1930. camera.point = [window.innerWidth / 2, window.innerHeight / 2]
  1931. document.documentElement.style.setProperty('--camera-zoom', '1')
  1932. },
  1933. /* ---------------------- History ---------------------- */
  1934. // History
  1935. popHistory() {
  1936. history.pop()
  1937. },
  1938. enableHistory() {
  1939. history.enable()
  1940. },
  1941. disableHistory() {
  1942. history.disable()
  1943. },
  1944. undo(data) {
  1945. history.undo(data)
  1946. },
  1947. redo(data) {
  1948. history.redo(data)
  1949. },
  1950. resetHistory() {
  1951. history.reset()
  1952. },
  1953. /* ------------------- Preferences ------------------ */
  1954. toggleDarkMode(data) {
  1955. data.settings.isDarkMode = !data.settings.isDarkMode
  1956. },
  1957. applyTheme(data) {
  1958. if (data.settings.isDarkMode && typeof document !== 'undefined') {
  1959. document.body.classList.remove(light)
  1960. document.body.classList.add(dark)
  1961. } else {
  1962. document.body.classList.remove(dark)
  1963. document.body.classList.add(light)
  1964. }
  1965. },
  1966. /* --------------------- Styles --------------------- */
  1967. toggleStylePanel(data) {
  1968. data.settings.isStyleOpen = !data.settings.isStyleOpen
  1969. },
  1970. closeStylePanel(data) {
  1971. data.settings.isStyleOpen = false
  1972. },
  1973. updateStyles(data, payload: Partial<ShapeStyles>) {
  1974. Object.assign(data.currentStyle, payload)
  1975. },
  1976. applyStylesToSelection(data, payload: Partial<ShapeStyles>) {
  1977. commands.style(data, payload)
  1978. },
  1979. /* ---------------------- Code ---------------------- */
  1980. closeCodePanel(data) {
  1981. data.settings.isCodeOpen = false
  1982. },
  1983. openCodePanel(data) {
  1984. data.settings.isCodeOpen = true
  1985. },
  1986. toggleCodePanel(data) {
  1987. data.settings.isCodeOpen = !data.settings.isCodeOpen
  1988. },
  1989. setGeneratedShapes(
  1990. data,
  1991. payload: { shapes: Shape[]; controls: CodeControl[] }
  1992. ) {
  1993. commands.generate(data, payload.shapes)
  1994. },
  1995. updateGeneratedShapes(data, payload, result: { shapes: Shape[] }) {
  1996. tld.setSelectedIds(data, [])
  1997. history.disable()
  1998. commands.generate(data, result.shapes)
  1999. history.enable()
  2000. },
  2001. setCodeControls(data, payload: { controls: CodeControl[] }) {
  2002. data.codeControls = Object.fromEntries(
  2003. payload.controls.map((control) => [control.id, control])
  2004. )
  2005. },
  2006. increaseCodeFontSize(data) {
  2007. data.settings.fontSize++
  2008. },
  2009. decreaseCodeFontSize(data) {
  2010. data.settings.fontSize--
  2011. },
  2012. updateControls(data, payload: { [key: string]: any }) {
  2013. for (const key in payload) {
  2014. data.codeControls[key].value = payload[key]
  2015. }
  2016. },
  2017. /* -------------------- Settings -------------------- */
  2018. enablePenLock(data) {
  2019. data.settings.isPenLocked = true
  2020. },
  2021. disablePenLock(data) {
  2022. data.settings.isPenLocked = false
  2023. },
  2024. toggleToolLock(data) {
  2025. data.settings.isToolLocked = !data.settings.isToolLocked
  2026. },
  2027. /* ------------------- Clipboard -------------------- */
  2028. copyToSvg(data) {
  2029. clipboard.copySelectionToSvg(data)
  2030. },
  2031. copyToClipboard(data) {
  2032. clipboard.copy(tld.getSelectedShapes(data))
  2033. },
  2034. copyStateToClipboard(data) {
  2035. clipboard.copyStringToClipboard(JSON.stringify(data))
  2036. },
  2037. pasteFromClipboard(data) {
  2038. clipboard.paste()
  2039. if (clipboard.fallback) {
  2040. try {
  2041. commands.paste(data, JSON.parse(clipboard.current).shapes)
  2042. } catch (e) {
  2043. console.warn('Could not paste that text.')
  2044. }
  2045. }
  2046. },
  2047. pasteShapesFromClipboard(data, payload: { shapes: Shape[] }) {
  2048. commands.paste(data, payload.shapes)
  2049. },
  2050. /* ---------------------- Data ---------------------- */
  2051. restoredPreviousDocument(data, payload: { roomId?: string }) {
  2052. storage.firstLoad(data, payload?.roomId)
  2053. },
  2054. saveToFileSystem(data) {
  2055. storage.saveToFileSystem(data)
  2056. },
  2057. saveAsToFileSystem(data) {
  2058. storage.saveAsToFileSystem(data)
  2059. },
  2060. loadFromFileSystem() {
  2061. storage.loadDocumentFromFilesystem()
  2062. },
  2063. loadDocumentFromJson(data, payload: { json: any }) {
  2064. storage.loadDocumentFromJson(data, payload.json)
  2065. },
  2066. saveAppState(data) {
  2067. storage.saveAppStateToLocalStorage(data)
  2068. },
  2069. saveDocumentState(data) {
  2070. storage.savePage(data)
  2071. storage.savePageState(data)
  2072. storage.saveAppStateToLocalStorage(data)
  2073. storage.saveDocumentToLocalStorage(data)
  2074. },
  2075. forceSave(data) {
  2076. storage.saveToFileSystem(data)
  2077. },
  2078. saveCode(data, payload: { code: string }) {
  2079. data.document.code[data.currentCodeFileId].code = payload.code
  2080. storage.saveDocumentToLocalStorage(data)
  2081. storage.saveAppStateToLocalStorage(data)
  2082. },
  2083. clearBoundsRotation(data) {
  2084. data.boundsRotation = 0
  2085. },
  2086. },
  2087. asyncs: {
  2088. async getUpdatedShapes(data) {
  2089. return updateFromCode(
  2090. data,
  2091. data.document.code[data.currentCodeFileId].code
  2092. )
  2093. },
  2094. },
  2095. values: {
  2096. selectedIds(data) {
  2097. return tld.getSelectedIds(data)
  2098. },
  2099. selectedBounds(data) {
  2100. return getSelectionBounds(data)
  2101. },
  2102. currentShapes(data) {
  2103. const page = tld.getPage(data)
  2104. return Object.values(page.shapes)
  2105. .filter((shape) => shape.parentId === page.id)
  2106. .sort((a, b) => a.childIndex - b.childIndex)
  2107. },
  2108. selectedStyle(data) {
  2109. const selectedIds = tld.getSelectedIds(data)
  2110. const { currentStyle } = data
  2111. if (selectedIds.length === 0) {
  2112. return currentStyle
  2113. }
  2114. const page = tld.getPage(data)
  2115. const shapeStyles = selectedIds.map((id) => page.shapes[id].style)
  2116. const commonStyle: ShapeStyles = {} as ShapeStyles
  2117. const overrides = new Set<string>([])
  2118. for (const shapeStyle of shapeStyles) {
  2119. for (const key in currentStyle) {
  2120. if (overrides.has(key)) continue
  2121. if (commonStyle[key] === undefined) {
  2122. commonStyle[key] = shapeStyle[key]
  2123. } else {
  2124. if (commonStyle[key] === shapeStyle[key]) continue
  2125. commonStyle[key] = currentStyle[key]
  2126. overrides.add(key)
  2127. }
  2128. }
  2129. }
  2130. return commonStyle
  2131. },
  2132. selectedRotation(data) {
  2133. const selectedIds = tld.getSelectedIds(data)
  2134. if (selectedIds.length === 1) {
  2135. const selected = selectedIds[0]
  2136. const page = tld.getPage(data)
  2137. return page.shapes[selected]?.rotation
  2138. } else {
  2139. return 0
  2140. }
  2141. },
  2142. shapesToRender(data) {
  2143. const viewport = tld.getViewport(data)
  2144. const page = tld.getPage(data)
  2145. const shapesToShow = Object.values(page.shapes).filter((shape) => {
  2146. if (shape.parentId !== page.id) return false
  2147. const shapeBounds = getShapeUtils(shape).getBounds(shape)
  2148. return (
  2149. shape.type === ShapeType.Ray ||
  2150. shape.type === ShapeType.Line ||
  2151. boundsContain(viewport, shapeBounds) ||
  2152. boundsCollide(viewport, shapeBounds)
  2153. )
  2154. })
  2155. // Populate the shape tree
  2156. const tree: ShapeTreeNode[] = []
  2157. const selectedIds = tld.getSelectedIds(data)
  2158. shapesToShow
  2159. .sort((a, b) => a.childIndex - b.childIndex)
  2160. .forEach((shape) => tld.addToShapeTree(data, selectedIds, tree, shape))
  2161. return tree
  2162. },
  2163. },
  2164. options: {
  2165. onSend(eventName, payload, didCauseUpdate) {
  2166. logger.addToLog(eventName, payload, didCauseUpdate)
  2167. },
  2168. },
  2169. })
  2170. export default state
  2171. export const useSelector = createSelectorHook(state)
  2172. function getParentId(data: Data, id: string) {
  2173. const shape = tld.getPage(data).shapes[id]
  2174. return shape.parentId
  2175. }
  2176. function getPointedId(data: Data, id: string) {
  2177. const shape = tld.getPage(data).shapes[id]
  2178. if (!shape) return id
  2179. return shape.parentId === data.currentParentId ||
  2180. shape.parentId === data.currentPageId
  2181. ? id
  2182. : getPointedId(data, shape.parentId)
  2183. }
  2184. function getDrilledPointedId(data: Data, id: string) {
  2185. const shape = tld.getPage(data).shapes[id]
  2186. return shape.parentId === data.currentPageId ||
  2187. shape.parentId === data.pointedId ||
  2188. shape.parentId === data.currentParentId
  2189. ? id
  2190. : getDrilledPointedId(data, shape.parentId)
  2191. }
  2192. // function hasPointedIdInChildren(data: Data, id: string, pointedId: string) {
  2193. // const shape = getPage(data).shapes[id]
  2194. // if (shape.type !== ShapeType.Group) {
  2195. // return false
  2196. // }
  2197. // if (shape.children.includes(pointedId)) {
  2198. // return true
  2199. // }
  2200. // return shape.children.some((childId) =>
  2201. // hasPointedIdInChildren(data, childId, pointedId)
  2202. // )
  2203. // }
  2204. function getSelectionBounds(data: Data) {
  2205. const selectedIds = tld.getSelectedIds(data)
  2206. const page = tld.getPage(data)
  2207. const shapes = tld.getSelectedShapes(data)
  2208. if (selectedIds.length === 0) return null
  2209. if (selectedIds.length === 1) {
  2210. if (!shapes[0]) {
  2211. console.warn('Could not find that shape! Clearing selected IDs.')
  2212. tld.setSelectedIds(data, [])
  2213. return null
  2214. }
  2215. const shape = shapes[0]
  2216. const shapeUtils = getShapeUtils(shape)
  2217. if (!shapeUtils.canTransform) return null
  2218. let bounds = shapeUtils.getBounds(shape)
  2219. let parentId = shape.parentId
  2220. while (parentId !== data.currentPageId) {
  2221. const parent = page.shapes[parentId]
  2222. bounds = rotateBounds(
  2223. bounds,
  2224. getBoundsCenter(getShapeUtils(parent).getBounds(parent)),
  2225. parent.rotation
  2226. )
  2227. bounds.rotation = parent.rotation
  2228. parentId = parent.parentId
  2229. }
  2230. return bounds
  2231. }
  2232. const uniqueSelectedShapeIds: string[] = Array.from(
  2233. new Set(
  2234. Array.from(selectedIds.values()).flatMap((id) =>
  2235. tld.getDocumentBranch(data, id)
  2236. )
  2237. ).values()
  2238. )
  2239. const commonBounds = getCommonBounds(
  2240. ...uniqueSelectedShapeIds
  2241. .map((id) => page.shapes[id])
  2242. .filter((shape) => shape.type !== ShapeType.Group)
  2243. .map((shape) => getShapeUtils(shape).getRotatedBounds(shape))
  2244. )
  2245. return commonBounds
  2246. }
  2247. // const skippedLogs = new Set<string>([
  2248. // 'MOVED_POINTER',
  2249. // 'MOVED_OVER_SHAPE',
  2250. // 'RESIZED_WINDOW',
  2251. // 'HOVERED_SHAPE',
  2252. // 'UNHOVERED_SHAPE',
  2253. // 'PANNED_CAMERA',
  2254. // ])
  2255. // state.enableLog(true)
  2256. // state.onUpdate((s) => console.log(s.log.filter((l) => !skippedLogs.has(l))))