Browse Source

Replace

main
Steve Ruiz 3 years ago
parent
commit
5998879e24
100 changed files with 57 additions and 20150 deletions
  1. 0
    3
      .eslintignore
  2. 11
    0
      .eslintrc.js
  3. 0
    49
      .eslintrc.json
  4. 0
    2
      .gitattributes
  5. 8
    35
      .gitignore
  6. 17
    0
      .npmignore
  7. 0
    6
      .prettierrc
  8. 21
    0
      LICENSE
  9. 0
    239
      __tests__/README.md
  10. 0
    326
      __tests__/__mocks__/data.json
  11. 0
    298
      __tests__/__mocks__/document.json
  12. 0
    280
      __tests__/__snapshots__/code.test.ts.snap
  13. 0
    57
      __tests__/__snapshots__/dashes.test.ts.snap
  14. 0
    809
      __tests__/__snapshots__/project.test.ts.snap
  15. 0
    72
      __tests__/bounds.test.ts
  16. 0
    232
      __tests__/children.test.ts
  17. 0
    284
      __tests__/code.test.ts
  18. 0
    852
      __tests__/commands/__snapshots__/transform.test.ts.snap
  19. 0
    40
      __tests__/commands/align.test.ts
  20. 0
    21
      __tests__/commands/change-page.test.ts
  21. 0
    38
      __tests__/commands/create-page.test.ts
  22. 0
    78
      __tests__/commands/delete-page.test.ts
  23. 0
    40
      __tests__/commands/delete-selected.ts
  24. 0
    120
      __tests__/commands/delete.test.ts
  25. 0
    37
      __tests__/commands/distribute.test.ts
  26. 0
    21
      __tests__/commands/draw.test.ts
  27. 0
    109
      __tests__/commands/duplicate-page.test.ts
  28. 0
    116
      __tests__/commands/duplicate.test.ts
  29. 0
    21
      __tests__/commands/edit.test.ts
  30. 0
    21
      __tests__/commands/generate.test.ts
  31. 0
    151
      __tests__/commands/group.test.ts
  32. 0
    40
      __tests__/commands/move-to-page.test.ts
  33. 0
    55
      __tests__/commands/rename-page.test.ts
  34. 0
    237
      __tests__/commands/toggle.test.ts
  35. 0
    347
      __tests__/commands/transform.test.ts
  36. 0
    36
      __tests__/commands/translate.test.ts
  37. 0
    49
      __tests__/coop.test.ts
  38. 0
    31
      __tests__/create.test.ts
  39. 0
    34
      __tests__/dashes.test.ts
  40. 0
    61
      __tests__/locked.test.ts
  41. 0
    44
      __tests__/project.test.ts
  42. 0
    81
      __tests__/selection.test.ts
  43. 0
    122
      __tests__/shapes/arrow.test.ts
  44. 0
    101
      __tests__/shapes/draw.test.ts
  45. 0
    104
      __tests__/shapes/ellipse.test.ts
  46. 0
    104
      __tests__/shapes/rectangle.test.ts
  47. 0
    79
      __tests__/shapes/text.test.ts
  48. 0
    31
      __tests__/style.test.ts
  49. 0
    836
      __tests__/test-utils.ts
  50. 0
    38
      __tests__/tools.test.ts
  51. 0
    3
      babel.config.js
  52. 0
    78
      components/canvas/bounds/bounding-box.tsx
  53. 0
    77
      components/canvas/bounds/bounds-bg.tsx
  54. 0
    36
      components/canvas/bounds/center-handle.tsx
  55. 0
    57
      components/canvas/bounds/corner-handle.tsx
  56. 0
    44
      components/canvas/bounds/edge-handle.tsx
  57. 0
    81
      components/canvas/bounds/handles.tsx
  58. 0
    38
      components/canvas/bounds/rotate-handle.tsx
  59. 0
    23
      components/canvas/brush.tsx
  60. 0
    99
      components/canvas/canvas.tsx
  61. 0
    368
      components/canvas/context-menu/context-menu.tsx
  62. 0
    29
      components/canvas/coop/coop.tsx
  63. 0
    65
      components/canvas/coop/cursor.tsx
  64. 0
    43
      components/canvas/defs.tsx
  65. 0
    43
      components/canvas/hovered-shape.tsx
  66. 0
    19
      components/canvas/misc.tsx
  67. 0
    57
      components/canvas/page.tsx
  68. 0
    149
      components/canvas/shape.tsx
  69. 0
    121
      components/code-panel/code-docs.tsx
  70. 0
    248
      components/code-panel/code-editor.tsx
  71. 0
    235
      components/code-panel/code-panel.tsx
  72. 0
    129
      components/code-panel/docs-content.ts
  73. 0
    5459
      components/code-panel/es5-lib.ts
  74. 0
    3469
      components/code-panel/types-import.ts
  75. 0
    165
      components/controls-panel/control.tsx
  76. 0
    76
      components/controls-panel/controls-panel.tsx
  77. 0
    224
      components/debug-panel/debug-panel.tsx
  78. 0
    66
      components/editor.tsx
  79. 0
    123
      components/menu/menu.tsx
  80. 0
    135
      components/page-panel/page-options.tsx
  81. 0
    106
      components/page-panel/page-panel.tsx
  82. 0
    136
      components/panel.tsx
  83. 0
    60
      components/status-bar.tsx
  84. 0
    155
      components/style-panel/align-distribute.tsx
  85. 0
    45
      components/style-panel/quick-color-select.tsx
  86. 0
    57
      components/style-panel/quick-dash-select.tsx
  87. 0
    43
      components/style-panel/quick-fill-select.tsx
  88. 0
    50
      components/style-panel/quick-size-select.tsx
  89. 0
    214
      components/style-panel/shapes-functions.tsx
  90. 0
    101
      components/style-panel/style-panel.tsx
  91. 0
    32
      components/tools-panel/back-to-content.tsx
  92. 0
    200
      components/tools-panel/tools-panel.tsx
  93. 0
    24
      components/tools-panel/undo-redo.tsx
  94. 0
    43
      components/tools-panel/zoom.tsx
  95. 0
    74
      components/tooltip.tsx
  96. 0
    31
      decs.d.ts
  97. 0
    57
      hooks/useBoundsEvents.ts
  98. 0
    34
      hooks/useCamera.ts
  99. 0
    142
      hooks/useCanvasEvents.ts
  100. 0
    0
      hooks/useHandleEvents.ts

+ 0
- 3
.eslintignore View File

@@ -1,3 +0,0 @@
1
-**/node_modules/*
2
-**/out/*
3
-**/.next/*

+ 11
- 0
.eslintrc.js View File

@@ -0,0 +1,11 @@
1
+module.exports = {
2
+  root: true,
3
+  parser: '@typescript-eslint/parser',
4
+  plugins: [
5
+    '@typescript-eslint',
6
+  ],
7
+  extends: [
8
+    'eslint:recommended',
9
+    'plugin:@typescript-eslint/recommended',
10
+  ],
11
+};

+ 0
- 49
.eslintrc.json View File

@@ -1,49 +0,0 @@
1
-{
2
-  "parser": "@typescript-eslint/parser",
3
-  "plugins": ["@typescript-eslint"],
4
-  "extends": [
5
-    "eslint:recommended",
6
-    "plugin:react/recommended",
7
-    "plugin:@typescript-eslint/recommended"
8
-    // Uncomment the following lines to enable eslint-config-prettier
9
-    // Is not enabled right now to avoid issues with the Next.js repo
10
-    // "prettier",
11
-  ],
12
-  "env": {
13
-    "es6": true,
14
-    "browser": true,
15
-    "jest": true,
16
-    "node": true
17
-  },
18
-  "settings": {
19
-    "react": {
20
-      "version": "detect"
21
-    }
22
-  },
23
-  "ignorePatterns": "**/*.js",
24
-  "rules": {
25
-    "react/react-in-jsx-scope": 0,
26
-    "react/display-name": 0,
27
-    "react/prop-types": 0,
28
-    "@typescript-eslint/no-extra-semi": 0,
29
-    "@typescript-eslint/explicit-function-return-type": 0,
30
-    "@typescript-eslint/explicit-member-accessibility": 0,
31
-    "@typescript-eslint/indent": 0,
32
-    "@typescript-eslint/member-delimiter-style": 0,
33
-    "@typescript-eslint/no-explicit-any": 0,
34
-    "@typescript-eslint/no-var-requires": 0,
35
-    "@typescript-eslint/no-use-before-define": 0,
36
-    "@typescript-eslint/no-unused-vars": [
37
-      2,
38
-      {
39
-        "argsIgnorePattern": "^_"
40
-      }
41
-    ],
42
-    "no-console": [
43
-      2,
44
-      {
45
-        "allow": ["warn", "error"]
46
-      }
47
-    ]
48
-  }
49
-}

+ 0
- 2
.gitattributes View File

@@ -1,2 +0,0 @@
1
-# Auto detect text files and perform LF normalization
2
-* text=auto

+ 8
- 35
.gitignore View File

@@ -1,37 +1,10 @@
1
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
1
+node_modules/
2
+build/
3
+lib/
4
+dist/
5
+docs/
6
+.idea/*
2 7
 
3
-# dependencies
4
-/dist
5
-/node_modules
6
-/.pnp
7
-.pnp.js
8
-
9
-# testing
10
-/coverage
11
-
12
-# next.js
13
-/.next/
14
-/out/
15
-
16
-# production
17
-/build
18
-
19
-# misc
20 8
 .DS_Store
21
-*.pem
22
-
23
-# debug
24
-npm-debug.log*
25
-yarn-debug.log*
26
-yarn-error.log*
27
-
28
-# local env files
29
-.env.local
30
-.env.development.local
31
-.env.test.local
32
-.env.production.local
33
-
34
-# vercel
35
-.vercel
36
-
37
-cypress/integration/examples
9
+coverage
10
+*.log

+ 17
- 0
.npmignore View File

@@ -0,0 +1,17 @@
1
+/.github/
2
+/.vscode/
3
+/node_modules/
4
+/build/
5
+/tmp/
6
+.idea/*
7
+/docs/
8
+
9
+coverage
10
+*.log
11
+.gitlab-ci.yml
12
+
13
+package-lock.json
14
+/*.tgz
15
+/tmp*
16
+/mnt/
17
+/package/

+ 0
- 6
.prettierrc View File

@@ -1,6 +0,0 @@
1
-{
2
-  "semi": false,
3
-  "singleQuote": true,
4
-  "tabWidth": 2,
5
-  "useTabs": false
6
-}

+ 21
- 0
LICENSE View File

@@ -0,0 +1,21 @@
1
+MIT License
2
+
3
+Copyright (c) 2021 Chris Hager
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 0
- 239
__tests__/README.md View File

@@ -1,239 +0,0 @@
1
-# Testing Guide
2
-
3
-Writing tests for tldraw? Thank you! This guide will get you started.
4
-
5
-- [Getting Started](#getting-started)
6
-- [How to Test](#how-to-test)
7
-- [What to Test](#what-to-test)
8
-- [I Found a Bug!](#i-found-a-bug)
9
-- [TestUtils](#test-utils)
10
-- [Conclusion and Tips](#conclusion-and-tips)
11
-
12
-## Getting Started
13
-
14
-This project uses Jest for its unit tests.
15
-
16
-- To run the test suite, run `yarn test` in your terminal.
17
-- To start the test watcher, run `yarn test:watch`.
18
-- To update the test snapshots, run `yarn test:update`.
19
-
20
-Tests live inside of the `__tests__` folder.
21
-
22
-To create a new test, create a file named `myTest.test.ts` inside of the `__tests__` folder.
23
-
24
-## How to Test
25
-
26
-In tldraw, we write our tests against application's _state_.
27
-
28
-Remember that in tldraw, user interactions send _events_ to the app's _state_, where they produce a change (or not) depending on the state's configuration and current status. To test a feature, we "manually" send those same events to the state machine and then check whether the events produced the change we expected.
29
-
30
-To test a feature, we'll need to:
31
-
32
-- learn how the features works in tldraw
33
-- identify the events involved
34
-- identify the outcome of the events
35
-- reproduce the events in our test
36
-- test the outcome
37
-
38
-### Example
39
-
40
-Let's say we want to test the "create a page" feature of the app.
41
-
42
-We'd start by creating a new file named `create-page.test.ts`. Here's some boilerplace to get us started.
43
-
44
-```ts
45
-// __tests__/create-page.test.ts
46
-
47
-import TestState from '../test-utils'
48
-const tt = new TestState()
49
-
50
-it('creates a new page', () => {})
51
-```
52
-
53
-In the code above, we import our `TestState` class, create a new instance for this test file, then have a unit test that will assert something about our app's behavior.
54
-
55
-In the app's UI, we can find a button labelled "create page".
56
-
57
-```tsx
58
-// page-panel.tsx
59
-
60
-<DropdownMenuButton onSelect={() => state.send('CREATED_PAGE')} />
61
-```
62
-
63
-Because we're only testing the state machine, we don't have to worry about how the `DropdownMenuButton` component works, or whether `onSelect` is implemented correctly. Instead, our only concern is the call to `state.send`, where we "send" the `CREATED_PAGE` event to the app's central state machine.
64
-
65
-Back in our test, we can send that the `CREATED_PAGE` event ourselves and check whether it's produced the correct outcome.
66
-
67
-```ts
68
-// __tests__/create-page.test.ts
69
-
70
-import TestState from '../test-utils'
71
-const tt = new TestState()
72
-
73
-it('creates a new page', () => {
74
-  const pageCountBefore = Object.keys(tt.state.data.document.pages).length
75
-
76
-  tt.state.send('CREATED_PAGE')
77
-
78
-  const pageCountAfter = Object.keys(tt.state.data.document.pages).length
79
-
80
-  expect(pageCountAfter).toEqual(pageCountBefore + 1)
81
-})
82
-```
83
-
84
-If we run our tests (with `yarn test`) or if we're already in watch mode (`yarn test:watch`) then our tests should update. If it worked, hooray! Now try to make it fail and see what that looks like, too.
85
-
86
-## What to Test
87
-
88
-While a test like "create a page" is pretty self-explanatory, most features are at least a little complex.
89
-
90
-To _fully_ test a feature, we would need to:
91
-
92
-- test the entire outcome
93
-- testing every circumstance under which the outcome could be different
94
-
95
-Let's take another look at the `CREATED_PAGE` event.
96
-
97
-If we search for the event in the state machine itself, we can find where and how event is being handled.
98
-
99
-```ts
100
-// state/state.ts
101
-
102
-ready: {
103
-  on: {
104
-    // ...
105
-    CREATED_PAGE: {
106
-      unless: ['isReadOnly', 'isInSession'],
107
-      do: 'createPage',
108
-    }
109
-  }
110
-}
111
-```
112
-
113
-Here's where we can see what exactly we need to test. The event can tell us a few things:
114
-
115
-- It should only run when the "ready" state is active
116
-- It never run when the app is in read only mode
117
-- It should never run when the app is in a session (like drawing or rotating)
118
-
119
-These are all things that we could test. For example:
120
-
121
-```ts
122
-// __tests__/create-page.test.ts
123
-
124
-import TestState from '../test-utils'
125
-const tt = new TestState()
126
-
127
-it('does not create a new page in read only mode', () => {
128
-  tt.state.send('TOGGLED_READ_ONLY')
129
-
130
-  expect(tt.state.data.isReadOnly).toBe(true)
131
-
132
-  const pageCountBefore = Object.keys(tt.state.data.document.pages).length
133
-
134
-  tt.state.send('CREATED_PAGE')
135
-
136
-  const pageCountAfter = Object.keys(tt.state.data.document.pages).length
137
-
138
-  expect(pageCountAfter).toEqual(pageCountBefore)
139
-})
140
-```
141
-
142
-> Note that we're using a different event, `TOGGLED_READ_ONLY`, in order to get the state into the correct condition to make our test. When using events like this, it's a good idea to assert that the state is how you expect it to be before you make your "real" test. Here that means testing that the state's `data.isReadOnly` boolean is `true` before we test the `CREATED_PAGE` event.
143
-
144
-We can also look at the `createPage` action.
145
-
146
-```ts
147
-// state/state.ts
148
-
149
-createPage(data) {
150
-  commands.createPage(data, true)
151
-},
152
-```
153
-
154
-If we follow this call, we'll find the `createPage` command (`state/commands/create-page.ts`). This command is more complex, but it gives us more to test:
155
-
156
-- did we correctly iterate the numbers in the new page's name?
157
-- did we get the correct child index for the new page?
158
-- did we save the current page to local storage?
159
-- did we save the new page to local storage?
160
-- did we add the new page to the document?
161
-- did we add the new page state to the document?
162
-- did we go to the new page?
163
-- when we undo the command, will we remove the new page / page state?
164
-- when we redo the command, will we put the new page / page state back?
165
-
166
-To _fully_ test a feature, we'll need to write tests that cover all of these.
167
-
168
-### Todo Tests
169
-
170
-...but while full test coverage is a goal, it's not always within reach. If you're not able to test everything about a feature, it's a good idea to write "placeholders" for the tests that need to be written.
171
-
172
-```ts
173
-describe('when creating a new page...', () => {
174
-  it('sets the correct child index for the new page', () => {
175
-    // TODO
176
-  })
177
-
178
-  it('sets the correct name for the new page', () => {
179
-    // TODO
180
-  })
181
-
182
-  it('saves the document to local storage', () => {
183
-    // TODO
184
-  })
185
-})
186
-```
187
-
188
-### Snapshots
189
-
190
-An even better way to improve coverage when dealing with complex tests is to write "snapshot" tests.
191
-
192
-```ts
193
-describe('when creating a new page...', () => {
194
-  it('updates the document', () => {
195
-    tt.state.send('CREATED_PAGE')
196
-    expect(tt.state.data).toMatchSnapshot()
197
-  })
198
-})
199
-```
200
-
201
-While snapshot tests don't assert specific things about a feature's implementation, they will at least help flag changes in other parts of the app that might break the feature. For example, if we accidentally made the app start in read only mode, then the snapshot outcome of `CREATED_PAGE` would be different—and the test would fail.
202
-
203
-## I Found a Bug!
204
-
205
-As you write your tests, chances are you'll find some part of the application that just doesn't work the way it should. If it's your own code, then go ahead and make your fix. If the bug is in code that someone else has written, and if the fix seems complicated, then consider reaching out to the author on [Discord](https://discord.gg/a3H98DGSXS) or on the Github issue for help.
206
-
207
-## TestUtils
208
-
209
-While you can test every feature in tldraw by sending events to the state, the `TestUtils` class is designed to make certain things easier. By convention, I'll refer to an instance of the `TestUtils` class as `tt`.
210
-
211
-```ts
212
-import TestState from '../test-utils'
213
-const tt = new TestState()
214
-```
215
-
216
-The `TestUtils` instance wraps an instance of the app's state machine (`tt.state`). It also exposes the state's data as `tt.data`, as well as the state's helper methods (`tt.send`, `tt.isIn`, etc.)
217
-
218
-- `tt.resetDocumentState` will clear the document and reset the app state.
219
-- `tt.createShape` will create a new shape on the page.
220
-- `tt.clickShape` will click a the indicated shape
221
-
222
-Check the `test-utils.ts` file for the rest of the API. Feel free to add your own methods if you have a reason for doing so.
223
-
224
-## Conclusion and Tips
225
-
226
-To wrap up, thanks again for writing tests for tldraw. Quality in creative software is extremely important: nothing's worse than losing work to a bug, but even lesser terrors—getting kicked out of creative flow by unexpected behavior, or having to accomodate an accidental quirk—can make an app unusable.
227
-
228
-To sum up what we've covered:
229
-
230
-- Do a bit of digging into a feature's events and their outcome(s)
231
-- Test the app's state machine (view `TestUtils`), not the React view
232
-- Use the `TestUtils` class for complex events like clicking and dragging
233
-- Write "todo" tests for the things you can't get to
234
-- Ask original authors if you find a complex bug
235
-- Ask for help on [Discord](https://discord.gg/a3H98DGSXS)
236
-
237
-Thanks!
238
-
239
--Steve (@steveruizok)

+ 0
- 326
__tests__/__mocks__/data.json View File

@@ -1,326 +0,0 @@
1
-{
2
-  "isReadOnly": false,
3
-  "settings": {
4
-    "fontSize": 13,
5
-    "isDarkMode": false,
6
-    "isCodeOpen": false,
7
-    "isDebugMode": false,
8
-    "isDebugOpen": false,
9
-    "isStyleOpen": false,
10
-    "isToolLocked": false,
11
-    "isPenLocked": false,
12
-    "nudgeDistanceLarge": 10,
13
-    "nudgeDistanceSmall": 1
14
-  },
15
-  "currentStyle": {
16
-    "size": "Medium",
17
-    "color": "Black",
18
-    "dash": "Draw",
19
-    "isFilled": false
20
-  },
21
-  "activeTool": "select",
22
-  "editingId": null,
23
-  "boundsRotation": 0,
24
-  "currentPageId": "page1",
25
-  "currentParentId": "page1",
26
-  "currentCodeFileId": "file0",
27
-  "codeControls": {},
28
-  "document": {
29
-    "id": "TESTING",
30
-    "name": "My Document",
31
-    "pages": {
32
-      "page1": {
33
-        "id": "page1",
34
-        "type": "page",
35
-        "name": "Page 1",
36
-        "childIndex": 0,
37
-        "shapes": {
38
-          "e43559cb-6f41-4ae4-9c49-158ed1ad2f72": {
39
-            "id": "e43559cb-6f41-4ae4-9c49-158ed1ad2f72",
40
-            "type": "rectangle",
41
-            "name": "Rectangle",
42
-            "parentId": "page1",
43
-            "childIndex": 3,
44
-            "point": [171.47, 288.63],
45
-            "size": [176.22, 192.26],
46
-            "radius": 2,
47
-            "rotation": 0,
48
-            "style": {
49
-              "color": "Black",
50
-              "size": "Medium",
51
-              "isFilled": false,
52
-              "dash": "Draw"
53
-            }
54
-          },
55
-          "13448777-d8f5-46cd-8a70-a4259211902e": {
56
-            "id": "13448777-d8f5-46cd-8a70-a4259211902e",
57
-            "type": "rectangle",
58
-            "name": "Rectangle",
59
-            "parentId": "page1",
60
-            "childIndex": 4,
61
-            "point": [511.7, 404.19],
62
-            "size": [181.08999999999992, 150.40999999999997],
63
-            "radius": 2,
64
-            "rotation": 0,
65
-            "style": {
66
-              "color": "Black",
67
-              "size": "Medium",
68
-              "isFilled": false,
69
-              "dash": "Draw"
70
-            }
71
-          },
72
-          "75010635-8dfb-48ea-9250-719e50e58f02": {
73
-            "id": "75010635-8dfb-48ea-9250-719e50e58f02",
74
-            "type": "rectangle",
75
-            "name": "Rectangle",
76
-            "parentId": "page1",
77
-            "childIndex": 5,
78
-            "point": [384.09, 378.45],
79
-            "size": [95.20999999999992, 91.1799999999999],
80
-            "radius": 2,
81
-            "rotation": 0,
82
-            "style": {
83
-              "color": "Black",
84
-              "size": "Medium",
85
-              "isFilled": false,
86
-              "dash": "Draw"
87
-            }
88
-          },
89
-          "c892d665-3311-4e25-a0bf-c4632d777f7e": {
90
-            "id": "c892d665-3311-4e25-a0bf-c4632d777f7e",
91
-            "type": "ellipse",
92
-            "name": "Ellipse",
93
-            "parentId": "page1",
94
-            "childIndex": 6,
95
-            "point": [162.45, 679.23],
96
-            "radiusX": 102.99999999999997,
97
-            "radiusY": 102.99999999999994,
98
-            "rotation": 0,
99
-            "style": {
100
-              "color": "Black",
101
-              "size": "Medium",
102
-              "isFilled": false,
103
-              "dash": "Draw"
104
-            }
105
-          },
106
-          "51641de1-9787-41b8-afcc-2c85fd1b24c7": {
107
-            "id": "51641de1-9787-41b8-afcc-2c85fd1b24c7",
108
-            "type": "ellipse",
109
-            "name": "Ellipse",
110
-            "parentId": "page1",
111
-            "childIndex": 7,
112
-            "point": [517.18, 783.54],
113
-            "radiusX": 102.99999999999997,
114
-            "radiusY": 102.99999999999994,
115
-            "rotation": 0,
116
-            "style": {
117
-              "color": "Black",
118
-              "size": "Medium",
119
-              "isFilled": false,
120
-              "dash": "Draw"
121
-            }
122
-          },
123
-          "e08c415e-3db3-4d3b-878e-28ce693ec1b0": {
124
-            "id": "e08c415e-3db3-4d3b-878e-28ce693ec1b0",
125
-            "type": "ellipse",
126
-            "name": "Ellipse",
127
-            "parentId": "page1",
128
-            "childIndex": 8,
129
-            "point": [398.99, 810.79],
130
-            "radiusX": 45.484999999999985,
131
-            "radiusY": 45.48499999999996,
132
-            "rotation": 0,
133
-            "style": {
134
-              "color": "Black",
135
-              "size": "Medium",
136
-              "isFilled": false,
137
-              "dash": "Draw"
138
-            }
139
-          },
140
-          "fee77127-e779-4576-882b-b1bf7c7e132f": {
141
-            "id": "fee77127-e779-4576-882b-b1bf7c7e132f",
142
-            "type": "arrow",
143
-            "name": "Arrow",
144
-            "parentId": "page1",
145
-            "childIndex": 9,
146
-            "point": [252.85, 1057.5],
147
-            "rotation": 0,
148
-            "bend": 0,
149
-            "handles": {
150
-              "start": {
151
-                "id": "start",
152
-                "index": 0,
153
-                "point": [0, 0]
154
-              },
155
-              "end": {
156
-                "id": "end",
157
-                "index": 1,
158
-                "point": [0.09000000000000341, 208]
159
-              },
160
-              "bend": {
161
-                "id": "bend",
162
-                "index": 2,
163
-                "point": [0.045000000000001705, 104]
164
-              }
165
-            },
166
-            "decorations": {
167
-              "start": null,
168
-              "middle": null,
169
-              "end": "Arrow"
170
-            },
171
-            "style": {
172
-              "color": "Black",
173
-              "size": "Medium",
174
-              "isFilled": false,
175
-              "dash": "Draw"
176
-            }
177
-          },
178
-          "2d842ace-ebc5-4e83-acdf-de29352e5e62": {
179
-            "id": "2d842ace-ebc5-4e83-acdf-de29352e5e62",
180
-            "type": "arrow",
181
-            "name": "Arrow",
182
-            "parentId": "page1",
183
-            "childIndex": 10,
184
-            "point": [616.9, 1124.3],
185
-            "rotation": 0,
186
-            "bend": 0,
187
-            "handles": {
188
-              "start": {
189
-                "id": "start",
190
-                "index": 0,
191
-                "point": [0, 0]
192
-              },
193
-              "end": {
194
-                "id": "end",
195
-                "index": 1,
196
-                "point": [2.4500000000000455, 185.20000000000005]
197
-              },
198
-              "bend": {
199
-                "id": "bend",
200
-                "index": 2,
201
-                "point": [1.2250000000000227, 92.60000000000002]
202
-              }
203
-            },
204
-            "decorations": {
205
-              "start": null,
206
-              "middle": null,
207
-              "end": "Arrow"
208
-            },
209
-            "style": {
210
-              "color": "Black",
211
-              "size": "Medium",
212
-              "isFilled": false,
213
-              "dash": "Draw"
214
-            }
215
-          },
216
-          "b8e4e2c5-c662-4587-bf80-b9820ad8ad7f": {
217
-            "id": "b8e4e2c5-c662-4587-bf80-b9820ad8ad7f",
218
-            "type": "arrow",
219
-            "name": "Arrow",
220
-            "parentId": "page1",
221
-            "childIndex": 11,
222
-            "point": [425.18, 1143.2],
223
-            "rotation": 0,
224
-            "bend": 0,
225
-            "handles": {
226
-              "start": {
227
-                "id": "start",
228
-                "index": 0,
229
-                "point": [0, 0]
230
-              },
231
-              "end": {
232
-                "id": "end",
233
-                "index": 1,
234
-                "point": [1.8500000000000227, 95.70000000000005]
235
-              },
236
-              "bend": {
237
-                "id": "bend",
238
-                "index": 2,
239
-                "point": [0.9250000000000114, 47.85000000000002]
240
-              }
241
-            },
242
-            "decorations": {
243
-              "start": null,
244
-              "middle": null,
245
-              "end": "Arrow"
246
-            },
247
-            "style": {
248
-              "color": "Black",
249
-              "size": "Medium",
250
-              "isFilled": false,
251
-              "dash": "Draw"
252
-            }
253
-          },
254
-          "38e9e750-16c2-4476-93ab-21aeb5f8858f": {
255
-            "id": "38e9e750-16c2-4476-93ab-21aeb5f8858f",
256
-            "type": "text",
257
-            "name": "Text",
258
-            "parentId": "page1",
259
-            "childIndex": 12,
260
-            "point": [207.16, 1422.4],
261
-            "rotation": 0,
262
-            "style": {
263
-              "color": "Black",
264
-              "size": "Medium",
265
-              "isFilled": false,
266
-              "dash": "Draw"
267
-            },
268
-            "text": "Hello",
269
-            "scale": 1
270
-          },
271
-          "5ba998df-c036-447a-9b88-d96c71394f52": {
272
-            "id": "5ba998df-c036-447a-9b88-d96c71394f52",
273
-            "type": "text",
274
-            "name": "Text",
275
-            "parentId": "page1",
276
-            "childIndex": 13,
277
-            "point": [389.57, 1496.5],
278
-            "rotation": 0,
279
-            "style": {
280
-              "color": "Black",
281
-              "size": "Medium",
282
-              "isFilled": false,
283
-              "dash": "Draw"
284
-            },
285
-            "text": "Hello",
286
-            "scale": 1
287
-          },
288
-          "3c688979-b190-4270-915b-7d8dd22a2bb7": {
289
-            "id": "3c688979-b190-4270-915b-7d8dd22a2bb7",
290
-            "type": "text",
291
-            "name": "Text",
292
-            "parentId": "page1",
293
-            "childIndex": 14,
294
-            "point": [564.06, 1558.1],
295
-            "rotation": 0,
296
-            "style": {
297
-              "color": "Black",
298
-              "size": "Medium",
299
-              "isFilled": false,
300
-              "dash": "Draw"
301
-            },
302
-            "text": "Hello",
303
-            "scale": 1
304
-          }
305
-        }
306
-      }
307
-    },
308
-    "code": {
309
-      "file0": {
310
-        "id": "file0",
311
-        "name": "index.ts",
312
-        "code": "\nconst draw = new Draw({\n  points: [\n    ...Utils.getPointsBetween([0, 0], [20, 50]),\n    ...Utils.getPointsBetween([20, 50], [100, 20], 3),\n    ...Utils.getPointsBetween([100, 20], [100, 100], 10),\n    [100, 100],\n  ],\n})\n\nconst rectangle = new Rectangle({\n  point: [200, 0],\n  style: {\n    color: ColorStyle.Blue,\n  },\n})\n\nconst ellipse = new Ellipse({\n  point: [400, 0],\n})\n\nconst arrow = new Arrow({\n  start: [600, 0],\n  end: [700, 100],\n})\n\nconst radius = 1000\nconst count = 100\nconst center = [350, 50]\n\nfor (let i = 0; i < count; i++) {\n  const point = Vec.rotWith(\n    Vec.add(center, [radius, 0]),\n    center,\n    (Math.PI * 2 * i) / count\n  )\n\n  const dot = new Dot({\n    point,\n  })\n}\n        "
313
-      }
314
-    }
315
-  },
316
-  "pageStates": {
317
-    "page1": {
318
-      "id": "page1",
319
-      "camera": {
320
-        "point": [0, -145],
321
-        "zoom": 1
322
-      },
323
-      "selectedIds": {}
324
-    }
325
-  }
326
-}

+ 0
- 298
__tests__/__mocks__/document.json View File

@@ -1,298 +0,0 @@
1
-{
2
-  "document": {
3
-    "id": "TESTING",
4
-    "name": "My Document",
5
-    "pages": {
6
-      "page1": {
7
-        "id": "page1",
8
-        "type": "page",
9
-        "name": "Page 1",
10
-        "childIndex": 0,
11
-        "shapes": {
12
-          "e43559cb-6f41-4ae4-9c49-158ed1ad2f72": {
13
-            "id": "e43559cb-6f41-4ae4-9c49-158ed1ad2f72",
14
-            "type": "rectangle",
15
-            "name": "Rectangle",
16
-            "parentId": "page1",
17
-            "childIndex": 3,
18
-            "point": [100, 100],
19
-            "size": [100, 100],
20
-            "radius": 2,
21
-            "rotation": 0,
22
-            "style": {
23
-              "color": "Black",
24
-              "size": "Medium",
25
-              "isFilled": false,
26
-              "dash": "Draw"
27
-            }
28
-          },
29
-          "13448777-d8f5-46cd-8a70-a4259211902e": {
30
-            "id": "13448777-d8f5-46cd-8a70-a4259211902e",
31
-            "type": "rectangle",
32
-            "name": "Rectangle",
33
-            "parentId": "page1",
34
-            "childIndex": 4,
35
-            "point": [500, 400],
36
-            "size": [200, 200],
37
-            "radius": 2,
38
-            "rotation": 0,
39
-            "style": {
40
-              "color": "Black",
41
-              "size": "Medium",
42
-              "isFilled": false,
43
-              "dash": "Draw"
44
-            }
45
-          },
46
-          "75010635-8dfb-48ea-9250-719e50e58f02": {
47
-            "id": "75010635-8dfb-48ea-9250-719e50e58f02",
48
-            "type": "rectangle",
49
-            "name": "Rectangle",
50
-            "parentId": "page1",
51
-            "childIndex": 5,
52
-            "point": [384.09, 378.45],
53
-            "size": [95.20999999999992, 91.1799999999999],
54
-            "radius": 2,
55
-            "rotation": 0,
56
-            "style": {
57
-              "color": "Black",
58
-              "size": "Medium",
59
-              "isFilled": false,
60
-              "dash": "Draw"
61
-            }
62
-          },
63
-          "c892d665-3311-4e25-a0bf-c4632d777f7e": {
64
-            "id": "c892d665-3311-4e25-a0bf-c4632d777f7e",
65
-            "type": "ellipse",
66
-            "name": "Ellipse",
67
-            "parentId": "page1",
68
-            "childIndex": 6,
69
-            "point": [162.45, 679.23],
70
-            "radiusX": 102.99999999999997,
71
-            "radiusY": 102.99999999999994,
72
-            "rotation": 0,
73
-            "style": {
74
-              "color": "Black",
75
-              "size": "Medium",
76
-              "isFilled": false,
77
-              "dash": "Draw"
78
-            }
79
-          },
80
-          "51641de1-9787-41b8-afcc-2c85fd1b24c7": {
81
-            "id": "51641de1-9787-41b8-afcc-2c85fd1b24c7",
82
-            "type": "ellipse",
83
-            "name": "Ellipse",
84
-            "parentId": "page1",
85
-            "childIndex": 7,
86
-            "point": [517.18, 783.54],
87
-            "radiusX": 102.99999999999997,
88
-            "radiusY": 102.99999999999994,
89
-            "rotation": 0,
90
-            "style": {
91
-              "color": "Black",
92
-              "size": "Medium",
93
-              "isFilled": false,
94
-              "dash": "Draw"
95
-            }
96
-          },
97
-          "e08c415e-3db3-4d3b-878e-28ce693ec1b0": {
98
-            "id": "e08c415e-3db3-4d3b-878e-28ce693ec1b0",
99
-            "type": "ellipse",
100
-            "name": "Ellipse",
101
-            "parentId": "page1",
102
-            "childIndex": 8,
103
-            "point": [398.99, 810.79],
104
-            "radiusX": 45.484999999999985,
105
-            "radiusY": 45.48499999999996,
106
-            "rotation": 0,
107
-            "style": {
108
-              "color": "Black",
109
-              "size": "Medium",
110
-              "isFilled": false,
111
-              "dash": "Draw"
112
-            }
113
-          },
114
-          "fee77127-e779-4576-882b-b1bf7c7e132f": {
115
-            "id": "fee77127-e779-4576-882b-b1bf7c7e132f",
116
-            "type": "arrow",
117
-            "name": "Arrow",
118
-            "parentId": "page1",
119
-            "childIndex": 9,
120
-            "point": [252.85, 1057.5],
121
-            "rotation": 0,
122
-            "bend": 0,
123
-            "handles": {
124
-              "start": {
125
-                "id": "start",
126
-                "index": 0,
127
-                "point": [0, 0]
128
-              },
129
-              "end": {
130
-                "id": "end",
131
-                "index": 1,
132
-                "point": [0.09000000000000341, 208]
133
-              },
134
-              "bend": {
135
-                "id": "bend",
136
-                "index": 2,
137
-                "point": [0.045000000000001705, 104]
138
-              }
139
-            },
140
-            "decorations": {
141
-              "start": null,
142
-              "middle": null,
143
-              "end": "Arrow"
144
-            },
145
-            "style": {
146
-              "color": "Black",
147
-              "size": "Medium",
148
-              "isFilled": false,
149
-              "dash": "Draw"
150
-            }
151
-          },
152
-          "2d842ace-ebc5-4e83-acdf-de29352e5e62": {
153
-            "id": "2d842ace-ebc5-4e83-acdf-de29352e5e62",
154
-            "type": "arrow",
155
-            "name": "Arrow",
156
-            "parentId": "page1",
157
-            "childIndex": 10,
158
-            "point": [616.9, 1124.3],
159
-            "rotation": 0,
160
-            "bend": 0,
161
-            "handles": {
162
-              "start": {
163
-                "id": "start",
164
-                "index": 0,
165
-                "point": [0, 0]
166
-              },
167
-              "end": {
168
-                "id": "end",
169
-                "index": 1,
170
-                "point": [2.4500000000000455, 185.20000000000005]
171
-              },
172
-              "bend": {
173
-                "id": "bend",
174
-                "index": 2,
175
-                "point": [1.2250000000000227, 92.60000000000002]
176
-              }
177
-            },
178
-            "decorations": {
179
-              "start": null,
180
-              "middle": null,
181
-              "end": "Arrow"
182
-            },
183
-            "style": {
184
-              "color": "Black",
185
-              "size": "Medium",
186
-              "isFilled": false,
187
-              "dash": "Draw"
188
-            }
189
-          },
190
-          "b8e4e2c5-c662-4587-bf80-b9820ad8ad7f": {
191
-            "id": "b8e4e2c5-c662-4587-bf80-b9820ad8ad7f",
192
-            "type": "arrow",
193
-            "name": "Arrow",
194
-            "parentId": "page1",
195
-            "childIndex": 11,
196
-            "point": [425.18, 1143.2],
197
-            "rotation": 0,
198
-            "bend": 0,
199
-            "handles": {
200
-              "start": {
201
-                "id": "start",
202
-                "index": 0,
203
-                "point": [0, 0]
204
-              },
205
-              "end": {
206
-                "id": "end",
207
-                "index": 1,
208
-                "point": [1.8500000000000227, 95.70000000000005]
209
-              },
210
-              "bend": {
211
-                "id": "bend",
212
-                "index": 2,
213
-                "point": [0.9250000000000114, 47.85000000000002]
214
-              }
215
-            },
216
-            "decorations": {
217
-              "start": null,
218
-              "middle": null,
219
-              "end": "Arrow"
220
-            },
221
-            "style": {
222
-              "color": "Black",
223
-              "size": "Medium",
224
-              "isFilled": false,
225
-              "dash": "Draw"
226
-            }
227
-          },
228
-          "38e9e750-16c2-4476-93ab-21aeb5f8858f": {
229
-            "id": "38e9e750-16c2-4476-93ab-21aeb5f8858f",
230
-            "type": "text",
231
-            "name": "Text",
232
-            "parentId": "page1",
233
-            "childIndex": 12,
234
-            "point": [207.16, 1422.4],
235
-            "rotation": 0,
236
-            "style": {
237
-              "color": "Black",
238
-              "size": "Medium",
239
-              "isFilled": false,
240
-              "dash": "Draw"
241
-            },
242
-            "text": "Hello",
243
-            "scale": 1
244
-          },
245
-          "5ba998df-c036-447a-9b88-d96c71394f52": {
246
-            "id": "5ba998df-c036-447a-9b88-d96c71394f52",
247
-            "type": "text",
248
-            "name": "Text",
249
-            "parentId": "page1",
250
-            "childIndex": 13,
251
-            "point": [389.57, 1496.5],
252
-            "rotation": 0,
253
-            "style": {
254
-              "color": "Black",
255
-              "size": "Medium",
256
-              "isFilled": false,
257
-              "dash": "Draw"
258
-            },
259
-            "text": "Hello",
260
-            "scale": 1
261
-          },
262
-          "3c688979-b190-4270-915b-7d8dd22a2bb7": {
263
-            "id": "3c688979-b190-4270-915b-7d8dd22a2bb7",
264
-            "type": "text",
265
-            "name": "Text",
266
-            "parentId": "page1",
267
-            "childIndex": 14,
268
-            "point": [564.06, 1558.1],
269
-            "rotation": 0,
270
-            "style": {
271
-              "color": "Black",
272
-              "size": "Medium",
273
-              "isFilled": false,
274
-              "dash": "Draw"
275
-            },
276
-            "text": "Hello",
277
-            "scale": 1
278
-          }
279
-        }
280
-      }
281
-    },
282
-    "code": {
283
-      "file0": {
284
-        "id": "file0",
285
-        "name": "index.ts",
286
-        "code": "\nconst draw = new Draw({\n  points: [\n    ...Utils.getPointsBetween([0, 0], [20, 50]),\n    ...Utils.getPointsBetween([20, 50], [100, 20], 3),\n    ...Utils.getPointsBetween([100, 20], [100, 100], 10),\n    [100, 100],\n  ],\n})\n\nconst rectangle = new Rectangle({\n  point: [200, 0],\n  style: {\n    color: ColorStyle.Blue,\n  },\n})\n\nconst ellipse = new Ellipse({\n  point: [400, 0],\n})\n\nconst arrow = new Arrow({\n  start: [600, 0],\n  end: [700, 100],\n})\n\nconst radius = 1000\nconst count = 100\nconst center = [350, 50]\n\nfor (let i = 0; i < count; i++) {\n  const point = Vec.rotWith(\n    Vec.add(center, [radius, 0]),\n    center,\n    (Math.PI * 2 * i) / count\n  )\n\n  const dot = new Dot({\n    point,\n  })\n}\n        "
287
-      }
288
-    }
289
-  },
290
-  "pageState": {
291
-    "id": "page1",
292
-    "camera": {
293
-      "point": [0, -145],
294
-      "zoom": 1
295
-    },
296
-    "selectedIds": {}
297
-  }
298
-}

+ 0
- 280
__tests__/__snapshots__/code.test.ts.snap View File

@@ -1,280 +0,0 @@
1
-// Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
-exports[`selection creates a code control: generated code controls from code 1`] = `
4
-Object {
5
-  "test-number-control": Object {
6
-    "id": "test-number-control",
7
-    "label": "x",
8
-    "step": 1,
9
-    "type": "number",
10
-    "value": 0,
11
-  },
12
-}
13
-`;
14
-
15
-exports[`selection generates a draw shape: generated draw from code 1`] = `
16
-Array [
17
-  Object {
18
-    "childIndex": 1,
19
-    "id": "test-draw",
20
-    "isGenerated": true,
21
-    "name": "Test draw",
22
-    "parentId": "page1",
23
-    "point": Array [
24
-      0,
25
-      0,
26
-    ],
27
-    "points": Array [
28
-      Array [
29
-        100,
30
-        100,
31
-      ],
32
-      Array [
33
-        200,
34
-        200,
35
-      ],
36
-      Array [
37
-        300,
38
-        300,
39
-      ],
40
-    ],
41
-    "rotation": 0,
42
-    "style": Object {
43
-      "color": "Red",
44
-      "dash": "Dotted",
45
-      "isFilled": false,
46
-      "size": "Medium",
47
-    },
48
-    "type": "draw",
49
-  },
50
-]
51
-`;
52
-
53
-exports[`selection generates a rectangle shape: generated rectangle from code 1`] = `
54
-Array [
55
-  Object {
56
-    "childIndex": 1,
57
-    "id": "test-rectangle",
58
-    "isGenerated": true,
59
-    "name": "Test Rectangle",
60
-    "parentId": "page1",
61
-    "point": Array [
62
-      100,
63
-      100,
64
-    ],
65
-    "radius": 2,
66
-    "rotation": 0,
67
-    "size": Array [
68
-      200,
69
-      200,
70
-    ],
71
-    "style": Object {
72
-      "color": "Red",
73
-      "dash": "Dotted",
74
-      "isFilled": false,
75
-      "size": "Medium",
76
-    },
77
-    "type": "rectangle",
78
-  },
79
-]
80
-`;
81
-
82
-exports[`selection generates a text shape: generated draw from code 1`] = `
83
-Array [
84
-  Object {
85
-    "childIndex": 1,
86
-    "id": "test-text",
87
-    "isGenerated": true,
88
-    "name": "Test text",
89
-    "parentId": "page1",
90
-    "point": Array [
91
-      100,
92
-      100,
93
-    ],
94
-    "rotation": 0,
95
-    "scale": 1,
96
-    "style": Object {
97
-      "color": "Red",
98
-      "dash": "Dotted",
99
-      "isFilled": false,
100
-      "size": "Large",
101
-    },
102
-    "text": "Hello world!",
103
-    "type": "text",
104
-  },
105
-]
106
-`;
107
-
108
-exports[`selection generates an arrow shape: generated draw from code 1`] = `
109
-Array [
110
-  Object {
111
-    "bend": 0,
112
-    "childIndex": 1,
113
-    "decorations": Object {
114
-      "end": "Arrow",
115
-      "middle": null,
116
-      "start": null,
117
-    },
118
-    "handles": Object {
119
-      "bend": Object {
120
-        "id": "bend",
121
-        "index": 2,
122
-        "point": Array [
123
-          50,
124
-          50,
125
-        ],
126
-      },
127
-      "end": Object {
128
-        "id": "end",
129
-        "index": 1,
130
-        "point": Array [
131
-          100,
132
-          100,
133
-        ],
134
-      },
135
-      "start": Object {
136
-        "id": "start",
137
-        "index": 0,
138
-        "point": Array [
139
-          0,
140
-          0,
141
-        ],
142
-      },
143
-    },
144
-    "id": "test-draw",
145
-    "isGenerated": true,
146
-    "name": "Test draw",
147
-    "parentId": "page1",
148
-    "point": Array [
149
-      0,
150
-      0,
151
-    ],
152
-    "points": Array [
153
-      Array [
154
-        100,
155
-        100,
156
-      ],
157
-      Array [
158
-        200,
159
-        200,
160
-      ],
161
-      Array [
162
-        300,
163
-        300,
164
-      ],
165
-    ],
166
-    "rotation": 0,
167
-    "style": Object {
168
-      "color": "Red",
169
-      "dash": "Dotted",
170
-      "isFilled": false,
171
-      "size": "Medium",
172
-    },
173
-    "type": "arrow",
174
-  },
175
-]
176
-`;
177
-
178
-exports[`selection generates an ellipse shape: generated ellipse from code 1`] = `
179
-Array [
180
-  Object {
181
-    "childIndex": 1,
182
-    "id": "test-ellipse",
183
-    "isGenerated": true,
184
-    "name": "Test ellipse",
185
-    "parentId": "page1",
186
-    "point": Array [
187
-      100,
188
-      100,
189
-    ],
190
-    "radiusX": 100,
191
-    "radiusY": 200,
192
-    "rotation": 0,
193
-    "style": Object {
194
-      "color": "Red",
195
-      "dash": "Dotted",
196
-      "isFilled": false,
197
-      "size": "Medium",
198
-    },
199
-    "type": "ellipse",
200
-  },
201
-]
202
-`;
203
-
204
-exports[`selection generates shapes: generated rectangle from code 1`] = `
205
-Array [
206
-  Object {
207
-    "childIndex": 1,
208
-    "id": "test-rectangle",
209
-    "isGenerated": true,
210
-    "name": "Test Rectangle",
211
-    "parentId": "page1",
212
-    "point": Array [
213
-      100,
214
-      100,
215
-    ],
216
-    "radius": 2,
217
-    "rotation": 0,
218
-    "size": Array [
219
-      200,
220
-      200,
221
-    ],
222
-    "style": Object {
223
-      "color": "Red",
224
-      "dash": "Dotted",
225
-      "isFilled": false,
226
-      "size": "Medium",
227
-    },
228
-    "type": "rectangle",
229
-  },
230
-]
231
-`;
232
-
233
-exports[`selection updates a code control: data in state after changing control 1`] = `
234
-Object {
235
-  "test-number-control": Object {
236
-    "id": "test-number-control",
237
-    "label": "x",
238
-    "step": 1,
239
-    "type": "number",
240
-    "value": 100,
241
-  },
242
-  "test-vector-control": Object {
243
-    "id": "test-vector-control",
244
-    "isNormalized": false,
245
-    "label": "size",
246
-    "type": "vector",
247
-    "value": Array [
248
-      0,
249
-      0,
250
-    ],
251
-  },
252
-}
253
-`;
254
-
255
-exports[`selection updates a code control: rectangle in state after changing code control 1`] = `
256
-Object {
257
-  "childIndex": 1,
258
-  "id": "test-rectangle",
259
-  "isGenerated": true,
260
-  "name": "Test Rectangle",
261
-  "parentId": "page1",
262
-  "point": Array [
263
-    0,
264
-    100,
265
-  ],
266
-  "radius": 2,
267
-  "rotation": 0,
268
-  "size": Array [
269
-    0,
270
-    0,
271
-  ],
272
-  "style": Object {
273
-    "color": "Red",
274
-    "dash": "Dotted",
275
-    "isFilled": false,
276
-    "size": "Medium",
277
-  },
278
-  "type": "rectangle",
279
-}
280
-`;

+ 0
- 57
__tests__/__snapshots__/dashes.test.ts.snap View File

@@ -1,57 +0,0 @@
1
-// Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
-exports[`ellipse dash props renders dashed props on a circle correctly: large dashed circle dash props 1`] = `
4
-Object {
5
-  "strokeDasharray": "16 17.333333333333332",
6
-  "strokeDashoffset": "8",
7
-}
8
-`;
9
-
10
-exports[`ellipse dash props renders dashed props on a circle correctly: large dashed ellipse dash props 1`] = `
11
-Object {
12
-  "strokeDasharray": "16 17.333333333333332",
13
-  "strokeDashoffset": "8",
14
-}
15
-`;
16
-
17
-exports[`ellipse dash props renders dashed props on a circle correctly: small dashed circle dash props 1`] = `
18
-Object {
19
-  "strokeDasharray": "8 8.666666666666666",
20
-  "strokeDashoffset": "4",
21
-}
22
-`;
23
-
24
-exports[`ellipse dash props renders dashed props on a circle correctly: small dashed ellipse dash props 1`] = `
25
-Object {
26
-  "strokeDasharray": "8 8.666666666666666",
27
-  "strokeDashoffset": "4",
28
-}
29
-`;
30
-
31
-exports[`ellipse dash props renders dotted props on a circle correctly: large dotted circle dash props 1`] = `
32
-Object {
33
-  "strokeDasharray": "0.08 16.586666666666666",
34
-  "strokeDashoffset": "0",
35
-}
36
-`;
37
-
38
-exports[`ellipse dash props renders dotted props on a circle correctly: large dotted ellipse dash props 1`] = `
39
-Object {
40
-  "strokeDasharray": "0.08 16.586666666666666",
41
-  "strokeDashoffset": "0",
42
-}
43
-`;
44
-
45
-exports[`ellipse dash props renders dotted props on a circle correctly: small dotted circle dash props 1`] = `
46
-Object {
47
-  "strokeDasharray": "0.04 8.293333333333333",
48
-  "strokeDashoffset": "0",
49
-}
50
-`;
51
-
52
-exports[`ellipse dash props renders dotted props on a circle correctly: small dotted ellipse dash props 1`] = `
53
-Object {
54
-  "strokeDasharray": "0.04 8.293333333333333",
55
-  "strokeDashoffset": "0",
56
-}
57
-`;

+ 0
- 809
__tests__/__snapshots__/project.test.ts.snap View File

@@ -1,809 +0,0 @@
1
-// Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
-exports[`project loads file from json: data after mount from file 1`] = `
4
-Object {
5
-  "code": Object {
6
-    "file0": Object {
7
-      "code": "
8
-const draw = new Draw({
9
-  points: [
10
-    ...Utils.getPointsBetween([0, 0], [20, 50]),
11
-    ...Utils.getPointsBetween([20, 50], [100, 20], 3),
12
-    ...Utils.getPointsBetween([100, 20], [100, 100], 10),
13
-    [100, 100],
14
-  ],
15
-})
16
-
17
-const rectangle = new Rectangle({
18
-  point: [200, 0],
19
-  style: {
20
-    color: ColorStyle.Blue,
21
-  },
22
-})
23
-
24
-const ellipse = new Ellipse({
25
-  point: [400, 0],
26
-})
27
-
28
-const arrow = new Arrow({
29
-  start: [600, 0],
30
-  end: [700, 100],
31
-})
32
-
33
-const radius = 1000
34
-const count = 100
35
-const center = [350, 50]
36
-
37
-for (let i = 0; i < count; i++) {
38
-  const point = Vec.rotWith(
39
-    Vec.add(center, [radius, 0]),
40
-    center,
41
-    (Math.PI * 2 * i) / count
42
-  )
43
-
44
-  const dot = new Dot({
45
-    point,
46
-  })
47
-}
48
-        ",
49
-      "id": "file0",
50
-      "name": "index.ts",
51
-    },
52
-  },
53
-  "id": "TESTING",
54
-  "name": "My Document",
55
-  "pages": Object {
56
-    "page1": Object {
57
-      "childIndex": 0,
58
-      "id": "page1",
59
-      "name": "Page 1",
60
-      "shapes": Object {
61
-        "13448777-d8f5-46cd-8a70-a4259211902e": Object {
62
-          "childIndex": 4,
63
-          "id": "13448777-d8f5-46cd-8a70-a4259211902e",
64
-          "name": "Rectangle",
65
-          "parentId": "page1",
66
-          "point": Array [
67
-            500,
68
-            400,
69
-          ],
70
-          "radius": 2,
71
-          "rotation": 0,
72
-          "size": Array [
73
-            200,
74
-            200,
75
-          ],
76
-          "style": Object {
77
-            "color": "Black",
78
-            "dash": "Draw",
79
-            "isFilled": false,
80
-            "size": "Medium",
81
-          },
82
-          "type": "rectangle",
83
-        },
84
-        "2d842ace-ebc5-4e83-acdf-de29352e5e62": Object {
85
-          "bend": 0,
86
-          "childIndex": 10,
87
-          "decorations": Object {
88
-            "end": "Arrow",
89
-            "middle": null,
90
-            "start": null,
91
-          },
92
-          "handles": Object {
93
-            "bend": Object {
94
-              "id": "bend",
95
-              "index": 2,
96
-              "point": Array [
97
-                1.2250000000000227,
98
-                92.60000000000002,
99
-              ],
100
-            },
101
-            "end": Object {
102
-              "id": "end",
103
-              "index": 1,
104
-              "point": Array [
105
-                2.4500000000000455,
106
-                185.20000000000005,
107
-              ],
108
-            },
109
-            "start": Object {
110
-              "id": "start",
111
-              "index": 0,
112
-              "point": Array [
113
-                0,
114
-                0,
115
-              ],
116
-            },
117
-          },
118
-          "id": "2d842ace-ebc5-4e83-acdf-de29352e5e62",
119
-          "name": "Arrow",
120
-          "parentId": "page1",
121
-          "point": Array [
122
-            616.9,
123
-            1124.3,
124
-          ],
125
-          "rotation": 0,
126
-          "style": Object {
127
-            "color": "Black",
128
-            "dash": "Draw",
129
-            "isFilled": false,
130
-            "size": "Medium",
131
-          },
132
-          "type": "arrow",
133
-        },
134
-        "38e9e750-16c2-4476-93ab-21aeb5f8858f": Object {
135
-          "childIndex": 12,
136
-          "id": "38e9e750-16c2-4476-93ab-21aeb5f8858f",
137
-          "name": "Text",
138
-          "parentId": "page1",
139
-          "point": Array [
140
-            207.16,
141
-            1422.4,
142
-          ],
143
-          "rotation": 0,
144
-          "scale": 1,
145
-          "style": Object {
146
-            "color": "Black",
147
-            "dash": "Draw",
148
-            "isFilled": false,
149
-            "size": "Medium",
150
-          },
151
-          "text": "Hello",
152
-          "type": "text",
153
-        },
154
-        "3c688979-b190-4270-915b-7d8dd22a2bb7": Object {
155
-          "childIndex": 14,
156
-          "id": "3c688979-b190-4270-915b-7d8dd22a2bb7",
157
-          "name": "Text",
158
-          "parentId": "page1",
159
-          "point": Array [
160
-            564.06,
161
-            1558.1,
162
-          ],
163
-          "rotation": 0,
164
-          "scale": 1,
165
-          "style": Object {
166
-            "color": "Black",
167
-            "dash": "Draw",
168
-            "isFilled": false,
169
-            "size": "Medium",
170
-          },
171
-          "text": "Hello",
172
-          "type": "text",
173
-        },
174
-        "51641de1-9787-41b8-afcc-2c85fd1b24c7": Object {
175
-          "childIndex": 7,
176
-          "id": "51641de1-9787-41b8-afcc-2c85fd1b24c7",
177
-          "name": "Ellipse",
178
-          "parentId": "page1",
179
-          "point": Array [
180
-            517.18,
181
-            783.54,
182
-          ],
183
-          "radiusX": 102.99999999999997,
184
-          "radiusY": 102.99999999999994,
185
-          "rotation": 0,
186
-          "style": Object {
187
-            "color": "Black",
188
-            "dash": "Draw",
189
-            "isFilled": false,
190
-            "size": "Medium",
191
-          },
192
-          "type": "ellipse",
193
-        },
194
-        "5ba998df-c036-447a-9b88-d96c71394f52": Object {
195
-          "childIndex": 13,
196
-          "id": "5ba998df-c036-447a-9b88-d96c71394f52",
197
-          "name": "Text",
198
-          "parentId": "page1",
199
-          "point": Array [
200
-            389.57,
201
-            1496.5,
202
-          ],
203
-          "rotation": 0,
204
-          "scale": 1,
205
-          "style": Object {
206
-            "color": "Black",
207
-            "dash": "Draw",
208
-            "isFilled": false,
209
-            "size": "Medium",
210
-          },
211
-          "text": "Hello",
212
-          "type": "text",
213
-        },
214
-        "75010635-8dfb-48ea-9250-719e50e58f02": Object {
215
-          "childIndex": 5,
216
-          "id": "75010635-8dfb-48ea-9250-719e50e58f02",
217
-          "name": "Rectangle",
218
-          "parentId": "page1",
219
-          "point": Array [
220
-            384.09,
221
-            378.45,
222
-          ],
223
-          "radius": 2,
224
-          "rotation": 0,
225
-          "size": Array [
226
-            95.20999999999992,
227
-            91.1799999999999,
228
-          ],
229
-          "style": Object {
230
-            "color": "Black",
231
-            "dash": "Draw",
232
-            "isFilled": false,
233
-            "size": "Medium",
234
-          },
235
-          "type": "rectangle",
236
-        },
237
-        "b8e4e2c5-c662-4587-bf80-b9820ad8ad7f": Object {
238
-          "bend": 0,
239
-          "childIndex": 11,
240
-          "decorations": Object {
241
-            "end": "Arrow",
242
-            "middle": null,
243
-            "start": null,
244
-          },
245
-          "handles": Object {
246
-            "bend": Object {
247
-              "id": "bend",
248
-              "index": 2,
249
-              "point": Array [
250
-                0.9250000000000114,
251
-                47.85000000000002,
252
-              ],
253
-            },
254
-            "end": Object {
255
-              "id": "end",
256
-              "index": 1,
257
-              "point": Array [
258
-                1.8500000000000227,
259
-                95.70000000000005,
260
-              ],
261
-            },
262
-            "start": Object {
263
-              "id": "start",
264
-              "index": 0,
265
-              "point": Array [
266
-                0,
267
-                0,
268
-              ],
269
-            },
270
-          },
271
-          "id": "b8e4e2c5-c662-4587-bf80-b9820ad8ad7f",
272
-          "name": "Arrow",
273
-          "parentId": "page1",
274
-          "point": Array [
275
-            425.18,
276
-            1143.2,
277
-          ],
278
-          "rotation": 0,
279
-          "style": Object {
280
-            "color": "Black",
281
-            "dash": "Draw",
282
-            "isFilled": false,
283
-            "size": "Medium",
284
-          },
285
-          "type": "arrow",
286
-        },
287
-        "c892d665-3311-4e25-a0bf-c4632d777f7e": Object {
288
-          "childIndex": 6,
289
-          "id": "c892d665-3311-4e25-a0bf-c4632d777f7e",
290
-          "name": "Ellipse",
291
-          "parentId": "page1",
292
-          "point": Array [
293
-            162.45,
294
-            679.23,
295
-          ],
296
-          "radiusX": 102.99999999999997,
297
-          "radiusY": 102.99999999999994,
298
-          "rotation": 0,
299
-          "style": Object {
300
-            "color": "Black",
301
-            "dash": "Draw",
302
-            "isFilled": false,
303
-            "size": "Medium",
304
-          },
305
-          "type": "ellipse",
306
-        },
307
-        "e08c415e-3db3-4d3b-878e-28ce693ec1b0": Object {
308
-          "childIndex": 8,
309
-          "id": "e08c415e-3db3-4d3b-878e-28ce693ec1b0",
310
-          "name": "Ellipse",
311
-          "parentId": "page1",
312
-          "point": Array [
313
-            398.99,
314
-            810.79,
315
-          ],
316
-          "radiusX": 45.484999999999985,
317
-          "radiusY": 45.48499999999996,
318
-          "rotation": 0,
319
-          "style": Object {
320
-            "color": "Black",
321
-            "dash": "Draw",
322
-            "isFilled": false,
323
-            "size": "Medium",
324
-          },
325
-          "type": "ellipse",
326
-        },
327
-        "e43559cb-6f41-4ae4-9c49-158ed1ad2f72": Object {
328
-          "childIndex": 3,
329
-          "id": "e43559cb-6f41-4ae4-9c49-158ed1ad2f72",
330
-          "name": "Rectangle",
331
-          "parentId": "page1",
332
-          "point": Array [
333
-            100,
334
-            100,
335
-          ],
336
-          "radius": 2,
337
-          "rotation": 0,
338
-          "size": Array [
339
-            100,
340
-            100,
341
-          ],
342
-          "style": Object {
343
-            "color": "Black",
344
-            "dash": "Draw",
345
-            "isFilled": false,
346
-            "size": "Medium",
347
-          },
348
-          "type": "rectangle",
349
-        },
350
-        "fee77127-e779-4576-882b-b1bf7c7e132f": Object {
351
-          "bend": 0,
352
-          "childIndex": 9,
353
-          "decorations": Object {
354
-            "end": "Arrow",
355
-            "middle": null,
356
-            "start": null,
357
-          },
358
-          "handles": Object {
359
-            "bend": Object {
360
-              "id": "bend",
361
-              "index": 2,
362
-              "point": Array [
363
-                0.045000000000001705,
364
-                104,
365
-              ],
366
-            },
367
-            "end": Object {
368
-              "id": "end",
369
-              "index": 1,
370
-              "point": Array [
371
-                0.09000000000000341,
372
-                208,
373
-              ],
374
-            },
375
-            "start": Object {
376
-              "id": "start",
377
-              "index": 0,
378
-              "point": Array [
379
-                0,
380
-                0,
381
-              ],
382
-            },
383
-          },
384
-          "id": "fee77127-e779-4576-882b-b1bf7c7e132f",
385
-          "name": "Arrow",
386
-          "parentId": "page1",
387
-          "point": Array [
388
-            252.85,
389
-            1057.5,
390
-          ],
391
-          "rotation": 0,
392
-          "style": Object {
393
-            "color": "Black",
394
-            "dash": "Draw",
395
-            "isFilled": false,
396
-            "size": "Medium",
397
-          },
398
-          "type": "arrow",
399
-        },
400
-      },
401
-      "type": "page",
402
-    },
403
-  },
404
-}
405
-`;
406
-
407
-exports[`restoring project remounts the state after mutating the current state: data after re-mount from file 1`] = `
408
-Object {
409
-  "code": Object {
410
-    "file0": Object {
411
-      "code": "
412
-const draw = new Draw({
413
-  points: [
414
-    ...Utils.getPointsBetween([0, 0], [20, 50]),
415
-    ...Utils.getPointsBetween([20, 50], [100, 20], 3),
416
-    ...Utils.getPointsBetween([100, 20], [100, 100], 10),
417
-    [100, 100],
418
-  ],
419
-})
420
-
421
-const rectangle = new Rectangle({
422
-  point: [200, 0],
423
-  style: {
424
-    color: ColorStyle.Blue,
425
-  },
426
-})
427
-
428
-const ellipse = new Ellipse({
429
-  point: [400, 0],
430
-})
431
-
432
-const arrow = new Arrow({
433
-  start: [600, 0],
434
-  end: [700, 100],
435
-})
436
-
437
-const radius = 1000
438
-const count = 100
439
-const center = [350, 50]
440
-
441
-for (let i = 0; i < count; i++) {
442
-  const point = Vec.rotWith(
443
-    Vec.add(center, [radius, 0]),
444
-    center,
445
-    (Math.PI * 2 * i) / count
446
-  )
447
-
448
-  const dot = new Dot({
449
-    point,
450
-  })
451
-}
452
-        ",
453
-      "id": "file0",
454
-      "name": "index.ts",
455
-    },
456
-  },
457
-  "id": "TESTING",
458
-  "name": "My Document",
459
-  "pages": Object {
460
-    "page1": Object {
461
-      "childIndex": 0,
462
-      "id": "page1",
463
-      "name": "Page 1",
464
-      "shapes": Object {
465
-        "13448777-d8f5-46cd-8a70-a4259211902e": Object {
466
-          "childIndex": 4,
467
-          "id": "13448777-d8f5-46cd-8a70-a4259211902e",
468
-          "name": "Rectangle",
469
-          "parentId": "page1",
470
-          "point": Array [
471
-            500,
472
-            400,
473
-          ],
474
-          "radius": 2,
475
-          "rotation": 0,
476
-          "size": Array [
477
-            200,
478
-            200,
479
-          ],
480
-          "style": Object {
481
-            "color": "Black",
482
-            "dash": "Draw",
483
-            "isFilled": false,
484
-            "size": "Medium",
485
-          },
486
-          "type": "rectangle",
487
-        },
488
-        "2d842ace-ebc5-4e83-acdf-de29352e5e62": Object {
489
-          "bend": 0,
490
-          "childIndex": 10,
491
-          "decorations": Object {
492
-            "end": "Arrow",
493
-            "middle": null,
494
-            "start": null,
495
-          },
496
-          "handles": Object {
497
-            "bend": Object {
498
-              "id": "bend",
499
-              "index": 2,
500
-              "point": Array [
501
-                1.2250000000000227,
502
-                92.60000000000002,
503
-              ],
504
-            },
505
-            "end": Object {
506
-              "id": "end",
507
-              "index": 1,
508
-              "point": Array [
509
-                2.4500000000000455,
510
-                185.20000000000005,
511
-              ],
512
-            },
513
-            "start": Object {
514
-              "id": "start",
515
-              "index": 0,
516
-              "point": Array [
517
-                0,
518
-                0,
519
-              ],
520
-            },
521
-          },
522
-          "id": "2d842ace-ebc5-4e83-acdf-de29352e5e62",
523
-          "name": "Arrow",
524
-          "parentId": "page1",
525
-          "point": Array [
526
-            616.9,
527
-            1124.3,
528
-          ],
529
-          "rotation": 0,
530
-          "style": Object {
531
-            "color": "Black",
532
-            "dash": "Draw",
533
-            "isFilled": false,
534
-            "size": "Medium",
535
-          },
536
-          "type": "arrow",
537
-        },
538
-        "38e9e750-16c2-4476-93ab-21aeb5f8858f": Object {
539
-          "childIndex": 12,
540
-          "id": "38e9e750-16c2-4476-93ab-21aeb5f8858f",
541
-          "name": "Text",
542
-          "parentId": "page1",
543
-          "point": Array [
544
-            207.16,
545
-            1422.4,
546
-          ],
547
-          "rotation": 0,
548
-          "scale": 1,
549
-          "style": Object {
550
-            "color": "Black",
551
-            "dash": "Draw",
552
-            "isFilled": false,
553
-            "size": "Medium",
554
-          },
555
-          "text": "Hello",
556
-          "type": "text",
557
-        },
558
-        "3c688979-b190-4270-915b-7d8dd22a2bb7": Object {
559
-          "childIndex": 14,
560
-          "id": "3c688979-b190-4270-915b-7d8dd22a2bb7",
561
-          "name": "Text",
562
-          "parentId": "page1",
563
-          "point": Array [
564
-            564.06,
565
-            1558.1,
566
-          ],
567
-          "rotation": 0,
568
-          "scale": 1,
569
-          "style": Object {
570
-            "color": "Black",
571
-            "dash": "Draw",
572
-            "isFilled": false,
573
-            "size": "Medium",
574
-          },
575
-          "text": "Hello",
576
-          "type": "text",
577
-        },
578
-        "51641de1-9787-41b8-afcc-2c85fd1b24c7": Object {
579
-          "childIndex": 7,
580
-          "id": "51641de1-9787-41b8-afcc-2c85fd1b24c7",
581
-          "name": "Ellipse",
582
-          "parentId": "page1",
583
-          "point": Array [
584
-            517.18,
585
-            783.54,
586
-          ],
587
-          "radiusX": 102.99999999999997,
588
-          "radiusY": 102.99999999999994,
589
-          "rotation": 0,
590
-          "style": Object {
591
-            "color": "Black",
592
-            "dash": "Draw",
593
-            "isFilled": false,
594
-            "size": "Medium",
595
-          },
596
-          "type": "ellipse",
597
-        },
598
-        "5ba998df-c036-447a-9b88-d96c71394f52": Object {
599
-          "childIndex": 13,
600
-          "id": "5ba998df-c036-447a-9b88-d96c71394f52",
601
-          "name": "Text",
602
-          "parentId": "page1",
603
-          "point": Array [
604
-            389.57,
605
-            1496.5,
606
-          ],
607
-          "rotation": 0,
608
-          "scale": 1,
609
-          "style": Object {
610
-            "color": "Black",
611
-            "dash": "Draw",
612
-            "isFilled": false,
613
-            "size": "Medium",
614
-          },
615
-          "text": "Hello",
616
-          "type": "text",
617
-        },
618
-        "75010635-8dfb-48ea-9250-719e50e58f02": Object {
619
-          "childIndex": 5,
620
-          "id": "75010635-8dfb-48ea-9250-719e50e58f02",
621
-          "name": "Rectangle",
622
-          "parentId": "page1",
623
-          "point": Array [
624
-            384.09,
625
-            378.45,
626
-          ],
627
-          "radius": 2,
628
-          "rotation": 0,
629
-          "size": Array [
630
-            95.20999999999992,
631
-            91.1799999999999,
632
-          ],
633
-          "style": Object {
634
-            "color": "Black",
635
-            "dash": "Draw",
636
-            "isFilled": false,
637
-            "size": "Medium",
638
-          },
639
-          "type": "rectangle",
640
-        },
641
-        "b8e4e2c5-c662-4587-bf80-b9820ad8ad7f": Object {
642
-          "bend": 0,
643
-          "childIndex": 11,
644
-          "decorations": Object {
645
-            "end": "Arrow",
646
-            "middle": null,
647
-            "start": null,
648
-          },
649
-          "handles": Object {
650
-            "bend": Object {
651
-              "id": "bend",
652
-              "index": 2,
653
-              "point": Array [
654
-                0.9250000000000114,
655
-                47.85000000000002,
656
-              ],
657
-            },
658
-            "end": Object {
659
-              "id": "end",
660
-              "index": 1,
661
-              "point": Array [
662
-                1.8500000000000227,
663
-                95.70000000000005,
664
-              ],
665
-            },
666
-            "start": Object {
667
-              "id": "start",
668
-              "index": 0,
669
-              "point": Array [
670
-                0,
671
-                0,
672
-              ],
673
-            },
674
-          },
675
-          "id": "b8e4e2c5-c662-4587-bf80-b9820ad8ad7f",
676
-          "name": "Arrow",
677
-          "parentId": "page1",
678
-          "point": Array [
679
-            425.18,
680
-            1143.2,
681
-          ],
682
-          "rotation": 0,
683
-          "style": Object {
684
-            "color": "Black",
685
-            "dash": "Draw",
686
-            "isFilled": false,
687
-            "size": "Medium",
688
-          },
689
-          "type": "arrow",
690
-        },
691
-        "c892d665-3311-4e25-a0bf-c4632d777f7e": Object {
692
-          "childIndex": 6,
693
-          "id": "c892d665-3311-4e25-a0bf-c4632d777f7e",
694
-          "name": "Ellipse",
695
-          "parentId": "page1",
696
-          "point": Array [
697
-            162.45,
698
-            679.23,
699
-          ],
700
-          "radiusX": 102.99999999999997,
701
-          "radiusY": 102.99999999999994,
702
-          "rotation": 0,
703
-          "style": Object {
704
-            "color": "Black",
705
-            "dash": "Draw",
706
-            "isFilled": false,
707
-            "size": "Medium",
708
-          },
709
-          "type": "ellipse",
710
-        },
711
-        "e08c415e-3db3-4d3b-878e-28ce693ec1b0": Object {
712
-          "childIndex": 8,
713
-          "id": "e08c415e-3db3-4d3b-878e-28ce693ec1b0",
714
-          "name": "Ellipse",
715
-          "parentId": "page1",
716
-          "point": Array [
717
-            398.99,
718
-            810.79,
719
-          ],
720
-          "radiusX": 45.484999999999985,
721
-          "radiusY": 45.48499999999996,
722
-          "rotation": 0,
723
-          "style": Object {
724
-            "color": "Black",
725
-            "dash": "Draw",
726
-            "isFilled": false,
727
-            "size": "Medium",
728
-          },
729
-          "type": "ellipse",
730
-        },
731
-        "e43559cb-6f41-4ae4-9c49-158ed1ad2f72": Object {
732
-          "childIndex": 3,
733
-          "id": "e43559cb-6f41-4ae4-9c49-158ed1ad2f72",
734
-          "name": "Rectangle",
735
-          "parentId": "page1",
736
-          "point": Array [
737
-            100,
738
-            100,
739
-          ],
740
-          "radius": 2,
741
-          "rotation": 0,
742
-          "size": Array [
743
-            100,
744
-            100,
745
-          ],
746
-          "style": Object {
747
-            "color": "Black",
748
-            "dash": "Draw",
749
-            "isFilled": false,
750
-            "size": "Medium",
751
-          },
752
-          "type": "rectangle",
753
-        },
754
-        "fee77127-e779-4576-882b-b1bf7c7e132f": Object {
755
-          "bend": 0,
756
-          "childIndex": 9,
757
-          "decorations": Object {
758
-            "end": "Arrow",
759
-            "middle": null,
760
-            "start": null,
761
-          },
762
-          "handles": Object {
763
-            "bend": Object {
764
-              "id": "bend",
765
-              "index": 2,
766
-              "point": Array [
767
-                0.045000000000001705,
768
-                104,
769
-              ],
770
-            },
771
-            "end": Object {
772
-              "id": "end",
773
-              "index": 1,
774
-              "point": Array [
775
-                0.09000000000000341,
776
-                208,
777
-              ],
778
-            },
779
-            "start": Object {
780
-              "id": "start",
781
-              "index": 0,
782
-              "point": Array [
783
-                0,
784
-                0,
785
-              ],
786
-            },
787
-          },
788
-          "id": "fee77127-e779-4576-882b-b1bf7c7e132f",
789
-          "name": "Arrow",
790
-          "parentId": "page1",
791
-          "point": Array [
792
-            252.85,
793
-            1057.5,
794
-          ],
795
-          "rotation": 0,
796
-          "style": Object {
797
-            "color": "Black",
798
-            "dash": "Draw",
799
-            "isFilled": false,
800
-            "size": "Medium",
801
-          },
802
-          "type": "arrow",
803
-        },
804
-      },
805
-      "type": "page",
806
-    },
807
-  },
808
-}
809
-`;

+ 0
- 72
__tests__/bounds.test.ts View File

@@ -1,72 +0,0 @@
1
-import { getShapeUtils } from 'state/shape-utils'
2
-import { getCommonBounds } from 'utils'
3
-import TestState, { arrowId, rectangleId } from './test-utils'
4
-
5
-describe('selection', () => {
6
-  const tt = new TestState()
7
-
8
-  it('measures correct bounds for selected item', () => {
9
-    // Note: Each item should test its own bounds in its ./shapes/[shape].tsx file
10
-
11
-    const shape = tt.getShape(rectangleId)
12
-
13
-    tt.deselectAll().clickShape(rectangleId)
14
-
15
-    expect(tt.state.values.selectedBounds).toStrictEqual(
16
-      getShapeUtils(shape).getBounds(shape)
17
-    )
18
-  })
19
-
20
-  it('measures correct bounds for rotated selected item', () => {
21
-    const shape = tt.getShape(rectangleId)
22
-
23
-    getShapeUtils(shape).rotateBy(shape, Math.PI * 2 * Math.random())
24
-
25
-    tt.deselectAll().clickShape(rectangleId)
26
-
27
-    expect(tt.state.values.selectedBounds).toStrictEqual(
28
-      getShapeUtils(shape).getBounds(shape)
29
-    )
30
-
31
-    getShapeUtils(shape).rotateBy(shape, -Math.PI * 2 * Math.random())
32
-
33
-    expect(tt.state.values.selectedBounds).toStrictEqual(
34
-      getShapeUtils(shape).getBounds(shape)
35
-    )
36
-  })
37
-
38
-  it('measures correct bounds for selected items', () => {
39
-    const shape1 = tt.getShape(rectangleId)
40
-    const shape2 = tt.getShape(arrowId)
41
-
42
-    tt.deselectAll()
43
-      .clickShape(shape1.id)
44
-      .clickShape(shape2.id, { shiftKey: true })
45
-
46
-    expect(tt.state.values.selectedBounds).toStrictEqual(
47
-      getCommonBounds(
48
-        getShapeUtils(shape1).getRotatedBounds(shape1),
49
-        getShapeUtils(shape2).getRotatedBounds(shape2)
50
-      )
51
-    )
52
-  })
53
-
54
-  it('measures correct bounds for rotated selected items', () => {
55
-    const shape1 = tt.getShape(rectangleId)
56
-    const shape2 = tt.getShape(arrowId)
57
-
58
-    getShapeUtils(shape1).rotateBy(shape1, Math.PI * 2 * Math.random())
59
-    getShapeUtils(shape2).rotateBy(shape2, Math.PI * 2 * Math.random())
60
-
61
-    tt.deselectAll()
62
-      .clickShape(shape1.id)
63
-      .clickShape(shape2.id, { shiftKey: true })
64
-
65
-    expect(tt.state.values.selectedBounds).toStrictEqual(
66
-      getCommonBounds(
67
-        getShapeUtils(shape1).getRotatedBounds(shape1),
68
-        getShapeUtils(shape2).getRotatedBounds(shape2)
69
-      )
70
-    )
71
-  })
72
-})

+ 0
- 232
__tests__/children.test.ts View File

@@ -1,232 +0,0 @@
1
-import { MoveType, ShapeType } from 'types'
2
-import TestState from './test-utils'
3
-
4
-describe('shapes with children', () => {
5
-  const tt = new TestState()
6
-
7
-  tt.resetDocumentState()
8
-    .createShape(
9
-      {
10
-        type: ShapeType.Rectangle,
11
-        point: [0, 0],
12
-        size: [100, 100],
13
-        childIndex: 1,
14
-      },
15
-      'delete-me-bottom'
16
-    )
17
-    .createShape(
18
-      {
19
-        type: ShapeType.Rectangle,
20
-        point: [0, 0],
21
-        size: [100, 100],
22
-        childIndex: 2,
23
-      },
24
-      '1'
25
-    )
26
-    .createShape(
27
-      {
28
-        type: ShapeType.Rectangle,
29
-        point: [300, 0],
30
-        size: [100, 100],
31
-        childIndex: 3,
32
-      },
33
-      '2'
34
-    )
35
-    .createShape(
36
-      {
37
-        type: ShapeType.Rectangle,
38
-        point: [0, 300],
39
-        size: [100, 100],
40
-        childIndex: 4,
41
-      },
42
-      'delete-me-middle'
43
-    )
44
-    .createShape(
45
-      {
46
-        type: ShapeType.Rectangle,
47
-        point: [0, 300],
48
-        size: [100, 100],
49
-        childIndex: 5,
50
-      },
51
-      '3'
52
-    )
53
-    .createShape(
54
-      {
55
-        type: ShapeType.Rectangle,
56
-        point: [300, 300],
57
-        size: [100, 100],
58
-        childIndex: 6,
59
-      },
60
-      '4'
61
-    )
62
-
63
-  // Delete shapes at the start and in the middle of the list
64
-
65
-  tt.clickShape('delete-me-bottom')
66
-    .send('DELETED')
67
-    .clickShape('delete-me-middle')
68
-    .send('DELETED')
69
-
70
-  it('has shapes in order', () => {
71
-    expect(
72
-      Object.values(tt.data.document.pages[tt.data.currentParentId].shapes)
73
-        .sort((a, b) => a.childIndex - b.childIndex)
74
-        .map((shape) => shape.childIndex)
75
-    ).toStrictEqual([2, 3, 5, 6])
76
-  })
77
-
78
-  it('moves a shape to back', () => {
79
-    tt.clickShape('3').send('MOVED', {
80
-      type: MoveType.ToBack,
81
-    })
82
-
83
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['3', '1', '2', '4'])
84
-  })
85
-
86
-  it('moves two adjacent siblings to back', () => {
87
-    tt.clickShape('4').clickShape('2', { shiftKey: true }).send('MOVED', {
88
-      type: MoveType.ToBack,
89
-    })
90
-
91
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['2', '4', '3', '1'])
92
-  })
93
-
94
-  it('moves two non-adjacent siblings to back', () => {
95
-    tt.clickShape('4').clickShape('1', { shiftKey: true }).send('MOVED', {
96
-      type: MoveType.ToBack,
97
-    })
98
-
99
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['4', '1', '2', '3'])
100
-  })
101
-
102
-  it('moves a shape backward', () => {
103
-    tt.clickShape('3').send('MOVED', {
104
-      type: MoveType.Backward,
105
-    })
106
-
107
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['4', '1', '3', '2'])
108
-  })
109
-
110
-  it('moves a shape at first index backward', () => {
111
-    tt.clickShape('4').send('MOVED', {
112
-      type: MoveType.Backward,
113
-    })
114
-
115
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['4', '1', '3', '2'])
116
-  })
117
-
118
-  it('moves two adjacent siblings backward', () => {
119
-    tt.clickShape('3').clickShape('2', { shiftKey: true }).send('MOVED', {
120
-      type: MoveType.Backward,
121
-    })
122
-
123
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['4', '3', '2', '1'])
124
-  })
125
-
126
-  it('moves two non-adjacent siblings backward', () => {
127
-    tt.clickShape('3')
128
-      .clickShape('1', { shiftKey: true })
129
-      .send('MOVED', { type: MoveType.Backward })
130
-
131
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['3', '4', '1', '2'])
132
-  })
133
-
134
-  it('moves two adjacent siblings backward at zero index', () => {
135
-    tt.clickShape('3').clickShape('4', { shiftKey: true }).send('MOVED', {
136
-      type: MoveType.Backward,
137
-    })
138
-
139
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['3', '4', '1', '2'])
140
-  })
141
-
142
-  it('moves a shape forward', () => {
143
-    tt.clickShape('4').send('MOVED', {
144
-      type: MoveType.Forward,
145
-    })
146
-
147
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['3', '1', '4', '2'])
148
-  })
149
-
150
-  it('moves a shape forward at the top index', () => {
151
-    tt.clickShape('2').send('MOVED', {
152
-      type: MoveType.Forward,
153
-    })
154
-
155
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['3', '1', '4', '2'])
156
-  })
157
-
158
-  it('moves two adjacent siblings forward', () => {
159
-    tt.deselectAll()
160
-      .clickShape('4')
161
-      .clickShape('1', { shiftKey: true })
162
-      .send('MOVED', {
163
-        type: MoveType.Forward,
164
-      })
165
-
166
-    expect(tt.idsAreSelected(['1', '4'])).toBe(true)
167
-
168
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['3', '2', '1', '4'])
169
-  })
170
-
171
-  it('moves two non-adjacent siblings forward', () => {
172
-    tt.deselectAll()
173
-      .clickShape('3')
174
-      .clickShape('1', { shiftKey: true })
175
-      .send('MOVED', {
176
-        type: MoveType.Forward,
177
-      })
178
-
179
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['2', '3', '4', '1'])
180
-  })
181
-
182
-  it('moves two adjacent siblings forward at top index', () => {
183
-    tt.deselectAll()
184
-      .clickShape('3')
185
-      .clickShape('1', { shiftKey: true })
186
-      .send('MOVED', {
187
-        type: MoveType.Forward,
188
-      })
189
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['2', '4', '3', '1'])
190
-  })
191
-
192
-  it('moves a shape to front', () => {
193
-    tt.deselectAll().clickShape('2').send('MOVED', {
194
-      type: MoveType.ToFront,
195
-    })
196
-
197
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['4', '3', '1', '2'])
198
-  })
199
-
200
-  it('moves two adjacent siblings to front', () => {
201
-    tt.deselectAll()
202
-      .clickShape('3')
203
-      .clickShape('1', { shiftKey: true })
204
-      .send('MOVED', {
205
-        type: MoveType.ToFront,
206
-      })
207
-
208
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['4', '2', '3', '1'])
209
-  })
210
-
211
-  it('moves two non-adjacent siblings to front', () => {
212
-    tt.deselectAll()
213
-      .clickShape('4')
214
-      .clickShape('3', { shiftKey: true })
215
-      .send('MOVED', {
216
-        type: MoveType.ToFront,
217
-      })
218
-
219
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['2', '1', '4', '3'])
220
-  })
221
-
222
-  it('moves siblings already at front to front', () => {
223
-    tt.deselectAll()
224
-      .clickShape('4')
225
-      .clickShape('3', { shiftKey: true })
226
-      .send('MOVED', {
227
-        type: MoveType.ToFront,
228
-      })
229
-
230
-    expect(tt.getSortedPageShapeIds()).toStrictEqual(['2', '1', '4', '3'])
231
-  })
232
-})

+ 0
- 284
__tests__/code.test.ts View File

@@ -1,284 +0,0 @@
1
-import { generateFromCode } from 'state/code/generate'
2
-import * as json from './__mocks__/document.json'
3
-import TestState from './test-utils'
4
-
5
-jest.useRealTimers()
6
-
7
-const tt = new TestState()
8
-tt.resetDocumentState()
9
-  .send('LOADED_FROM_FILE', { json: JSON.stringify(json) })
10
-  .send('CLEARED_PAGE')
11
-  .save()
12
-
13
-describe('selection', () => {
14
-  it('opens and closes the code panel', () => {
15
-    expect(tt.data.settings.isCodeOpen).toBe(false)
16
-    tt.send('TOGGLED_CODE_PANEL_OPEN')
17
-    expect(tt.data.settings.isCodeOpen).toBe(true)
18
-    tt.send('TOGGLED_CODE_PANEL_OPEN')
19
-    expect(tt.data.settings.isCodeOpen).toBe(false)
20
-  })
21
-
22
-  it('saves changes to code', () => {
23
-    expect(tt.getSortedPageShapeIds().length).toBe(0)
24
-
25
-    const code = `// hello world!`
26
-
27
-    tt.send('SAVED_CODE', { code })
28
-
29
-    expect(tt.data.document.code[tt.data.currentCodeFileId].code).toBe(code)
30
-  })
31
-
32
-  it('generates shapes', async () => {
33
-    const code = `
34
-    const rectangle = new Rectangle({
35
-      id: "test-rectangle",
36
-      name: 'Test Rectangle',
37
-      point: [100, 100],
38
-      size: [200, 200],
39
-      style: {
40
-        size: SizeStyle.Medium,
41
-        color: ColorStyle.Red,
42
-        dash: DashStyle.Dotted,
43
-      },
44
-    })
45
-    `
46
-
47
-    const { controls, shapes } = await generateFromCode(tt.data, code)
48
-
49
-    tt.send('GENERATED_FROM_CODE', { controls, shapes })
50
-
51
-    expect(tt.getShapes()).toMatchSnapshot('generated rectangle from code')
52
-  })
53
-
54
-  it('creates a code control', async () => {
55
-    const code = `
56
-    new NumberControl({
57
-      id: "test-number-control",
58
-      label: "x"
59
-    })
60
-    `
61
-
62
-    const { controls, shapes } = await generateFromCode(tt.data, code)
63
-
64
-    tt.send('GENERATED_FROM_CODE', { controls, shapes })
65
-
66
-    expect(tt.data.codeControls).toMatchSnapshot(
67
-      'generated code controls from code'
68
-    )
69
-  })
70
-
71
-  it('updates a code control', async () => {
72
-    const code = `
73
-    new NumberControl({
74
-      id: "test-number-control",
75
-      label: "x"
76
-    })
77
-
78
-    new VectorControl({
79
-      id: "test-vector-control",
80
-      label: "size"
81
-    })
82
-
83
-    const rectangle = new Rectangle({
84
-      id: "test-rectangle",
85
-      name: 'Test Rectangle',
86
-      point: [controls.x, 100],
87
-      size: controls.size,
88
-      style: {
89
-        size: SizeStyle.Medium,
90
-        color: ColorStyle.Red,
91
-        dash: DashStyle.Dotted,
92
-      },
93
-    })
94
-    `
95
-
96
-    const { controls, shapes } = await generateFromCode(tt.data, code)
97
-
98
-    tt.send('GENERATED_FROM_CODE', { controls, shapes })
99
-
100
-    tt.send('CHANGED_CODE_CONTROL', { 'test-number-control': 100 })
101
-
102
-    expect(tt.data.codeControls).toMatchSnapshot(
103
-      'data in state after changing control'
104
-    )
105
-
106
-    expect(tt.getShape('test-rectangle')).toMatchSnapshot(
107
-      'rectangle in state after changing code control'
108
-    )
109
-  })
110
-
111
-  /* -------------------- Readonly -------------------- */
112
-
113
-  it('does not saves changes to code when readonly', () => {
114
-    tt.send('CLEARED_PAGE')
115
-
116
-    expect(tt.getShapes().length).toBe(0)
117
-
118
-    const code = `// hello world!`
119
-
120
-    tt.send('SAVED_CODE', { code })
121
-      .send('TOGGLED_READ_ONLY')
122
-      .send('SAVED_CODE', { code: '' })
123
-
124
-    expect(tt.data.document.code[tt.data.currentCodeFileId].code).toBe(code)
125
-
126
-    tt.send('TOGGLED_READ_ONLY').send('SAVED_CODE', { code: '' })
127
-
128
-    expect(tt.data.document.code[tt.data.currentCodeFileId].code).toBe('')
129
-  })
130
-
131
-  /* --------------------- Methods -------------------- */
132
-
133
-  it('moves shape to front', async () => {
134
-    null
135
-  })
136
-
137
-  it('moves shape forward', async () => {
138
-    null
139
-  })
140
-
141
-  it('moves shape backward', async () => {
142
-    null
143
-  })
144
-
145
-  it('moves shape to back', async () => {
146
-    null
147
-  })
148
-
149
-  it('rotates a shape', async () => {
150
-    null
151
-  })
152
-
153
-  it('rotates a shape by a delta', async () => {
154
-    null
155
-  })
156
-
157
-  it('translates a shape', async () => {
158
-    null
159
-  })
160
-
161
-  it('translates a shape by a delta', async () => {
162
-    null
163
-  })
164
-
165
-  /* --------------------- Shapes --------------------- */
166
-
167
-  it('generates a rectangle shape', async () => {
168
-    tt.send('CLEARED_PAGE')
169
-    const code = `
170
-    const rectangle = new Rectangle({
171
-      id: "test-rectangle",
172
-      name: 'Test Rectangle',
173
-      point: [100, 100],
174
-      size: [200, 200],
175
-      style: {
176
-        size: SizeStyle.Medium,
177
-        color: ColorStyle.Red,
178
-        dash: DashStyle.Dotted,
179
-      },
180
-    })
181
-    `
182
-
183
-    const { controls, shapes } = await generateFromCode(tt.data, code)
184
-
185
-    tt.send('GENERATED_FROM_CODE', { controls, shapes })
186
-
187
-    expect(tt.getShapes()).toMatchSnapshot('generated rectangle from code')
188
-  })
189
-
190
-  it('changes a rectangle size', async () => {
191
-    null
192
-  })
193
-
194
-  it('generates an ellipse shape', async () => {
195
-    tt.send('CLEARED_PAGE')
196
-    const code = `
197
-    const ellipse = new Ellipse({
198
-      id: 'test-ellipse',
199
-      name: 'Test ellipse',
200
-      point: [100, 100],
201
-      radiusX: 100,
202
-      radiusY: 200,
203
-      style: {
204
-        size: SizeStyle.Medium,
205
-        color: ColorStyle.Red,
206
-        dash: DashStyle.Dotted,
207
-      },
208
-    })
209
-    `
210
-
211
-    const { controls, shapes } = await generateFromCode(tt.data, code)
212
-
213
-    tt.send('GENERATED_FROM_CODE', { controls, shapes })
214
-
215
-    expect(tt.getShapes()).toMatchSnapshot('generated ellipse from code')
216
-  })
217
-
218
-  it('generates a draw shape', async () => {
219
-    tt.send('CLEARED_PAGE')
220
-    const code = `
221
-    const ellipse = new Draw({
222
-      id: 'test-draw',
223
-      name: 'Test draw',
224
-      points: [[100, 100], [200,200], [300,300]],
225
-      style: {
226
-        size: SizeStyle.Medium,
227
-        color: ColorStyle.Red,
228
-        dash: DashStyle.Dotted,
229
-      },
230
-    })
231
-    `
232
-
233
-    const { controls, shapes } = await generateFromCode(tt.data, code)
234
-
235
-    tt.send('GENERATED_FROM_CODE', { controls, shapes })
236
-
237
-    expect(tt.getShapes()).toMatchSnapshot('generated draw from code')
238
-  })
239
-
240
-  it('generates an arrow shape', async () => {
241
-    tt.send('CLEARED_PAGE')
242
-    const code = `
243
-    const draw = new Arrow({
244
-      id: 'test-draw',
245
-      name: 'Test draw',
246
-      points: [[100, 100], [200,200], [300,300]],
247
-      style: {
248
-        size: SizeStyle.Medium,
249
-        color: ColorStyle.Red,
250
-        dash: DashStyle.Dotted,
251
-      },
252
-    })
253
-    `
254
-
255
-    const { controls, shapes } = await generateFromCode(tt.data, code)
256
-
257
-    tt.send('GENERATED_FROM_CODE', { controls, shapes })
258
-
259
-    expect(tt.getShapes()).toMatchSnapshot('generated draw from code')
260
-  })
261
-
262
-  it('generates a text shape', async () => {
263
-    tt.send('CLEARED_PAGE')
264
-    const code = `
265
-    const text = new Text({
266
-      id: 'test-text',
267
-      name: 'Test text',
268
-      point: [100, 100],
269
-      text: 'Hello world!',
270
-      style: {
271
-        size: SizeStyle.Large,
272
-        color: ColorStyle.Red,
273
-        dash: DashStyle.Dotted,
274
-      },
275
-    })
276
-    `
277
-
278
-    const { controls, shapes } = await generateFromCode(tt.data, code)
279
-
280
-    tt.send('GENERATED_FROM_CODE', { controls, shapes })
281
-
282
-    expect(tt.getShapes()).toMatchSnapshot('generated draw from code')
283
-  })
284
-})

+ 0
- 852
__tests__/commands/__snapshots__/transform.test.ts.snap View File

@@ -1,852 +0,0 @@
1
-// Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
-exports[`transform command snapshot tests shift-transforms corners 1`] = `
4
-Object {
5
-  "height": 593.73,
6
-  "maxX": 700,
7
-  "maxY": 600,
8
-  "minX": -12.476,
9
-  "minY": 6.27,
10
-  "width": 712.476,
11
-}
12
-`;
13
-
14
-exports[`transform command snapshot tests shift-transforms corners 2`] = `
15
-Object {
16
-  "rect1": Object {
17
-    "point": Array [
18
-      -12.476,
19
-      6.27,
20
-    ],
21
-    "size": Array [
22
-      118.75,
23
-      118.75,
24
-    ],
25
-  },
26
-  "rect2": Object {
27
-    "point": Array [
28
-      462.51,
29
-      362.51,
30
-    ],
31
-    "size": Array [
32
-      237.49,
33
-      237.49,
34
-    ],
35
-  },
36
-}
37
-`;
38
-
39
-exports[`transform command snapshot tests shift-transforms corners 3`] = `
40
-Object {
41
-  "height": 531.6320000000001,
42
-  "maxX": 737.9599999999999,
43
-  "maxY": 600,
44
-  "minX": 100,
45
-  "minY": 68.368,
46
-  "width": 637.9599999999999,
47
-}
48
-`;
49
-
50
-exports[`transform command snapshot tests shift-transforms corners 4`] = `
51
-Object {
52
-  "rect1": Object {
53
-    "point": Array [
54
-      100,
55
-      68.368,
56
-    ],
57
-    "size": Array [
58
-      106.33,
59
-      106.33,
60
-    ],
61
-  },
62
-  "rect2": Object {
63
-    "point": Array [
64
-      525.31,
65
-      387.35,
66
-    ],
67
-    "size": Array [
68
-      212.65,
69
-      212.65,
70
-    ],
71
-  },
72
-}
73
-`;
74
-
75
-exports[`transform command snapshot tests shift-transforms corners 5`] = `
76
-Object {
77
-  "height": 533.62,
78
-  "maxX": 740.3499999999999,
79
-  "maxY": 633.62,
80
-  "minX": 100,
81
-  "minY": 100,
82
-  "width": 640.3499999999999,
83
-}
84
-`;
85
-
86
-exports[`transform command snapshot tests shift-transforms corners 6`] = `
87
-Object {
88
-  "rect1": Object {
89
-    "point": Array [
90
-      100,
91
-      100,
92
-    ],
93
-    "size": Array [
94
-      106.72,
95
-      106.72,
96
-    ],
97
-  },
98
-  "rect2": Object {
99
-    "point": Array [
100
-      526.9,
101
-      420.17,
102
-    ],
103
-    "size": Array [
104
-      213.45,
105
-      213.45,
106
-    ],
107
-  },
108
-}
109
-`;
110
-
111
-exports[`transform command snapshot tests shift-transforms corners 7`] = `
112
-Object {
113
-  "height": 490.52,
114
-  "maxX": 700,
115
-  "maxY": 590.52,
116
-  "minX": 111.38,
117
-  "minY": 100,
118
-  "width": 588.62,
119
-}
120
-`;
121
-
122
-exports[`transform command snapshot tests shift-transforms corners 8`] = `
123
-Object {
124
-  "rect1": Object {
125
-    "point": Array [
126
-      111.38,
127
-      100,
128
-    ],
129
-    "size": Array [
130
-      98.104,
131
-      98.104,
132
-    ],
133
-  },
134
-  "rect2": Object {
135
-    "point": Array [
136
-      503.79,
137
-      394.31,
138
-    ],
139
-    "size": Array [
140
-      196.21,
141
-      196.21,
142
-    ],
143
-  },
144
-}
145
-`;
146
-
147
-exports[`transform command snapshot tests shift-transforms edges 1`] = `
148
-Object {
149
-  "height": 593.73,
150
-  "maxX": 756.24,
151
-  "maxY": 600,
152
-  "minX": 43.762,
153
-  "minY": 6.27,
154
-  "width": 712.4780000000001,
155
-}
156
-`;
157
-
158
-exports[`transform command snapshot tests shift-transforms edges 2`] = `
159
-Object {
160
-  "rect1": Object {
161
-    "point": Array [
162
-      43.762,
163
-      6.27,
164
-    ],
165
-    "size": Array [
166
-      118.75,
167
-      118.75,
168
-    ],
169
-  },
170
-  "rect2": Object {
171
-    "point": Array [
172
-      518.75,
173
-      362.51,
174
-    ],
175
-    "size": Array [
176
-      237.49,
177
-      237.49,
178
-    ],
179
-  },
180
-}
181
-`;
182
-
183
-exports[`transform command snapshot tests shift-transforms edges 3`] = `
184
-Object {
185
-  "height": 531.6260000000001,
186
-  "maxX": 737.9599999999999,
187
-  "maxY": 615.8100000000001,
188
-  "minX": 100,
189
-  "minY": 84.184,
190
-  "width": 637.9599999999999,
191
-}
192
-`;
193
-
194
-exports[`transform command snapshot tests shift-transforms edges 4`] = `
195
-Object {
196
-  "rect1": Object {
197
-    "point": Array [
198
-      100,
199
-      84.184,
200
-    ],
201
-    "size": Array [
202
-      106.33,
203
-      106.33,
204
-    ],
205
-  },
206
-  "rect2": Object {
207
-    "point": Array [
208
-      525.31,
209
-      403.16,
210
-    ],
211
-    "size": Array [
212
-      212.65,
213
-      212.65,
214
-    ],
215
-  },
216
-}
217
-`;
218
-
219
-exports[`transform command snapshot tests shift-transforms edges 5`] = `
220
-Object {
221
-  "height": 411.35,
222
-  "maxX": 646.81,
223
-  "maxY": 511.35,
224
-  "minX": 153.19,
225
-  "minY": 100,
226
-  "width": 493.61999999999995,
227
-}
228
-`;
229
-
230
-exports[`transform command snapshot tests shift-transforms edges 6`] = `
231
-Object {
232
-  "rect1": Object {
233
-    "point": Array [
234
-      153.19,
235
-      100,
236
-    ],
237
-    "size": Array [
238
-      82.269,
239
-      82.269,
240
-    ],
241
-  },
242
-  "rect2": Object {
243
-    "point": Array [
244
-      482.27,
245
-      346.81,
246
-    ],
247
-    "size": Array [
248
-      164.54,
249
-      164.54,
250
-    ],
251
-  },
252
-}
253
-`;
254
-
255
-exports[`transform command snapshot tests shift-transforms edges 7`] = `
256
-Object {
257
-  "height": 490.52,
258
-  "maxX": 700,
259
-  "maxY": 595.26,
260
-  "minX": 111.38,
261
-  "minY": 104.74,
262
-  "width": 588.62,
263
-}
264
-`;
265
-
266
-exports[`transform command snapshot tests shift-transforms edges 8`] = `
267
-Object {
268
-  "rect1": Object {
269
-    "point": Array [
270
-      111.38,
271
-      104.74,
272
-    ],
273
-    "size": Array [
274
-      98.104,
275
-      98.104,
276
-    ],
277
-  },
278
-  "rect2": Object {
279
-    "point": Array [
280
-      503.79,
281
-      399.05,
282
-    ],
283
-    "size": Array [
284
-      196.21,
285
-      196.21,
286
-    ],
287
-  },
288
-}
289
-`;
290
-
291
-exports[`transform command snapshot tests transforms corners 1`] = `
292
-Object {
293
-  "height": 593.73,
294
-  "maxX": 700,
295
-  "maxY": 600,
296
-  "minX": 27.892,
297
-  "minY": 6.27,
298
-  "width": 672.108,
299
-}
300
-`;
301
-
302
-exports[`transform command snapshot tests transforms corners 2`] = `
303
-Object {
304
-  "rect1": Object {
305
-    "point": Array [
306
-      27.892,
307
-      6.27,
308
-    ],
309
-    "size": Array [
310
-      112.02,
311
-      118.75,
312
-    ],
313
-  },
314
-  "rect2": Object {
315
-    "point": Array [
316
-      475.96,
317
-      362.51,
318
-    ],
319
-    "size": Array [
320
-      224.04,
321
-      237.49,
322
-    ],
323
-  },
324
-}
325
-`;
326
-
327
-exports[`transform command snapshot tests transforms corners 3`] = `
328
-Object {
329
-  "height": 490.17,
330
-  "maxX": 737.9599999999999,
331
-  "maxY": 600,
332
-  "minX": 100,
333
-  "minY": 109.83,
334
-  "width": 637.9599999999999,
335
-}
336
-`;
337
-
338
-exports[`transform command snapshot tests transforms corners 4`] = `
339
-Object {
340
-  "rect1": Object {
341
-    "point": Array [
342
-      100,
343
-      109.83,
344
-    ],
345
-    "size": Array [
346
-      106.33,
347
-      98.034,
348
-    ],
349
-  },
350
-  "rect2": Object {
351
-    "point": Array [
352
-      525.31,
353
-      403.93,
354
-    ],
355
-    "size": Array [
356
-      212.65,
357
-      196.07,
358
-    ],
359
-  },
360
-}
361
-`;
362
-
363
-exports[`transform command snapshot tests transforms corners 5`] = `
364
-Object {
365
-  "height": 411.35,
366
-  "maxX": 740.3499999999999,
367
-  "maxY": 511.35,
368
-  "minX": 100,
369
-  "minY": 100,
370
-  "width": 640.3499999999999,
371
-}
372
-`;
373
-
374
-exports[`transform command snapshot tests transforms corners 6`] = `
375
-Object {
376
-  "rect1": Object {
377
-    "point": Array [
378
-      100,
379
-      100,
380
-    ],
381
-    "size": Array [
382
-      106.72,
383
-      82.269,
384
-    ],
385
-  },
386
-  "rect2": Object {
387
-    "point": Array [
388
-      526.9,
389
-      346.81,
390
-    ],
391
-    "size": Array [
392
-      213.45,
393
-      164.54,
394
-    ],
395
-  },
396
-}
397
-`;
398
-
399
-exports[`transform command snapshot tests transforms corners 7`] = `
400
-Object {
401
-  "height": 437.4,
402
-  "maxX": 700,
403
-  "maxY": 537.4,
404
-  "minX": 111.38,
405
-  "minY": 100,
406
-  "width": 588.62,
407
-}
408
-`;
409
-
410
-exports[`transform command snapshot tests transforms corners 8`] = `
411
-Object {
412
-  "rect1": Object {
413
-    "point": Array [
414
-      111.38,
415
-      100,
416
-    ],
417
-    "size": Array [
418
-      98.104,
419
-      87.479,
420
-    ],
421
-  },
422
-  "rect2": Object {
423
-    "point": Array [
424
-      503.79,
425
-      362.44,
426
-    ],
427
-    "size": Array [
428
-      196.21,
429
-      174.96,
430
-    ],
431
-  },
432
-}
433
-`;
434
-
435
-exports[`transform command snapshot tests transforms edges 1`] = `
436
-Object {
437
-  "height": 593.73,
438
-  "maxX": 700,
439
-  "maxY": 600,
440
-  "minX": 100,
441
-  "minY": 6.27,
442
-  "width": 600,
443
-}
444
-`;
445
-
446
-exports[`transform command snapshot tests transforms edges 2`] = `
447
-Object {
448
-  "rect1": Object {
449
-    "point": Array [
450
-      100,
451
-      6.27,
452
-    ],
453
-    "size": Array [
454
-      100,
455
-      118.75,
456
-    ],
457
-  },
458
-  "rect2": Object {
459
-    "point": Array [
460
-      500,
461
-      362.51,
462
-    ],
463
-    "size": Array [
464
-      200,
465
-      237.49,
466
-    ],
467
-  },
468
-}
469
-`;
470
-
471
-exports[`transform command snapshot tests transforms edges 3`] = `
472
-Object {
473
-  "height": 500,
474
-  "maxX": 737.9599999999999,
475
-  "maxY": 600,
476
-  "minX": 100,
477
-  "minY": 100,
478
-  "width": 637.9599999999999,
479
-}
480
-`;
481
-
482
-exports[`transform command snapshot tests transforms edges 4`] = `
483
-Object {
484
-  "rect1": Object {
485
-    "point": Array [
486
-      100,
487
-      100,
488
-    ],
489
-    "size": Array [
490
-      106.33,
491
-      100,
492
-    ],
493
-  },
494
-  "rect2": Object {
495
-    "point": Array [
496
-      525.31,
497
-      400,
498
-    ],
499
-    "size": Array [
500
-      212.65,
501
-      200,
502
-    ],
503
-  },
504
-}
505
-`;
506
-
507
-exports[`transform command snapshot tests transforms edges 5`] = `
508
-Object {
509
-  "height": 411.35,
510
-  "maxX": 700,
511
-  "maxY": 511.35,
512
-  "minX": 100,
513
-  "minY": 100,
514
-  "width": 600,
515
-}
516
-`;
517
-
518
-exports[`transform command snapshot tests transforms edges 6`] = `
519
-Object {
520
-  "rect1": Object {
521
-    "point": Array [
522
-      100,
523
-      100,
524
-    ],
525
-    "size": Array [
526
-      100,
527
-      82.269,
528
-    ],
529
-  },
530
-  "rect2": Object {
531
-    "point": Array [
532
-      500,
533
-      346.81,
534
-    ],
535
-    "size": Array [
536
-      200,
537
-      164.54,
538
-    ],
539
-  },
540
-}
541
-`;
542
-
543
-exports[`transform command snapshot tests transforms edges 7`] = `
544
-Object {
545
-  "height": 500,
546
-  "maxX": 700,
547
-  "maxY": 600,
548
-  "minX": 111.38,
549
-  "minY": 100,
550
-  "width": 588.62,
551
-}
552
-`;
553
-
554
-exports[`transform command snapshot tests transforms edges 8`] = `
555
-Object {
556
-  "rect1": Object {
557
-    "point": Array [
558
-      111.38,
559
-      100,
560
-    ],
561
-    "size": Array [
562
-      98.104,
563
-      100,
564
-    ],
565
-  },
566
-  "rect2": Object {
567
-    "point": Array [
568
-      503.79,
569
-      400,
570
-    ],
571
-    "size": Array [
572
-      196.21,
573
-      200,
574
-    ],
575
-  },
576
-}
577
-`;
578
-
579
-exports[`transform command transforms from the bottom edge 1`] = `
580
-Object {
581
-  "rect1": Object {
582
-    "point": Array [
583
-      100,
584
-      100,
585
-    ],
586
-    "size": Array [
587
-      100,
588
-      120,
589
-    ],
590
-  },
591
-  "rect2": Object {
592
-    "point": Array [
593
-      500,
594
-      460,
595
-    ],
596
-    "size": Array [
597
-      200,
598
-      240,
599
-    ],
600
-  },
601
-}
602
-`;
603
-
604
-exports[`transform command transforms from the bottom-left corner 1`] = `
605
-Object {
606
-  "rect1": Object {
607
-    "point": Array [
608
-      200,
609
-      100,
610
-    ],
611
-    "size": Array [
612
-      83.333,
613
-      120,
614
-    ],
615
-  },
616
-  "rect2": Object {
617
-    "point": Array [
618
-      533.33,
619
-      460,
620
-    ],
621
-    "size": Array [
622
-      166.67,
623
-      240,
624
-    ],
625
-  },
626
-}
627
-`;
628
-
629
-exports[`transform command transforms from the bottom-right corner 1`] = `
630
-Object {
631
-  "rect1": Object {
632
-    "point": Array [
633
-      100,
634
-      100,
635
-    ],
636
-    "size": Array [
637
-      116.67,
638
-      120,
639
-    ],
640
-  },
641
-  "rect2": Object {
642
-    "point": Array [
643
-      566.67,
644
-      460,
645
-    ],
646
-    "size": Array [
647
-      233.33,
648
-      240,
649
-    ],
650
-  },
651
-}
652
-`;
653
-
654
-exports[`transform command transforms from the left edge 1`] = `
655
-Object {
656
-  "rect1": Object {
657
-    "point": Array [
658
-      200,
659
-      100,
660
-    ],
661
-    "size": Array [
662
-      83.333,
663
-      100,
664
-    ],
665
-  },
666
-  "rect2": Object {
667
-    "point": Array [
668
-      533.33,
669
-      400,
670
-    ],
671
-    "size": Array [
672
-      166.67,
673
-      200,
674
-    ],
675
-  },
676
-}
677
-`;
678
-
679
-exports[`transform command transforms from the right edge 1`] = `
680
-Object {
681
-  "rect1": Object {
682
-    "point": Array [
683
-      100,
684
-      100,
685
-    ],
686
-    "size": Array [
687
-      116.67,
688
-      100,
689
-    ],
690
-  },
691
-  "rect2": Object {
692
-    "point": Array [
693
-      566.67,
694
-      400,
695
-    ],
696
-    "size": Array [
697
-      233.33,
698
-      200,
699
-    ],
700
-  },
701
-}
702
-`;
703
-
704
-exports[`transform command transforms from the top edge 1`] = `
705
-Object {
706
-  "rect1": Object {
707
-    "point": Array [
708
-      100,
709
-      200,
710
-    ],
711
-    "size": Array [
712
-      100,
713
-      80,
714
-    ],
715
-  },
716
-  "rect2": Object {
717
-    "point": Array [
718
-      500,
719
-      440,
720
-    ],
721
-    "size": Array [
722
-      200,
723
-      160,
724
-    ],
725
-  },
726
-}
727
-`;
728
-
729
-exports[`transform command transforms from the top-left corner 1`] = `
730
-Object {
731
-  "rect1": Object {
732
-    "point": Array [
733
-      200,
734
-      200,
735
-    ],
736
-    "size": Array [
737
-      83.333,
738
-      80,
739
-    ],
740
-  },
741
-  "rect2": Object {
742
-    "point": Array [
743
-      533.33,
744
-      440,
745
-    ],
746
-    "size": Array [
747
-      166.67,
748
-      160,
749
-    ],
750
-  },
751
-}
752
-`;
753
-
754
-exports[`transform command transforms from the top-right corner 1`] = `
755
-Object {
756
-  "rect1": Object {
757
-    "point": Array [
758
-      100,
759
-      200,
760
-    ],
761
-    "size": Array [
762
-      116.67,
763
-      80,
764
-    ],
765
-  },
766
-  "rect2": Object {
767
-    "point": Array [
768
-      566.67,
769
-      440,
770
-    ],
771
-    "size": Array [
772
-      233.33,
773
-      160,
774
-    ],
775
-  },
776
-}
777
-`;
778
-
779
-exports[`transform command when transforming from the bottom-right corner does command 1`] = `
780
-Object {
781
-  "rect1": Object {
782
-    "point": Array [
783
-      100,
784
-      100,
785
-    ],
786
-    "size": Array [
787
-      116.67,
788
-      120,
789
-    ],
790
-  },
791
-  "rect2": Object {
792
-    "point": Array [
793
-      566.67,
794
-      460,
795
-    ],
796
-    "size": Array [
797
-      233.33,
798
-      240,
799
-    ],
800
-  },
801
-}
802
-`;
803
-
804
-exports[`transform command when transforming from the bottom-right corner re-does command 1`] = `
805
-Object {
806
-  "rect1": Object {
807
-    "point": Array [
808
-      100,
809
-      100,
810
-    ],
811
-    "size": Array [
812
-      116.67,
813
-      120,
814
-    ],
815
-  },
816
-  "rect2": Object {
817
-    "point": Array [
818
-      566.67,
819
-      460,
820
-    ],
821
-    "size": Array [
822
-      233.33,
823
-      240,
824
-    ],
825
-  },
826
-}
827
-`;
828
-
829
-exports[`transform command when transforming from the bottom-right corner un-does command 1`] = `
830
-Object {
831
-  "rect1": Object {
832
-    "point": Array [
833
-      100,
834
-      100,
835
-    ],
836
-    "size": Array [
837
-      100,
838
-      100,
839
-    ],
840
-  },
841
-  "rect2": Object {
842
-    "point": Array [
843
-      500,
844
-      400,
845
-    ],
846
-    "size": Array [
847
-      200,
848
-      200,
849
-    ],
850
-  },
851
-}
852
-`;

+ 0
- 40
__tests__/commands/align.test.ts View File

@@ -1,40 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('align command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState()
6
-
7
-  describe('when one item is selected', () => {
8
-    it('does command', () => {
9
-      // TODO
10
-      null
11
-    })
12
-
13
-    it('un-does command', () => {
14
-      // TODO
15
-      null
16
-    })
17
-
18
-    it('re-does command', () => {
19
-      // TODO
20
-      null
21
-    })
22
-  })
23
-
24
-  describe('when multiple items are selected', () => {
25
-    it('does command', () => {
26
-      // TODO
27
-      null
28
-    })
29
-
30
-    it('un-does command', () => {
31
-      // TODO
32
-      null
33
-    })
34
-
35
-    it('re-does command', () => {
36
-      // TODO
37
-      null
38
-    })
39
-  })
40
-})

+ 0
- 21
__tests__/commands/change-page.test.ts View File

@@ -1,21 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('change page command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState()
6
-
7
-  it('does command', () => {
8
-    // TODO
9
-    null
10
-  })
11
-
12
-  it('un-does command', () => {
13
-    // TODO
14
-    null
15
-  })
16
-
17
-  it('re-does command', () => {
18
-    // TODO
19
-    null
20
-  })
21
-})

+ 0
- 38
__tests__/commands/create-page.test.ts View File

@@ -1,38 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('create page command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState().save()
6
-
7
-  describe('creates a page', () => {
8
-    it('does command', () => {
9
-      expect(Object.keys(tt.data.document.pages).length).toBe(1)
10
-
11
-      tt.send('CREATED_PAGE')
12
-
13
-      expect(Object.keys(tt.data.document.pages).length).toBe(2)
14
-    })
15
-
16
-    it('changes to the new page', () => {
17
-      tt.restore().send('CREATED_PAGE')
18
-
19
-      const pageId = Object.keys(tt.data.document.pages)[1]
20
-
21
-      expect(tt.data.currentPageId).toBe(pageId)
22
-    })
23
-
24
-    it('un-does command', () => {
25
-      tt.restore().send('CREATED_PAGE').undo()
26
-      expect(Object.keys(tt.data.document.pages).length).toBe(1)
27
-      const pageId = Object.keys(tt.data.document.pages)[0]
28
-      expect(tt.data.currentPageId).toBe(pageId)
29
-    })
30
-
31
-    it('re-does command', () => {
32
-      tt.restore().send('CREATED_PAGE').undo().redo()
33
-      expect(Object.keys(tt.data.document.pages).length).toBe(2)
34
-      const pageId = Object.keys(tt.data.document.pages)[1]
35
-      expect(tt.data.currentPageId).toBe(pageId)
36
-    })
37
-  })
38
-})

+ 0
- 78
__tests__/commands/delete-page.test.ts View File

@@ -1,78 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('delete page command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState().save()
6
-
7
-  it('does command', () => {
8
-    tt.reset().restore().send('CREATED_PAGE')
9
-    expect(Object.keys(tt.data.document.pages).length).toBe(2)
10
-
11
-    const pageId = Object.keys(tt.data.document.pages)[1]
12
-    tt.send('DELETED_PAGE', { id: pageId })
13
-
14
-    expect(Object.keys(tt.data.document.pages).length).toBe(1)
15
-
16
-    const firstPageId = Object.keys(tt.data.document.pages)[0]
17
-    expect(tt.data.currentPageId).toBe(firstPageId)
18
-  })
19
-
20
-  it('un-does command', () => {
21
-    tt.reset().restore().send('CREATED_PAGE')
22
-    expect(Object.keys(tt.data.document.pages).length).toBe(2)
23
-
24
-    const pageId = Object.keys(tt.data.document.pages)[1]
25
-    tt.send('DELETED_PAGE', { id: pageId }).undo()
26
-
27
-    expect(Object.keys(tt.data.document.pages).length).toBe(2)
28
-
29
-    expect(tt.data.currentPageId).toBe(pageId)
30
-  })
31
-
32
-  it('re-does command', () => {
33
-    tt.reset().restore().send('CREATED_PAGE')
34
-    expect(Object.keys(tt.data.document.pages).length).toBe(2)
35
-
36
-    const pageId = Object.keys(tt.data.document.pages)[1]
37
-    tt.send('DELETED_PAGE', { id: pageId }).undo().redo()
38
-
39
-    expect(Object.keys(tt.data.document.pages).length).toBe(1)
40
-
41
-    const firstPageId = Object.keys(tt.data.document.pages)[0]
42
-    expect(tt.data.currentPageId).toBe(firstPageId)
43
-  })
44
-
45
-  describe('when first page is selected', () => {
46
-    it('does command', () => {
47
-      // TODO
48
-      null
49
-    })
50
-
51
-    it('un-does command', () => {
52
-      // TODO
53
-      null
54
-    })
55
-
56
-    it('re-does command', () => {
57
-      // TODO
58
-      null
59
-    })
60
-  })
61
-
62
-  describe('when project only has one page', () => {
63
-    it('does command', () => {
64
-      // TODO
65
-      null
66
-    })
67
-
68
-    it('un-does command', () => {
69
-      // TODO
70
-      null
71
-    })
72
-
73
-    it('re-does command', () => {
74
-      // TODO
75
-      null
76
-    })
77
-  })
78
-})

+ 0
- 40
__tests__/commands/delete-selected.ts View File

@@ -1,40 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('delete-selected command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState()
6
-
7
-  describe('when one item is selected', () => {
8
-    it('does command', () => {
9
-      // TODO
10
-      null
11
-    })
12
-
13
-    it('un-does command', () => {
14
-      // TODO
15
-      null
16
-    })
17
-
18
-    it('re-does command', () => {
19
-      // TODO
20
-      null
21
-    })
22
-  })
23
-
24
-  describe('when multiple items are selected', () => {
25
-    it('does command', () => {
26
-      // TODO
27
-      null
28
-    })
29
-
30
-    it('un-does command', () => {
31
-      // TODO
32
-      null
33
-    })
34
-
35
-    it('re-does command', () => {
36
-      // TODO
37
-      null
38
-    })
39
-  })
40
-})

+ 0
- 120
__tests__/commands/delete.test.ts View File

@@ -1,120 +0,0 @@
1
-import { ShapeType } from 'types'
2
-import TestState, { rectangleId, arrowId } from '../test-utils'
3
-
4
-describe('delete command', () => {
5
-  const tt = new TestState()
6
-
7
-  describe('deleting single shapes', () => {
8
-    it('deletes a shape and undoes the delete', () => {
9
-      tt.deselectAll().clickShape(rectangleId).pressDelete()
10
-
11
-      expect(tt.idsAreSelected([])).toBe(true)
12
-
13
-      expect(tt.getShape(rectangleId)).toBe(undefined)
14
-
15
-      tt.undo()
16
-
17
-      expect(tt.getShape(rectangleId)).toBeTruthy()
18
-      expect(tt.idsAreSelected([rectangleId])).toBe(true)
19
-
20
-      tt.redo()
21
-
22
-      expect(tt.getShape(rectangleId)).toBe(undefined)
23
-    })
24
-  })
25
-
26
-  describe('deleting and restoring grouped shapes', () => {
27
-    it('creates a group', () => {
28
-      tt.reset()
29
-        .deselectAll()
30
-        .clickShape(rectangleId)
31
-        .clickShape(arrowId, { shiftKey: true })
32
-        .send('GROUPED')
33
-
34
-      const group = tt.getOnlySelectedShape()
35
-
36
-      // Should select the group
37
-      expect(tt.assertShapeProps(group, { type: ShapeType.Group })).toBe(true)
38
-
39
-      const arrow = tt.getShape(arrowId)
40
-
41
-      // The arrow should be have the group as its parent
42
-      expect(tt.assertShapeProps(arrow, { parentId: group.id })).toBe(true)
43
-    })
44
-
45
-    it('selects the new group', () => {
46
-      const groupId = tt.getShape(arrowId).parentId
47
-
48
-      expect(tt.idsAreSelected([groupId])).toBe(true)
49
-    })
50
-
51
-    it('assigns a new parent', () => {
52
-      const groupId = tt.getShape(arrowId).parentId
53
-
54
-      expect(groupId === tt.data.currentPageId).toBe(false)
55
-    })
56
-
57
-    // Rectangle has the same new parent?
58
-    it('assigns new parent to all selected shapes', () => {
59
-      const groupId = tt.getShape(arrowId).parentId
60
-
61
-      expect(tt.hasParent(arrowId, groupId)).toBe(true)
62
-    })
63
-  })
64
-
65
-  describe('selecting within the group', () => {
66
-    it('selects the group when pointing a shape', () => {
67
-      const groupId = tt.getShape(arrowId).parentId
68
-
69
-      tt.deselectAll().clickShape(rectangleId)
70
-
71
-      expect(tt.idsAreSelected([groupId])).toBe(true)
72
-    })
73
-
74
-    it('keeps selection when pointing group shape', () => {
75
-      const groupId = tt.getShape(arrowId).parentId
76
-
77
-      tt.deselectAll().clickShape(groupId)
78
-
79
-      expect(tt.idsAreSelected([groupId])).toBe(true)
80
-    })
81
-
82
-    it('selects a grouped shape by double-pointing', () => {
83
-      tt.deselectAll().doubleClickShape(rectangleId)
84
-
85
-      expect(tt.idsAreSelected([rectangleId])).toBe(true)
86
-    })
87
-
88
-    it('selects a sibling on point after double-pointing into a grouped shape children', () => {
89
-      tt.deselectAll().doubleClickShape(rectangleId).clickShape(arrowId)
90
-
91
-      expect(tt.idsAreSelected([arrowId])).toBe(true)
92
-    })
93
-
94
-    it('rises up a selection level when escape is pressed', () => {
95
-      const groupId = tt.getShape(arrowId).parentId
96
-
97
-      tt.deselectAll().doubleClickShape(rectangleId).send('CANCELLED')
98
-
99
-      tt.clickShape(rectangleId)
100
-
101
-      expect(tt.idsAreSelected([groupId])).toBe(true)
102
-    })
103
-
104
-    // it('deletes and restores one shape', () => {
105
-    //   // Delete the rectangle first
106
-    //   state.send('UNDO')
107
-
108
-    //   expect(tld.getShape(tt.data, rectangleId)).toBeTruthy()
109
-    //   expect(tt.idsAreSelected([rectangleId])).toBe(true)
110
-
111
-    //   state.send('REDO')
112
-
113
-    //   expect(tld.getShape(tt.data, rectangleId)).toBe(undefined)
114
-
115
-    //   state.send('UNDO')
116
-
117
-    //   expect(tld.getShape(tt.data, rectangleId)).toBeTruthy()
118
-    //   expect(tt.idsAreSelected([rectangleId])).toBe(true)
119
-  })
120
-})

+ 0
- 37
__tests__/commands/distribute.test.ts View File

@@ -1,37 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('distribute command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState()
6
-
7
-  describe('when one item is selected', () => {
8
-    it('does not change anything', () => {
9
-      // TODO
10
-      null
11
-    })
12
-  })
13
-
14
-  describe('when two items are selected', () => {
15
-    it('does not change anything', () => {
16
-      // TODO
17
-      null
18
-    })
19
-  })
20
-
21
-  describe('when three or more items are selected', () => {
22
-    it('does command', () => {
23
-      // TODO
24
-      null
25
-    })
26
-
27
-    it('un-does command', () => {
28
-      // TODO
29
-      null
30
-    })
31
-
32
-    it('re-does command', () => {
33
-      // TODO
34
-      null
35
-    })
36
-  })
37
-})

+ 0
- 21
__tests__/commands/draw.test.ts View File

@@ -1,21 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('draw command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState()
6
-
7
-  it('does command', () => {
8
-    // TODO
9
-    null
10
-  })
11
-
12
-  it('un-does command', () => {
13
-    // TODO
14
-    null
15
-  })
16
-
17
-  it('re-does command', () => {
18
-    // TODO
19
-    null
20
-  })
21
-})

+ 0
- 109
__tests__/commands/duplicate-page.test.ts View File

@@ -1,109 +0,0 @@
1
-import { ShapeType } from 'types'
2
-import TestState from '../test-utils'
3
-
4
-describe('duplicate page command', () => {
5
-  const tt = new TestState()
6
-  tt.resetDocumentState()
7
-    .createShape(
8
-      {
9
-        type: ShapeType.Rectangle,
10
-        point: [0, 0],
11
-        size: [100, 100],
12
-        childIndex: 1,
13
-      },
14
-      'rect1'
15
-    )
16
-    .save()
17
-
18
-  describe('duplicates a page', () => {
19
-    it('does, undoes, and redoes command', () => {
20
-      tt.reset().restore()
21
-
22
-      expect(Object.keys(tt.data.document.pages).length).toBe(1)
23
-      const pageId = Object.keys(tt.data.document.pages)[0]
24
-      expect(tt.getShape('rect1').parentId).toBe(pageId)
25
-
26
-      tt.send('DUPLICATED_PAGE', { id: pageId })
27
-
28
-      expect(Object.keys(tt.data.document.pages).length).toBe(2)
29
-
30
-      const newPageId = Object.keys(tt.data.document.pages)[1]
31
-
32
-      expect(tt.data.currentPageId).toBe(newPageId)
33
-
34
-      expect(tt.getShape('rect1').parentId).toBe(newPageId)
35
-
36
-      tt.undo()
37
-
38
-      expect(Object.keys(tt.data.document.pages).length).toBe(1)
39
-      expect(tt.data.currentPageId).toBe(Object.keys(tt.data.document.pages)[0])
40
-
41
-      expect(tt.getShape('rect1').parentId).toBe(pageId)
42
-
43
-      tt.redo()
44
-
45
-      expect(Object.keys(tt.data.document.pages).length).toBe(2)
46
-      expect(tt.data.currentPageId).toBe(Object.keys(tt.data.document.pages)[1])
47
-
48
-      expect(tt.getShape('rect1').parentId).toBe(newPageId)
49
-    })
50
-  })
51
-
52
-  describe('duplicates a page other than the current page', () => {
53
-    tt.restore()
54
-      .reset()
55
-      .send('CREATED_PAGE')
56
-      .createShape(
57
-        {
58
-          type: ShapeType.Rectangle,
59
-          point: [0, 0],
60
-          size: [100, 100],
61
-          childIndex: 1,
62
-        },
63
-        'rect2'
64
-      )
65
-      .send('CHANGED_PAGE', { id: 'page1' })
66
-
67
-    const firstPageId = Object.keys(tt.data.document.pages)[0]
68
-
69
-    // We should be back on the first page
70
-    expect(tt.data.currentPageId).toBe(firstPageId)
71
-
72
-    // But we should have two pages
73
-    expect(Object.keys(tt.data.document.pages).length).toBe(2)
74
-
75
-    const secondPageId = Object.keys(tt.data.document.pages)[1]
76
-
77
-    // Now we duplicate the second page
78
-    tt.send('DUPLICATED_PAGE', { id: secondPageId })
79
-
80
-    // We should now have three pages
81
-    expect(Object.keys(tt.data.document.pages).length).toBe(3)
82
-
83
-    // The third page should also have a shape named rect2
84
-    const thirdPageId = Object.keys(tt.data.document.pages)[2]
85
-
86
-    // We should have changed pages to the third page
87
-    expect(tt.data.currentPageId).toBe(thirdPageId)
88
-
89
-    // And it should be the parent of the third page
90
-    expect(tt.getShape('rect2').parentId).toBe(thirdPageId)
91
-
92
-    tt.undo()
93
-
94
-    // We should still be on the first page, but we should
95
-    // have only two pages; the third page should be deleted
96
-    expect(Object.keys(tt.data.document.pages).length).toBe(2)
97
-    expect(tt.data.document.pages[thirdPageId]).toBe(undefined)
98
-    expect(tt.data.currentPageId).toBe(firstPageId)
99
-
100
-    tt.redo()
101
-
102
-    // We should be back on the third page
103
-    expect(Object.keys(tt.data.document.pages).length).toBe(3)
104
-    expect(tt.data.document.pages[thirdPageId]).toBeTruthy()
105
-    expect(tt.data.currentPageId).toBe(Object.keys(tt.data.document.pages)[2])
106
-
107
-    expect(tt.getShape('rect2').parentId).toBe(thirdPageId)
108
-  })
109
-})

+ 0
- 116
__tests__/commands/duplicate.test.ts View File

@@ -1,116 +0,0 @@
1
-import { ShapeType } from 'types'
2
-import TestState from '../test-utils'
3
-
4
-describe('duplicate command', () => {
5
-  const tt = new TestState()
6
-  tt.resetDocumentState()
7
-    .createShape(
8
-      {
9
-        type: ShapeType.Rectangle,
10
-        point: [0, 0],
11
-        size: [100, 100],
12
-      },
13
-      'rectangleShape'
14
-    )
15
-    .createShape(
16
-      {
17
-        type: ShapeType.Ellipse,
18
-        point: [150, 150],
19
-        radiusX: 50,
20
-        radiusY: 50,
21
-      },
22
-      'ellipseShape'
23
-    )
24
-    .save()
25
-
26
-  describe('when one item is selected', () => {
27
-    it('does, undoes and redoes command', () => {
28
-      tt.restore()
29
-
30
-      const shapesBeforeDuplication = tt.getSortedPageShapeIds()
31
-
32
-      tt.clickShape('rectangleShape').send('DUPLICATED')
33
-
34
-      const shapesAfterDuplication = tt.getSortedPageShapeIds()
35
-
36
-      const duplicatedShapeId = tt.selectedIds[0]
37
-      const duplicatedShape = tt.getShape(duplicatedShapeId)
38
-
39
-      expect(shapesAfterDuplication.length).toEqual(
40
-        shapesBeforeDuplication.length + 1
41
-      )
42
-      expect(
43
-        tt.assertShapeProps(duplicatedShape, {
44
-          type: ShapeType.Rectangle,
45
-          size: [100, 100],
46
-        })
47
-      )
48
-
49
-      tt.undo()
50
-
51
-      const shapesAfterUndo = tt.getSortedPageShapeIds()
52
-
53
-      expect(shapesAfterUndo.length).toEqual(shapesBeforeDuplication.length)
54
-      expect(tt.getShape(duplicatedShapeId)).toBe(undefined)
55
-      expect(tt.idsAreSelected(['rectangleShape'])).toBe(true)
56
-
57
-      tt.redo()
58
-
59
-      expect(tt.getShape(duplicatedShapeId)).toBeTruthy()
60
-      expect(tt.idsAreSelected([duplicatedShapeId])).toBe(true)
61
-    })
62
-  })
63
-
64
-  describe('when multiple items are selected', () => {
65
-    it('does, undoes and redoes command', () => {
66
-      tt.restore()
67
-
68
-      const shapesBeforeDuplication = tt.getSortedPageShapeIds()
69
-
70
-      tt.clickShape('rectangleShape')
71
-        .clickShape('ellipseShape', { shiftKey: true })
72
-        .send('DUPLICATED')
73
-
74
-      const shapesAfterDuplication = tt.getSortedPageShapeIds()
75
-
76
-      const duplicatedShapesIds = tt.selectedIds
77
-      const [duplicatedRectangle, duplicatedEllipse] = duplicatedShapesIds.map(
78
-        (shapeId) => tt.getShape(shapeId)
79
-      )
80
-
81
-      expect(shapesAfterDuplication.length).toEqual(
82
-        shapesBeforeDuplication.length * 2
83
-      )
84
-      expect(
85
-        tt.assertShapeProps(duplicatedRectangle, {
86
-          type: ShapeType.Rectangle,
87
-          size: [100, 100],
88
-        })
89
-      )
90
-      expect(
91
-        tt.assertShapeProps(duplicatedEllipse, {
92
-          type: ShapeType.Ellipse,
93
-          radiusX: 50,
94
-          radiusY: 50,
95
-        })
96
-      )
97
-
98
-      tt.undo()
99
-
100
-      const shapesAfterUndo = tt.getSortedPageShapeIds()
101
-
102
-      expect(shapesAfterUndo.length).toEqual(shapesBeforeDuplication.length)
103
-      duplicatedShapesIds.forEach((shapeId) => {
104
-        expect(tt.getShape(shapeId)).toBe(undefined)
105
-      })
106
-      expect(tt.idsAreSelected(['rectangleShape', 'ellipseShape'])).toBe(true)
107
-
108
-      tt.redo()
109
-
110
-      duplicatedShapesIds.forEach((shapeId) => {
111
-        expect(tt.getShape(shapeId)).toBeTruthy()
112
-      })
113
-      expect(tt.idsAreSelected(duplicatedShapesIds)).toBe(true)
114
-    })
115
-  })
116
-})

+ 0
- 21
__tests__/commands/edit.test.ts View File

@@ -1,21 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('edit command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState()
6
-
7
-  it('does command', () => {
8
-    // TODO
9
-    null
10
-  })
11
-
12
-  it('un-does command', () => {
13
-    // TODO
14
-    null
15
-  })
16
-
17
-  it('re-does command', () => {
18
-    // TODO
19
-    null
20
-  })
21
-})

+ 0
- 21
__tests__/commands/generate.test.ts View File

@@ -1,21 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('generate command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState()
6
-
7
-  it('does command', () => {
8
-    // TODO
9
-    null
10
-  })
11
-
12
-  it('un-does command', () => {
13
-    // TODO
14
-    null
15
-  })
16
-
17
-  it('re-does command', () => {
18
-    // TODO
19
-    null
20
-  })
21
-})

+ 0
- 151
__tests__/commands/group.test.ts View File

@@ -1,151 +0,0 @@
1
-import { ShapeType } from 'types'
2
-import TestState from '../test-utils'
3
-
4
-describe('group command', () => {
5
-  const tt = new TestState()
6
-  tt.resetDocumentState()
7
-    .createShape(
8
-      {
9
-        type: ShapeType.Rectangle,
10
-        point: [0, 0],
11
-        size: [100, 100],
12
-        childIndex: 1,
13
-        isLocked: false,
14
-        isHidden: false,
15
-        isAspectRatioLocked: false,
16
-      },
17
-      'rect1'
18
-    )
19
-    .createShape(
20
-      {
21
-        type: ShapeType.Rectangle,
22
-        point: [400, 0],
23
-        size: [100, 100],
24
-        childIndex: 2,
25
-        isHidden: false,
26
-        isLocked: false,
27
-        isAspectRatioLocked: false,
28
-      },
29
-      'rect2'
30
-    )
31
-    .save()
32
-
33
-  // it('deletes the group if it has only one child', () => {
34
-  //   tt.restore()
35
-  //     .clickShape('rect1')
36
-  //     .clickShape('rect2', { shiftKey: true })
37
-  //     .send('GROUPED')
38
-
39
-  //   const groupId = tt.getShape('rect1').parentId
40
-
41
-  //   expect(groupId === tt.data.currentPageId).toBe(false)
42
-
43
-  //   tt.doubleClickShape('rect1')
44
-
45
-  //   tt.send('DELETED')
46
-
47
-  //   expect(tt.getShape(groupId)).toBe(undefined)
48
-  //   expect(tt.getShape('rect2')).toBeTruthy()
49
-  // })
50
-
51
-  it('deletes the group if all children are deleted', () => {
52
-    tt.restore()
53
-      .clickShape('rect1')
54
-      .clickShape('rect2', { shiftKey: true })
55
-      .send('GROUPED')
56
-
57
-    const groupId = tt.getShape('rect1').parentId
58
-
59
-    expect(groupId === tt.data.currentPageId).toBe(false)
60
-
61
-    tt.doubleClickShape('rect1').clickShape('rect2', { shiftKey: true })
62
-
63
-    tt.send('DELETED')
64
-
65
-    expect(tt.getShape(groupId)).toBe(undefined)
66
-  })
67
-
68
-  it('creates a group', () => {
69
-    tt.restore()
70
-      .clickShape('rect1')
71
-      .clickShape('rect2', { shiftKey: true })
72
-      .send('GROUPED')
73
-
74
-    const groupId = tt.getShape('rect1').parentId
75
-
76
-    expect(groupId === tt.data.currentPageId).toBe(false)
77
-  })
78
-
79
-  it('selects the group on single click', () => {
80
-    tt.restore()
81
-      .clickShape('rect1')
82
-      .clickShape('rect2', { shiftKey: true })
83
-      .send('GROUPED')
84
-      .clickShape('rect1')
85
-
86
-    const groupId = tt.getShape('rect1').parentId
87
-
88
-    expect(tt.selectedIds).toEqual([groupId])
89
-  })
90
-
91
-  it('selects the item on double click', () => {
92
-    tt.restore()
93
-      .clickShape('rect1')
94
-      .clickShape('rect2', { shiftKey: true })
95
-      .send('GROUPED')
96
-      .doubleClickShape('rect1')
97
-
98
-    const groupId = tt.getShape('rect1').parentId
99
-
100
-    expect(tt.data.currentParentId).toBe(groupId)
101
-
102
-    expect(tt.selectedIds).toEqual(['rect1'])
103
-  })
104
-
105
-  it('resets currentPageId when clicking the canvas', () => {
106
-    tt.restore()
107
-      .clickShape('rect1')
108
-      .clickShape('rect2', { shiftKey: true })
109
-      .send('GROUPED')
110
-      .doubleClickShape('rect1')
111
-      .clickCanvas()
112
-      .clickShape('rect1')
113
-
114
-    const groupId = tt.getShape('rect1').parentId
115
-
116
-    expect(tt.data.currentParentId).toBe(tt.data.currentPageId)
117
-
118
-    expect(tt.selectedIds).toEqual([groupId])
119
-  })
120
-
121
-  it('creates a group and undoes and redoes', () => {
122
-    tt.restore()
123
-      .clickShape('rect1')
124
-      .clickShape('rect2', { shiftKey: true })
125
-      .send('GROUPED')
126
-
127
-    const groupId = tt.getShape('rect1').parentId
128
-
129
-    expect(groupId === tt.data.currentPageId).toBe(false)
130
-
131
-    tt.undo()
132
-
133
-    expect(tt.getShape('rect1').parentId === tt.data.currentPageId).toBe(true)
134
-    expect(tt.getShape(groupId)).toBe(undefined)
135
-
136
-    tt.redo()
137
-
138
-    expect(tt.getShape('rect1').parentId === tt.data.currentPageId).toBe(false)
139
-    expect(tt.getShape(groupId)).toBeTruthy()
140
-  })
141
-
142
-  it('groups shapes with different parents', () => {
143
-    // TODO
144
-    null
145
-  })
146
-
147
-  it('does not group a parent group shape and its child', () => {
148
-    // TODO
149
-    null
150
-  })
151
-})

+ 0
- 40
__tests__/commands/move-to-page.test.ts View File

@@ -1,40 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('move-to-page command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState()
6
-
7
-  describe('when one item is selected', () => {
8
-    it('does not change anything', () => {
9
-      // TODO
10
-      null
11
-    })
12
-  })
13
-
14
-  describe('when multiple items are selected', () => {
15
-    it('does command', () => {
16
-      // TODO
17
-      null
18
-    })
19
-
20
-    it('un-does command', () => {
21
-      // TODO
22
-      null
23
-    })
24
-
25
-    it('re-does command', () => {
26
-      // TODO
27
-      null
28
-    })
29
-  })
30
-
31
-  it('reparents children of groups to page', () => {
32
-    // TODO
33
-    null
34
-  })
35
-
36
-  it('correctly preserves moved groups', () => {
37
-    // TODO
38
-    null
39
-  })
40
-})

+ 0
- 55
__tests__/commands/rename-page.test.ts View File

@@ -1,55 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('rename page command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState().save()
6
-
7
-  describe('renames a page', () => {
8
-    it('does, undoes, and redoes command', () => {
9
-      tt.restore().reset().send('CREATED_PAGE')
10
-
11
-      const pageId = Object.keys(tt.data.document.pages)[1]
12
-
13
-      expect(tt.data.document.pages[pageId].name).toBe('Page 2')
14
-
15
-      tt.send('RENAMED_PAGE', { id: pageId, name: 'My First Page' })
16
-
17
-      expect(tt.data.document.pages[pageId].name).toBe('My First Page')
18
-
19
-      tt.undo()
20
-
21
-      expect(tt.data.document.pages[pageId].name).toBe('Page 2')
22
-
23
-      tt.redo()
24
-
25
-      expect(tt.data.document.pages[pageId].name).toBe('My First Page')
26
-    })
27
-  })
28
-
29
-  describe('renames a page other than the current page', () => {
30
-    tt.restore()
31
-      .reset()
32
-      .send('CREATED_PAGE')
33
-      .send('CHANGED_PAGE', { id: 'page1' })
34
-
35
-    expect(Object.keys(tt.data.document.pages).length).toBe(2)
36
-
37
-    expect(tt.data.currentPageId).toBe('page1')
38
-
39
-    const secondPageId = Object.keys(tt.data.document.pages)[1]
40
-
41
-    expect(tt.data.document.pages[secondPageId].name).toBe('Page 2')
42
-
43
-    tt.send('RENAMED_PAGE', { id: secondPageId, name: 'My Second Page' })
44
-
45
-    expect(tt.data.document.pages[secondPageId].name).toBe('My Second Page')
46
-
47
-    tt.undo()
48
-
49
-    expect(tt.data.document.pages[secondPageId].name).toBe('Page 2')
50
-
51
-    tt.redo()
52
-
53
-    expect(tt.data.document.pages[secondPageId].name).toBe('My Second Page')
54
-  })
55
-})

+ 0
- 237
__tests__/commands/toggle.test.ts View File

@@ -1,237 +0,0 @@
1
-import { ShapeType } from 'types'
2
-import TestState from '../test-utils'
3
-
4
-describe('toggle command', () => {
5
-  const tt = new TestState()
6
-  tt.resetDocumentState()
7
-    .createShape(
8
-      {
9
-        type: ShapeType.Rectangle,
10
-        point: [0, 0],
11
-        size: [100, 100],
12
-        childIndex: 1,
13
-        isLocked: false,
14
-        isHidden: false,
15
-        isAspectRatioLocked: false,
16
-      },
17
-      'rect1'
18
-    )
19
-    .createShape(
20
-      {
21
-        type: ShapeType.Rectangle,
22
-        point: [400, 0],
23
-        size: [100, 100],
24
-        childIndex: 2,
25
-        isHidden: false,
26
-        isLocked: false,
27
-        isAspectRatioLocked: false,
28
-      },
29
-      'rect2'
30
-    )
31
-    .createShape(
32
-      {
33
-        type: ShapeType.Rectangle,
34
-        point: [800, 0],
35
-        size: [100, 100],
36
-        childIndex: 3,
37
-        isHidden: true,
38
-        isLocked: true,
39
-        isAspectRatioLocked: true,
40
-      },
41
-      'rect3'
42
-    )
43
-    .createShape(
44
-      {
45
-        type: ShapeType.Rectangle,
46
-        point: [1000, 0],
47
-        size: [100, 100],
48
-        childIndex: 4,
49
-        isHidden: true,
50
-        isLocked: true,
51
-        isAspectRatioLocked: true,
52
-      },
53
-      'rect4'
54
-    )
55
-    .save()
56
-
57
-  describe('toggles properties on single shape', () => {
58
-    it('does command', () => {
59
-      tt.restore().clickShape('rect1')
60
-
61
-      tt.send('TOGGLED_SHAPE_LOCK')
62
-
63
-      expect(tt.getShape('rect1').isLocked).toBe(true)
64
-
65
-      tt.send('TOGGLED_SHAPE_LOCK')
66
-
67
-      expect(tt.getShape('rect1').isLocked).toBe(false)
68
-    })
69
-
70
-    it('undoes and redoes command', () => {
71
-      // Restore the saved data state, with the initial selection
72
-      tt.restore().clickShape('rect1')
73
-
74
-      tt.send('TOGGLED_SHAPE_LOCK')
75
-
76
-      tt.send('UNDO')
77
-
78
-      expect(tt.getShape('rect1').isLocked).toBe(false)
79
-
80
-      tt.send('REDO')
81
-
82
-      expect(tt.getShape('rect1').isLocked).toBe(true)
83
-    })
84
-  })
85
-
86
-  describe('toggles properties on shapes with matching properties, starting from false', () => {
87
-    it('does command', () => {
88
-      // Restore the saved data state, with the initial selection
89
-      tt.restore().clickShape('rect1').clickShape('rect2', { shiftKey: true })
90
-
91
-      tt.send('TOGGLED_SHAPE_LOCK')
92
-
93
-      expect(tt.getShape('rect1').isLocked).toBe(true)
94
-      expect(tt.getShape('rect2').isLocked).toBe(true)
95
-
96
-      tt.send('TOGGLED_SHAPE_LOCK')
97
-
98
-      expect(tt.getShape('rect1').isLocked).toBe(false)
99
-      expect(tt.getShape('rect2').isLocked).toBe(false)
100
-
101
-      tt.send('TOGGLED_SHAPE_LOCK')
102
-
103
-      expect(tt.getShape('rect1').isLocked).toBe(true)
104
-      expect(tt.getShape('rect2').isLocked).toBe(true)
105
-    })
106
-
107
-    it('undoes and redoes command', () => {
108
-      // Restore the saved data state, with the initial selection
109
-      tt.restore().clickShape('rect1').clickShape('rect2', { shiftKey: true })
110
-
111
-      tt.send('TOGGLED_SHAPE_LOCK').undo()
112
-
113
-      expect(tt.getShape('rect1').isLocked).toBe(false)
114
-      expect(tt.getShape('rect2').isLocked).toBe(false)
115
-
116
-      tt.redo()
117
-
118
-      expect(tt.getShape('rect1').isLocked).toBe(true)
119
-      expect(tt.getShape('rect2').isLocked).toBe(true)
120
-    })
121
-  })
122
-
123
-  describe('toggles properties on shapes with matching properties, starting from true', () => {
124
-    it('does command', () => {
125
-      // Restore the saved data state, with the initial selection
126
-      tt.restore().clickShape('rect3').clickShape('rect4', { shiftKey: true })
127
-
128
-      tt.send('TOGGLED_SHAPE_LOCK')
129
-
130
-      expect(tt.getShape('rect3').isLocked).toBe(false)
131
-      expect(tt.getShape('rect4').isLocked).toBe(false)
132
-
133
-      tt.send('TOGGLED_SHAPE_LOCK')
134
-
135
-      expect(tt.getShape('rect3').isLocked).toBe(true)
136
-      expect(tt.getShape('rect4').isLocked).toBe(true)
137
-    })
138
-
139
-    it('undoes and redoes command', () => {
140
-      // Restore the saved data state, with the initial selection
141
-      tt.restore().clickShape('rect3').clickShape('rect4', { shiftKey: true })
142
-
143
-      tt.send('TOGGLED_SHAPE_LOCK').undo()
144
-
145
-      expect(tt.getShape('rect3').isLocked).toBe(true)
146
-      expect(tt.getShape('rect4').isLocked).toBe(true)
147
-
148
-      tt.redo()
149
-
150
-      expect(tt.getShape('rect3').isLocked).toBe(false)
151
-      expect(tt.getShape('rect4').isLocked).toBe(false)
152
-    })
153
-  })
154
-
155
-  describe('toggles properties on shapes with different properties', () => {
156
-    it('does command', () => {
157
-      // Restore the saved data state, with the initial selection
158
-      tt.restore()
159
-        .clickShape('rect1')
160
-        .clickShape('rect2', { shiftKey: true })
161
-        .clickShape('rect3', { shiftKey: true })
162
-
163
-      tt.send('TOGGLED_SHAPE_LOCK')
164
-
165
-      expect(tt.getShape('rect1').isLocked).toBe(true)
166
-      expect(tt.getShape('rect2').isLocked).toBe(true)
167
-      expect(tt.getShape('rect3').isLocked).toBe(true)
168
-
169
-      tt.send('TOGGLED_SHAPE_LOCK')
170
-
171
-      expect(tt.getShape('rect1').isLocked).toBe(false)
172
-      expect(tt.getShape('rect2').isLocked).toBe(false)
173
-      expect(tt.getShape('rect3').isLocked).toBe(false)
174
-    })
175
-
176
-    it('undoes and redoes command', () => {
177
-      tt.restore()
178
-        .clickShape('rect1')
179
-        .clickShape('rect2', { shiftKey: true })
180
-        .clickShape('rect3', { shiftKey: true })
181
-
182
-      tt.send('TOGGLED_SHAPE_LOCK').undo()
183
-
184
-      expect(tt.getShape('rect1').isLocked).toBe(false)
185
-      expect(tt.getShape('rect2').isLocked).toBe(false)
186
-      expect(tt.getShape('rect3').isLocked).toBe(true)
187
-
188
-      tt.redo()
189
-
190
-      expect(tt.getShape('rect1').isLocked).toBe(true)
191
-      expect(tt.getShape('rect2').isLocked).toBe(true)
192
-      expect(tt.getShape('rect3').isLocked).toBe(true)
193
-    })
194
-  })
195
-
196
-  describe('catch all for other toggle props', () => {
197
-    const eventPropertyPairs = {
198
-      TOGGLED_SHAPE_LOCK: 'isLocked',
199
-      TOGGLED_SHAPE_HIDE: 'isHidden',
200
-      TOGGLED_SHAPE_ASPECT_LOCK: 'isAspectRatioLocked',
201
-    }
202
-
203
-    it('toggles all event property pairs', () => {
204
-      Object.entries(eventPropertyPairs).forEach(([eventName, propName]) => {
205
-        // Restore the saved data state, with the initial selection
206
-        tt.restore()
207
-          .clickShape('rect1')
208
-          .clickShape('rect2', { shiftKey: true })
209
-          .clickShape('rect3', { shiftKey: true })
210
-
211
-        tt.send(eventName)
212
-
213
-        expect(tt.getShape('rect1')[propName]).toBe(true)
214
-        expect(tt.getShape('rect2')[propName]).toBe(true)
215
-        expect(tt.getShape('rect3')[propName]).toBe(true)
216
-
217
-        tt.undo()
218
-
219
-        expect(tt.getShape('rect1')[propName]).toBe(false)
220
-        expect(tt.getShape('rect2')[propName]).toBe(false)
221
-        expect(tt.getShape('rect3')[propName]).toBe(true)
222
-
223
-        tt.redo()
224
-
225
-        expect(tt.getShape('rect1')[propName]).toBe(true)
226
-        expect(tt.getShape('rect2')[propName]).toBe(true)
227
-        expect(tt.getShape('rect3')[propName]).toBe(true)
228
-
229
-        tt.send(eventName)
230
-
231
-        expect(tt.getShape('rect1')[propName]).toBe(false)
232
-        expect(tt.getShape('rect2')[propName]).toBe(false)
233
-        expect(tt.getShape('rect3')[propName]).toBe(false)
234
-      })
235
-    })
236
-  })
237
-})

+ 0
- 347
__tests__/commands/transform.test.ts View File

@@ -1,347 +0,0 @@
1
-import { Corner, Edge, RectangleShape, ShapeType } from 'types'
2
-import { rng } from 'utils'
3
-import TestState from '../test-utils'
4
-
5
-describe('transform command', () => {
6
-  const tt = new TestState()
7
-  tt.resetDocumentState()
8
-    .createShape(
9
-      {
10
-        type: ShapeType.Rectangle,
11
-        point: [100, 100],
12
-        size: [100, 100],
13
-        childIndex: 1,
14
-      },
15
-      'rect1'
16
-    )
17
-    .createShape(
18
-      {
19
-        type: ShapeType.Rectangle,
20
-        point: [500, 400],
21
-        size: [200, 200],
22
-        childIndex: 2,
23
-      },
24
-      'rect2'
25
-    )
26
-    .clickShape('rect1')
27
-    .clickShape('rect2', { shiftKey: true })
28
-    .save()
29
-
30
-  function getSnapInfo() {
31
-    return {
32
-      rect1: {
33
-        point: tt.getShape<RectangleShape>('rect1').point,
34
-        size: tt.getShape<RectangleShape>('rect1').size,
35
-      },
36
-      rect2: {
37
-        point: tt.getShape<RectangleShape>('rect2').point,
38
-        size: tt.getShape<RectangleShape>('rect2').size,
39
-      },
40
-    }
41
-  }
42
-
43
-  it('sets up initial bounds', () => {
44
-    expect(tt.selectedIds).toEqual(['rect1', 'rect2'])
45
-
46
-    expect(tt.state.values.selectedBounds).toMatchObject({
47
-      minX: 100,
48
-      minY: 100,
49
-      maxX: 700,
50
-      maxY: 600,
51
-      width: 600,
52
-      height: 500,
53
-    })
54
-  })
55
-
56
-  describe('when transforming from the bottom-right corner', () => {
57
-    it('does command', () => {
58
-      // Restore the saved data state, with the initial selection
59
-      tt.restore()
60
-
61
-      // Move the bounds handle
62
-      tt.startClickingBoundsHandle(Corner.BottomRight)
63
-        .movePointerBy([100, 100])
64
-        .stopClickingBounds()
65
-
66
-      // Ensure the bounds have been transformed
67
-      expect(tt.state.values.selectedBounds).toMatchObject({
68
-        minX: 100,
69
-        minY: 100,
70
-        maxX: 800,
71
-        maxY: 700,
72
-        width: 700,
73
-        height: 600,
74
-      })
75
-
76
-      expect(getSnapInfo()).toMatchSnapshot()
77
-    })
78
-
79
-    it('un-does command', () => {
80
-      // Repeat the same actions, but add an undo at the end
81
-      tt.restore()
82
-        .startClickingBoundsHandle(Corner.BottomRight)
83
-        .movePointerBy([100, 100])
84
-        .stopClickingBounds()
85
-        .undo()
86
-
87
-      // Expect the bounds to be the initial bounds
88
-      expect(tt.state.values.selectedBounds).toMatchObject({
89
-        minX: 100,
90
-        minY: 100,
91
-        maxX: 700,
92
-        maxY: 600,
93
-        width: 600,
94
-        height: 500,
95
-      })
96
-
97
-      expect(getSnapInfo()).toMatchSnapshot()
98
-    })
99
-
100
-    it('re-does command', () => {
101
-      // Repeat the same actions but add an undo and a redo at the end
102
-      tt.restore()
103
-        .startClickingBoundsHandle(Corner.BottomRight)
104
-        .movePointerBy([100, 100])
105
-        .stopClickingBounds()
106
-        .undo()
107
-        .redo()
108
-
109
-      // Expect the bounds to be the transformed bounds
110
-      expect(tt.state.values.selectedBounds).toMatchObject({
111
-        minX: 100,
112
-        minY: 100,
113
-        maxX: 800,
114
-        maxY: 700,
115
-        width: 700,
116
-        height: 600,
117
-      })
118
-
119
-      expect(getSnapInfo()).toMatchSnapshot()
120
-    })
121
-  })
122
-
123
-  // From here on, let's assume that the undo and redos work as expected,
124
-  // so let's only test the command's execution.
125
-
126
-  it('transforms from the top edge', () => {
127
-    tt.restore()
128
-      .startClickingBoundsHandle(Edge.Top)
129
-      .movePointerBy([100, 100])
130
-      .stopClickingBounds()
131
-
132
-    // Ensure the bounds have been transformed
133
-    expect(tt.state.values.selectedBounds).toMatchObject({
134
-      minX: 100,
135
-      minY: 200,
136
-      maxX: 700,
137
-      maxY: 600,
138
-      width: 600,
139
-      height: 400,
140
-    })
141
-
142
-    expect(getSnapInfo()).toMatchSnapshot()
143
-  })
144
-
145
-  it('transforms from the right edge', () => {
146
-    tt.restore()
147
-      .startClickingBoundsHandle(Edge.Right)
148
-      .movePointerBy([100, 100])
149
-      .stopClickingBounds()
150
-
151
-    // Ensure the bounds have been transformed
152
-    expect(tt.state.values.selectedBounds).toMatchObject({
153
-      minX: 100,
154
-      minY: 100,
155
-      maxX: 800,
156
-      maxY: 600,
157
-      width: 700,
158
-      height: 500,
159
-    })
160
-
161
-    expect(getSnapInfo()).toMatchSnapshot()
162
-  })
163
-
164
-  it('transforms from the bottom edge', () => {
165
-    tt.restore()
166
-      .startClickingBoundsHandle(Edge.Bottom)
167
-      .movePointerBy([100, 100])
168
-      .stopClickingBounds()
169
-
170
-    // Ensure the bounds have been transformed
171
-    expect(tt.state.values.selectedBounds).toMatchObject({
172
-      minX: 100,
173
-      minY: 100,
174
-      maxX: 700,
175
-      maxY: 700,
176
-      width: 600,
177
-      height: 600,
178
-    })
179
-
180
-    expect(getSnapInfo()).toMatchSnapshot()
181
-  })
182
-
183
-  it('transforms from the left edge', () => {
184
-    tt.restore()
185
-      .startClickingBoundsHandle(Edge.Left)
186
-      .movePointerBy([100, 100])
187
-      .stopClickingBounds()
188
-
189
-    // Ensure the bounds have been transformed
190
-    expect(tt.state.values.selectedBounds).toMatchObject({
191
-      minX: 200,
192
-      minY: 100,
193
-      maxX: 700,
194
-      maxY: 600,
195
-      width: 500,
196
-      height: 500,
197
-    })
198
-
199
-    expect(getSnapInfo()).toMatchSnapshot()
200
-  })
201
-
202
-  it('transforms from the top-left corner', () => {
203
-    tt.restore()
204
-      .startClickingBoundsHandle(Corner.TopLeft)
205
-      .movePointerBy([100, 100])
206
-      .stopClickingBounds()
207
-
208
-    // Ensure the bounds have been transformed
209
-    expect(tt.state.values.selectedBounds).toMatchObject({
210
-      minX: 200,
211
-      minY: 200,
212
-      maxX: 700,
213
-      maxY: 600,
214
-      width: 500,
215
-      height: 400,
216
-    })
217
-
218
-    expect(getSnapInfo()).toMatchSnapshot()
219
-  })
220
-
221
-  it('transforms from the top-right corner', () => {
222
-    tt.restore()
223
-      .startClickingBoundsHandle(Corner.TopRight)
224
-      .movePointerBy([100, 100])
225
-      .stopClickingBounds()
226
-
227
-    // Ensure the bounds have been transformed
228
-    expect(tt.state.values.selectedBounds).toMatchObject({
229
-      minX: 100,
230
-      minY: 200,
231
-      maxX: 800,
232
-      maxY: 600,
233
-      width: 700,
234
-      height: 400,
235
-    })
236
-
237
-    expect(getSnapInfo()).toMatchSnapshot()
238
-  })
239
-
240
-  it('transforms from the bottom-right corner', () => {
241
-    tt.restore()
242
-      .startClickingBoundsHandle(Corner.BottomRight)
243
-      .movePointerBy([100, 100])
244
-      .stopClickingBounds()
245
-
246
-    // Ensure the bounds have been transformed
247
-    expect(tt.state.values.selectedBounds).toMatchObject({
248
-      minX: 100,
249
-      minY: 100,
250
-      maxX: 800,
251
-      maxY: 700,
252
-      width: 700,
253
-      height: 600,
254
-    })
255
-
256
-    expect(getSnapInfo()).toMatchSnapshot()
257
-  })
258
-
259
-  it('transforms from the bottom-left corner', () => {
260
-    tt.restore()
261
-      .startClickingBoundsHandle(Corner.BottomLeft)
262
-      .movePointerBy([100, 100])
263
-      .stopClickingBounds()
264
-
265
-    // Ensure the bounds have been transformed
266
-    expect(tt.state.values.selectedBounds).toMatchObject({
267
-      minX: 200,
268
-      minY: 100,
269
-      maxX: 700,
270
-      maxY: 700,
271
-      width: 500,
272
-      height: 600,
273
-    })
274
-
275
-    expect(getSnapInfo()).toMatchSnapshot()
276
-  })
277
-
278
-  describe('snapshot tests', () => {
279
-    it('transforms corners', () => {
280
-      const getRandom = rng('transform-tests-random-number-generator')
281
-
282
-      for (const corner of Object.values(Corner)) {
283
-        tt.restore()
284
-          .startClickingBoundsHandle(corner)
285
-          .movePointerBy([getRandom() * 200, getRandom() * 200])
286
-          .stopClickingBounds()
287
-
288
-        // Ensure the bounds have been transformed
289
-        expect(tt.state.values.selectedBounds).toMatchSnapshot()
290
-
291
-        expect(getSnapInfo()).toMatchSnapshot()
292
-      }
293
-    })
294
-
295
-    it('transforms edges', () => {
296
-      const getRandom = rng('transform-tests-random-number-generator')
297
-
298
-      for (const edge of Object.values(Edge)) {
299
-        tt.restore()
300
-          .startClickingBoundsHandle(edge)
301
-          .movePointerBy([getRandom() * 200, getRandom() * 200])
302
-          .stopClickingBounds()
303
-
304
-        // Ensure the bounds have been transformed
305
-        expect(tt.state.values.selectedBounds).toMatchSnapshot()
306
-
307
-        expect(getSnapInfo()).toMatchSnapshot()
308
-      }
309
-    })
310
-
311
-    it('shift-transforms corners', () => {
312
-      const getRandom = rng('transform-tests-random-number-generator')
313
-
314
-      for (const corner of Object.values(Corner)) {
315
-        tt.restore()
316
-          .startClickingBoundsHandle(corner)
317
-          .movePointerBy([getRandom() * 200, getRandom() * 200], {
318
-            shiftKey: true,
319
-          })
320
-          .stopClickingBounds()
321
-
322
-        // Ensure the bounds have been transformed
323
-        expect(tt.state.values.selectedBounds).toMatchSnapshot()
324
-
325
-        expect(getSnapInfo()).toMatchSnapshot()
326
-      }
327
-    })
328
-
329
-    it('shift-transforms edges', () => {
330
-      const getRandom = rng('transform-tests-random-number-generator')
331
-
332
-      for (const edge of Object.values(Edge)) {
333
-        tt.restore()
334
-          .startClickingBoundsHandle(edge)
335
-          .movePointerBy([getRandom() * 200, getRandom() * 200], {
336
-            shiftKey: true,
337
-          })
338
-          .stopClickingBounds()
339
-
340
-        // Ensure the bounds have been transformed
341
-        expect(tt.state.values.selectedBounds).toMatchSnapshot()
342
-
343
-        expect(getSnapInfo()).toMatchSnapshot()
344
-      }
345
-    })
346
-  })
347
-})

+ 0
- 36
__tests__/commands/translate.test.ts View File

@@ -1,36 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('translate command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState()
6
-
7
-  it('translates a single selected shape', () => {
8
-    // TODO
9
-    null
10
-  })
11
-
12
-  it('translates multiple selected shape', () => {
13
-    // TODO
14
-    null
15
-  })
16
-
17
-  it('translates while axis-locked', () => {
18
-    // TODO
19
-    null
20
-  })
21
-
22
-  it('translates after leaving axis-locked state', () => {
23
-    // TODO
24
-    null
25
-  })
26
-
27
-  it('creates clones while translating', () => {
28
-    // TODO
29
-    null
30
-  })
31
-
32
-  it('removes clones after leaving cloning state', () => {
33
-    // TODO
34
-    null
35
-  })
36
-})

+ 0
- 49
__tests__/coop.test.ts View File

@@ -1,49 +0,0 @@
1
-import state from 'state'
2
-import coopState from 'state/coop/coop-state'
3
-import * as json from './__mocks__/document.json'
4
-
5
-state.reset()
6
-state
7
-  .send('MOUNTED')
8
-  .send('MOUNTED_SHAPES')
9
-  .send('LOADED_FROM_FILE', { json: JSON.stringify(json) })
10
-state.send('CLEARED_PAGE')
11
-
12
-coopState.reset()
13
-
14
-describe('coop', () => {
15
-  it('joins a room', () => {
16
-    // TODO
17
-    null
18
-  })
19
-
20
-  it('leaves a room', () => {
21
-    // TODO
22
-    null
23
-  })
24
-
25
-  it('rejoins a room', () => {
26
-    // TODO
27
-    null
28
-  })
29
-
30
-  it('handles another user joining room', () => {
31
-    // TODO
32
-    null
33
-  })
34
-
35
-  it('handles another user leaving room', () => {
36
-    // TODO
37
-    null
38
-  })
39
-
40
-  it('sends mouse movements', () => {
41
-    // TODO
42
-    null
43
-  })
44
-
45
-  it('receives mouse movements', () => {
46
-    // TODO
47
-    null
48
-  })
49
-})

+ 0
- 31
__tests__/create.test.ts View File

@@ -1,31 +0,0 @@
1
-import state from 'state'
2
-import * as json from './__mocks__/document.json'
3
-
4
-state.reset()
5
-state
6
-  .send('MOUNTED')
7
-  .send('MOUNTED_SHAPES')
8
-  .send('LOADED_FROM_FILE', { json: JSON.stringify(json) })
9
-state.send('CLEARED_PAGE')
10
-
11
-describe('arrow shape', () => {
12
-  it('creates a shape', () => {
13
-    // TODO
14
-    null
15
-  })
16
-
17
-  it('cancels shape while creating', () => {
18
-    // TODO
19
-    null
20
-  })
21
-
22
-  it('removes shape on undo and restores it on redo', () => {
23
-    // TODO
24
-    null
25
-  })
26
-
27
-  it('does not create shape when readonly', () => {
28
-    // TODO
29
-    null
30
-  })
31
-})

+ 0
- 34
__tests__/dashes.test.ts View File

@@ -1,34 +0,0 @@
1
-import { DashStyle } from 'types'
2
-import { getPerfectDashProps } from 'utils'
3
-
4
-describe('ellipse dash props', () => {
5
-  it('renders dashed props on a circle correctly', () => {
6
-    expect(getPerfectDashProps(100, 4, DashStyle.Dashed)).toMatchSnapshot(
7
-      'small dashed circle dash props'
8
-    )
9
-    expect(getPerfectDashProps(100, 4, DashStyle.Dashed)).toMatchSnapshot(
10
-      'small dashed ellipse dash props'
11
-    )
12
-    expect(getPerfectDashProps(200, 8, DashStyle.Dashed)).toMatchSnapshot(
13
-      'large dashed circle dash props'
14
-    )
15
-    expect(getPerfectDashProps(200, 8, DashStyle.Dashed)).toMatchSnapshot(
16
-      'large dashed ellipse dash props'
17
-    )
18
-  })
19
-
20
-  it('renders dotted props on a circle correctly', () => {
21
-    expect(getPerfectDashProps(100, 4, DashStyle.Dotted)).toMatchSnapshot(
22
-      'small dotted circle dash props'
23
-    )
24
-    expect(getPerfectDashProps(100, 4, DashStyle.Dotted)).toMatchSnapshot(
25
-      'small dotted ellipse dash props'
26
-    )
27
-    expect(getPerfectDashProps(200, 8, DashStyle.Dotted)).toMatchSnapshot(
28
-      'large dotted circle dash props'
29
-    )
30
-    expect(getPerfectDashProps(200, 8, DashStyle.Dotted)).toMatchSnapshot(
31
-      'large dotted ellipse dash props'
32
-    )
33
-  })
34
-})

+ 0
- 61
__tests__/locked.test.ts View File

@@ -1,61 +0,0 @@
1
-import TestState from './test-utils'
2
-
3
-describe('lock command', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState()
6
-
7
-  it('toggles a locked shape', () => {
8
-    // TODO
9
-    null
10
-  })
11
-
12
-  it('selects a locked shape', () => {
13
-    // TODO
14
-    null
15
-  })
16
-
17
-  it('does not translate a locked shape', () => {
18
-    // TODO
19
-    null
20
-  })
21
-
22
-  it('does not translate a locked shape in a group', () => {
23
-    // TODO
24
-    null
25
-  })
26
-
27
-  it('does not rotate a locked shape', () => {
28
-    // TODO
29
-    null
30
-  })
31
-
32
-  it('does not rotate a locked shape in a group', () => {
33
-    // TODO
34
-    null
35
-  })
36
-
37
-  it('dpes not transform a locked single shape', () => {
38
-    // TODO
39
-    null
40
-  })
41
-
42
-  it('does not transform a locked shape in a multiple selection', () => {
43
-    // TODO
44
-    null
45
-  })
46
-
47
-  it('does not transform a locked shape in a group', () => {
48
-    // TODO
49
-    null
50
-  })
51
-
52
-  it('does not change the style of a locked shape', () => {
53
-    // TODO
54
-    null
55
-  })
56
-
57
-  it('does not change the handles of a locked shape', () => {
58
-    // TODO
59
-    null
60
-  })
61
-})

+ 0
- 44
__tests__/project.test.ts View File

@@ -1,44 +0,0 @@
1
-import state from 'state'
2
-import * as json from './__mocks__/document.json'
3
-
4
-describe('project', () => {
5
-  state.reset()
6
-  state.enableLog(true)
7
-
8
-  it('mounts the state', () => {
9
-    state.send('MOUNTED').send('MOUNTED_SHAPES')
10
-
11
-    expect(state.isIn('ready')).toBe(true)
12
-  })
13
-
14
-  it('loads file from json', () => {
15
-    state.send('LOADED_FROM_FILE', { json: JSON.stringify(json) })
16
-
17
-    expect(state.isIn('ready')).toBe(true)
18
-    expect(state.data.document).toMatchSnapshot('data after mount from file')
19
-  })
20
-})
21
-
22
-describe('restoring project', () => {
23
-  state.reset()
24
-  state.enableLog(true)
25
-
26
-  it('remounts the state after mutating the current state', () => {
27
-    state
28
-      .send('MOUNTED')
29
-      .send('MOUNTED_SHAPES')
30
-      .send('LOADED_FROM_FILE', { json: JSON.stringify(json) })
31
-      .send('CLEARED_PAGE')
32
-
33
-    expect(
34
-      state.data.document.pages[state.data.currentPageId].shapes
35
-    ).toStrictEqual({})
36
-
37
-    state
38
-      .send('MOUNTED')
39
-      .send('MOUNTED_SHAPES')
40
-      .send('LOADED_FROM_FILE', { json: JSON.stringify(json) })
41
-
42
-    expect(state.data.document).toMatchSnapshot('data after re-mount from file')
43
-  })
44
-})

+ 0
- 81
__tests__/selection.test.ts View File

@@ -1,81 +0,0 @@
1
-import TestState, { rectangleId, arrowId } from './test-utils'
2
-
3
-describe('selection', () => {
4
-  const tt = new TestState()
5
-
6
-  it('selects a shape', () => {
7
-    tt.deselectAll().clickShape(rectangleId)
8
-
9
-    expect(tt.idsAreSelected([rectangleId])).toBe(true)
10
-  })
11
-
12
-  it('selects and deselects a shape', () => {
13
-    tt.deselectAll().clickShape(rectangleId).clickCanvas()
14
-
15
-    expect(tt.idsAreSelected([])).toBe(true)
16
-  })
17
-
18
-  it('selects multiple shapes', () => {
19
-    tt.deselectAll()
20
-      .clickShape(rectangleId)
21
-      .clickShape(arrowId, { shiftKey: true })
22
-
23
-    expect(tt.idsAreSelected([rectangleId, arrowId])).toBe(true)
24
-  })
25
-
26
-  it('shift-selects to deselect shapes', () => {
27
-    tt.deselectAll()
28
-      .clickShape(rectangleId)
29
-      .clickShape(arrowId, { shiftKey: true })
30
-      .clickShape(rectangleId, { shiftKey: true })
31
-
32
-    expect(tt.idsAreSelected([arrowId])).toBe(true)
33
-  })
34
-
35
-  it('single-selects shape in selection on click', () => {
36
-    tt.deselectAll()
37
-      .clickShape(rectangleId)
38
-      .clickShape(arrowId, { shiftKey: true })
39
-      .clickShape(arrowId)
40
-
41
-    expect(tt.idsAreSelected([arrowId])).toBe(true)
42
-  })
43
-
44
-  it('single-selects shape in selection on pointerup only', () => {
45
-    tt.deselectAll()
46
-      .clickShape(rectangleId)
47
-      .clickShape(arrowId, { shiftKey: true })
48
-
49
-    expect(tt.idsAreSelected([rectangleId, arrowId])).toBe(true)
50
-
51
-    tt.startClick(arrowId)
52
-
53
-    expect(tt.idsAreSelected([rectangleId, arrowId])).toBe(true)
54
-
55
-    tt.stopClick(arrowId)
56
-
57
-    expect(tt.idsAreSelected([arrowId])).toBe(true)
58
-  })
59
-
60
-  it('selects shapes if shift key is lifted before pointerup', () => {
61
-    tt.deselectAll()
62
-      .clickShape(rectangleId)
63
-      .clickShape(arrowId, { shiftKey: true })
64
-      .startClick(rectangleId, { shiftKey: true })
65
-      .stopClick(rectangleId)
66
-
67
-    expect(tt.idsAreSelected([rectangleId])).toBe(true)
68
-  })
69
-
70
-  it('does not select on meta-click', () => {
71
-    tt.deselectAll().clickShape(rectangleId, { ctrlKey: true })
72
-
73
-    expect(tt.idsAreSelected([])).toBe(true)
74
-  })
75
-
76
-  it('does not select on meta-shift-click', () => {
77
-    tt.deselectAll().clickShape(rectangleId, { ctrlKey: true, shiftKey: true })
78
-
79
-    expect(tt.idsAreSelected([])).toBe(true)
80
-  })
81
-})

+ 0
- 122
__tests__/shapes/arrow.test.ts View File

@@ -1,122 +0,0 @@
1
-import { ArrowShape, ShapeType } from 'types'
2
-import TestState from '../test-utils'
3
-
4
-describe('arrow shape', () => {
5
-  const tt = new TestState()
6
-  tt.resetDocumentState().save()
7
-
8
-  describe('creating arrows', () => {
9
-    it('creates shape', () => {
10
-      tt.reset().restore().send('SELECTED_ARROW_TOOL')
11
-
12
-      expect(tt.state.isIn('arrow.creating')).toBe(true)
13
-
14
-      tt.startClick('canvas').movePointerBy([100, 100]).stopClick('canvas')
15
-
16
-      const id = tt.getSortedPageShapeIds()[0]
17
-
18
-      const shape = tt.getShape<ArrowShape>(id)
19
-
20
-      tt.assertShapeType(id, ShapeType.Arrow)
21
-
22
-      expect(shape.handles.start.point).toEqual([0, 0])
23
-      expect(shape.handles.bend.point).toEqual([50.5, 50.5])
24
-      expect(shape.handles.end.point).toEqual([101, 101])
25
-    })
26
-
27
-    it('creates shapes when pointing a shape', () => {
28
-      tt.reset().restore().send('SELECTED_ARROW_TOOL').send('TOGGLED_TOOL_LOCK')
29
-
30
-      tt.startClick('canvas').movePointerBy([100, 100]).stopClick('canvas')
31
-      tt.startClick('canvas').movePointerBy([-200, 100]).stopClick('canvas')
32
-
33
-      expect(tt.getSortedPageShapeIds()).toHaveLength(2)
34
-    })
35
-
36
-    it('creates shapes when shape locked', () => {
37
-      tt.reset()
38
-        .restore()
39
-        .createShape(
40
-          {
41
-            type: ShapeType.Rectangle,
42
-            point: [0, 0],
43
-            size: [100, 100],
44
-            childIndex: 1,
45
-          },
46
-          'rect1'
47
-        )
48
-        .send('SELECTED_ARROW_TOOL')
49
-
50
-      tt.startClick('rect1').movePointerBy([100, 100]).stopClick('canvas')
51
-
52
-      expect(tt.getSortedPageShapeIds()).toHaveLength(2)
53
-    })
54
-
55
-    it('cancels shape while creating', () => {
56
-      // TODO
57
-      null
58
-    })
59
-  })
60
-
61
-  it('moves shape', () => {
62
-    // TODO
63
-    null
64
-  })
65
-
66
-  it('rotates shape', () => {
67
-    // TODO
68
-    null
69
-  })
70
-
71
-  it('rotates shape in a group', () => {
72
-    // TODO
73
-    null
74
-  })
75
-
76
-  it('measures shape bounds', () => {
77
-    // TODO
78
-    null
79
-  })
80
-
81
-  it('measures shape rotated bounds', () => {
82
-    // TODO
83
-    null
84
-  })
85
-
86
-  it('transforms single shape', () => {
87
-    // TODO
88
-    null
89
-  })
90
-
91
-  it('transforms in a group', () => {
92
-    // TODO
93
-    null
94
-  })
95
-
96
-  /* -------------------- Specific -------------------- */
97
-
98
-  it('creates compass-aligned shape with shift key', () => {
99
-    // TODO
100
-    null
101
-  })
102
-
103
-  it('changes start handle', () => {
104
-    // TODO
105
-    null
106
-  })
107
-
108
-  it('changes end handle', () => {
109
-    // TODO
110
-    null
111
-  })
112
-
113
-  it('changes bend handle', () => {
114
-    // TODO
115
-    null
116
-  })
117
-
118
-  it('resets bend handle when double-pointed', () => {
119
-    // TODO
120
-    null
121
-  })
122
-})

+ 0
- 101
__tests__/shapes/draw.test.ts View File

@@ -1,101 +0,0 @@
1
-import { ShapeType } from 'types'
2
-import TestState from '../test-utils'
3
-
4
-describe('draw shape', () => {
5
-  const tt = new TestState()
6
-  tt.resetDocumentState().save()
7
-
8
-  describe('creating draws', () => {
9
-    it('creates shape', () => {
10
-      tt.reset().restore().send('SELECTED_DRAW_TOOL')
11
-
12
-      expect(tt.state.isIn('draw.creating')).toBe(true)
13
-
14
-      tt.startClick('canvas').movePointerBy([100, 100]).stopClick('canvas')
15
-
16
-      const id = tt.getSortedPageShapeIds()[0]
17
-
18
-      tt.assertShapeType(id, ShapeType.Draw)
19
-    })
20
-
21
-    it('creates shapes when pointing a shape', () => {
22
-      tt.reset().restore().send('SELECTED_DRAW_TOOL').send('TOGGLED_TOOL_LOCK')
23
-
24
-      tt.startClick('canvas').movePointerBy([100, 100]).stopClick('canvas')
25
-      tt.startClick('canvas').movePointerBy([-200, 100]).stopClick('canvas')
26
-
27
-      expect(tt.getSortedPageShapeIds()).toHaveLength(2)
28
-    })
29
-
30
-    it('creates shapes when shape locked', () => {
31
-      tt.reset()
32
-        .restore()
33
-        .createShape(
34
-          {
35
-            type: ShapeType.Rectangle,
36
-            point: [0, 0],
37
-            size: [100, 100],
38
-            childIndex: 1,
39
-          },
40
-          'rect1'
41
-        )
42
-        .send('SELECTED_DRAW_TOOL')
43
-
44
-      tt.startClick('rect1').movePointerBy([100, 100]).stopClick('canvas')
45
-
46
-      expect(tt.getSortedPageShapeIds()).toHaveLength(2)
47
-    })
48
-
49
-    it('cancels shape while creating', () => {
50
-      // TODO
51
-      null
52
-    })
53
-  })
54
-
55
-  it('moves shape', () => {
56
-    // TODO
57
-    null
58
-  })
59
-
60
-  it('rotates shape', () => {
61
-    // TODO
62
-    null
63
-  })
64
-
65
-  it('rotates shape in a group', () => {
66
-    // TODO
67
-    null
68
-  })
69
-
70
-  it('measures shape bounds', () => {
71
-    // TODO
72
-    null
73
-  })
74
-
75
-  it('measures shape rotated bounds', () => {
76
-    // TODO
77
-    null
78
-  })
79
-
80
-  it('transforms single shape', () => {
81
-    // TODO
82
-    null
83
-  })
84
-
85
-  it('transforms in a group', () => {
86
-    // TODO
87
-    null
88
-  })
89
-
90
-  /* -------------------- Specific -------------------- */
91
-
92
-  it('closes the shape when the start and end points are near enough', () => {
93
-    // TODO
94
-    null
95
-  })
96
-
97
-  it('remains closed after resizing up', () => {
98
-    // TODO
99
-    null
100
-  })
101
-})

+ 0
- 104
__tests__/shapes/ellipse.test.ts View File

@@ -1,104 +0,0 @@
1
-import { ShapeType } from 'types'
2
-import TestState from '../test-utils'
3
-
4
-describe('ellipse shape', () => {
5
-  const tt = new TestState()
6
-  tt.resetDocumentState().save()
7
-
8
-  describe('creating ellipses', () => {
9
-    it('creates shape', () => {
10
-      tt.reset().restore().send('SELECTED_ELLIPSE_TOOL')
11
-
12
-      expect(tt.state.isIn('ellipse.creating')).toBe(true)
13
-
14
-      tt.startClick('canvas').movePointerBy([100, 100]).stopClick('canvas')
15
-
16
-      const id = tt.getSortedPageShapeIds()[0]
17
-
18
-      tt.assertShapeType(id, ShapeType.Ellipse)
19
-    })
20
-
21
-    it('creates shapes when pointing a shape', () => {
22
-      tt.reset()
23
-        .restore()
24
-        .send('SELECTED_ELLIPSE_TOOL')
25
-        .send('TOGGLED_TOOL_LOCK')
26
-
27
-      tt.startClick('canvas').movePointerBy([100, 100]).stopClick('canvas')
28
-      tt.startClick('canvas').movePointerBy([-200, 100]).stopClick('canvas')
29
-
30
-      expect(tt.getSortedPageShapeIds()).toHaveLength(2)
31
-    })
32
-
33
-    it('creates shapes when shape locked', () => {
34
-      tt.reset()
35
-        .restore()
36
-        .createShape(
37
-          {
38
-            type: ShapeType.Rectangle,
39
-            point: [0, 0],
40
-            size: [100, 100],
41
-            childIndex: 1,
42
-          },
43
-          'rect1'
44
-        )
45
-        .send('SELECTED_ELLIPSE_TOOL')
46
-
47
-      tt.startClick('rect1').movePointerBy([100, 100]).stopClick('canvas')
48
-
49
-      expect(tt.getSortedPageShapeIds()).toHaveLength(2)
50
-    })
51
-
52
-    it('cancels shape while creating', () => {
53
-      // TODO
54
-      null
55
-    })
56
-  })
57
-
58
-  it('moves shape', () => {
59
-    // TODO
60
-    null
61
-  })
62
-
63
-  it('rotates shape', () => {
64
-    // TODO
65
-    null
66
-  })
67
-
68
-  it('rotates shape in a group', () => {
69
-    // TODO
70
-    null
71
-  })
72
-
73
-  it('measures shape bounds', () => {
74
-    // TODO
75
-    null
76
-  })
77
-
78
-  it('measures shape rotated bounds', () => {
79
-    // TODO
80
-    null
81
-  })
82
-
83
-  it('transforms single shape', () => {
84
-    // TODO
85
-    null
86
-  })
87
-
88
-  it('transforms in a group', () => {
89
-    // TODO
90
-    null
91
-  })
92
-
93
-  /* -------------------- Specific -------------------- */
94
-
95
-  it('creates aspect-ratio-locked shape with shift key', () => {
96
-    // TODO
97
-    null
98
-  })
99
-
100
-  it('resizes aspect-ratio-locked shape with shift key', () => {
101
-    // TODO
102
-    null
103
-  })
104
-})

+ 0
- 104
__tests__/shapes/rectangle.test.ts View File

@@ -1,104 +0,0 @@
1
-import { ShapeType } from 'types'
2
-import TestState from '../test-utils'
3
-
4
-describe('rectangle shape', () => {
5
-  const tt = new TestState()
6
-  tt.resetDocumentState().save()
7
-
8
-  describe('creating rectangles', () => {
9
-    it('creates shape', () => {
10
-      tt.reset().restore().send('SELECTED_RECTANGLE_TOOL')
11
-
12
-      expect(tt.state.isIn('rectangle.creating')).toBe(true)
13
-
14
-      tt.startClick('canvas').movePointerBy([100, 100]).stopClick('canvas')
15
-
16
-      const id = tt.getSortedPageShapeIds()[0]
17
-
18
-      tt.assertShapeType(id, ShapeType.Rectangle)
19
-    })
20
-
21
-    it('creates shapes when pointing a shape', () => {
22
-      tt.reset()
23
-        .restore()
24
-        .send('SELECTED_RECTANGLE_TOOL')
25
-        .send('TOGGLED_TOOL_LOCK')
26
-
27
-      tt.startClick('canvas').movePointerBy([100, 100]).stopClick('canvas')
28
-      tt.startClick('canvas').movePointerBy([-200, 100]).stopClick('canvas')
29
-
30
-      expect(tt.getSortedPageShapeIds()).toHaveLength(2)
31
-    })
32
-
33
-    it('creates shapes when shape locked', () => {
34
-      tt.reset()
35
-        .restore()
36
-        .createShape(
37
-          {
38
-            type: ShapeType.Rectangle,
39
-            point: [0, 0],
40
-            size: [100, 100],
41
-            childIndex: 1,
42
-          },
43
-          'rect1'
44
-        )
45
-        .send('SELECTED_RECTANGLE_TOOL')
46
-
47
-      tt.startClick('rect1').movePointerBy([100, 100]).stopClick('canvas')
48
-
49
-      expect(tt.getSortedPageShapeIds()).toHaveLength(2)
50
-    })
51
-
52
-    it('cancels shape while creating', () => {
53
-      // TODO
54
-      null
55
-    })
56
-  })
57
-
58
-  it('moves shape', () => {
59
-    // TODO
60
-    null
61
-  })
62
-
63
-  it('rotates shape', () => {
64
-    // TODO
65
-    null
66
-  })
67
-
68
-  it('rotates shape in a group', () => {
69
-    // TODO
70
-    null
71
-  })
72
-
73
-  it('measures shape bounds', () => {
74
-    // TODO
75
-    null
76
-  })
77
-
78
-  it('measures shape rotated bounds', () => {
79
-    // TODO
80
-    null
81
-  })
82
-
83
-  it('transforms single shape', () => {
84
-    // TODO
85
-    null
86
-  })
87
-
88
-  it('transforms in a group', () => {
89
-    // TODO
90
-    null
91
-  })
92
-
93
-  /* -------------------- Specific -------------------- */
94
-
95
-  it('creates aspect-ratio-locked shape with shift key', () => {
96
-    // TODO
97
-    null
98
-  })
99
-
100
-  it('resizes aspect-ratio-locked shape with shift key', () => {
101
-    // TODO
102
-    null
103
-  })
104
-})

+ 0
- 79
__tests__/shapes/text.test.ts View File

@@ -1,79 +0,0 @@
1
-import TestState from '../test-utils'
2
-
3
-describe('arrow shape', () => {
4
-  const tt = new TestState()
5
-  tt.resetDocumentState()
6
-
7
-  it('creates shape', () => {
8
-    tt.send('SELECTED_TEXT_TOOL')
9
-
10
-    expect(tt.state.isIn('text.creating')).toBe(true)
11
-
12
-    const id = tt.getSortedPageShapeIds()[0]
13
-
14
-    tt.clickCanvas()
15
-
16
-    expect(tt.state.isIn('editingShape')).toBe(true)
17
-
18
-    tt.send('EDITED_SHAPE', {
19
-      id,
20
-      change: { text: 'Hello world' },
21
-    })
22
-
23
-    tt.send('BLURRED_EDITING_SHAPE', { id: id })
24
-
25
-    expect(tt.state.isIn('selecting')).toBe(true)
26
-  })
27
-
28
-  it('cancels shape while creating', () => {
29
-    // TODO
30
-    null
31
-  })
32
-
33
-  it('moves shape', () => {
34
-    // TODO
35
-    null
36
-  })
37
-
38
-  it('rotates shape', () => {
39
-    // TODO
40
-    null
41
-  })
42
-
43
-  it('rotates shape in a group', () => {
44
-    // TODO
45
-    null
46
-  })
47
-
48
-  it('measures shape bounds', () => {
49
-    // TODO
50
-    null
51
-  })
52
-
53
-  it('measures shape rotated bounds', () => {
54
-    // TODO
55
-    null
56
-  })
57
-
58
-  it('transforms single shape', () => {
59
-    // TODO
60
-    null
61
-  })
62
-
63
-  it('transforms in a group', () => {
64
-    // TODO
65
-    null
66
-  })
67
-
68
-  /* -------------------- Specific -------------------- */
69
-
70
-  it('scales', () => {
71
-    // TODO
72
-    null
73
-  })
74
-
75
-  it('selects different text on tap while editing', () => {
76
-    // TODO
77
-    null
78
-  })
79
-})

+ 0
- 31
__tests__/style.test.ts View File

@@ -1,31 +0,0 @@
1
-import state from 'state'
2
-import * as json from './__mocks__/document.json'
3
-
4
-state.reset()
5
-state
6
-  .send('MOUNTED')
7
-  .send('MOUNTED_SHAPES')
8
-  .send('LOADED_FROM_FILE', { json: JSON.stringify(json) })
9
-state.send('CLEARED_PAGE')
10
-
11
-describe('shape styles', () => {
12
-  it('sets the color style of a shape', () => {
13
-    // TODO
14
-    null
15
-  })
16
-
17
-  it('sets the size style of a shape', () => {
18
-    // TODO
19
-    null
20
-  })
21
-
22
-  it('sets the dash style of a shape', () => {
23
-    // TODO
24
-    null
25
-  })
26
-
27
-  it('sets the isFilled style of a shape', () => {
28
-    // TODO
29
-    null
30
-  })
31
-})

+ 0
- 836
__tests__/test-utils.ts View File

@@ -1,836 +0,0 @@
1
-import _state from 'state'
2
-import tld from 'utils/tld'
3
-import inputs from 'state/inputs'
4
-import { createShape, getShapeUtils } from 'state/shape-utils'
5
-import { Corner, Data, Edge, Shape, ShapeType, ShapeUtility } from 'types'
6
-import { deepClone, deepCompareArrays, uniqueId, vec } from 'utils'
7
-import * as mockDocument from './__mocks__/document.json'
8
-
9
-type State = typeof _state
10
-
11
-export const rectangleId = 'e43559cb-6f41-4ae4-9c49-158ed1ad2f72'
12
-export const arrowId = 'fee77127-e779-4576-882b-b1bf7c7e132f'
13
-
14
-interface PointerOptions {
15
-  id?: number
16
-  x?: number
17
-  y?: number
18
-  shiftKey?: boolean
19
-  altKey?: boolean
20
-  ctrlKey?: boolean
21
-}
22
-
23
-class TestState {
24
-  _state: State
25
-  snapshot: Data
26
-
27
-  constructor() {
28
-    this._state = _state
29
-    this.state.send('TOGGLED_TEST_MODE')
30
-    this.snapshot = deepClone(this.state.data)
31
-    this.reset()
32
-  }
33
-
34
-  /**
35
-   * Get the underlying state-designer state.
36
-   *
37
-   * ### Example
38
-   *
39
-   *```ts
40
-   * tt.state
41
-   *```
42
-   */
43
-  get state(): State {
44
-    return this._state
45
-  }
46
-
47
-  /**
48
-   * Get the state's current data.
49
-   *
50
-   * ### Example
51
-   *
52
-   *```ts
53
-   * tt.data
54
-   *```
55
-   */
56
-  get data(): Readonly<Data> {
57
-    return this.state.data
58
-  }
59
-
60
-  /* -------- Reimplemenation of State Methods -------- */
61
-
62
-  /**
63
-   * Send a message to the state.
64
-   *
65
-   * ### Example
66
-   *
67
-   *```ts
68
-   * tt.send("MOVED_TO_FRONT")
69
-   *```
70
-   */
71
-  send(eventName: string, payload?: unknown): TestState {
72
-    this.state.send(eventName, payload)
73
-    return this
74
-  }
75
-
76
-  /**
77
-   * Check whether a state node is active. If multiple names are provided, then the method will return true only if ALL of the provided state nodes are active.
78
-   *
79
-   * ### Example
80
-   *
81
-   *```ts
82
-   * tt.isIn("ready") // true
83
-   * tt.isIn("ready", "selecting") // true
84
-   * tt.isInAny("ready", "notReady") // false
85
-   *```
86
-   */
87
-  isIn(...ids: string[]): boolean {
88
-    return this.state.isIn(...ids)
89
-  }
90
-
91
-  /**
92
-   * Check whether a state node is active. If multiple names are provided, then the method will return true if ANY of the provided state nodes are active.
93
-   *
94
-   * ### Example
95
-   *
96
-   *```ts
97
-   * tt.isIn("ready") // true
98
-   * tt.isIn("ready", "selecting") // true
99
-   * tt.isInAny("ready", "notReady") // true
100
-   *```
101
-   */
102
-  isInAny(...ids: string[]): boolean {
103
-    return this.state.isInAny(...ids)
104
-  }
105
-
106
-  /**
107
-   * Check whether the state can handle a certain event (and optionally payload). The method will return true if the event is handled by one or more currently active state nodes and if the event will pass its conditions (if present) in at least one of those handlers.
108
-   *
109
-   * ### Example
110
-   *
111
-   *```ts
112
-   * example
113
-   *```
114
-   */
115
-  can(eventName: string, payload?: unknown): boolean {
116
-    return this.state.can(eventName, payload)
117
-  }
118
-
119
-  /* -------------------- Specific -------------------- */
120
-
121
-  /**
122
-   * Save a snapshot of the state's current data.
123
-   *
124
-   * ### Example
125
-   *
126
-   *```ts
127
-   * tt.save()
128
-   * tt.restore()
129
-   *```
130
-   */
131
-  save(): TestState {
132
-    this.snapshot = deepClone(this.data)
133
-    return this
134
-  }
135
-
136
-  /**
137
-   * Restore the state's saved data.
138
-   *
139
-   * ### Example
140
-   *
141
-   *```ts
142
-   * tt.save()
143
-   * tt.restore()
144
-   *```
145
-   */
146
-  restore(): TestState {
147
-    this.state.forceData(this.snapshot)
148
-    return this
149
-  }
150
-
151
-  /**
152
-   * Reset the test state.
153
-   *
154
-   * ### Example
155
-   *
156
-   *```ts
157
-   * tt.reset()
158
-   *```
159
-   */
160
-  reset(): TestState {
161
-    this.state.reset()
162
-    this.state
163
-      .send('UNMOUNTED')
164
-      .send('MOUNTED', { roomId: 'TESTING' })
165
-      .send('MOUNTED_SHAPES')
166
-      .send('LOADED_FROM_FILE', { json: JSON.stringify(mockDocument) })
167
-
168
-    return this
169
-  }
170
-
171
-  /**
172
-   * Reset the document state. Will remove all shapes and extra pages.
173
-   *
174
-   * ### Example
175
-   *
176
-   *```ts
177
-   * tt.resetDocumentState()
178
-   *```
179
-   */
180
-  resetDocumentState(): TestState {
181
-    this.state.send('RESET_DOCUMENT_STATE').send('TOGGLED_TEST_MODE')
182
-    return this
183
-  }
184
-
185
-  /**
186
-   * Create a new shape on the current page. Optionally provide an id.
187
-   *
188
-   * ### Example
189
-   *
190
-   *```ts
191
-   * tt.createShape({ type: ShapeType.Rectangle, point: [100, 100]})
192
-   * tt.createShape({ type: ShapeType.Rectangle, point: [100, 100]}, "myId")
193
-   *```
194
-   */
195
-  createShape(props: Partial<Shape>, id = uniqueId()): TestState {
196
-    const shape = createShape(props.type, props)
197
-
198
-    getShapeUtils(shape)
199
-      .setProperty(shape, 'id', id)
200
-      .setProperty(shape, 'parentId', this.data.currentPageId)
201
-
202
-    this.data.document.pages[this.data.currentPageId].shapes[shape.id] = shape
203
-    return this
204
-  }
205
-
206
-  /**
207
-   * Click a shape.
208
-   *
209
-   * ### Example
210
-   *
211
-   *```ts
212
-   * tt.clickShape("myShapeId")
213
-   *```
214
-   */
215
-  clickShape(id: string, options: PointerOptions = {}): TestState {
216
-    const shape = tld.getShape(this.data, id)
217
-    const [x, y] = shape ? vec.add(shape.point, [1, 1]) : [0, 0]
218
-
219
-    this.state
220
-      .send(
221
-        'POINTED_SHAPE',
222
-        inputs.pointerDown(TestState.point({ x, y, ...options }), id)
223
-      )
224
-      .send(
225
-        'STOPPED_POINTING',
226
-        inputs.pointerUp(TestState.point({ x, y, ...options }), id)
227
-      )
228
-
229
-    return this
230
-  }
231
-
232
-  /**
233
-   * Start a click (but do not stop it).
234
-   *
235
-   * ### Example
236
-   *
237
-   *```ts
238
-   * tt.startClick("myShapeId")
239
-   *```
240
-   */
241
-  startClick(id: string, options: PointerOptions = {}): TestState {
242
-    const shape = tld.getShape(this.data, id)
243
-    const [x, y] = shape ? vec.add(shape.point, [1, 1]) : [0, 0]
244
-
245
-    if (id === 'canvas') {
246
-      this.state.send(
247
-        'POINTED_CANVAS',
248
-        inputs.pointerDown(TestState.point({ x, y, ...options }), id)
249
-      )
250
-      return this
251
-    }
252
-
253
-    this.state.send(
254
-      'POINTED_SHAPE',
255
-      inputs.pointerDown(TestState.point({ x, y, ...options }), id)
256
-    )
257
-
258
-    return this
259
-  }
260
-
261
-  /**
262
-   * Stop a click (after starting it).
263
-   *
264
-   * ### Example
265
-   *
266
-   *```ts
267
-   * tt.stopClick("myShapeId")
268
-   *```
269
-   */
270
-  stopClick(id: string, options: PointerOptions = {}): TestState {
271
-    const shape = tld.getShape(this.data, id)
272
-    const [x, y] = shape ? vec.add(shape.point, [1, 1]) : [0, 0]
273
-
274
-    this.state.send(
275
-      'STOPPED_POINTING',
276
-      inputs.pointerUp(TestState.point({ x, y, ...options }), id)
277
-    )
278
-
279
-    return this
280
-  }
281
-
282
-  /**
283
-   * Double click a shape.
284
-   *
285
-   * ### Example
286
-   *
287
-   *```ts
288
-   * tt.clickShape("myShapeId")
289
-   *```
290
-   */
291
-  doubleClickShape(id: string, options: PointerOptions = {}): TestState {
292
-    const shape = tld.getShape(this.data, id)
293
-    const [x, y] = shape ? vec.add(shape.point, [1, 1]) : [0, 0]
294
-
295
-    this.state
296
-      .send(
297
-        'DOUBLE_POINTED_SHAPE',
298
-        inputs.pointerDown(TestState.point({ x, y, ...options }), id)
299
-      )
300
-      .send(
301
-        'STOPPED_POINTING',
302
-        inputs.pointerUp(TestState.point({ x, y, ...options }), id)
303
-      )
304
-
305
-    return this
306
-  }
307
-
308
-  /**
309
-   * Click the canvas.
310
-   *
311
-   * ### Example
312
-   *
313
-   *```ts
314
-   * tt.clickCanvas("myShapeId")
315
-   *```
316
-   */
317
-  clickCanvas(options: PointerOptions = {}): TestState {
318
-    this.state
319
-      .send(
320
-        'POINTED_CANVAS',
321
-        inputs.pointerDown(TestState.point(options), 'canvas')
322
-      )
323
-      .send(
324
-        'STOPPED_POINTING',
325
-        inputs.pointerUp(TestState.point(options), 'canvas')
326
-      )
327
-
328
-    return this
329
-  }
330
-
331
-  /**
332
-   * Click the background / body of the bounding box.
333
-   *
334
-   * ### Example
335
-   *
336
-   *```ts
337
-   * tt.clickBounds()
338
-   *```
339
-   */
340
-  clickBounds(options: PointerOptions = {}): TestState {
341
-    this.state
342
-      .send(
343
-        'POINTED_BOUNDS',
344
-        inputs.pointerDown(TestState.point(options), 'bounds')
345
-      )
346
-      .send(
347
-        'STOPPED_POINTING',
348
-        inputs.pointerUp(TestState.point(options), 'bounds')
349
-      )
350
-
351
-    return this
352
-  }
353
-
354
-  /**
355
-   * Start clicking bounds.
356
-   *
357
-   * ### Example
358
-   *
359
-   *```ts
360
-   * tt.startClickingBounds()
361
-   *```
362
-   */
363
-  startClickingBounds(options: PointerOptions = {}): TestState {
364
-    this.state.send(
365
-      'POINTED_BOUNDS',
366
-      inputs.pointerDown(TestState.point(options), 'bounds')
367
-    )
368
-
369
-    return this
370
-  }
371
-
372
-  /**
373
-   * Stop clicking the bounding box.
374
-   *
375
-   * ### Example
376
-   *
377
-   *```ts
378
-   * tt.stopClickingBounds()
379
-   *```
380
-   */
381
-  stopClickingBounds(options: PointerOptions = {}): TestState {
382
-    this.state.send(
383
-      'STOPPED_POINTING',
384
-      inputs.pointerUp(TestState.point(options), 'bounds')
385
-    )
386
-
387
-    return this
388
-  }
389
-
390
-  /**
391
-   * Start clicking a bounds handle.
392
-   *
393
-   * ### Example
394
-   *
395
-   *```ts
396
-   * tt.startClickingBoundsHandle(Edge.Top)
397
-   *```
398
-   */
399
-  startClickingBoundsHandle(
400
-    handle: Corner | Edge | 'center',
401
-    options: PointerOptions = {}
402
-  ): TestState {
403
-    this.state.send(
404
-      'POINTED_BOUNDS_HANDLE',
405
-      inputs.pointerDown(TestState.point(options), handle)
406
-    )
407
-
408
-    return this
409
-  }
410
-
411
-  /**
412
-   * Move the pointer to a new point, or to several points in order.
413
-   *
414
-   * ### Example
415
-   *
416
-   *```ts
417
-   * tt.movePointerTo([100, 100])
418
-   * tt.movePointerTo([100, 100], { shiftKey: true })
419
-   * tt.movePointerTo([[100, 100], [150, 150], [200, 200]])
420
-   *```
421
-   */
422
-  movePointerTo(
423
-    to: number[] | number[][],
424
-    options: Omit<PointerOptions, 'x' | 'y'> = {}
425
-  ): TestState {
426
-    if (Array.isArray(to[0])) {
427
-      ;(to as number[][]).forEach(([x, y]) => {
428
-        this.state.send(
429
-          'MOVED_POINTER',
430
-          inputs.pointerMove(TestState.point({ x, y, ...options }))
431
-        )
432
-      })
433
-    } else {
434
-      const [x, y] = to as number[]
435
-      this.state.send(
436
-        'MOVED_POINTER',
437
-        inputs.pointerMove(TestState.point({ x, y, ...options }))
438
-      )
439
-    }
440
-
441
-    return this
442
-  }
443
-
444
-  /**
445
-   * Move the pointer by a delta.
446
-   *
447
-   * ### Example
448
-   *
449
-   *```ts
450
-   * tt.movePointerBy([10,10])
451
-   * tt.movePointerBy([10,10], { shiftKey: true })
452
-   *```
453
-   */
454
-  movePointerBy(
455
-    by: number[] | number[][],
456
-    options: Omit<PointerOptions, 'x' | 'y'> = {}
457
-  ): TestState {
458
-    let pt = inputs.pointer?.point || [0, 0]
459
-
460
-    if (Array.isArray(by[0])) {
461
-      ;(by as number[][]).forEach((delta) => {
462
-        pt = vec.add(pt, delta)
463
-
464
-        this.state.send(
465
-          'MOVED_POINTER',
466
-          inputs.pointerMove(
467
-            TestState.point({ x: pt[0], y: pt[1], ...options })
468
-          )
469
-        )
470
-      })
471
-    } else {
472
-      pt = vec.add(pt, by as number[])
473
-
474
-      this.state.send(
475
-        'MOVED_POINTER',
476
-        inputs.pointerMove(TestState.point({ x: pt[0], y: pt[1], ...options }))
477
-      )
478
-    }
479
-
480
-    return this
481
-  }
482
-
483
-  /**
484
-   * Move pointer over a shape. Will move the pointer to the top-left corner of the shape.
485
-   *
486
-   * ###
487
-   * ```
488
-   * tt.movePointerOverShape('myShapeId', [100, 100])
489
-   * ```
490
-   */
491
-  movePointerOverShape(
492
-    id: string,
493
-    options: Omit<PointerOptions, 'x' | 'y'> = {}
494
-  ): TestState {
495
-    const shape = tld.getShape(this.state.data, id)
496
-    const [x, y] = vec.add(shape.point, [1, 1])
497
-
498
-    this.state.send(
499
-      'MOVED_OVER_SHAPE',
500
-      inputs.pointerEnter(TestState.point({ x, y, ...options }), id)
501
-    )
502
-
503
-    return this
504
-  }
505
-
506
-  /**
507
-   * Move the pointer over a group. Will move the pointer to the top-left corner of the group.
508
-   *
509
-   * ### Example
510
-   *
511
-   *```ts
512
-   * tt.movePointerOverHandle('myGroupId')
513
-   * tt.movePointerOverHandle('myGroupId', { shiftKey: true })
514
-   *```
515
-   */
516
-  movePointerOverGroup(
517
-    id: string,
518
-    options: Omit<PointerOptions, 'x' | 'y'> = {}
519
-  ): TestState {
520
-    const shape = tld.getShape(this.state.data, id)
521
-    const [x, y] = vec.add(shape.point, [1, 1])
522
-
523
-    this.state.send(
524
-      'MOVED_OVER_GROUP',
525
-      inputs.pointerEnter(TestState.point({ x, y, ...options }), id)
526
-    )
527
-
528
-    return this
529
-  }
530
-
531
-  /**
532
-   * Move the pointer over a handle. Will move the pointer to the top-left corner of the handle.
533
-   *
534
-   * ### Example
535
-   *
536
-   *```ts
537
-   * tt.movePointerOverHandle('bend')
538
-   * tt.movePointerOverHandle('bend', { shiftKey: true })
539
-   *```
540
-   */
541
-  movePointerOverHandle(
542
-    id: string,
543
-    options: Omit<PointerOptions, 'x' | 'y'> = {}
544
-  ): TestState {
545
-    const shape = tld.getShape(this.state.data, id)
546
-    const handle = shape.handles?.[id]
547
-    const [x, y] = vec.add(handle.point, [1, 1])
548
-
549
-    this.state.send(
550
-      'MOVED_OVER_HANDLE',
551
-      inputs.pointerEnter(TestState.point({ x, y, ...options }), id)
552
-    )
553
-
554
-    return this
555
-  }
556
-
557
-  /**
558
-   * Select all shapes.
559
-   *
560
-   * ### Example
561
-   *
562
-   *```ts
563
-   * tt.deselectAll()
564
-   *```
565
-   */
566
-  selectAll(): TestState {
567
-    this.state.send('SELECTED_ALL')
568
-    return this
569
-  }
570
-
571
-  /**
572
-   * Deselect all shapes.
573
-   *
574
-   * ### Example
575
-   *
576
-   *```ts
577
-   * tt.deselectAll()
578
-   *```
579
-   */
580
-  deselectAll(): TestState {
581
-    this.state.send('DESELECTED_ALL')
582
-    return this
583
-  }
584
-
585
-  /**
586
-   * Delete the selected shapes.
587
-   *
588
-   * ### Example
589
-   *
590
-   *```ts
591
-   * tt.pressDelete()
592
-   *```
593
-   */
594
-  pressDelete(): TestState {
595
-    this.state.send('DELETED')
596
-    return this
597
-  }
598
-
599
-  /**
600
-   * Undo.
601
-   *
602
-   * ### Example
603
-   *
604
-   *```ts
605
-   * tt.undo()
606
-   *```
607
-   */
608
-  undo(): TestState {
609
-    this.state.send('UNDO')
610
-    return this
611
-  }
612
-
613
-  /**
614
-   * Redo.
615
-   *
616
-   * ### Example
617
-   *
618
-   *```ts
619
-   * tt.redo()
620
-   *```
621
-   */
622
-  redo(): TestState {
623
-    this.state.send('REDO')
624
-    return this
625
-  }
626
-
627
-  /* ---------------- Getting Data Out ---------------- */
628
-
629
-  /**
630
-   * Get a shape by its id. Note: the shape must be in the current page.
631
-   *
632
-   * ### Example
633
-   *
634
-   *```ts
635
-   * tt.getShape("myShapeId")
636
-   *```
637
-   */
638
-  getShape<T extends Shape>(id: string): T {
639
-    return tld.getShape(this.data, id) as T
640
-  }
641
-
642
-  /**
643
-   * Get the current selected ids.
644
-   *
645
-   * ### Example
646
-   *
647
-   *```ts
648
-   * example
649
-   *```
650
-   */
651
-  get selectedIds(): string[] {
652
-    return tld.getSelectedIds(this.data)
653
-  }
654
-
655
-  /**
656
-   * Get shapes for the current page.
657
-   *
658
-   * ### Example
659
-   *
660
-   *```ts
661
-   * tt.getShapes()
662
-   *```
663
-   */
664
-  getShapes(): Shape[] {
665
-    return Object.values(
666
-      this.data.document.pages[this.data.currentPageId].shapes
667
-    )
668
-  }
669
-
670
-  /**
671
-   * Get ids of the page's children sorted by their child index.
672
-   *
673
-   * ### Example
674
-   *
675
-   *```ts
676
-   * tt.getSortedPageShapes()
677
-   *```
678
-   */
679
-  getSortedPageShapeIds(): string[] {
680
-    return this.getShapes()
681
-      .sort((a, b) => a.childIndex - b.childIndex)
682
-      .map((shape) => shape.id)
683
-  }
684
-
685
-  /**
686
-   * Get the only selected shape. If more than one shape is selected, the test will fail.
687
-   *
688
-   * ### Example
689
-   *
690
-   *```ts
691
-   * tt.getOnlySelectedShape()
692
-   *```
693
-   */
694
-  getOnlySelectedShape(): Shape {
695
-    const selectedShapes = tld.getSelectedShapes(this.data)
696
-    return selectedShapes.length === 1 ? selectedShapes[0] : undefined
697
-  }
698
-
699
-  /**
700
-   * Get whether the provided ids are the current selected ids. If the `strict` argument is `true`, then the result will be false if the state has selected ids in addition to those provided.
701
-   *
702
-   * ### Example
703
-   *
704
-   *```ts
705
-   * tt.idsAreSelected(state.data, ['rectangleId', 'ellipseId'])
706
-   * tt.idsAreSelected(state.data, ['rectangleId', 'ellipseId'], true)
707
-   *```
708
-   */
709
-  idsAreSelected(ids: string[], strict = true): boolean {
710
-    const selectedIds = tld.getSelectedIds(this.data)
711
-    return (
712
-      (strict ? selectedIds.length === ids.length : true) &&
713
-      ids.every((id) => selectedIds.includes(id))
714
-    )
715
-  }
716
-
717
-  /**
718
-   * Get whether the shape with the provided id has the provided parent id.
719
-   *
720
-   * ### Example
721
-   *
722
-   *```ts
723
-   * tt.hasParent('childId', 'parentId')
724
-   *```
725
-   */
726
-  hasParent(childId: string, parentId: string): boolean {
727
-    return tld.getShape(this.data, childId).parentId === parentId
728
-  }
729
-
730
-  /**
731
-   * Assert that a shape has the provided type.
732
-   *
733
-   * ### Example
734
-   *
735
-   *```ts
736
-   * tt.example
737
-   *```
738
-   */
739
-  assertShapeType(shapeId: string, type: ShapeType): boolean {
740
-    const shape = tld.getShape(this.data, shapeId)
741
-    if (shape.type !== type) {
742
-      throw new TypeError(
743
-        `expected shape ${shapeId} to be of type ${type}, found ${shape?.type} instead`
744
-      )
745
-    }
746
-    return true
747
-  }
748
-
749
-  /**
750
-   * Assert that the provided shape has the provided props.
751
-   *
752
-   * ### Example
753
-   *
754
-   *```
755
-   * tt.assertShapeProps(myShape, { point: [0,0], style: { color: ColorStyle.Blue } } )
756
-   *```
757
-   */
758
-  assertShapeProps<T extends Shape>(
759
-    shape: T,
760
-    props: { [K in keyof Partial<T>]: T[K] }
761
-  ): boolean {
762
-    for (const key in props) {
763
-      let result: boolean
764
-      const value = props[key]
765
-
766
-      if (Array.isArray(value)) {
767
-        result = deepCompareArrays(value, shape[key] as typeof value)
768
-      } else if (typeof value === 'object') {
769
-        const target = shape[key] as typeof value
770
-        result =
771
-          target &&
772
-          Object.entries(value).every(([k, v]) => target[k] === props[key][v])
773
-      } else {
774
-        result = shape[key] === value
775
-      }
776
-
777
-      if (!result) {
778
-        throw new TypeError(
779
-          `expected shape ${shape.id} to have property ${key}: ${props[key]}, found ${key}: ${shape[key]} instead`
780
-        )
781
-      }
782
-    }
783
-
784
-    return true
785
-  }
786
-
787
-  /**
788
-   * Get a shape and test it.
789
-   *
790
-   * ### Example
791
-   *
792
-   *```ts
793
-   * tt.testShape("myShapeId", (myShape, utils) => expect(utils(myShape).getBounds()).toMatchSnapshot() )
794
-   *```
795
-   */
796
-  testShape<T extends Shape>(
797
-    id: string,
798
-    fn: (shape: T, shapeUtils: ShapeUtility<T>) => boolean
799
-  ): boolean {
800
-    const shape = this.getShape<T>(id)
801
-    return fn(shape, shape && getShapeUtils(shape))
802
-  }
803
-
804
-  /**
805
-   * Get a fake PointerEvent.
806
-   *
807
-   * ### Example
808
-   *
809
-   *```ts
810
-   * tt.point()
811
-   * tt.point({ x: 0, y: 0})
812
-   * tt.point({ x: 0, y: 0, shiftKey: true } )
813
-   *```
814
-   */
815
-  static point(options: PointerOptions = {} as PointerOptions): PointerEvent {
816
-    const {
817
-      id = 1,
818
-      x = 0,
819
-      y = 0,
820
-      shiftKey = false,
821
-      altKey = false,
822
-      ctrlKey = false,
823
-    } = options
824
-
825
-    return {
826
-      shiftKey,
827
-      altKey,
828
-      ctrlKey,
829
-      pointerId: id,
830
-      clientX: x,
831
-      clientY: y,
832
-    } as any
833
-  }
834
-}
835
-
836
-export default TestState

+ 0
- 38
__tests__/tools.test.ts View File

@@ -1,38 +0,0 @@
1
-import TestState from './test-utils'
2
-
3
-const TOOLS = [
4
-  'draw',
5
-  'rectangle',
6
-  'ellipse',
7
-  'arrow',
8
-  'text',
9
-  'line',
10
-  'ray',
11
-  'dot',
12
-]
13
-
14
-describe('when selecting tools', () => {
15
-  const tt = new TestState()
16
-
17
-  TOOLS.forEach((tool) => {
18
-    it(`selects ${tool} tool`, () => {
19
-      tt.reset().send(`SELECTED_${tool.toUpperCase()}_TOOL`)
20
-
21
-      expect(tt.data.activeTool).toBe(tool)
22
-      expect(tt.state.isIn(tool)).toBe(true)
23
-    })
24
-
25
-    TOOLS.forEach((otherTool) => {
26
-      if (otherTool === tool) return
27
-
28
-      it(`selects ${tool} tool from ${otherTool} tool`, () => {
29
-        tt.reset()
30
-          .send(`SELECTED_${tool.toUpperCase()}_TOOL`)
31
-          .send(`SELECTED_${otherTool.toUpperCase()}_TOOL`)
32
-
33
-        expect(tt.data.activeTool).toBe(otherTool)
34
-        expect(tt.state.isIn(otherTool)).toBe(true)
35
-      })
36
-    })
37
-  })
38
-})

+ 0
- 3
babel.config.js View File

@@ -1,3 +0,0 @@
1
-module.exports = {
2
-  presets: ['next/babel'],
3
-}

+ 0
- 78
components/canvas/bounds/bounding-box.tsx View File

@@ -1,78 +0,0 @@
1
-import * as React from 'react'
2
-import { Edge, Corner } from 'types'
3
-import { useSelector } from 'state'
4
-import { getBoundsCenter, isMobile } from 'utils'
5
-import tld from 'utils/tld'
6
-import CenterHandle from './center-handle'
7
-import CornerHandle from './corner-handle'
8
-import EdgeHandle from './edge-handle'
9
-import RotateHandle from './rotate-handle'
10
-
11
-export default function Bounds(): JSX.Element {
12
-  const isBrushing = useSelector((s) => s.isIn('brushSelecting'))
13
-
14
-  const shouldDisplay = useSelector((s) =>
15
-    s.isInAny('selecting', 'selectPinching')
16
-  )
17
-
18
-  const zoom = useSelector((s) => tld.getCurrentCamera(s.data).zoom)
19
-
20
-  const bounds = useSelector((s) => s.values.selectedBounds)
21
-
22
-  const rotation = useSelector((s) => s.values.selectedRotation)
23
-
24
-  const isAllLocked = useSelector((s) => {
25
-    const page = tld.getPage(s.data)
26
-    return s.values.selectedIds.every((id) => page.shapes[id]?.isLocked)
27
-  })
28
-
29
-  const isSingleHandles = useSelector((s) => {
30
-    const page = tld.getPage(s.data)
31
-    return (
32
-      s.values.selectedIds.length === 1 &&
33
-      page.shapes[s.values.selectedIds[0]]?.handles !== undefined
34
-    )
35
-  })
36
-
37
-  if (!bounds) return null
38
-
39
-  if (!shouldDisplay) return null
40
-
41
-  if (isSingleHandles) return null
42
-
43
-  const size = (isMobile() ? 10 : 8) / zoom // Touch target size
44
-  const center = getBoundsCenter(bounds)
45
-
46
-  return (
47
-    <g
48
-      pointerEvents={isBrushing ? 'none' : 'all'}
49
-      transform={`
50
-        rotate(${rotation * (180 / Math.PI)},${center})
51
-        translate(${bounds.minX},${bounds.minY})
52
-        rotate(${(bounds.rotation || 0) * (180 / Math.PI)}, 0, 0)`}
53
-    >
54
-      <CenterHandle bounds={bounds} isLocked={isAllLocked} />
55
-      {!isAllLocked && (
56
-        <>
57
-          <EdgeHandle size={size} bounds={bounds} edge={Edge.Top} />
58
-          <EdgeHandle size={size} bounds={bounds} edge={Edge.Right} />
59
-          <EdgeHandle size={size} bounds={bounds} edge={Edge.Bottom} />
60
-          <EdgeHandle size={size} bounds={bounds} edge={Edge.Left} />
61
-          <CornerHandle size={size} bounds={bounds} corner={Corner.TopLeft} />
62
-          <CornerHandle size={size} bounds={bounds} corner={Corner.TopRight} />
63
-          <CornerHandle
64
-            size={size}
65
-            bounds={bounds}
66
-            corner={Corner.BottomRight}
67
-          />
68
-          <CornerHandle
69
-            size={size}
70
-            bounds={bounds}
71
-            corner={Corner.BottomLeft}
72
-          />
73
-          <RotateHandle size={size} bounds={bounds} />
74
-        </>
75
-      )}
76
-    </g>
77
-  )
78
-}

+ 0
- 77
components/canvas/bounds/bounds-bg.tsx View File

@@ -1,77 +0,0 @@
1
-import { useRef } from 'react'
2
-import state, { useSelector } from 'state'
3
-import inputs from 'state/inputs'
4
-import styled from 'styles'
5
-import tld from 'utils/tld'
6
-
7
-function handlePointerDown(e: React.PointerEvent<SVGRectElement>) {
8
-  if (!inputs.canAccept(e.pointerId)) return
9
-  e.stopPropagation()
10
-  e.currentTarget.setPointerCapture(e.pointerId)
11
-  const info = inputs.pointerDown(e, 'bounds')
12
-
13
-  if (e.button === 0) {
14
-    state.send('POINTED_BOUNDS', info)
15
-  } else if (e.button === 2) {
16
-    state.send('RIGHT_POINTED', info)
17
-  }
18
-}
19
-
20
-function handlePointerUp(e: React.PointerEvent<SVGRectElement>) {
21
-  if (!inputs.canAccept(e.pointerId)) return
22
-  e.stopPropagation()
23
-  e.currentTarget.releasePointerCapture(e.pointerId)
24
-  state.send('STOPPED_POINTING', inputs.pointerUp(e, 'bounds'))
25
-}
26
-
27
-export default function BoundsBg(): JSX.Element {
28
-  const rBounds = useRef<SVGRectElement>(null)
29
-
30
-  const bounds = useSelector((state) => state.values.selectedBounds)
31
-
32
-  const shouldDisplay = useSelector((s) =>
33
-    s.isInAny('selecting', 'selectPinching')
34
-  )
35
-
36
-  const rotation = useSelector((s) => s.values.selectedRotation)
37
-
38
-  const isAllHandles = useSelector((s) => {
39
-    const selectedIds = s.values.selectedIds
40
-
41
-    if (selectedIds.length === 1) {
42
-      const page = tld.getPage(s.data)
43
-      const selected = selectedIds[0]
44
-
45
-      return (
46
-        selectedIds.length === 1 && page.shapes[selected]?.handles !== undefined
47
-      )
48
-    }
49
-  })
50
-
51
-  if (isAllHandles) return null
52
-  if (!bounds) return null
53
-  if (!shouldDisplay) return null
54
-
55
-  const { width, height } = bounds
56
-
57
-  return (
58
-    <StyledBoundsBg
59
-      ref={rBounds}
60
-      width={Math.max(1, width)}
61
-      height={Math.max(1, height)}
62
-      transform={`
63
-        rotate(${rotation * (180 / Math.PI)}, 
64
-        ${(bounds.minX + bounds.maxX) / 2}, 
65
-        ${(bounds.minY + bounds.maxY) / 2})
66
-        translate(${bounds.minX},${bounds.minY})
67
-        rotate(${(bounds.rotation || 0) * (180 / Math.PI)}, 0, 0)`}
68
-      onPointerDown={handlePointerDown}
69
-      onPointerUp={handlePointerUp}
70
-      pointerEvents="all"
71
-    />
72
-  )
73
-}
74
-
75
-const StyledBoundsBg = styled('rect', {
76
-  fill: '$boundsBg',
77
-})

+ 0
- 36
components/canvas/bounds/center-handle.tsx View File

@@ -1,36 +0,0 @@
1
-import styled from 'styles'
2
-import { Bounds } from 'types'
3
-
4
-export default function CenterHandle({
5
-  bounds,
6
-  isLocked,
7
-}: {
8
-  bounds: Bounds
9
-  isLocked: boolean
10
-}): JSX.Element {
11
-  return (
12
-    <StyledBounds
13
-      x={-1}
14
-      y={-1}
15
-      width={bounds.width + 2}
16
-      height={bounds.height + 2}
17
-      pointerEvents="none"
18
-      isLocked={isLocked}
19
-    />
20
-  )
21
-}
22
-
23
-const StyledBounds = styled('rect', {
24
-  fill: 'none',
25
-  stroke: '$bounds',
26
-  zStrokeWidth: 1.5,
27
-
28
-  variants: {
29
-    isLocked: {
30
-      true: {
31
-        zStrokeWidth: 1.5,
32
-        zDash: 2,
33
-      },
34
-    },
35
-  },
36
-})

+ 0
- 57
components/canvas/bounds/corner-handle.tsx View File

@@ -1,57 +0,0 @@
1
-import useBoundsEvents from 'hooks/useBoundsEvents'
2
-import styled from 'styles'
3
-import { Corner, Bounds } from 'types'
4
-
5
-export default function CornerHandle({
6
-  size,
7
-  corner,
8
-  bounds,
9
-}: {
10
-  size: number
11
-  bounds: Bounds
12
-  corner: Corner
13
-}): JSX.Element {
14
-  const events = useBoundsEvents(corner)
15
-
16
-  const isTop = corner === Corner.TopLeft || corner === Corner.TopRight
17
-  const isLeft = corner === Corner.TopLeft || corner === Corner.BottomLeft
18
-
19
-  return (
20
-    <g>
21
-      <StyledCorner
22
-        corner={corner}
23
-        x={(isLeft ? -1 : bounds.width + 1) - size}
24
-        y={(isTop ? -1 : bounds.height + 1) - size}
25
-        width={size * 2}
26
-        height={size * 2}
27
-        {...events}
28
-      />
29
-      <StyledCornerInner
30
-        x={(isLeft ? -1 : bounds.width + 1) - size / 2}
31
-        y={(isTop ? -1 : bounds.height + 1) - size / 2}
32
-        width={size}
33
-        height={size}
34
-        pointerEvents="none"
35
-      />
36
-    </g>
37
-  )
38
-}
39
-
40
-const StyledCorner = styled('rect', {
41
-  stroke: 'none',
42
-  fill: 'transparent',
43
-  variants: {
44
-    corner: {
45
-      [Corner.TopLeft]: { cursor: 'nwse-resize' },
46
-      [Corner.TopRight]: { cursor: 'nesw-resize' },
47
-      [Corner.BottomRight]: { cursor: 'nwse-resize' },
48
-      [Corner.BottomLeft]: { cursor: 'nesw-resize' },
49
-    },
50
-  },
51
-})
52
-
53
-const StyledCornerInner = styled('rect', {
54
-  stroke: '$bounds',
55
-  fill: '$canvas',
56
-  zStrokeWidth: 1.5,
57
-})

+ 0
- 44
components/canvas/bounds/edge-handle.tsx View File

@@ -1,44 +0,0 @@
1
-import useBoundsEvents from 'hooks/useBoundsEvents'
2
-import styled from 'styles'
3
-import { Edge, Bounds } from 'types'
4
-
5
-export default function EdgeHandle({
6
-  size,
7
-  bounds,
8
-  edge,
9
-}: {
10
-  size: number
11
-  bounds: Bounds
12
-  edge: Edge
13
-}): JSX.Element {
14
-  const events = useBoundsEvents(edge)
15
-
16
-  const isHorizontal = edge === Edge.Top || edge === Edge.Bottom
17
-  const isFarEdge = edge === Edge.Right || edge === Edge.Bottom
18
-
19
-  const { height, width } = bounds
20
-
21
-  return (
22
-    <StyledEdge
23
-      edge={edge}
24
-      x={isHorizontal ? size / 2 : (isFarEdge ? width + 1 : -1) - size / 2}
25
-      y={isHorizontal ? (isFarEdge ? height + 1 : -1) - size / 2 : size / 2}
26
-      width={isHorizontal ? Math.max(0, width + 1 - size) : size}
27
-      height={isHorizontal ? size : Math.max(0, height + 1 - size)}
28
-      {...events}
29
-    />
30
-  )
31
-}
32
-
33
-const StyledEdge = styled('rect', {
34
-  stroke: 'none',
35
-  fill: 'none',
36
-  variants: {
37
-    edge: {
38
-      [Edge.Top]: { cursor: 'ns-resize' },
39
-      [Edge.Right]: { cursor: 'ew-resize' },
40
-      [Edge.Bottom]: { cursor: 'ns-resize' },
41
-      [Edge.Left]: { cursor: 'ew-resize' },
42
-    },
43
-  },
44
-})

+ 0
- 81
components/canvas/bounds/handles.tsx View File

@@ -1,81 +0,0 @@
1
-import useHandleEvents from 'hooks/useHandleEvents'
2
-import { getShapeUtils } from 'state/shape-utils'
3
-import { useRef } from 'react'
4
-import { useSelector } from 'state'
5
-import styled from 'styles'
6
-import tld from 'utils/tld'
7
-import vec from 'utils/vec'
8
-
9
-export default function Handles(): JSX.Element {
10
-  const shape = useSelector(
11
-    (s) =>
12
-      s.values.selectedIds.length === 1 &&
13
-      tld.getPage(s.data).shapes[s.values.selectedIds[0]]
14
-  )
15
-
16
-  const isSelecting = useSelector((s) =>
17
-    s.isInAny('notPointing', 'pinching', 'translatingHandles')
18
-  )
19
-
20
-  if (!shape || !shape.handles || !isSelecting) return null
21
-
22
-  const center = getShapeUtils(shape).getCenter(shape)
23
-
24
-  return (
25
-    <g transform={`rotate(${shape.rotation * (180 / Math.PI)},${center})`}>
26
-      {Object.values(shape.handles).map((handle) => (
27
-        <Handle
28
-          key={handle.id}
29
-          id={handle.id}
30
-          point={vec.add(handle.point, shape.point)}
31
-        />
32
-      ))}
33
-    </g>
34
-  )
35
-}
36
-
37
-function Handle({ id, point }: { id: string; point: number[] }) {
38
-  const rGroup = useRef<SVGGElement>(null)
39
-  const events = useHandleEvents(id, rGroup)
40
-
41
-  return (
42
-    <StyledGroup
43
-      key={id}
44
-      className="handles"
45
-      ref={rGroup}
46
-      {...events}
47
-      pointerEvents="all"
48
-      transform={`translate(${point})`}
49
-    >
50
-      <HandleCircleOuter r={12} />
51
-      <use href="#handle" pointerEvents="none" />
52
-    </StyledGroup>
53
-  )
54
-}
55
-
56
-const StyledGroup = styled('g', {
57
-  '&:hover': {
58
-    cursor: 'pointer',
59
-  },
60
-  '&:active': {
61
-    cursor: 'none',
62
-  },
63
-})
64
-
65
-const HandleCircleOuter = styled('circle', {
66
-  fill: 'transparent',
67
-  stroke: 'none',
68
-  opacity: 0.2,
69
-  pointerEvents: 'all',
70
-  cursor: 'pointer',
71
-  transform: 'scale(var(--scale))',
72
-  '&:hover': {
73
-    fill: '$selected',
74
-    '& > *': {
75
-      stroke: '$selected',
76
-    },
77
-  },
78
-  '&:active': {
79
-    fill: '$selected',
80
-  },
81
-})

+ 0
- 38
components/canvas/bounds/rotate-handle.tsx View File

@@ -1,38 +0,0 @@
1
-import useHandleEvents from 'hooks/useBoundsEvents'
2
-import styled from 'styles'
3
-import { Bounds } from 'types'
4
-
5
-export default function Rotate({
6
-  bounds,
7
-  size,
8
-}: {
9
-  bounds: Bounds
10
-  size: number
11
-}): JSX.Element {
12
-  const events = useHandleEvents('rotate')
13
-
14
-  return (
15
-    <g cursor="grab" {...events}>
16
-      <circle
17
-        cx={bounds.width / 2}
18
-        cy={size * -2}
19
-        r={size * 2}
20
-        fill="transparent"
21
-        stroke="none"
22
-      />
23
-      <StyledRotateHandle
24
-        cx={bounds.width / 2}
25
-        cy={size * -2}
26
-        r={size / 2}
27
-        pointerEvents="none"
28
-      />
29
-    </g>
30
-  )
31
-}
32
-
33
-const StyledRotateHandle = styled('circle', {
34
-  stroke: '$bounds',
35
-  fill: '$canvas',
36
-  zStrokeWidth: 1.5,
37
-  cursor: 'grab',
38
-})

+ 0
- 23
components/canvas/brush.tsx View File

@@ -1,23 +0,0 @@
1
-import { useSelector } from 'state'
2
-import styled from 'styles'
3
-
4
-export default function Brush(): JSX.Element {
5
-  const brush = useSelector(({ data }) => data.brush)
6
-
7
-  if (!brush) return null
8
-
9
-  return (
10
-    <BrushRect
11
-      x={brush.minX}
12
-      y={brush.minY}
13
-      width={brush.width}
14
-      height={brush.height}
15
-    />
16
-  )
17
-}
18
-
19
-const BrushRect = styled('rect', {
20
-  fill: '$brushFill',
21
-  stroke: '$brushStroke',
22
-  zStrokeWidth: 1,
23
-})

+ 0
- 99
components/canvas/canvas.tsx View File

@@ -1,99 +0,0 @@
1
-import * as Sentry from '@sentry/node'
2
-import React, { useEffect, useRef } from 'react'
3
-
4
-import { ErrorBoundary } from 'react-error-boundary'
5
-import state, { useSelector } from 'state'
6
-import styled from 'styles'
7
-import useCamera from 'hooks/useCamera'
8
-import useCanvasEvents from 'hooks/useCanvasEvents'
9
-import useZoomEvents from 'hooks/useZoomEvents'
10
-import Bounds from './bounds/bounding-box'
11
-import BoundsBg from './bounds/bounds-bg'
12
-import Handles from './bounds/handles'
13
-import ContextMenu from './context-menu/context-menu'
14
-import Coop from './coop/coop'
15
-import Brush from './brush'
16
-import Defs from './defs'
17
-import Page from './page'
18
-import useSafariFocusOutFix from 'hooks/useSafariFocusOutFix'
19
-
20
-function resetError() {
21
-  null
22
-}
23
-
24
-export default function Canvas(): JSX.Element {
25
-  const rCanvas = useRef<SVGSVGElement>(null)
26
-  const rGroup = useRef<SVGGElement>(null)
27
-
28
-  useCamera(rGroup)
29
-
30
-  useZoomEvents()
31
-
32
-  useSafariFocusOutFix()
33
-
34
-  const events = useCanvasEvents(rCanvas)
35
-
36
-  const isSettingCamera = useSelector((s) => s.isIn('settingCamera'))
37
-  const isReady = useSelector((s) => s.isIn('ready'))
38
-
39
-  useEffect(() => {
40
-    if (isSettingCamera) {
41
-      state.send('MOUNTED_SHAPES')
42
-    }
43
-  }, [isSettingCamera])
44
-
45
-  return (
46
-    <ContextMenu>
47
-      <MainSVG ref={rCanvas} {...events}>
48
-        <ErrorBoundary FallbackComponent={ErrorFallback} onReset={resetError}>
49
-          <Defs />
50
-          <g ref={rGroup} id="shapes" opacity={isReady ? 1 : 0}>
51
-            <BoundsBg />
52
-            <Page />
53
-            <Coop />
54
-            <Bounds />
55
-            <Handles />
56
-            <Brush />
57
-          </g>
58
-        </ErrorBoundary>
59
-      </MainSVG>
60
-    </ContextMenu>
61
-  )
62
-}
63
-
64
-const MainSVG = styled('svg', {
65
-  position: 'fixed',
66
-  overflow: 'hidden',
67
-  top: 0,
68
-  left: 0,
69
-  width: '100%',
70
-  height: '100%',
71
-  touchAction: 'none',
72
-  zIndex: 100,
73
-  pointerEvents: 'all',
74
-  backgroundColor: '$canvas',
75
-  borderTop: '1px solid $border',
76
-  borderBottom: '1px solid $border',
77
-
78
-  '& *': {
79
-    userSelect: 'none',
80
-  },
81
-})
82
-
83
-function ErrorFallback({ error, resetErrorBoundary }) {
84
-  React.useEffect(() => {
85
-    const copy =
86
-      'Sorry, something went wrong. Press Ok to reset the document, or press cancel to continue and see if it resolves itself.'
87
-
88
-    console.error(error)
89
-
90
-    Sentry.captureException(error)
91
-
92
-    if (window.confirm(copy)) {
93
-      state.send('RESET_DOCUMENT_STATE')
94
-      resetErrorBoundary()
95
-    }
96
-  }, [])
97
-
98
-  return <g />
99
-}

+ 0
- 368
components/canvas/context-menu/context-menu.tsx View File

@@ -1,368 +0,0 @@
1
-import * as _ContextMenu from '@radix-ui/react-context-menu'
2
-import styled from 'styles'
3
-import {
4
-  IconWrapper,
5
-  breakpoints,
6
-  RowButton,
7
-  ContextMenuArrow,
8
-  ContextMenuDivider,
9
-  ContextMenuButton,
10
-  ContextMenuSubMenu,
11
-  ContextMenuIconButton,
12
-  ContextMenuRoot,
13
-  MenuContent,
14
-} from 'components/shared'
15
-import { commandKey, deepCompareArrays } from 'utils'
16
-import state, { useSelector } from 'state'
17
-import {
18
-  AlignType,
19
-  DistributeType,
20
-  MoveType,
21
-  ShapeType,
22
-  StretchType,
23
-} from 'types'
24
-import tld from 'utils/tld'
25
-import React, { useRef } from 'react'
26
-import {
27
-  ChevronRightIcon,
28
-  AlignBottomIcon,
29
-  AlignCenterHorizontallyIcon,
30
-  AlignCenterVerticallyIcon,
31
-  AlignLeftIcon,
32
-  AlignRightIcon,
33
-  AlignTopIcon,
34
-  SpaceEvenlyHorizontallyIcon,
35
-  SpaceEvenlyVerticallyIcon,
36
-  StretchHorizontallyIcon,
37
-  StretchVerticallyIcon,
38
-} from '@radix-ui/react-icons'
39
-import { Kbd } from 'components/shared'
40
-
41
-function alignTop() {
42
-  state.send('ALIGNED', { type: AlignType.Top })
43
-}
44
-
45
-function alignCenterVertical() {
46
-  state.send('ALIGNED', { type: AlignType.CenterVertical })
47
-}
48
-
49
-function alignBottom() {
50
-  state.send('ALIGNED', { type: AlignType.Bottom })
51
-}
52
-
53
-function stretchVertically() {
54
-  state.send('STRETCHED', { type: StretchType.Vertical })
55
-}
56
-
57
-function distributeVertically() {
58
-  state.send('DISTRIBUTED', { type: DistributeType.Vertical })
59
-}
60
-
61
-function alignLeft() {
62
-  state.send('ALIGNED', { type: AlignType.Left })
63
-}
64
-
65
-function alignCenterHorizontal() {
66
-  state.send('ALIGNED', { type: AlignType.CenterHorizontal })
67
-}
68
-
69
-function alignRight() {
70
-  state.send('ALIGNED', { type: AlignType.Right })
71
-}
72
-
73
-function stretchHorizontally() {
74
-  state.send('STRETCHED', { type: StretchType.Horizontal })
75
-}
76
-
77
-function distributeHorizontally() {
78
-  state.send('DISTRIBUTED', { type: DistributeType.Horizontal })
79
-}
80
-
81
-export default function ContextMenu({
82
-  children,
83
-}: {
84
-  children: React.ReactNode
85
-}): JSX.Element {
86
-  const selectedShapeIds = useSelector(
87
-    (s) => s.values.selectedIds,
88
-    deepCompareArrays
89
-  )
90
-
91
-  const rContent = useRef<HTMLDivElement>(null)
92
-
93
-  const hasGroupSelected = useSelector((s) =>
94
-    selectedShapeIds.some(
95
-      (id) => tld.getShape(s.data, id)?.type === ShapeType.Group
96
-    )
97
-  )
98
-
99
-  const hasTwoOrMore = selectedShapeIds.length > 1
100
-  const hasThreeOrMore = selectedShapeIds.length > 2
101
-
102
-  return (
103
-    <ContextMenuRoot>
104
-      <_ContextMenu.Trigger>{children}</_ContextMenu.Trigger>
105
-      <MenuContent as={_ContextMenu.Content} ref={rContent}>
106
-        {selectedShapeIds.length ? (
107
-          <>
108
-            {/* <ContextMenuButton onSelect={() => state.send('COPIED')}>
109
-              <span>Copy</span>
110
-              <Kbd>
111
-                <span>{commandKey()}</span>
112
-                <span>C</span>
113
-              </Kbd>
114
-            </ContextMenuButton>
115
-            <ContextMenuButton onSelect={() => state.send('CUT')}>
116
-              <span>Cut</span>
117
-              <Kbd>
118
-                <span>{commandKey()}</span>
119
-                <span>X</span>
120
-              </Kbd>
121
-            </ContextMenuButton>
122
-             */}
123
-            <ContextMenuButton onSelect={() => state.send('DUPLICATED')}>
124
-              <span>Duplicate</span>
125
-              <Kbd>
126
-                <span>{commandKey()}</span>
127
-                <span>D</span>
128
-              </Kbd>
129
-            </ContextMenuButton>
130
-            <ContextMenuDivider />
131
-            {hasGroupSelected ||
132
-              (hasTwoOrMore && (
133
-                <>
134
-                  {hasGroupSelected && (
135
-                    <ContextMenuButton onSelect={() => state.send('UNGROUPED')}>
136
-                      <span>Ungroup</span>
137
-                      <Kbd>
138
-                        <span>{commandKey()}</span>
139
-                        <span>⇧</span>
140
-                        <span>G</span>
141
-                      </Kbd>
142
-                    </ContextMenuButton>
143
-                  )}
144
-                  {hasTwoOrMore && (
145
-                    <ContextMenuButton onSelect={() => state.send('GROUPED')}>
146
-                      <span>Group</span>
147
-                      <Kbd>
148
-                        <span>{commandKey()}</span>
149
-                        <span>G</span>
150
-                      </Kbd>
151
-                    </ContextMenuButton>
152
-                  )}
153
-                </>
154
-              ))}
155
-            <ContextMenuSubMenu label="Move">
156
-              <ContextMenuButton
157
-                onSelect={() =>
158
-                  state.send('MOVED', {
159
-                    type: MoveType.ToFront,
160
-                  })
161
-                }
162
-              >
163
-                <span>To Front</span>
164
-                <Kbd>
165
-                  <span>{commandKey()}</span>
166
-                  <span>⇧</span>
167
-                  <span>]</span>
168
-                </Kbd>
169
-              </ContextMenuButton>
170
-
171
-              <ContextMenuButton
172
-                onSelect={() =>
173
-                  state.send('MOVED', {
174
-                    type: MoveType.Forward,
175
-                  })
176
-                }
177
-              >
178
-                <span>Forward</span>
179
-                <Kbd>
180
-                  <span>{commandKey()}</span>
181
-                  <span>]</span>
182
-                </Kbd>
183
-              </ContextMenuButton>
184
-              <ContextMenuButton
185
-                onSelect={() =>
186
-                  state.send('MOVED', {
187
-                    type: MoveType.Backward,
188
-                  })
189
-                }
190
-              >
191
-                <span>Backward</span>
192
-                <Kbd>
193
-                  <span>{commandKey()}</span>
194
-                  <span>[</span>
195
-                </Kbd>
196
-              </ContextMenuButton>
197
-              <ContextMenuButton
198
-                onSelect={() =>
199
-                  state.send('MOVED', {
200
-                    type: MoveType.ToBack,
201
-                  })
202
-                }
203
-              >
204
-                <span>To Back</span>
205
-                <Kbd>
206
-                  <span>{commandKey()}</span>
207
-                  <span>⇧</span>
208
-                  <span>[</span>
209
-                </Kbd>
210
-              </ContextMenuButton>
211
-            </ContextMenuSubMenu>
212
-            {hasTwoOrMore && (
213
-              <AlignDistributeSubMenu
214
-                hasTwoOrMore={hasTwoOrMore}
215
-                hasThreeOrMore={hasThreeOrMore}
216
-              />
217
-            )}
218
-            <MoveToPageMenu />
219
-            <ContextMenuButton onSelect={() => state.send('COPIED_TO_SVG')}>
220
-              <span>Copy to SVG</span>
221
-              <Kbd>
222
-                <span>{commandKey()}</span>
223
-                <span>⇧</span>
224
-                <span>C</span>
225
-              </Kbd>
226
-            </ContextMenuButton>
227
-            <ContextMenuDivider />
228
-            <ContextMenuButton onSelect={() => state.send('DELETED')}>
229
-              <span>Delete</span>
230
-              <Kbd>
231
-                <span>⌫</span>
232
-              </Kbd>
233
-            </ContextMenuButton>
234
-          </>
235
-        ) : (
236
-          <>
237
-            <ContextMenuButton onSelect={() => state.send('UNDO')}>
238
-              <span>Undo</span>
239
-              <Kbd>
240
-                <span>{commandKey()}</span>
241
-                <span>Z</span>
242
-              </Kbd>
243
-            </ContextMenuButton>
244
-            <ContextMenuButton onSelect={() => state.send('REDO')}>
245
-              <span>Redo</span>
246
-              <Kbd>
247
-                <span>{commandKey()}</span>
248
-                <span>⇧</span>
249
-                <span>Z</span>
250
-              </Kbd>
251
-            </ContextMenuButton>
252
-          </>
253
-        )}
254
-      </MenuContent>
255
-    </ContextMenuRoot>
256
-  )
257
-}
258
-
259
-function AlignDistributeSubMenu({
260
-  hasThreeOrMore,
261
-}: {
262
-  hasTwoOrMore: boolean
263
-  hasThreeOrMore: boolean
264
-}) {
265
-  return (
266
-    <ContextMenuRoot>
267
-      <_ContextMenu.TriggerItem as={RowButton} bp={breakpoints}>
268
-        <span>Align / Distribute</span>
269
-        <IconWrapper size="small">
270
-          <ChevronRightIcon />
271
-        </IconWrapper>
272
-      </_ContextMenu.TriggerItem>
273
-      <StyledGrid
274
-        as={_ContextMenu.Content}
275
-        sideOffset={2}
276
-        alignOffset={-2}
277
-        selectedStyle={hasThreeOrMore ? 'threeOrMore' : 'twoOrMore'}
278
-      >
279
-        <ContextMenuIconButton onSelect={alignLeft}>
280
-          <AlignLeftIcon />
281
-        </ContextMenuIconButton>
282
-        <ContextMenuIconButton onSelect={alignCenterHorizontal}>
283
-          <AlignCenterHorizontallyIcon />
284
-        </ContextMenuIconButton>
285
-        <ContextMenuIconButton onSelect={alignRight}>
286
-          <AlignRightIcon />
287
-        </ContextMenuIconButton>
288
-        <ContextMenuIconButton onSelect={stretchHorizontally}>
289
-          <StretchHorizontallyIcon />
290
-        </ContextMenuIconButton>
291
-        {hasThreeOrMore && (
292
-          <ContextMenuIconButton onSelect={distributeHorizontally}>
293
-            <SpaceEvenlyHorizontallyIcon />
294
-          </ContextMenuIconButton>
295
-        )}
296
-
297
-        <ContextMenuIconButton onSelect={alignTop}>
298
-          <AlignTopIcon />
299
-        </ContextMenuIconButton>
300
-        <ContextMenuIconButton onSelect={alignCenterVertical}>
301
-          <AlignCenterVerticallyIcon />
302
-        </ContextMenuIconButton>
303
-        <ContextMenuIconButton onSelect={alignBottom}>
304
-          <AlignBottomIcon />
305
-        </ContextMenuIconButton>
306
-        <ContextMenuIconButton onSelect={stretchVertically}>
307
-          <StretchVerticallyIcon />
308
-        </ContextMenuIconButton>
309
-        {hasThreeOrMore && (
310
-          <ContextMenuIconButton onSelect={distributeVertically}>
311
-            <SpaceEvenlyVerticallyIcon />
312
-          </ContextMenuIconButton>
313
-        )}
314
-        <ContextMenuArrow offset={13} />
315
-      </StyledGrid>
316
-    </ContextMenuRoot>
317
-  )
318
-}
319
-
320
-const StyledGrid = styled(MenuContent, {
321
-  display: 'grid',
322
-  variants: {
323
-    selectedStyle: {
324
-      threeOrMore: {
325
-        gridTemplateColumns: 'repeat(5, auto)',
326
-      },
327
-      twoOrMore: {
328
-        gridTemplateColumns: 'repeat(4, auto)',
329
-      },
330
-    },
331
-  },
332
-})
333
-
334
-function MoveToPageMenu() {
335
-  const documentPages = useSelector((s) => s.data.document.pages)
336
-  const currentPageId = useSelector((s) => s.data.currentPageId)
337
-
338
-  if (!documentPages[currentPageId]) return null
339
-
340
-  const sorted = Object.values(documentPages)
341
-    .sort((a, b) => a.childIndex - b.childIndex)
342
-    .filter((a) => a.id !== currentPageId)
343
-
344
-  if (sorted.length === 0) return null
345
-
346
-  return (
347
-    <ContextMenuRoot>
348
-      <ContextMenuButton>
349
-        <span>Move To Page</span>
350
-        <IconWrapper size="small">
351
-          <ChevronRightIcon />
352
-        </IconWrapper>
353
-      </ContextMenuButton>
354
-      <MenuContent as={_ContextMenu.Content} sideOffset={2} alignOffset={-2}>
355
-        {sorted.map(({ id, name }) => (
356
-          <ContextMenuButton
357
-            key={id}
358
-            disabled={id === currentPageId}
359
-            onSelect={() => state.send('MOVED_TO_PAGE', { id })}
360
-          >
361
-            <span>{name}</span>
362
-          </ContextMenuButton>
363
-        ))}
364
-        <ContextMenuArrow offset={13} />
365
-      </MenuContent>
366
-    </ContextMenuRoot>
367
-  )
368
-}

+ 0
- 29
components/canvas/coop/coop.tsx View File

@@ -1,29 +0,0 @@
1
-import Cursor from './cursor'
2
-import { useCoopSelector } from 'state/coop/coop-state'
3
-import { useSelector } from 'state'
4
-
5
-export default function Presence(): JSX.Element {
6
-  const others = useCoopSelector((s) => s.data.others)
7
-  const currentPageId = useSelector((s) => s.data.currentPageId)
8
-
9
-  if (!others) return null
10
-
11
-  return (
12
-    <>
13
-      {Object.values(others)
14
-        .filter(({ presence }) => presence?.pageId === currentPageId)
15
-        .map(({ connectionId, presence }) => {
16
-          return (
17
-            <Cursor
18
-              key={`cursor-${connectionId}`}
19
-              color={'red'}
20
-              duration={presence.duration}
21
-              times={presence.times}
22
-              bufferedXs={presence.bufferedXs}
23
-              bufferedYs={presence.bufferedYs}
24
-            />
25
-          )
26
-        })}
27
-    </>
28
-  )
29
-}

+ 0
- 65
components/canvas/coop/cursor.tsx View File

@@ -1,65 +0,0 @@
1
-import React from 'react'
2
-import styled from 'styles'
3
-import { motion } from 'framer-motion'
4
-
5
-export default function Cursor({
6
-  color = 'dodgerblue',
7
-  duration = 0,
8
-  bufferedXs = [],
9
-  bufferedYs = [],
10
-  times = [],
11
-}: {
12
-  color: string
13
-  duration: number
14
-  bufferedXs: number[]
15
-  bufferedYs: number[]
16
-  times: number[]
17
-}): JSX.Element {
18
-  return (
19
-    <StyledCursor
20
-      color={color}
21
-      initial={false}
22
-      animate={{
23
-        x: bufferedXs,
24
-        y: bufferedYs,
25
-        transition: {
26
-          type: 'tween',
27
-          ease: 'linear',
28
-          duration,
29
-          times,
30
-        },
31
-      }}
32
-      width="35px"
33
-      height="35px"
34
-      viewBox="0 0 35 35"
35
-      version="1.1"
36
-      pointerEvents="none"
37
-      xmlns="http://www.w3.org/2000/svg"
38
-      xmlnsXlink="http://www.w3.org/1999/xlink"
39
-    >
40
-      <path
41
-        d="M12,24.4219 L12,8.4069 L23.591,20.0259 L16.81,20.0259 L16.399,20.1499 L12,24.4219 Z"
42
-        fill="#ffffff"
43
-      />
44
-      <path
45
-        d="M21.0845,25.0962 L17.4795,26.6312 L12.7975,15.5422 L16.4835,13.9892 L21.0845,25.0962 Z"
46
-        fill="#ffffff"
47
-      />
48
-      <path
49
-        d="M19.751,24.4155 L17.907,25.1895 L14.807,17.8155 L16.648,17.0405 L19.751,24.4155 Z"
50
-        fill="currentColor"
51
-      />
52
-      <path
53
-        d="M13,10.814 L13,22.002 L15.969,19.136 L16.397,18.997 L21.165,18.997 L13,10.814 Z"
54
-        fill="currentColor"
55
-      />
56
-    </StyledCursor>
57
-  )
58
-}
59
-
60
-const StyledCursor = styled(motion.g, {
61
-  position: 'absolute',
62
-  zIndex: 1000,
63
-  top: 0,
64
-  left: 0,
65
-})

+ 0
- 43
components/canvas/defs.tsx View File

@@ -1,43 +0,0 @@
1
-import React from 'react'
2
-import { useSelector } from 'state'
3
-import tld from 'utils/tld'
4
-import { DotCircle, Handle } from './misc'
5
-import styled from 'styles'
6
-
7
-export default function Defs(): JSX.Element {
8
-  return (
9
-    <defs>
10
-      <DotCircle id="dot" r={4} />
11
-      <Handle id="handle" r={4} />
12
-      <ExpandDef />
13
-      <HoverDef />
14
-    </defs>
15
-  )
16
-}
17
-
18
-function ExpandDef() {
19
-  const zoom = useSelector((s) => tld.getCurrentCamera(s.data).zoom)
20
-  return (
21
-    <filter id="expand">
22
-      <feMorphology operator="dilate" radius={0.5 / zoom} />
23
-    </filter>
24
-  )
25
-}
26
-
27
-function HoverDef() {
28
-  return (
29
-    <filter id="hover">
30
-      <StyledShadow
31
-        dx="2"
32
-        dy="2"
33
-        stdDeviation="0.5"
34
-        floodOpacity="1"
35
-        floodColor="blue"
36
-      />
37
-    </filter>
38
-  )
39
-}
40
-
41
-const StyledShadow = styled('feDropShadow', {
42
-  floodColor: '$selected',
43
-})

+ 0
- 43
components/canvas/hovered-shape.tsx View File

@@ -1,43 +0,0 @@
1
-import { memo } from 'react'
2
-import tld from 'utils/tld'
3
-import { getShapeUtils } from 'state/shape-utils'
4
-import vec from 'utils/vec'
5
-import styled from 'styles'
6
-import { useSelector } from 'state'
7
-import { getShapeStyle } from 'state/shape-styles'
8
-
9
-function HoveredShape({ id }: { id: string }) {
10
-  const transform = useSelector((s) => {
11
-    const shape = tld.getShape(s.data, id)
12
-    const center = getShapeUtils(shape).getCenter(shape)
13
-    const rotation = shape.rotation * (180 / Math.PI)
14
-    const parentPoint = tld.getShape(s.data, shape.parentId)?.point || [0, 0]
15
-
16
-    return `
17
-      translate(${vec.neg(parentPoint)})
18
-      rotate(${rotation}, ${center})
19
-      translate(${shape.point})
20
-  `
21
-  })
22
-
23
-  const strokeWidth = useSelector((s) => {
24
-    const shape = tld.getShape(s.data, id)
25
-    const style = getShapeStyle(shape.style, s.data.settings.isDarkMode)
26
-    return +style.strokeWidth
27
-  })
28
-
29
-  return (
30
-    <StyledHoverShape
31
-      href={'#' + id}
32
-      transform={transform}
33
-      strokeWidth={strokeWidth + 8}
34
-    />
35
-  )
36
-}
37
-
38
-const StyledHoverShape = styled('use', {
39
-  stroke: '$selected',
40
-  opacity: 0.1,
41
-})
42
-
43
-export default memo(HoveredShape)

+ 0
- 19
components/canvas/misc.tsx View File

@@ -1,19 +0,0 @@
1
-import styled from 'styles'
2
-
3
-export const DotCircle = styled('circle', {
4
-  transform: 'scale(var(--scale))',
5
-  fill: '$canvas',
6
-  stroke: '$text',
7
-  strokeWidth: '2',
8
-})
9
-
10
-export const Handle = styled('circle', {
11
-  transform: 'scale(var(--scale))',
12
-  fill: '$canvas',
13
-  stroke: '$selected',
14
-  strokeWidth: '2',
15
-})
16
-
17
-export const ThinLine = styled('line', {
18
-  zStrokeWidth: 1,
19
-})

+ 0
- 57
components/canvas/page.tsx View File

@@ -1,57 +0,0 @@
1
-import { useSelector } from 'state'
2
-import { ShapeTreeNode } from 'types'
3
-import ShapeComponent from './shape'
4
-
5
-export default function Page(): JSX.Element {
6
-  const shapesToRender = useSelector((s) => s.values.shapesToRender)
7
-
8
-  const allowHovers = useSelector((s) =>
9
-    s.isInAny('selecting', 'text', 'editingShape')
10
-  )
11
-
12
-  return (
13
-    <>
14
-      {shapesToRender.map((node) => (
15
-        <ShapeNode key={node.shape.id} node={node} allowHovers={allowHovers} />
16
-      ))}
17
-    </>
18
-  )
19
-}
20
-
21
-interface ShapeNodeProps {
22
-  node: ShapeTreeNode
23
-  allowHovers: boolean
24
-}
25
-
26
-const ShapeNode = ({
27
-  node: {
28
-    shape,
29
-    children,
30
-    isEditing,
31
-    isHovered,
32
-    isDarkMode,
33
-    isSelected,
34
-    isCurrentParent,
35
-  },
36
-  allowHovers,
37
-}: ShapeNodeProps) => {
38
-  return (
39
-    <>
40
-      <ShapeComponent
41
-        shape={shape}
42
-        isEditing={isEditing}
43
-        isHovered={allowHovers && isHovered}
44
-        isSelected={isSelected}
45
-        isDarkMode={isDarkMode}
46
-        isCurrentParent={isCurrentParent}
47
-      />
48
-      {children.map((childNode) => (
49
-        <ShapeNode
50
-          key={childNode.shape.id}
51
-          node={childNode}
52
-          allowHovers={allowHovers}
53
-        />
54
-      ))}
55
-    </>
56
-  )
57
-}

+ 0
- 149
components/canvas/shape.tsx View File

@@ -1,149 +0,0 @@
1
-import useShapeEvents from 'hooks/useShapeEvents'
2
-import { Shape as _Shape, ShapeType, TextShape } from 'types'
3
-import { getShapeUtils } from 'state/shape-utils'
4
-import { shallowEqual } from 'utils'
5
-import { memo, useRef } from 'react'
6
-import styled from 'styles'
7
-
8
-interface ShapeProps {
9
-  shape: _Shape
10
-  isEditing: boolean
11
-  isHovered: boolean
12
-  isSelected: boolean
13
-  isDarkMode: boolean
14
-  isCurrentParent: boolean
15
-}
16
-
17
-const Shape = memo(
18
-  ({
19
-    shape,
20
-    isEditing,
21
-    isHovered,
22
-    isSelected,
23
-    isDarkMode,
24
-    isCurrentParent,
25
-  }: ShapeProps) => {
26
-    const rGroup = useRef<SVGGElement>(null)
27
-    const events = useShapeEvents(shape.id, isCurrentParent, rGroup)
28
-    const utils = getShapeUtils(shape)
29
-
30
-    const center = utils.getCenter(shape)
31
-    const rotation = shape.rotation * (180 / Math.PI)
32
-    const transform = `rotate(${rotation}, ${center}) translate(${shape.point})`
33
-
34
-    return (
35
-      <ShapeGroup
36
-        ref={rGroup}
37
-        id={shape.id}
38
-        transform={transform}
39
-        isCurrentParent={isCurrentParent}
40
-        filter={isHovered ? 'url(#expand)' : 'none'}
41
-        {...events}
42
-      >
43
-        {isEditing && shape.type === ShapeType.Text ? (
44
-          <EditingTextShape shape={shape} isDarkMode={isDarkMode} />
45
-        ) : (
46
-          <RenderedShape
47
-            shape={shape}
48
-            isEditing={isEditing}
49
-            isHovered={isHovered}
50
-            isSelected={isSelected}
51
-            isDarkMode={isDarkMode}
52
-            isCurrentParent={isCurrentParent}
53
-          />
54
-        )}
55
-      </ShapeGroup>
56
-    )
57
-  },
58
-  shallowEqual
59
-)
60
-
61
-export default Shape
62
-
63
-interface RenderedShapeProps {
64
-  shape: _Shape
65
-  isEditing: boolean
66
-  isHovered: boolean
67
-  isSelected: boolean
68
-  isDarkMode: boolean
69
-  isCurrentParent: boolean
70
-}
71
-
72
-const RenderedShape = memo(
73
-  function RenderedShape({
74
-    shape,
75
-    isEditing,
76
-    isHovered,
77
-    isSelected,
78
-    isDarkMode,
79
-    isCurrentParent,
80
-  }: RenderedShapeProps) {
81
-    return getShapeUtils(shape).render(shape, {
82
-      isEditing,
83
-      isHovered,
84
-      isSelected,
85
-      isDarkMode,
86
-      isCurrentParent,
87
-    })
88
-  },
89
-  (prev, next) => {
90
-    if (
91
-      prev.isEditing !== next.isEditing ||
92
-      prev.isHovered !== next.isHovered ||
93
-      prev.isSelected !== next.isSelected ||
94
-      prev.isDarkMode !== next.isDarkMode ||
95
-      prev.isCurrentParent !== next.isCurrentParent
96
-    ) {
97
-      return false
98
-    }
99
-
100
-    if (next.shape !== prev.shape) {
101
-      return !getShapeUtils(next.shape).shouldRender(next.shape, prev.shape)
102
-    }
103
-
104
-    return true
105
-  }
106
-)
107
-
108
-function EditingTextShape({
109
-  shape,
110
-  isDarkMode,
111
-}: {
112
-  shape: TextShape
113
-  isDarkMode: boolean
114
-}) {
115
-  const ref = useRef<HTMLTextAreaElement>(null)
116
-
117
-  return getShapeUtils(shape).render(shape, {
118
-    ref,
119
-    isEditing: true,
120
-    isHovered: false,
121
-    isSelected: false,
122
-    isDarkMode,
123
-    isCurrentParent: false,
124
-  })
125
-}
126
-
127
-const ShapeGroup = styled('g', {
128
-  outline: 'none',
129
-
130
-  '& > *[data-shy=true]': {
131
-    opacity: 0,
132
-  },
133
-
134
-  '&:hover': {
135
-    '& > *[data-shy=true]': {
136
-      opacity: 1,
137
-    },
138
-  },
139
-
140
-  variants: {
141
-    isCurrentParent: {
142
-      true: {
143
-        '& > *[data-shy=true]': {
144
-          opacity: 1,
145
-        },
146
-      },
147
-    },
148
-  },
149
-})

+ 0
- 121
components/code-panel/code-docs.tsx View File

@@ -1,121 +0,0 @@
1
-import styled from 'styles'
2
-import ReactMarkdown from 'react-markdown'
3
-import docs from './docs-content'
4
-
5
-export default function CodeDocs({
6
-  isHidden,
7
-}: {
8
-  isHidden: boolean
9
-}): JSX.Element {
10
-  return (
11
-    <StyledDocs isHidden={isHidden}>
12
-      <ReactMarkdown>{docs.content}</ReactMarkdown>
13
-    </StyledDocs>
14
-  )
15
-}
16
-
17
-const StyledDocs = styled('div', {
18
-  position: 'absolute',
19
-  backgroundColor: '$panel',
20
-  top: 0,
21
-  left: 0,
22
-  width: '100%',
23
-  height: '100%',
24
-  padding: 16,
25
-  overflowY: 'scroll',
26
-  userSelect: 'none',
27
-  paddingBottom: 64,
28
-  fontFamily: '$body',
29
-  fontWeight: 500,
30
-
31
-  variants: {
32
-    isHidden: {
33
-      true: {
34
-        visibility: 'hidden',
35
-      },
36
-      false: {
37
-        visibility: 'visible',
38
-      },
39
-    },
40
-  },
41
-
42
-  '& p': {
43
-    fontSize: '$3',
44
-    lineHeight: '1.3',
45
-  },
46
-
47
-  '& ol, ul': {
48
-    fontSize: '$3',
49
-    lineHeight: '1.3',
50
-    marginTop: 16,
51
-    marginBottom: 16,
52
-  },
53
-
54
-  '& li': {
55
-    fontSize: '$3',
56
-    lineHeight: '1.5',
57
-  },
58
-
59
-  '& code': {
60
-    font: '$mono',
61
-  },
62
-
63
-  '& hr': {
64
-    margin: '32px 0',
65
-    borderColor: '$muted',
66
-  },
67
-
68
-  '& h2': {
69
-    margin: '40px 0px 24px 0',
70
-  },
71
-
72
-  '& h3': {
73
-    fontSize: 20,
74
-    margin: '48px 0px 32px 0px',
75
-  },
76
-
77
-  '& h3 > code': {
78
-    fontWeight: 600,
79
-    font: '$mono',
80
-  },
81
-
82
-  '& h4': {
83
-    margin: '32px 0px 0px 0px',
84
-  },
85
-
86
-  '& h4 > code': {
87
-    font: '$mono',
88
-    fontSize: 16,
89
-    userSelect: 'all',
90
-  },
91
-
92
-  '& h4 > code > i': {
93
-    fontSize: 14,
94
-    color: '$muted',
95
-  },
96
-
97
-  '& pre': {
98
-    border: '1px solid $brushStroke',
99
-    font: '$mono',
100
-    fontWeight: 420,
101
-    lineHeight: 1.5,
102
-    padding: 16,
103
-    borderRadius: 4,
104
-    userSelect: 'all',
105
-    margin: '24px 0',
106
-  },
107
-
108
-  '& p > code, blockquote > code': {
109
-    padding: '2px 4px',
110
-    borderRadius: 2,
111
-    color: '$text',
112
-    backgroundColor: '$codeHl',
113
-  },
114
-
115
-  '& blockquote': {
116
-    backgroundColor: '$overlay',
117
-    padding: 12,
118
-    margin: '20px 0',
119
-    borderRadius: 8,
120
-  },
121
-})

+ 0
- 248
components/code-panel/code-editor.tsx View File

@@ -1,248 +0,0 @@
1
-import Editor, { Monaco } from '@monaco-editor/react'
2
-import { useTheme } from 'next-themes'
3
-import libImport from './es5-lib'
4
-import typesImport from './types-import'
5
-import React, { useCallback, useEffect, useRef } from 'react'
6
-import styled from 'styles'
7
-import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
8
-import { getFormattedCode } from 'utils/code'
9
-import { metaKey } from 'utils'
10
-
11
-export type IMonaco = typeof monaco
12
-
13
-export type IMonacoEditor = monaco.editor.IStandaloneCodeEditor
14
-
15
-const modifierKeys = ['Escape', 'Meta', 'Control', 'Shift', 'Option', 'Alt']
16
-
17
-interface Props {
18
-  value: string
19
-  error: { line: number; column: number }
20
-  fontSize: number
21
-  monacoRef?: React.MutableRefObject<IMonaco>
22
-  editorRef?: React.MutableRefObject<IMonacoEditor>
23
-  readOnly?: boolean
24
-  onMount?: (value: string, editor: IMonacoEditor) => void
25
-  onUnmount?: (editor: IMonacoEditor) => void
26
-  onChange?: (value: string, editor: IMonacoEditor) => void
27
-  onSave?: (value: string, editor: IMonacoEditor) => void
28
-  onError?: (error: Error, line: number, col: number) => void
29
-  onKey?: () => void
30
-}
31
-
32
-export default function CodeEditor({
33
-  editorRef,
34
-  monacoRef,
35
-  fontSize,
36
-  value,
37
-  error,
38
-  readOnly,
39
-  onChange,
40
-  onSave,
41
-  onKey,
42
-}: Props): JSX.Element {
43
-  const { theme } = useTheme()
44
-
45
-  const rEditor = useRef<IMonacoEditor>(null)
46
-  const rMonaco = useRef<IMonaco>(null)
47
-
48
-  const handleBeforeMount = useCallback((monaco: Monaco) => {
49
-    if (monacoRef) {
50
-      monacoRef.current = monaco
51
-    }
52
-
53
-    rMonaco.current = monaco
54
-
55
-    // Set the compiler options.
56
-
57
-    monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
58
-      allowJs: true,
59
-      checkJs: true,
60
-      strict: true,
61
-      noLib: true,
62
-      lib: ['es6'],
63
-      target: monaco.languages.typescript.ScriptTarget.ES2016,
64
-      allowNonTsExtensions: true,
65
-    })
66
-
67
-    // Sync the intellisense on load.
68
-
69
-    monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true)
70
-
71
-    // Run both semantic and syntax validation.
72
-
73
-    monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
74
-      noSemanticValidation: false,
75
-      noSyntaxValidation: false,
76
-    })
77
-
78
-    // Add custom types
79
-
80
-    monaco.languages.typescript.typescriptDefaults.addExtraLib(
81
-      typesImport.content
82
-    )
83
-
84
-    // Add es5 library types
85
-
86
-    monaco.languages.typescript.typescriptDefaults.addExtraLib(
87
-      libImport.content
88
-    )
89
-
90
-    // Use prettier as a formatter
91
-
92
-    monaco.languages.registerDocumentFormattingEditProvider('typescript', {
93
-      async provideDocumentFormattingEdits(model) {
94
-        try {
95
-          const text = getFormattedCode(model.getValue())
96
-
97
-          return [
98
-            {
99
-              range: model.getFullModelRange(),
100
-              text,
101
-            },
102
-          ]
103
-        } catch (e) {
104
-          return [
105
-            {
106
-              range: model.getFullModelRange(),
107
-              text: model.getValue(),
108
-            },
109
-          ]
110
-        }
111
-      },
112
-    })
113
-  }, [])
114
-
115
-  const handleMount = useCallback((editor: IMonacoEditor) => {
116
-    if (editorRef) {
117
-      editorRef.current = editor
118
-    }
119
-    rEditor.current = editor
120
-
121
-    editor.updateOptions({
122
-      fontSize,
123
-      fontFamily: "'Recursive Mono', monospace",
124
-      wordBasedSuggestions: false,
125
-      minimap: { enabled: false },
126
-      lightbulb: {
127
-        enabled: false,
128
-      },
129
-      readOnly,
130
-    })
131
-  }, [])
132
-
133
-  const handleChange = useCallback((code: string | undefined) => {
134
-    onChange(code, rEditor.current)
135
-  }, [])
136
-
137
-  const handleKeydown = useCallback(
138
-    (e: React.KeyboardEvent<HTMLDivElement>) => {
139
-      e.stopPropagation()
140
-
141
-      !modifierKeys.includes(e.key) && onKey?.()
142
-
143
-      if ((e.key === 's' || e.key === 'Enter') && metaKey(e)) {
144
-        const editor = rEditor.current
145
-
146
-        if (!editor) return
147
-
148
-        editor
149
-          .getAction('editor.action.formatDocument')
150
-          .run()
151
-          .then(() =>
152
-            onSave(rEditor.current?.getModel().getValue(), rEditor.current)
153
-          )
154
-
155
-        e.preventDefault()
156
-      }
157
-      if (e.key === 'p' && metaKey(e)) {
158
-        e.preventDefault()
159
-      }
160
-
161
-      if (e.key === 'd' && metaKey(e)) {
162
-        e.preventDefault()
163
-      }
164
-    },
165
-    []
166
-  )
167
-
168
-  const handleKeyUp = useCallback(
169
-    (e: React.KeyboardEvent<HTMLDivElement>) => e.stopPropagation(),
170
-    []
171
-  )
172
-
173
-  const rDecorations = useRef<any>([])
174
-
175
-  useEffect(() => {
176
-    const monaco = rMonaco.current
177
-    if (!monaco) return
178
-    const editor = rEditor.current
179
-    if (!editor) return
180
-
181
-    if (!error) {
182
-      rDecorations.current = editor.deltaDecorations(rDecorations.current, [])
183
-      return
184
-    }
185
-
186
-    if (!error.line) return
187
-
188
-    rDecorations.current = editor.deltaDecorations(rDecorations.current, [
189
-      {
190
-        range: new monaco.Range(
191
-          Number(error.line) - 1,
192
-          0,
193
-          Number(error.line) - 1,
194
-          0
195
-        ),
196
-        options: {
197
-          isWholeLine: true,
198
-          className: 'editorLineError',
199
-        },
200
-      },
201
-    ])
202
-  }, [error])
203
-
204
-  useEffect(() => {
205
-    const monaco = rMonaco.current
206
-    if (!monaco) return
207
-    monaco.editor.setTheme(theme === 'dark' ? 'vs-dark' : 'light')
208
-  }, [theme])
209
-
210
-  useEffect(() => {
211
-    const editor = rEditor.current
212
-    if (!editor) return
213
-
214
-    editor.updateOptions({
215
-      fontSize,
216
-    })
217
-  }, [fontSize])
218
-
219
-  return (
220
-    <EditorContainer onKeyDown={handleKeydown} onKeyUp={handleKeyUp}>
221
-      <Editor
222
-        height="100%"
223
-        language="typescript"
224
-        value={value}
225
-        theme={theme === 'dark' ? 'vs-dark' : 'light'}
226
-        beforeMount={handleBeforeMount}
227
-        onMount={handleMount}
228
-        onChange={handleChange}
229
-        defaultPath="index.ts"
230
-      />
231
-    </EditorContainer>
232
-  )
233
-}
234
-
235
-const EditorContainer = styled('div', {
236
-  height: '100%',
237
-  pointerEvents: 'all',
238
-  userSelect: 'all',
239
-
240
-  '& > *': {
241
-    userSelect: 'all',
242
-    pointerEvents: 'all',
243
-  },
244
-
245
-  '.editorLineError': {
246
-    backgroundColor: '$lineError',
247
-  },
248
-})

+ 0
- 235
components/code-panel/code-panel.tsx View File

@@ -1,235 +0,0 @@
1
-/* eslint-disable @typescript-eslint/ban-ts-comment */
2
-import styled from 'styles'
3
-import { useStateDesigner } from '@state-designer/react'
4
-import React, { useCallback, useEffect, useRef } from 'react'
5
-import state, { useSelector } from 'state'
6
-import { CodeError, CodeFile, CodeResult } from 'types'
7
-import CodeDocs from './code-docs'
8
-import { generateFromCode } from 'state/code/generate'
9
-import * as Panel from '../panel'
10
-import { breakpoints, IconButton } from '../shared'
11
-import {
12
-  Cross2Icon,
13
-  CodeIcon,
14
-  PlayIcon,
15
-  ChevronUpIcon,
16
-  ChevronDownIcon,
17
-} from '@radix-ui/react-icons'
18
-import dynamic from 'next/dynamic'
19
-import { ReaderIcon } from '@radix-ui/react-icons'
20
-const CodeEditor = dynamic(() => import('./code-editor'))
21
-
22
-const increaseCodeSize = () => state.send('INCREASED_CODE_FONT_SIZE')
23
-const decreaseCodeSize = () => state.send('DECREASED_CODE_FONT_SIZE')
24
-const toggleCodePanel = () => state.send('TOGGLED_CODE_PANEL_OPEN')
25
-const handleWheel = (e: React.WheelEvent) => e.stopPropagation()
26
-
27
-export default function CodePanel(): JSX.Element {
28
-  const rContainer = useRef<HTMLDivElement>(null)
29
-  const isReadOnly = useSelector((s) => s.data.isReadOnly)
30
-  const fileId = useSelector((s) => s.data.currentCodeFileId)
31
-  const file = useSelector(
32
-    (s) => s.data.document.code[s.data.currentCodeFileId]
33
-  )
34
-  const isOpen = useSelector((s) => s.data.settings.isCodeOpen)
35
-  const fontSize = useSelector((s) => s.data.settings.fontSize)
36
-
37
-  const local = useStateDesigner({
38
-    data: {
39
-      code: file.code,
40
-      error: null as CodeError | null,
41
-    },
42
-    on: {
43
-      MOUNTED: 'setCode',
44
-      CHANGED_FILE: 'loadFile',
45
-    },
46
-    initial: 'editingCode',
47
-    states: {
48
-      editingCode: {
49
-        on: {
50
-          RAN_CODE: { do: 'saveCode', to: 'evaluatingCode' },
51
-          SAVED_CODE: { do: 'saveCode', to: 'evaluatingCode' },
52
-          CHANGED_CODE: { secretlyDo: 'setCode' },
53
-          CLEARED_ERROR: { if: 'hasError', do: 'clearError' },
54
-          TOGGLED_DOCS: { to: 'viewingDocs' },
55
-        },
56
-      },
57
-      evaluatingCode: {
58
-        async: {
59
-          await: 'evalCode',
60
-          onResolve: {
61
-            do: ['clearError', 'sendResultToGlobalState'],
62
-            to: 'editingCode',
63
-          },
64
-          onReject: { do: 'setErrorFromResult', to: 'editingCode' },
65
-        },
66
-      },
67
-      viewingDocs: {
68
-        on: {
69
-          TOGGLED_DOCS: { to: 'editingCode' },
70
-        },
71
-      },
72
-    },
73
-    conditions: {
74
-      hasError(data) {
75
-        return !!data.error
76
-      },
77
-    },
78
-    actions: {
79
-      loadFile(data, payload: { file: CodeFile }) {
80
-        data.code = payload.file.code
81
-      },
82
-      setCode(data, payload: { code: string }) {
83
-        data.code = payload.code
84
-      },
85
-      saveCode(data) {
86
-        const { code } = data
87
-        state.send('SAVED_CODE', { code })
88
-      },
89
-      clearError(data) {
90
-        data.error = null
91
-      },
92
-      setErrorFromResult(data, payload, result: CodeResult) {
93
-        data.error = result.error
94
-      },
95
-      sendResultToGlobalState(data, payload, result: CodeResult) {
96
-        state.send('GENERATED_FROM_CODE', result)
97
-      },
98
-    },
99
-    asyncs: {
100
-      evalCode(data) {
101
-        return new Promise((resolve, reject) => {
102
-          generateFromCode(state.data, data.code).then((result) => {
103
-            if (result.error !== null) {
104
-              reject(result)
105
-            } else {
106
-              resolve(result)
107
-            }
108
-          })
109
-        })
110
-      },
111
-    },
112
-  })
113
-
114
-  useEffect(() => {
115
-    local.send('CHANGED_FILE', { file })
116
-  }, [file])
117
-
118
-  useEffect(() => {
119
-    local.send('MOUNTED', { code: state.data.document.code[fileId].code })
120
-    return () => {
121
-      state.send('CHANGED_CODE', { fileId, code: local.data.code })
122
-    }
123
-  }, [])
124
-
125
-  const handleCodeChange = useCallback(
126
-    (code: string) => local.send('CHANGED_CODE', { code }),
127
-    [local]
128
-  )
129
-
130
-  const handleSave = useCallback(() => local.send('SAVED_CODE'), [local])
131
-
132
-  const handleKey = useCallback(() => local.send('CLEARED_ERROR'), [local])
133
-
134
-  const toggleDocs = useCallback(() => local.send('TOGGLED_DOCS'), [local])
135
-
136
-  const { error } = local.data
137
-
138
-  return (
139
-    <Panel.Root
140
-      dir="ltr"
141
-      bp={breakpoints}
142
-      data-bp-desktop
143
-      ref={rContainer}
144
-      isOpen={isOpen}
145
-      variant="code"
146
-      onWheel={handleWheel}
147
-    >
148
-      {isOpen ? (
149
-        <Panel.Layout>
150
-          <Panel.Header side="left">
151
-            <IconButton bp={breakpoints} size="small" onClick={toggleCodePanel}>
152
-              <Cross2Icon />
153
-            </IconButton>
154
-            <h3>Code</h3>
155
-            <ButtonsGroup>
156
-              <FontSizeButtons>
157
-                <IconButton
158
-                  bp={breakpoints}
159
-                  size="small"
160
-                  disabled={!local.isIn('editingCode')}
161
-                  onClick={increaseCodeSize}
162
-                >
163
-                  <ChevronUpIcon />
164
-                </IconButton>
165
-                <IconButton
166
-                  size="small"
167
-                  disabled={!local.isIn('editingCode')}
168
-                  onClick={decreaseCodeSize}
169
-                >
170
-                  <ChevronDownIcon />
171
-                </IconButton>
172
-              </FontSizeButtons>
173
-              <IconButton bp={breakpoints} size="small" onClick={toggleDocs}>
174
-                <ReaderIcon />
175
-              </IconButton>
176
-              <IconButton
177
-                bp={breakpoints}
178
-                size="small"
179
-                disabled={!local.isIn('editingCode')}
180
-                onClick={handleSave}
181
-              >
182
-                <PlayIcon />
183
-              </IconButton>
184
-            </ButtonsGroup>
185
-          </Panel.Header>
186
-          <Panel.Content>
187
-            <CodeEditor
188
-              fontSize={fontSize}
189
-              readOnly={isReadOnly}
190
-              value={file.code}
191
-              error={error}
192
-              onChange={handleCodeChange}
193
-              onSave={handleSave}
194
-              onKey={handleKey}
195
-            />
196
-            <CodeDocs isHidden={!local.isIn('viewingDocs')} />
197
-          </Panel.Content>
198
-
199
-          {error && <Panel.Footer>{error.message}</Panel.Footer>}
200
-        </Panel.Layout>
201
-      ) : (
202
-        <IconButton bp={breakpoints} size="small" onClick={toggleCodePanel}>
203
-          <CodeIcon />
204
-        </IconButton>
205
-      )}
206
-    </Panel.Root>
207
-  )
208
-}
209
-
210
-const ButtonsGroup = styled('div', {
211
-  gridRow: '1',
212
-  gridColumn: '3',
213
-  display: 'flex',
214
-})
215
-
216
-const FontSizeButtons = styled('div', {
217
-  paddingRight: 4,
218
-  display: 'flex',
219
-  flexDirection: 'column',
220
-
221
-  '& > button': {
222
-    height: '50%',
223
-    '&:nth-of-type(1)': {
224
-      alignItems: 'flex-end',
225
-    },
226
-
227
-    '&:nth-of-type(2)': {
228
-      alignItems: 'flex-start',
229
-    },
230
-
231
-    '& svg': {
232
-      height: 12,
233
-    },
234
-  },
235
-})

+ 0
- 129
components/code-panel/docs-content.ts View File

@@ -1,129 +0,0 @@
1
-/* eslint-disable */
2
-
3
-// HEY! DO NOT MODIFY THIS FILE. THE CONTENTS OF THIS FILE
4
-// ARE AUTO-GENERATED BY A SCRIPT AT: /scripts/docs-gen.js
5
-// ANY CHANGES WILL BE LOST WHEN THE SCRIPT RUNS AGAIN!
6
-
7
-export default {
8
-  name: 'docs-content.ts',
9
-  content: `
10
-Welcome to the documentation for tldraw's code editor. You can use the code editor to create shapes using JavaScript or TypeScript code.
11
-
12
-\`\`\`ts
13
-const rect = new Rectangle({
14
-  point: [100, 100],
15
-  size: [200, 200],
16
-  style: {
17
-    color: ColorStyle.Blue,
18
-  },
19
-})
20
-
21
-rect.x = 300
22
-\`\`\`
23
-
24
-To run your code, press **Command + S**.
25
-
26
-Your new shapes will appear on the canvas. You can interact with code-created shapes just like any other shape: you can move the shape, change its style, delete it, etc.
27
-
28
-Each time you run your code, any existing code-created shapes will be replaced by your new code-created shapes. If you want to keep your code-created shapes, select the shapes that you want to keep, press **Command + D** to duplicate them, and move them off to the side.
29
-
30
-## Shapes
31
-
32
-You can use the code editor to create any of the regular shapes:
33
-
34
-- Draw
35
-- Rectangle
36
-- Ellipse
37
-- Arrow
38
-- Text
39
-
40
-You can also create shapes that can _only_ be created with code:
41
-
42
-- Dot
43
-- Ray
44
-- Line
45
-- Polyline
46
-
47
-Each of these shapes is a \`class\`. To create the shape, use the following syntax:
48
-
49
-\`\`\`ts
50
-const myShape = new Rectangle()
51
-\`\`\`
52
-
53
-You can also create a shape with custom properties like this:
54
-
55
-\`\`\`ts
56
-const myShape = new Rectangle({
57
-  point: [100, 100],
58
-  size: [200, 200],
59
-  style: {
60
-    color: ColorStyle.Blue,
61
-    size: SizeStyle.Large,
62
-    dash: DashStyle.Dotted,
63
-  },
64
-})
65
-\`\`\`
66
-
67
-Once you've created a shape, you can set its properties like this:
68
-
69
-\`\`\`ts
70
-const myShape = new Rectangle()
71
-
72
-myShape.x = 100
73
-myShape.color = ColorStyle.Red
74
-\`\`\`
75
-
76
-You can find more information on each shape class by clicking its name in the list above.
77
-
78
-## Controls
79
-
80
-In addition to shapes, you can also use code to create controls.
81
-
82
-\`\`\`ts
83
-new NumberControl({
84
-  label: 'x',
85
-  value: 0,
86
-})
87
-
88
-const myShape = new Rectangle({
89
-  point: [controls.x, 0],
90
-})
91
-\`\`\`
92
-
93
-Once you've created a control, the app's will display a panel where you can edit the control's value. As you edit the value, your code will run again with the control's new value.
94
-
95
-There are two kinds of controls:
96
-
97
-- NumberControl
98
-- VectorControl
99
-- TextControl
100
-
101
-Each of these controls is a \`class\`. To create the control, use the following syntax:
102
-
103
-\`\`\`ts
104
-const control = new TextControl({
105
-  label: 'myLabel',
106
-  value: 'my value',
107
-})
108
-\`\`\`
109
-
110
-Once you've created a control, you can use its value in your code like this:
111
-
112
-\`\`\`ts
113
-const myShape = new Text({
114
-  text: controls.myLabel,
115
-})
116
-\`\`\`
117
-
118
-You can find more information on each control class by clicking its name in the list above.
119
-
120
-## Shape Classes
121
-
122
-...
123
-
124
-## Control Classes
125
-
126
-...
127
-
128
-`,
129
-}

+ 0
- 5459
components/code-panel/es5-lib.ts
File diff suppressed because it is too large
View File


+ 0
- 3469
components/code-panel/types-import.ts
File diff suppressed because it is too large
View File


+ 0
- 165
components/controls-panel/control.tsx View File

@@ -1,165 +0,0 @@
1
-import state, { useSelector } from 'state'
2
-import styled from 'styles'
3
-import {
4
-  ControlType,
5
-  NumberCodeControl,
6
-  TextCodeControl,
7
-  VectorCodeControl,
8
-} from 'types'
9
-
10
-export default function Control({ id }: { id: string }): JSX.Element {
11
-  const control = useSelector((s) => s.data.codeControls[id])
12
-
13
-  if (!control) return null
14
-
15
-  return (
16
-    <>
17
-      <label>{control.label}</label>
18
-      {(() => {
19
-        switch (control.type) {
20
-          case ControlType.Number:
21
-            return <NumberControl {...control} />
22
-          case ControlType.Vector:
23
-            return <VectorControl {...control} />
24
-          case ControlType.Text:
25
-            return <TextControl {...control} />
26
-        }
27
-      })()}
28
-    </>
29
-  )
30
-}
31
-
32
-function NumberControl({ id, min, max, step, value }: NumberCodeControl) {
33
-  return (
34
-    <Inputs>
35
-      <input
36
-        type="range"
37
-        min={min}
38
-        max={max}
39
-        step={step}
40
-        value={value}
41
-        onChange={(e) =>
42
-          state.send('CHANGED_CODE_CONTROL', {
43
-            [id]: Number(e.currentTarget.value),
44
-          })
45
-        }
46
-      />
47
-      <input
48
-        type="number"
49
-        min={min}
50
-        max={max}
51
-        step={step}
52
-        value={value}
53
-        onChange={(e) =>
54
-          state.send('CHANGED_CODE_CONTROL', {
55
-            [id]: Number(e.currentTarget.value),
56
-          })
57
-        }
58
-      />
59
-    </Inputs>
60
-  )
61
-}
62
-
63
-function VectorControl({
64
-  id,
65
-  value,
66
-  min = -Infinity,
67
-  max = Infinity,
68
-  step = 0.01,
69
-  isNormalized = false,
70
-}: VectorCodeControl) {
71
-  return (
72
-    <Inputs>
73
-      <input
74
-        type="range"
75
-        min={isNormalized ? -1 : min}
76
-        max={isNormalized ? 1 : max}
77
-        step={step}
78
-        value={value[0]}
79
-        onChange={(e) =>
80
-          state.send('CHANGED_CODE_CONTROL', {
81
-            [id]: [Number(e.currentTarget.value), value[1]],
82
-          })
83
-        }
84
-      />
85
-      <input
86
-        type="number"
87
-        min={isNormalized ? -1 : min}
88
-        max={isNormalized ? 1 : max}
89
-        step={step}
90
-        value={value[0]}
91
-        onChange={(e) =>
92
-          state.send('CHANGED_CODE_CONTROL', {
93
-            [id]: [Number(e.currentTarget.value), value[1]],
94
-          })
95
-        }
96
-      />
97
-      <input
98
-        type="range"
99
-        min={isNormalized ? -1 : min}
100
-        max={isNormalized ? 1 : max}
101
-        step={step}
102
-        value={value[1]}
103
-        onChange={(e) =>
104
-          state.send('CHANGED_CODE_CONTROL', {
105
-            [id]: [value[0], Number(e.currentTarget.value)],
106
-          })
107
-        }
108
-      />
109
-      <input
110
-        type="number"
111
-        min={isNormalized ? -1 : min}
112
-        max={isNormalized ? 1 : max}
113
-        step={step}
114
-        value={value[1]}
115
-        onChange={(e) =>
116
-          state.send('CHANGED_CODE_CONTROL', {
117
-            [id]: [value[0], Number(e.currentTarget.value)],
118
-          })
119
-        }
120
-      />
121
-    </Inputs>
122
-  )
123
-}
124
-
125
-function TextControl({ id, value }: TextCodeControl) {
126
-  return (
127
-    <Inputs>
128
-      <input
129
-        type="text"
130
-        value={value}
131
-        onChange={(e) =>
132
-          state.send('CHANGED_CODE_CONTROL', {
133
-            [id]: e.currentTarget.value,
134
-          })
135
-        }
136
-      />
137
-    </Inputs>
138
-  )
139
-}
140
-
141
-const Inputs = styled('div', {
142
-  display: 'flex',
143
-  gap: '8px',
144
-  height: '100%',
145
-
146
-  '& input': {
147
-    font: '$ui',
148
-    width: '64px',
149
-    fontSize: '$1',
150
-    border: '1px solid $inputBorder',
151
-    backgroundColor: '$input',
152
-    color: '$text',
153
-    height: '100%',
154
-    padding: '0px 6px',
155
-  },
156
-  "& input[type='range']": {
157
-    padding: 0,
158
-    flexGrow: 2,
159
-  },
160
-  "& input[type='text']": {
161
-    minWidth: 200,
162
-    padding: 4,
163
-    flexGrow: 2,
164
-  },
165
-})

+ 0
- 76
components/controls-panel/controls-panel.tsx View File

@@ -1,76 +0,0 @@
1
-/* eslint-disable @typescript-eslint/ban-ts-comment */
2
-import styled from 'styles'
3
-import React, { useRef } from 'react'
4
-import state, { useSelector } from 'state'
5
-import { X } from 'react-feather'
6
-import { breakpoints, IconButton } from 'components/shared'
7
-import * as Panel from '../panel'
8
-import Control from './control'
9
-import { deepCompareArrays } from 'utils'
10
-
11
-function handleClose() {
12
-  state.send('CLOSED_CONTROLS')
13
-}
14
-
15
-const stopKeyboardPropagation = (e: KeyboardEvent | React.KeyboardEvent) =>
16
-  e.stopPropagation()
17
-
18
-export default function ControlPanel(): JSX.Element {
19
-  const rContainer = useRef<HTMLDivElement>(null)
20
-  const isOpen = useSelector((s) => Object.keys(s.data.codeControls).length > 0)
21
-
22
-  const codeControls = useSelector(
23
-    (state) => Object.keys(state.data.codeControls),
24
-    deepCompareArrays
25
-  )
26
-
27
-  if (codeControls.length === 0) {
28
-    return null
29
-  }
30
-
31
-  return (
32
-    <Panel.Root
33
-      ref={rContainer}
34
-      dir="ltr"
35
-      data-bp-desktop
36
-      variant="controls"
37
-      isOpen={isOpen}
38
-      onKeyDown={stopKeyboardPropagation}
39
-      onKeyUp={stopKeyboardPropagation}
40
-    >
41
-      <Panel.Layout>
42
-        <Panel.Header>
43
-          <IconButton bp={breakpoints} size="small" onClick={handleClose}>
44
-            <X />
45
-          </IconButton>
46
-          <h3>Controls</h3>
47
-        </Panel.Header>
48
-        <ControlsList>
49
-          {codeControls.map((id) => (
50
-            <Control key={id} id={id} />
51
-          ))}
52
-        </ControlsList>
53
-      </Panel.Layout>
54
-    </Panel.Root>
55
-  )
56
-}
57
-
58
-const ControlsList = styled(Panel.Content, {
59
-  padding: 12,
60
-  display: 'grid',
61
-  gridTemplateColumns: '1fr 4fr',
62
-  gridAutoRows: '24px',
63
-  alignItems: 'center',
64
-  gridColumnGap: '8px',
65
-  gridRowGap: '8px',
66
-
67
-  '& input': {
68
-    font: '$ui',
69
-    fontSize: '$1',
70
-    border: '1px solid $inputBorder',
71
-    backgroundColor: '$input',
72
-    color: '$text',
73
-    height: '100%',
74
-    padding: '0px 6px',
75
-  },
76
-})

+ 0
- 224
components/debug-panel/debug-panel.tsx View File

@@ -1,224 +0,0 @@
1
-/* eslint-disable @typescript-eslint/ban-ts-comment */
2
-import styled from 'styles'
3
-import React, { useRef } from 'react'
4
-import state, { useSelector } from 'state'
5
-import * as Panel from 'components/panel'
6
-import {
7
-  breakpoints,
8
-  IconButton,
9
-  RowButton,
10
-  IconWrapper,
11
-} from 'components/shared'
12
-import {
13
-  Cross2Icon,
14
-  PlayIcon,
15
-  DotIcon,
16
-  CrumpledPaperIcon,
17
-  StopIcon,
18
-  ClipboardIcon,
19
-  ClipboardCopyIcon,
20
-  TrashIcon,
21
-} from '@radix-ui/react-icons'
22
-import logger from 'state/logger'
23
-import { useStateDesigner } from '@state-designer/react'
24
-
25
-const stopPropagation = (e: React.KeyboardEvent) => e.stopPropagation()
26
-const toggleDebugPanel = () => state.send('TOGGLED_DEBUG_PANEL')
27
-const handleStateCopy = () => state.send('COPIED_STATE_TO_CLIPBOARD')
28
-const handleError = () => {
29
-  throw Error('Error!')
30
-}
31
-
32
-export default function CodePanel(): JSX.Element {
33
-  const rContainer = useRef<HTMLDivElement>(null)
34
-  const isDebugging = useSelector((s) => s.data.settings.isDebugMode)
35
-  const isOpen = useSelector((s) => s.data.settings.isDebugOpen)
36
-
37
-  const rTextArea = useRef<HTMLTextAreaElement>(null)
38
-
39
-  const local = useStateDesigner({
40
-    initial: 'stopped',
41
-    data: {
42
-      log: '',
43
-    },
44
-    states: {
45
-      stopped: {
46
-        on: {
47
-          CHANGED_LOG: 'setLog',
48
-          COPIED_LOG: { if: 'hasLog', do: 'copyLog' },
49
-          PLAYED_BACK_LOG: { if: 'hasLog', do: 'playbackLog' },
50
-          STARTED_LOGGING: { do: 'startLogger', to: 'logging' },
51
-        },
52
-      },
53
-      logging: {
54
-        on: {
55
-          STOPPED_LOGGING: { do: 'stopLogger', to: 'stopped' },
56
-        },
57
-      },
58
-    },
59
-    conditions: {
60
-      hasLog(data) {
61
-        return data.log !== ''
62
-      },
63
-    },
64
-    actions: {
65
-      setLog(data, payload: { value: string }) {
66
-        data.log = payload.value
67
-      },
68
-      startLogger(data) {
69
-        logger.start(state.data)
70
-        data.log = ''
71
-      },
72
-      stopLogger(data) {
73
-        logger.stop(state.data)
74
-        data.log = logger.copyToJson()
75
-      },
76
-      playbackLog(data) {
77
-        logger.playback(state.data, data.log)
78
-      },
79
-      copyLog() {
80
-        logger.copyToJson()
81
-      },
82
-    },
83
-  })
84
-
85
-  if (!isDebugging) return null
86
-
87
-  const handleLoggingStop = () => local.send('STOPPED_LOGGING')
88
-
89
-  const handlePlayback = () =>
90
-    local.send('PLAYED_BACK_LOG', { log: rTextArea.current?.value })
91
-
92
-  const handleLoggingStart = () => local.send('STARTED_LOGGING')
93
-
94
-  const handleLoggingCopy = () => local.send('COPIED_DEBUG_LOG')
95
-
96
-  return (
97
-    <StylePanelRoot
98
-      dir="ltr"
99
-      bp={breakpoints}
100
-      data-bp-desktop
101
-      ref={rContainer}
102
-      variant="code"
103
-      onWheel={(e) => e.stopPropagation()}
104
-    >
105
-      {isOpen ? (
106
-        <Panel.Layout onKeyDown={stopPropagation}>
107
-          <Panel.Header side="left">
108
-            <IconButton
109
-              bp={breakpoints}
110
-              size="small"
111
-              onClick={toggleDebugPanel}
112
-            >
113
-              <Cross2Icon />
114
-            </IconButton>
115
-            <span>Debugging</span>
116
-            <div />
117
-          </Panel.Header>
118
-          <Panel.Content>
119
-            <hr />
120
-            <RowButton bp={breakpoints} onClick={handleStateCopy}>
121
-              <span>Copy State</span>
122
-              <IconWrapper size="small">
123
-                <ClipboardCopyIcon />
124
-              </IconWrapper>
125
-            </RowButton>
126
-            <RowButton bp={breakpoints} onClick={handleError}>
127
-              <span>Create Error</span>
128
-              <IconWrapper size="small">
129
-                <TrashIcon />
130
-              </IconWrapper>
131
-            </RowButton>
132
-            <hr />
133
-            {local.isIn('stopped') ? (
134
-              <RowButton bp={breakpoints} onClick={handleLoggingStart}>
135
-                <span>Start Logger</span>
136
-                <IconWrapper size="small">
137
-                  <DotIcon />
138
-                </IconWrapper>
139
-              </RowButton>
140
-            ) : (
141
-              <RowButton bp={breakpoints} onClick={handleLoggingStop}>
142
-                <span>Stop Logger</span>
143
-                <IconWrapper size="small">
144
-                  <StopIcon />
145
-                </IconWrapper>
146
-              </RowButton>
147
-            )}
148
-            <JSONTextAreaWrapper>
149
-              <IconButton
150
-                bp={breakpoints}
151
-                onClick={handleLoggingCopy}
152
-                disabled={!local.can('COPIED_LOG')}
153
-                style={{ position: 'absolute', top: 2, right: 2 }}
154
-              >
155
-                <ClipboardIcon />
156
-              </IconButton>
157
-              <JSONTextArea
158
-                ref={rTextArea}
159
-                value={local.data.log}
160
-                onChange={(e) =>
161
-                  local.send('CHANGED_LOG', { value: e.currentTarget.value })
162
-                }
163
-              />
164
-            </JSONTextAreaWrapper>
165
-            <RowButton
166
-              bp={breakpoints}
167
-              onClick={handlePlayback}
168
-              disabled={!local.can('PLAYED_BACK_LOG')}
169
-            >
170
-              <span>Play Back Log</span>
171
-              <IconWrapper size="small">
172
-                <PlayIcon />
173
-              </IconWrapper>
174
-            </RowButton>
175
-          </Panel.Content>
176
-        </Panel.Layout>
177
-      ) : (
178
-        <IconButton bp={breakpoints} size="small" onClick={toggleDebugPanel}>
179
-          <CrumpledPaperIcon />
180
-        </IconButton>
181
-      )}
182
-    </StylePanelRoot>
183
-  )
184
-}
185
-
186
-const StylePanelRoot = styled(Panel.Root, {
187
-  width: 'fit-content',
188
-  maxWidth: 'fit-content',
189
-  overflow: 'hidden',
190
-  position: 'relative',
191
-  border: '1px solid $panel',
192
-  boxShadow: '$4',
193
-  display: 'flex',
194
-  flexDirection: 'column',
195
-  alignItems: 'center',
196
-  pointerEvents: 'all',
197
-  padding: '$0',
198
-
199
-  '& hr': {
200
-    marginTop: 2,
201
-    marginBottom: 2,
202
-    marginLeft: '-$0',
203
-    border: 'none',
204
-    height: 1,
205
-    backgroundColor: '$brushFill',
206
-    width: 'calc(100% + 4px)',
207
-  },
208
-})
209
-
210
-const JSONTextAreaWrapper = styled('div', {
211
-  position: 'relative',
212
-  margin: '4px 0',
213
-})
214
-
215
-const JSONTextArea = styled('textarea', {
216
-  minHeight: '100px',
217
-  width: '100%',
218
-  font: '$mono',
219
-  backgroundColor: '$panel',
220
-  border: '1px solid $border',
221
-  borderRadius: '4px',
222
-  padding: '4px',
223
-  outline: 'none',
224
-})

+ 0
- 66
components/editor.tsx View File

@@ -1,66 +0,0 @@
1
-import useKeyboardEvents from 'hooks/useKeyboardEvents'
2
-import useLoadOnMount from 'hooks/useLoadOnMount'
3
-import useStateTheme from 'hooks/useStateTheme'
4
-import Menu from './menu/menu'
5
-import Canvas from './canvas/canvas'
6
-import ToolsPanel from './tools-panel/tools-panel'
7
-import StylePanel from './style-panel/style-panel'
8
-import styled from 'styles'
9
-import PagePanel from './page-panel/page-panel'
10
-import CodePanel from './code-panel/code-panel'
11
-import DebugPanel from './debug-panel/debug-panel'
12
-import ControlsPanel from './controls-panel/controls-panel'
13
-
14
-export default function Editor({ roomId }: { roomId?: string }): JSX.Element {
15
-  useLoadOnMount(roomId)
16
-  useStateTheme()
17
-  useKeyboardEvents()
18
-
19
-  return (
20
-    <Layout>
21
-      <MenuButtons>
22
-        <Menu />
23
-        <DebugPanel />
24
-        <CodePanel />
25
-        <PagePanel />
26
-      </MenuButtons>
27
-      <ControlsPanel />
28
-      <Spacer />
29
-      <StylePanel />
30
-      <Canvas />
31
-      <ToolsPanel />
32
-    </Layout>
33
-  )
34
-}
35
-
36
-const Spacer = styled('div', {
37
-  flexGrow: 2,
38
-})
39
-
40
-const MenuButtons = styled('div', {
41
-  display: 'flex',
42
-  gap: 8,
43
-})
44
-
45
-const Layout = styled('main', {
46
-  position: 'fixed',
47
-  overflow: 'hidden',
48
-  top: 0,
49
-  left: 0,
50
-  bottom: 0,
51
-  right: 0,
52
-  height: '100%',
53
-  width: '100%',
54
-  padding: '8px 8px 0 8px',
55
-  zIndex: 200,
56
-  display: 'flex',
57
-  alignItems: 'flex-start',
58
-  justifyContent: 'flex-start',
59
-  boxSizing: 'border-box',
60
-  outline: 'none',
61
-
62
-  pointerEvents: 'none',
63
-  '& > *': {
64
-    PointerEvent: 'all',
65
-  },
66
-})

+ 0
- 123
components/menu/menu.tsx View File

@@ -1,123 +0,0 @@
1
-import * as React from 'react'
2
-import { ExitIcon, HamburgerMenuIcon } from '@radix-ui/react-icons'
3
-import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
4
-import { memo } from 'react'
5
-import {
6
-  FloatingContainer,
7
-  DropdownMenuRoot,
8
-  MenuContent,
9
-  IconButton,
10
-  breakpoints,
11
-  DropdownMenuButton,
12
-  DropdownMenuSubMenu,
13
-  DropdownMenuDivider,
14
-  DropdownMenuCheckboxItem,
15
-  IconWrapper,
16
-  Kbd,
17
-} from '../shared'
18
-import state, { useSelector } from 'state'
19
-import { commandKey } from 'utils'
20
-import { signOut } from 'next-auth/client'
21
-import { useTheme } from 'next-themes'
22
-
23
-const handleNew = () => state.send('CREATED_NEW_PROJECT')
24
-const handleSave = () => state.send('SAVED')
25
-const handleLoad = () => state.send('LOADED_FROM_FILE_STSTEM')
26
-const toggleDebugMode = () => state.send('TOGGLED_DEBUG_MODE')
27
-
28
-function Menu() {
29
-  return (
30
-    <FloatingContainer>
31
-      <DropdownMenuRoot>
32
-        <DropdownMenu.Trigger as={IconButton} bp={breakpoints}>
33
-          <HamburgerMenuIcon />
34
-        </DropdownMenu.Trigger>
35
-        <DropdownMenu.Content as={MenuContent} sideOffset={8} align="end">
36
-          <DropdownMenuButton onSelect={handleNew} disabled={true}>
37
-            <span>New Project</span>
38
-            <Kbd>
39
-              <span>{commandKey()}</span>
40
-              <span>N</span>
41
-            </Kbd>
42
-          </DropdownMenuButton>
43
-          <DropdownMenuDivider />
44
-          <DropdownMenuButton onSelect={handleLoad}>
45
-            <span>Open...</span>
46
-            <Kbd>
47
-              <span>{commandKey()}</span>
48
-              <span>L</span>
49
-            </Kbd>
50
-          </DropdownMenuButton>
51
-          <RecentFiles />
52
-          <DropdownMenuDivider />
53
-          <DropdownMenuButton onSelect={handleSave}>
54
-            <span>Save</span>
55
-            <Kbd>
56
-              <span>{commandKey()}</span>
57
-              <span>S</span>
58
-            </Kbd>
59
-          </DropdownMenuButton>
60
-          <DropdownMenuButton onSelect={handleSave}>
61
-            <span>Save As...</span>
62
-            <Kbd>
63
-              <span>⇧</span>
64
-              <span>{commandKey()}</span>
65
-              <span>S</span>
66
-            </Kbd>
67
-          </DropdownMenuButton>
68
-          <DropdownMenuDivider />
69
-          <Preferences />
70
-          <DropdownMenuDivider />
71
-          <DropdownMenuButton onSelect={signOut}>
72
-            <span>Sign Out</span>
73
-            <IconWrapper size="small">
74
-              <ExitIcon />
75
-            </IconWrapper>
76
-          </DropdownMenuButton>
77
-        </DropdownMenu.Content>
78
-      </DropdownMenuRoot>
79
-    </FloatingContainer>
80
-  )
81
-}
82
-
83
-export default memo(Menu)
84
-
85
-function RecentFiles() {
86
-  return (
87
-    <DropdownMenuSubMenu label="Open Recent..." disabled={true}>
88
-      <DropdownMenuButton>
89
-        <span>Project A</span>
90
-      </DropdownMenuButton>
91
-      <DropdownMenuButton>
92
-        <span>Project B</span>
93
-      </DropdownMenuButton>
94
-      <DropdownMenuButton>
95
-        <span>Project C</span>
96
-      </DropdownMenuButton>
97
-    </DropdownMenuSubMenu>
98
-  )
99
-}
100
-
101
-function Preferences() {
102
-  const { theme, setTheme } = useTheme()
103
-
104
-  const isDebugMode = useSelector((s) => s.data.settings.isDebugMode)
105
-  const isDarkMode = theme === 'dark'
106
-
107
-  return (
108
-    <DropdownMenuSubMenu label="Preferences">
109
-      <DropdownMenuCheckboxItem
110
-        checked={isDarkMode}
111
-        onCheckedChange={() => setTheme(isDarkMode ? 'light' : 'dark')}
112
-      >
113
-        <span>Dark Mode</span>
114
-      </DropdownMenuCheckboxItem>
115
-      <DropdownMenuCheckboxItem
116
-        checked={isDebugMode}
117
-        onCheckedChange={toggleDebugMode}
118
-      >
119
-        <span>Debug Mode</span>
120
-      </DropdownMenuCheckboxItem>
121
-    </DropdownMenuSubMenu>
122
-  )
123
-}

+ 0
- 135
components/page-panel/page-options.tsx View File

@@ -1,135 +0,0 @@
1
-import * as Dialog from '@radix-ui/react-alert-dialog'
2
-import { MixerVerticalIcon } from '@radix-ui/react-icons'
3
-import {
4
-  breakpoints,
5
-  IconButton,
6
-  DialogOverlay,
7
-  DialogContent,
8
-  RowButton,
9
-  MenuTextInput,
10
-  DialogInputWrapper,
11
-  Divider,
12
-} from 'components/shared'
13
-import { useEffect, useRef, useState } from 'react'
14
-import state, { useSelector } from 'state'
15
-import { Page } from 'types'
16
-
17
-export default function PageOptions({ page }: { page: Page }): JSX.Element {
18
-  const [isOpen, setIsOpen] = useState(false)
19
-
20
-  const rInput = useRef<HTMLInputElement>(null)
21
-
22
-  const hasOnlyOnePage = useSelector(
23
-    (s) => Object.keys(s.data.document.pages).length <= 1
24
-  )
25
-
26
-  const [name, setName] = useState(page.name)
27
-
28
-  function handleNameChange(e: React.ChangeEvent<HTMLInputElement>) {
29
-    setName(e.currentTarget.value)
30
-  }
31
-
32
-  function handleDuplicate() {
33
-    state.send('DUPLICATED_PAGE', { id: page.id })
34
-  }
35
-
36
-  function handleDelete() {
37
-    state.send('DELETED_PAGE', { id: page.id })
38
-  }
39
-
40
-  function handleOpenChange(isOpen: boolean) {
41
-    setIsOpen(isOpen)
42
-
43
-    if (isOpen) {
44
-      return
45
-    }
46
-
47
-    if (page.name.length === 0) {
48
-      state.send('RENAMED_PAGE', {
49
-        id: page.id,
50
-        name: 'Page',
51
-      })
52
-    }
53
-
54
-    state.send('SAVED_PAGE_RENAME', { id: page.id })
55
-  }
56
-
57
-  function handleSave() {
58
-    state.send('RENAMED_PAGE', {
59
-      id: page.id,
60
-      name,
61
-    })
62
-  }
63
-
64
-  function stopPropagation(e: React.KeyboardEvent<HTMLDivElement>) {
65
-    e.stopPropagation()
66
-  }
67
-
68
-  function handleKeydown(e: React.KeyboardEvent<HTMLDivElement>) {
69
-    if (e.key === 'Enter') {
70
-      handleSave()
71
-      setIsOpen(false)
72
-    }
73
-  }
74
-
75
-  useEffect(() => {
76
-    if (isOpen) {
77
-      setTimeout(() => {
78
-        rInput.current?.focus()
79
-        rInput.current?.select()
80
-      }, 0)
81
-    }
82
-  }, [isOpen])
83
-
84
-  return (
85
-    <Dialog.Root open={isOpen} onOpenChange={handleOpenChange}>
86
-      <Dialog.Trigger
87
-        as={IconButton}
88
-        bp={breakpoints}
89
-        size="small"
90
-        data-shy="true"
91
-      >
92
-        <MixerVerticalIcon />
93
-      </Dialog.Trigger>
94
-      <Dialog.Overlay as={DialogOverlay} />
95
-      <Dialog.Content
96
-        as={DialogContent}
97
-        onKeyDown={stopPropagation}
98
-        onKeyUp={stopPropagation}
99
-      >
100
-        <DialogInputWrapper>
101
-          <MenuTextInput
102
-            ref={rInput}
103
-            value={name}
104
-            onChange={handleNameChange}
105
-            onKeyDown={handleKeydown}
106
-          />
107
-        </DialogInputWrapper>
108
-        <Divider />
109
-        <Dialog.Action
110
-          as={RowButton}
111
-          bp={breakpoints}
112
-          onClick={handleDuplicate}
113
-        >
114
-          Duplicate
115
-        </Dialog.Action>
116
-        <Dialog.Action
117
-          as={RowButton}
118
-          bp={breakpoints}
119
-          disabled={hasOnlyOnePage}
120
-          onClick={handleDelete}
121
-          warn={true}
122
-        >
123
-          Delete
124
-        </Dialog.Action>
125
-        <Divider />
126
-        <Dialog.Action as={RowButton} bp={breakpoints} onClick={handleSave}>
127
-          Save
128
-        </Dialog.Action>
129
-        <Dialog.Cancel as={RowButton} bp={breakpoints}>
130
-          Cancel
131
-        </Dialog.Cancel>
132
-      </Dialog.Content>
133
-    </Dialog.Root>
134
-  )
135
-}

+ 0
- 106
components/page-panel/page-panel.tsx View File

@@ -1,106 +0,0 @@
1
-import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
2
-import styled from 'styles'
3
-import {
4
-  breakpoints,
5
-  DropdownMenuButton,
6
-  DropdownMenuDivider,
7
-  RowButton,
8
-  MenuContent,
9
-  FloatingContainer,
10
-  IconWrapper,
11
-} from 'components/shared'
12
-import PageOptions from './page-options'
13
-import { PlusIcon, CheckIcon } from '@radix-ui/react-icons'
14
-import state, { useSelector } from 'state'
15
-import { useEffect, useRef, useState } from 'react'
16
-
17
-function handleCreatePage() {
18
-  state.send('CREATED_PAGE')
19
-}
20
-
21
-export default function PagePanel(): JSX.Element {
22
-  const rIsOpen = useRef(false)
23
-  const [isOpen, setIsOpen] = useState(false)
24
-
25
-  useEffect(() => {
26
-    if (rIsOpen.current !== isOpen) {
27
-      rIsOpen.current = isOpen
28
-    }
29
-  }, [isOpen])
30
-
31
-  const documentPages = useSelector((s) => s.data.document.pages)
32
-  const currentPageId = useSelector((s) => s.data.currentPageId)
33
-
34
-  if (!documentPages[currentPageId]) return null
35
-
36
-  const sorted = Object.values(documentPages).sort(
37
-    (a, b) => a.childIndex - b.childIndex
38
-  )
39
-
40
-  return (
41
-    <DropdownMenu.Root
42
-      dir="ltr"
43
-      open={isOpen}
44
-      onOpenChange={(isOpen) => {
45
-        if (rIsOpen.current !== isOpen) {
46
-          setIsOpen(isOpen)
47
-        }
48
-      }}
49
-    >
50
-      <FloatingContainer>
51
-        <RowButton as={DropdownMenu.Trigger} bp={breakpoints} variant="noIcon">
52
-          <span>{documentPages[currentPageId].name}</span>
53
-        </RowButton>
54
-      </FloatingContainer>
55
-      <MenuContent as={DropdownMenu.Content} sideOffset={8} align="start">
56
-        <DropdownMenu.RadioGroup
57
-          value={currentPageId}
58
-          onValueChange={(id) => {
59
-            setIsOpen(false)
60
-            state.send('CHANGED_PAGE', { id })
61
-          }}
62
-        >
63
-          {sorted.map((page) => (
64
-            <ButtonWithOptions key={page.id}>
65
-              <DropdownMenu.RadioItem
66
-                as={RowButton}
67
-                bp={breakpoints}
68
-                value={page.id}
69
-                variant="pageButton"
70
-              >
71
-                <span>{page.name}</span>
72
-                <DropdownMenu.ItemIndicator>
73
-                  <IconWrapper size="small">
74
-                    <CheckIcon />
75
-                  </IconWrapper>
76
-                </DropdownMenu.ItemIndicator>
77
-              </DropdownMenu.RadioItem>
78
-              <PageOptions page={page} />
79
-            </ButtonWithOptions>
80
-          ))}
81
-        </DropdownMenu.RadioGroup>
82
-        <DropdownMenuDivider />
83
-        <DropdownMenuButton onSelect={handleCreatePage}>
84
-          <span>Create Page</span>
85
-          <IconWrapper size="small">
86
-            <PlusIcon />
87
-          </IconWrapper>
88
-        </DropdownMenuButton>
89
-      </MenuContent>
90
-    </DropdownMenu.Root>
91
-  )
92
-}
93
-
94
-const ButtonWithOptions = styled('div', {
95
-  display: 'grid',
96
-  gridTemplateColumns: '1fr auto',
97
-  gridAutoFlow: 'column',
98
-
99
-  '& > *[data-shy="true"]': {
100
-    opacity: 0,
101
-  },
102
-
103
-  '&:hover > *[data-shy="true"]': {
104
-    opacity: 1,
105
-  },
106
-})

+ 0
- 136
components/panel.tsx View File

@@ -1,136 +0,0 @@
1
-import styled from 'styles'
2
-
3
-export const Root = styled('div', {
4
-  position: 'relative',
5
-  backgroundColor: '$panel',
6
-  borderRadius: '4px',
7
-  overflow: 'hidden',
8
-  pointerEvents: 'all',
9
-  userSelect: 'none',
10
-  zIndex: 200,
11
-  border: '1px solid $panel',
12
-  boxShadow: '$4',
13
-  font: '$ui',
14
-
15
-  variants: {
16
-    bp: {
17
-      mobile: {},
18
-      small: {},
19
-    },
20
-    variant: {
21
-      code: {},
22
-      controls: {
23
-        position: 'absolute',
24
-        right: 156,
25
-      },
26
-    },
27
-    isOpen: {
28
-      true: {},
29
-      false: {
30
-        padding: '$0',
31
-        height: 38,
32
-        width: 38,
33
-      },
34
-    },
35
-  },
36
-  compoundVariants: [
37
-    {
38
-      isOpen: true,
39
-      variant: 'code',
40
-      css: {
41
-        position: 'absolute',
42
-        top: 8,
43
-        left: 8,
44
-        right: 8,
45
-        bottom: 48,
46
-        maxWidth: 680,
47
-        zIndex: 1000,
48
-      },
49
-    },
50
-    {
51
-      isOpen: true,
52
-      variant: 'code',
53
-      bp: 'small',
54
-      css: {
55
-        position: 'absolute',
56
-        top: 8,
57
-        left: 8,
58
-        right: 8,
59
-        bottom: 128,
60
-        maxWidth: 720,
61
-        zIndex: 1000,
62
-      },
63
-    },
64
-  ],
65
-})
66
-
67
-export const Layout = styled('div', {
68
-  display: 'grid',
69
-  gridTemplateColumns: '1fr',
70
-  gridTemplateRows: 'auto 1fr',
71
-  gridAutoRows: '28px',
72
-  height: '100%',
73
-  width: 'auto',
74
-  minWidth: '100%',
75
-  maxWidth: 560,
76
-  overflow: 'hidden',
77
-  userSelect: 'none',
78
-  pointerEvents: 'all',
79
-})
80
-
81
-export const Header = styled('div', {
82
-  pointerEvents: 'all',
83
-  display: 'flex',
84
-  width: '100%',
85
-  alignItems: 'center',
86
-  justifyContent: 'space-between',
87
-  padding: '$0',
88
-  position: 'relative',
89
-
90
-  '& h3': {
91
-    position: 'absolute',
92
-    top: 0,
93
-    left: 0,
94
-    width: '100%',
95
-    height: '100%',
96
-    textAlign: 'center',
97
-    padding: 0,
98
-    margin: 0,
99
-    display: 'flex',
100
-    justifyContent: 'center',
101
-    alignItems: 'center',
102
-    fontSize: '13px',
103
-    pointerEvents: 'none',
104
-    userSelect: 'none',
105
-  },
106
-
107
-  variants: {
108
-    side: {
109
-      left: {
110
-        flexDirection: 'row',
111
-      },
112
-      right: {
113
-        flexDirection: 'row-reverse',
114
-      },
115
-    },
116
-  },
117
-})
118
-
119
-export const ButtonsGroup = styled('div', {
120
-  display: 'flex',
121
-})
122
-
123
-export const Content = styled('div', {
124
-  position: 'relative',
125
-  pointerEvents: 'all',
126
-  overflowY: 'scroll',
127
-})
128
-
129
-export const Footer = styled('div', {
130
-  overflowX: 'scroll',
131
-  color: '$text',
132
-  font: '$debug',
133
-  padding: '0 12px',
134
-  display: 'flex',
135
-  alignItems: 'center',
136
-})

+ 0
- 60
components/status-bar.tsx View File

@@ -1,60 +0,0 @@
1
-import { useStateDesigner } from '@state-designer/react'
2
-import state from 'state'
3
-import styled from 'styles'
4
-
5
-const size: any = { '@sm': 'small' }
6
-
7
-export default function StatusBar(): JSX.Element {
8
-  const local = useStateDesigner(state)
9
-
10
-  const shapesInView = state.values.shapesToRender.length
11
-
12
-  const active = local.active
13
-    .slice(1)
14
-    .map((s) => {
15
-      const states = s.split('.')
16
-      return states[states.length - 1]
17
-    })
18
-    .join(' | ')
19
-
20
-  const log = local.log[0]
21
-
22
-  if (process.env.NODE_ENV !== 'development') return null
23
-
24
-  return (
25
-    <StatusBarContainer size={size}>
26
-      <Section>
27
-        {active} - {log}
28
-      </Section>
29
-      <Section>{shapesInView || '0'} Shapes</Section>
30
-    </StatusBarContainer>
31
-  )
32
-}
33
-
34
-const StatusBarContainer = styled('div', {
35
-  height: 40,
36
-  userSelect: 'none',
37
-  borderTop: '1px solid $border',
38
-  gridArea: 'status',
39
-  display: 'flex',
40
-  color: '$text',
41
-  justifyContent: 'space-between',
42
-  alignItems: 'center',
43
-  backgroundColor: '$panel',
44
-  gap: 8,
45
-  fontSize: '$0',
46
-  padding: '0 16px',
47
-
48
-  variants: {
49
-    size: {
50
-      small: {
51
-        fontSize: '$1',
52
-      },
53
-    },
54
-  },
55
-})
56
-
57
-const Section = styled('div', {
58
-  whiteSpace: 'nowrap',
59
-  overflow: 'hidden',
60
-})

+ 0
- 155
components/style-panel/align-distribute.tsx View File

@@ -1,155 +0,0 @@
1
-import {
2
-  AlignBottomIcon,
3
-  AlignCenterHorizontallyIcon,
4
-  AlignCenterVerticallyIcon,
5
-  AlignLeftIcon,
6
-  AlignRightIcon,
7
-  AlignTopIcon,
8
-  SpaceEvenlyHorizontallyIcon,
9
-  SpaceEvenlyVerticallyIcon,
10
-  StretchHorizontallyIcon,
11
-  StretchVerticallyIcon,
12
-} from '@radix-ui/react-icons'
13
-import { breakpoints, ButtonsRow, IconButton } from 'components/shared'
14
-import { memo } from 'react'
15
-import state from 'state'
16
-import { AlignType, DistributeType, StretchType } from 'types'
17
-
18
-function alignTop() {
19
-  state.send('ALIGNED', { type: AlignType.Top })
20
-}
21
-
22
-function alignCenterVertical() {
23
-  state.send('ALIGNED', { type: AlignType.CenterVertical })
24
-}
25
-
26
-function alignBottom() {
27
-  state.send('ALIGNED', { type: AlignType.Bottom })
28
-}
29
-
30
-function stretchVertically() {
31
-  state.send('STRETCHED', { type: StretchType.Vertical })
32
-}
33
-
34
-function distributeVertically() {
35
-  state.send('DISTRIBUTED', { type: DistributeType.Vertical })
36
-}
37
-
38
-function alignLeft() {
39
-  state.send('ALIGNED', { type: AlignType.Left })
40
-}
41
-
42
-function alignCenterHorizontal() {
43
-  state.send('ALIGNED', { type: AlignType.CenterHorizontal })
44
-}
45
-
46
-function alignRight() {
47
-  state.send('ALIGNED', { type: AlignType.Right })
48
-}
49
-
50
-function stretchHorizontally() {
51
-  state.send('STRETCHED', { type: StretchType.Horizontal })
52
-}
53
-
54
-function distributeHorizontally() {
55
-  state.send('DISTRIBUTED', { type: DistributeType.Horizontal })
56
-}
57
-
58
-function AlignDistribute({
59
-  hasTwoOrMore,
60
-  hasThreeOrMore,
61
-}: {
62
-  hasTwoOrMore: boolean
63
-  hasThreeOrMore: boolean
64
-}): JSX.Element {
65
-  return (
66
-    <>
67
-      <ButtonsRow>
68
-        <IconButton
69
-          bp={breakpoints}
70
-          size="small"
71
-          disabled={!hasTwoOrMore}
72
-          onClick={alignLeft}
73
-        >
74
-          <AlignLeftIcon />
75
-        </IconButton>
76
-        <IconButton
77
-          bp={breakpoints}
78
-          size="small"
79
-          disabled={!hasTwoOrMore}
80
-          onClick={alignCenterHorizontal}
81
-        >
82
-          <AlignCenterHorizontallyIcon />
83
-        </IconButton>
84
-        <IconButton
85
-          bp={breakpoints}
86
-          size="small"
87
-          disabled={!hasTwoOrMore}
88
-          onClick={alignRight}
89
-        >
90
-          <AlignRightIcon />
91
-        </IconButton>
92
-        <IconButton
93
-          bp={breakpoints}
94
-          size="small"
95
-          disabled={!hasTwoOrMore}
96
-          onClick={stretchHorizontally}
97
-        >
98
-          <StretchHorizontallyIcon />
99
-        </IconButton>
100
-        <IconButton
101
-          bp={breakpoints}
102
-          size="small"
103
-          disabled={!hasThreeOrMore}
104
-          onClick={distributeHorizontally}
105
-        >
106
-          <SpaceEvenlyHorizontallyIcon />
107
-        </IconButton>
108
-      </ButtonsRow>
109
-      <ButtonsRow>
110
-        <IconButton
111
-          bp={breakpoints}
112
-          size="small"
113
-          disabled={!hasTwoOrMore}
114
-          onClick={alignTop}
115
-        >
116
-          <AlignTopIcon />
117
-        </IconButton>
118
-        <IconButton
119
-          bp={breakpoints}
120
-          size="small"
121
-          disabled={!hasTwoOrMore}
122
-          onClick={alignCenterVertical}
123
-        >
124
-          <AlignCenterVerticallyIcon />
125
-        </IconButton>
126
-        <IconButton
127
-          bp={breakpoints}
128
-          size="small"
129
-          disabled={!hasTwoOrMore}
130
-          onClick={alignBottom}
131
-        >
132
-          <AlignBottomIcon />
133
-        </IconButton>
134
-        <IconButton
135
-          bp={breakpoints}
136
-          size="small"
137
-          disabled={!hasTwoOrMore}
138
-          onClick={stretchVertically}
139
-        >
140
-          <StretchVerticallyIcon />
141
-        </IconButton>
142
-        <IconButton
143
-          bp={breakpoints}
144
-          size="small"
145
-          disabled={!hasThreeOrMore}
146
-          onClick={distributeVertically}
147
-        >
148
-          <SpaceEvenlyVerticallyIcon />
149
-        </IconButton>
150
-      </ButtonsRow>
151
-    </>
152
-  )
153
-}
154
-
155
-export default memo(AlignDistribute)

+ 0
- 45
components/style-panel/quick-color-select.tsx View File

@@ -1,45 +0,0 @@
1
-import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
2
-import { DropdownMenuIconTriggerButton } from 'components/shared'
3
-import { strokes } from 'state/shape-styles'
4
-import state, { useSelector } from 'state'
5
-import { BoxIcon, StyleDropdownItem, StyleDropdownContent } from './shared'
6
-import { useTheme } from 'next-themes'
7
-import { ColorStyle } from 'types'
8
-
9
-function handleColorChange(color: ColorStyle): void {
10
-  state.send('CHANGED_STYLE', { color })
11
-}
12
-
13
-export default function QuickColorSelect(): JSX.Element {
14
-  const color = useSelector((s) => s.values.selectedStyle.color)
15
-  const { theme } = useTheme()
16
-
17
-  return (
18
-    <DropdownMenu.Root dir="ltr">
19
-      <DropdownMenuIconTriggerButton label="Color">
20
-        <BoxIcon fill={strokes[theme][color]} stroke={strokes[theme][color]} />
21
-      </DropdownMenuIconTriggerButton>
22
-      <DropdownMenu.Content sideOffset={8}>
23
-        <DropdownMenu.DropdownMenuRadioGroup
24
-          value={color}
25
-          onValueChange={handleColorChange}
26
-          as={StyleDropdownContent}
27
-        >
28
-          {Object.keys(strokes[theme]).map((colorStyle: ColorStyle) => (
29
-            <DropdownMenu.DropdownMenuRadioItem
30
-              as={StyleDropdownItem}
31
-              key={colorStyle}
32
-              title={colorStyle}
33
-              value={colorStyle}
34
-            >
35
-              <BoxIcon
36
-                fill={strokes[theme][colorStyle]}
37
-                stroke={strokes[theme][colorStyle]}
38
-              />
39
-            </DropdownMenu.DropdownMenuRadioItem>
40
-          ))}
41
-        </DropdownMenu.DropdownMenuRadioGroup>
42
-      </DropdownMenu.Content>
43
-    </DropdownMenu.Root>
44
-  )
45
-}

+ 0
- 57
components/style-panel/quick-dash-select.tsx View File

@@ -1,57 +0,0 @@
1
-import React, { memo } from 'react'
2
-import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
3
-import { DropdownMenuIconTriggerButton } from 'components/shared'
4
-import state, { useSelector } from 'state'
5
-import { DashStyle } from 'types'
6
-import {
7
-  DashDrawIcon,
8
-  DashDottedIcon,
9
-  DashSolidIcon,
10
-  DashDashedIcon,
11
-  StyleDropdownContent,
12
-  StyleDropdownItem,
13
-} from './shared'
14
-
15
-const dashes = {
16
-  [DashStyle.Draw]: <DashDrawIcon />,
17
-  [DashStyle.Solid]: <DashSolidIcon />,
18
-  [DashStyle.Dashed]: <DashDashedIcon />,
19
-  [DashStyle.Dotted]: <DashDottedIcon />,
20
-}
21
-
22
-function changeDashStyle(dash: DashStyle): void {
23
-  state.send('CHANGED_STYLE', { dash })
24
-}
25
-
26
-function QuickdashSelect(): JSX.Element {
27
-  const dash = useSelector((s) => s.values.selectedStyle.dash)
28
-
29
-  return (
30
-    <DropdownMenu.Root dir="ltr">
31
-      <DropdownMenuIconTriggerButton label="Dash">
32
-        {dashes[dash]}
33
-      </DropdownMenuIconTriggerButton>
34
-      <DropdownMenu.Content sideOffset={8}>
35
-        <DropdownMenu.DropdownMenuRadioGroup
36
-          as={StyleDropdownContent}
37
-          direction="vertical"
38
-          value={dash}
39
-          onValueChange={changeDashStyle}
40
-        >
41
-          {Object.keys(DashStyle).map((dashStyle: DashStyle) => (
42
-            <DropdownMenu.DropdownMenuRadioItem
43
-              as={StyleDropdownItem}
44
-              key={dashStyle}
45
-              isActive={dash === dashStyle}
46
-              value={dashStyle}
47
-            >
48
-              {dashes[dashStyle]}
49
-            </DropdownMenu.DropdownMenuRadioItem>
50
-          ))}
51
-        </DropdownMenu.DropdownMenuRadioGroup>
52
-      </DropdownMenu.Content>
53
-    </DropdownMenu.Root>
54
-  )
55
-}
56
-
57
-export default memo(QuickdashSelect)

+ 0
- 43
components/style-panel/quick-fill-select.tsx View File

@@ -1,43 +0,0 @@
1
-import * as Checkbox from '@radix-ui/react-checkbox'
2
-import tld from 'utils/tld'
3
-import { breakpoints, IconButton, IconWrapper } from '../shared'
4
-import { BoxIcon, IsFilledFillIcon } from './shared'
5
-import state, { useSelector } from 'state'
6
-import { getShapeUtils } from 'state/shape-utils'
7
-import Tooltip from 'components/tooltip'
8
-
9
-function handleIsFilledChange(isFilled: boolean) {
10
-  state.send('CHANGED_STYLE', { isFilled })
11
-}
12
-
13
-export default function IsFilledPicker(): JSX.Element {
14
-  const isFilled = useSelector((s) => s.values.selectedStyle.isFilled)
15
-  const canFill = useSelector((s) => {
16
-    const selectedShapes = tld.getSelectedShapes(s.data)
17
-
18
-    return (
19
-      selectedShapes.length === 0 ||
20
-      selectedShapes.some((shape) => getShapeUtils(shape).canStyleFill)
21
-    )
22
-  })
23
-
24
-  return (
25
-    <Checkbox.Root
26
-      dir="ltr"
27
-      as={IconButton}
28
-      bp={breakpoints}
29
-      checked={isFilled}
30
-      disabled={!canFill}
31
-      onCheckedChange={handleIsFilledChange}
32
-    >
33
-      <Tooltip label="Fill">
34
-        <IconWrapper>
35
-          <BoxIcon />
36
-          <Checkbox.Indicator>
37
-            <IsFilledFillIcon />
38
-          </Checkbox.Indicator>
39
-        </IconWrapper>
40
-      </Tooltip>
41
-    </Checkbox.Root>
42
-  )
43
-}

+ 0
- 50
components/style-panel/quick-size-select.tsx View File

@@ -1,50 +0,0 @@
1
-import { memo } from 'react'
2
-import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
3
-import { DropdownMenuIconTriggerButton } from 'components/shared'
4
-import { Circle } from 'react-feather'
5
-import state, { useSelector } from 'state'
6
-import { SizeStyle } from 'types'
7
-import { StyleDropdownContent, StyleDropdownItem } from './shared'
8
-
9
-const sizes = {
10
-  [SizeStyle.Small]: 6,
11
-  [SizeStyle.Medium]: 12,
12
-  [SizeStyle.Large]: 22,
13
-}
14
-
15
-function changeSizeStyle(size: SizeStyle): void {
16
-  state.send('CHANGED_STYLE', { size })
17
-}
18
-
19
-function QuickSizeSelect(): JSX.Element {
20
-  const size = useSelector((s) => s.values.selectedStyle.size)
21
-
22
-  return (
23
-    <DropdownMenu.Root dir="ltr">
24
-      <DropdownMenuIconTriggerButton label="Size">
25
-        <Circle size={sizes[size]} stroke="none" fill="currentColor" />
26
-      </DropdownMenuIconTriggerButton>
27
-      <DropdownMenu.Content sideOffset={8}>
28
-        <DropdownMenu.DropdownMenuRadioGroup
29
-          as={StyleDropdownContent}
30
-          direction="vertical"
31
-          value={size}
32
-          onValueChange={changeSizeStyle}
33
-        >
34
-          {Object.keys(SizeStyle).map((sizeStyle: SizeStyle) => (
35
-            <DropdownMenu.DropdownMenuRadioItem
36
-              key={sizeStyle}
37
-              as={StyleDropdownItem}
38
-              isActive={size === sizeStyle}
39
-              value={sizeStyle}
40
-            >
41
-              <Circle size={sizes[sizeStyle]} />
42
-            </DropdownMenu.DropdownMenuRadioItem>
43
-          ))}
44
-        </DropdownMenu.DropdownMenuRadioGroup>
45
-      </DropdownMenu.Content>
46
-    </DropdownMenu.Root>
47
-  )
48
-}
49
-
50
-export default memo(QuickSizeSelect)

+ 0
- 214
components/style-panel/shapes-functions.tsx View File

@@ -1,214 +0,0 @@
1
-import tld from 'utils/tld'
2
-import state, { useSelector } from 'state'
3
-import { IconButton, ButtonsRow, breakpoints } from 'components/shared'
4
-import { memo } from 'react'
5
-import { MoveType, ShapeType } from 'types'
6
-import { Trash2 } from 'react-feather'
7
-import Tooltip from 'components/tooltip'
8
-import {
9
-  ArrowDownIcon,
10
-  ArrowUpIcon,
11
-  AspectRatioIcon,
12
-  CopyIcon,
13
-  GroupIcon,
14
-  LockClosedIcon,
15
-  LockOpen1Icon,
16
-  PinBottomIcon,
17
-  PinTopIcon,
18
-  RotateCounterClockwiseIcon,
19
-} from '@radix-ui/react-icons'
20
-import { commandKey } from 'utils'
21
-
22
-function handleRotateCcw() {
23
-  state.send('ROTATED_CCW')
24
-}
25
-
26
-function handleDuplicate() {
27
-  state.send('DUPLICATED')
28
-}
29
-
30
-function handleGroup() {
31
-  state.send('GROUPED')
32
-}
33
-
34
-function handleUngroup() {
35
-  state.send('UNGROUPED')
36
-}
37
-
38
-function handleLock() {
39
-  state.send('TOGGLED_SHAPE_LOCK')
40
-}
41
-
42
-function handleAspectLock() {
43
-  state.send('TOGGLED_SHAPE_ASPECT_LOCK')
44
-}
45
-
46
-function handleMoveToBack() {
47
-  state.send('MOVED', { type: MoveType.ToBack })
48
-}
49
-
50
-function handleMoveBackward() {
51
-  state.send('MOVED', { type: MoveType.Backward })
52
-}
53
-
54
-function handleMoveForward() {
55
-  state.send('MOVED', { type: MoveType.Forward })
56
-}
57
-
58
-function handleMoveToFront() {
59
-  state.send('MOVED', { type: MoveType.ToFront })
60
-}
61
-
62
-function handleDelete() {
63
-  state.send('DELETED')
64
-}
65
-
66
-function ShapesFunctions() {
67
-  const isAllLocked = useSelector((s) => {
68
-    const page = tld.getPage(s.data)
69
-    return s.values.selectedIds.every((id) => page.shapes[id].isLocked)
70
-  })
71
-
72
-  const isAllAspectLocked = useSelector((s) => {
73
-    const page = tld.getPage(s.data)
74
-    return s.values.selectedIds.every(
75
-      (id) => page.shapes[id].isAspectRatioLocked
76
-    )
77
-  })
78
-
79
-  const isAllGrouped = useSelector((s) => {
80
-    const selectedShapes = tld.getSelectedShapes(s.data)
81
-    return selectedShapes.every(
82
-      (shape) =>
83
-        shape.type === ShapeType.Group ||
84
-        (shape.parentId === selectedShapes[0].parentId &&
85
-          selectedShapes[0].parentId !== s.data.currentPageId)
86
-    )
87
-  })
88
-
89
-  const hasSelection = useSelector((s) => {
90
-    return tld.getSelectedIds(s.data).length > 0
91
-  })
92
-
93
-  const hasMultipleSelection = useSelector((s) => {
94
-    return tld.getSelectedIds(s.data).length > 1
95
-  })
96
-
97
-  return (
98
-    <>
99
-      <ButtonsRow>
100
-        <IconButton
101
-          bp={breakpoints}
102
-          disabled={!hasSelection}
103
-          size="small"
104
-          onClick={handleDuplicate}
105
-        >
106
-          <Tooltip label="Duplicate" kbd={`${commandKey()}D`}>
107
-            <CopyIcon />
108
-          </Tooltip>
109
-        </IconButton>
110
-
111
-        <IconButton
112
-          disabled={!hasSelection}
113
-          size="small"
114
-          onClick={handleRotateCcw}
115
-        >
116
-          <Tooltip label="Rotate">
117
-            <RotateCounterClockwiseIcon />
118
-          </Tooltip>
119
-        </IconButton>
120
-
121
-        <IconButton
122
-          bp={breakpoints}
123
-          disabled={!hasSelection}
124
-          size="small"
125
-          onClick={handleLock}
126
-        >
127
-          <Tooltip label="Toogle Locked" kbd={`${commandKey()}L`}>
128
-            {isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon opacity={0.4} />}
129
-          </Tooltip>
130
-        </IconButton>
131
-
132
-        <IconButton
133
-          bp={breakpoints}
134
-          disabled={!hasSelection}
135
-          size="small"
136
-          onClick={handleAspectLock}
137
-        >
138
-          <Tooltip label="Toogle Aspect Ratio Lock">
139
-            <AspectRatioIcon opacity={isAllAspectLocked ? 1 : 0.4} />
140
-          </Tooltip>
141
-        </IconButton>
142
-
143
-        <IconButton
144
-          bp={breakpoints}
145
-          disabled={!isAllGrouped && !hasMultipleSelection}
146
-          size="small"
147
-          onClick={isAllGrouped ? handleUngroup : handleGroup}
148
-        >
149
-          <Tooltip label="Group" kbd={`${commandKey()}G`}>
150
-            <GroupIcon opacity={isAllGrouped ? 1 : 0.4} />
151
-          </Tooltip>
152
-        </IconButton>
153
-      </ButtonsRow>
154
-      <ButtonsRow>
155
-        <IconButton
156
-          bp={breakpoints}
157
-          disabled={!hasSelection}
158
-          size="small"
159
-          onClick={handleMoveToBack}
160
-        >
161
-          <Tooltip label="Move to Back" kbd={`${commandKey()}⇧[`}>
162
-            <PinBottomIcon />
163
-          </Tooltip>
164
-        </IconButton>
165
-
166
-        <IconButton
167
-          bp={breakpoints}
168
-          disabled={!hasSelection}
169
-          size="small"
170
-          onClick={handleMoveBackward}
171
-        >
172
-          <Tooltip label="Move Backward" kbd={`${commandKey()}[`}>
173
-            <ArrowDownIcon />
174
-          </Tooltip>
175
-        </IconButton>
176
-
177
-        <IconButton
178
-          bp={breakpoints}
179
-          disabled={!hasSelection}
180
-          size="small"
181
-          onClick={handleMoveForward}
182
-        >
183
-          <Tooltip label="Move Forward" kbd={`${commandKey()}]`}>
184
-            <ArrowUpIcon />
185
-          </Tooltip>
186
-        </IconButton>
187
-
188
-        <IconButton
189
-          bp={breakpoints}
190
-          disabled={!hasSelection}
191
-          size="small"
192
-          onClick={handleMoveToFront}
193
-        >
194
-          <Tooltip label="More to Front" kbd={`${commandKey()}⇧]`}>
195
-            <PinTopIcon />
196
-          </Tooltip>
197
-        </IconButton>
198
-
199
-        <IconButton
200
-          bp={breakpoints}
201
-          disabled={!hasSelection}
202
-          size="small"
203
-          onClick={handleDelete}
204
-        >
205
-          <Tooltip label="Delete" kbd="⌫">
206
-            <Trash2 size="15" />
207
-          </Tooltip>
208
-        </IconButton>
209
-      </ButtonsRow>
210
-    </>
211
-  )
212
-}
213
-
214
-export default memo(ShapesFunctions)

+ 0
- 101
components/style-panel/style-panel.tsx View File

@@ -1,101 +0,0 @@
1
-import state, { useSelector } from 'state'
2
-import {
3
-  IconButton,
4
-  ButtonsRow,
5
-  breakpoints,
6
-  RowButton,
7
-  FloatingContainer,
8
-  Divider,
9
-  Kbd,
10
-} from 'components/shared'
11
-import ShapesFunctions from './shapes-functions'
12
-import AlignDistribute from './align-distribute'
13
-import QuickColorSelect from './quick-color-select'
14
-import QuickSizeSelect from './quick-size-select'
15
-import QuickDashSelect from './quick-dash-select'
16
-import QuickFillSelect from './quick-fill-select'
17
-import Tooltip from 'components/tooltip'
18
-import { DotsHorizontalIcon, Cross2Icon } from '@radix-ui/react-icons'
19
-import { commandKey, isMobile } from 'utils'
20
-
21
-const handleStylePanelOpen = () => state.send('TOGGLED_STYLE_PANEL_OPEN')
22
-const handleCopy = () => state.send('COPIED')
23
-const handlePaste = () => state.send('PASTED')
24
-const handleCopyToSvg = () => state.send('COPIED_TO_SVG')
25
-
26
-export default function StylePanel(): JSX.Element {
27
-  const isOpen = useSelector((s) => s.data.settings.isStyleOpen)
28
-
29
-  return (
30
-    <FloatingContainer direction="column">
31
-      <ButtonsRow>
32
-        <QuickColorSelect />
33
-        <QuickSizeSelect />
34
-        <QuickDashSelect />
35
-        <QuickFillSelect />
36
-        <IconButton
37
-          bp={breakpoints}
38
-          title="Style"
39
-          size="small"
40
-          onPointerDown={handleStylePanelOpen}
41
-        >
42
-          <Tooltip label={isOpen ? 'Close' : 'More'}>
43
-            {isOpen ? <Cross2Icon /> : <DotsHorizontalIcon />}
44
-          </Tooltip>
45
-        </IconButton>
46
-      </ButtonsRow>
47
-      {isOpen && <SelectedShapeContent />}
48
-    </FloatingContainer>
49
-  )
50
-}
51
-
52
-function SelectedShapeContent(): JSX.Element {
53
-  const selectedShapesCount = useSelector((s) => s.values.selectedIds.length)
54
-
55
-  const showKbds = !isMobile()
56
-
57
-  return (
58
-    <>
59
-      <Divider />
60
-      <ShapesFunctions />
61
-      <Divider />
62
-      <AlignDistribute
63
-        hasTwoOrMore={selectedShapesCount > 1}
64
-        hasThreeOrMore={selectedShapesCount > 2}
65
-      />
66
-      <Divider />
67
-      <RowButton
68
-        bp={breakpoints}
69
-        disabled={selectedShapesCount === 0}
70
-        onClick={handleCopy}
71
-      >
72
-        <span>Copy</span>
73
-        {showKbds && (
74
-          <Kbd>
75
-            <span>{commandKey()}</span>
76
-            <span>C</span>
77
-          </Kbd>
78
-        )}
79
-      </RowButton>
80
-      <RowButton bp={breakpoints} onClick={handlePaste}>
81
-        <span>Paste</span>
82
-        {showKbds && (
83
-          <Kbd>
84
-            <span>{commandKey()}</span>
85
-            <span>V</span>
86
-          </Kbd>
87
-        )}
88
-      </RowButton>
89
-      <RowButton bp={breakpoints} onClick={handleCopyToSvg}>
90
-        <span>Copy to SVG</span>
91
-        {showKbds && (
92
-          <Kbd>
93
-            <span>⇧</span>
94
-            <span>{commandKey()}</span>
95
-            <span>C</span>
96
-          </Kbd>
97
-        )}
98
-      </RowButton>
99
-    </>
100
-  )
101
-}

+ 0
- 32
components/tools-panel/back-to-content.tsx View File

@@ -1,32 +0,0 @@
1
-import { FloatingContainer, RowButton } from 'components/shared'
2
-import { motion } from 'framer-motion'
3
-import { memo } from 'react'
4
-import state, { useSelector } from 'state'
5
-import styled from 'styles'
6
-
7
-function BackToContent() {
8
-  const shouldDisplay = useSelector((s) => {
9
-    const { currentShapes, shapesToRender } = s.values
10
-    return currentShapes.length > 0 && shapesToRender.length === 0
11
-  })
12
-
13
-  if (!shouldDisplay) return null
14
-
15
-  return (
16
-    <BackToContentButton initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
17
-      <RowButton onClick={() => state.send('ZOOMED_TO_CONTENT')}>
18
-        Back to content
19
-      </RowButton>
20
-    </BackToContentButton>
21
-  )
22
-}
23
-
24
-export default memo(BackToContent)
25
-
26
-const BackToContentButton = styled(motion(FloatingContainer), {
27
-  pointerEvents: 'all',
28
-  width: 'fit-content',
29
-  gridRow: 1,
30
-  flexGrow: 2,
31
-  display: 'block',
32
-})

+ 0
- 200
components/tools-panel/tools-panel.tsx View File

@@ -1,200 +0,0 @@
1
-import {
2
-  ArrowTopRightIcon,
3
-  CircleIcon,
4
-  CursorArrowIcon,
5
-  LockClosedIcon,
6
-  LockOpen1Icon,
7
-  Pencil1Icon,
8
-  SquareIcon,
9
-  TextIcon,
10
-} from '@radix-ui/react-icons'
11
-import * as React from 'react'
12
-import state, { useSelector } from 'state'
13
-import StatusBar from 'components/status-bar'
14
-import { FloatingContainer } from 'components/shared'
15
-import { PrimaryButton, SecondaryButton } from './shared'
16
-import styled from 'styles'
17
-import { ShapeType } from 'types'
18
-import UndoRedo from './undo-redo'
19
-import Zoom from './zoom'
20
-import BackToContent from './back-to-content'
21
-
22
-const selectArrowTool = () => state.send('SELECTED_ARROW_TOOL')
23
-const selectDrawTool = () => state.send('SELECTED_DRAW_TOOL')
24
-const selectEllipseTool = () => state.send('SELECTED_ELLIPSE_TOOL')
25
-const selectTextTool = () => state.send('SELECTED_TEXT_TOOL')
26
-const selectRectangleTool = () => state.send('SELECTED_RECTANGLE_TOOL')
27
-const selectSelectTool = () => state.send('SELECTED_SELECT_TOOL')
28
-const toggleToolLock = () => state.send('TOGGLED_TOOL_LOCK')
29
-
30
-export default function ToolsPanel(): JSX.Element {
31
-  const activeTool = useSelector((s) => s.data.activeTool)
32
-
33
-  const isToolLocked = useSelector((s) => s.data.settings.isToolLocked)
34
-
35
-  return (
36
-    <ToolsPanelContainer>
37
-      <LeftWrap size={{ '@initial': 'mobile', '@sm': 'small' }}>
38
-        <Zoom />
39
-        <FloatingContainer>
40
-          <SecondaryButton
41
-            label={'Select'}
42
-            kbd={'1'}
43
-            onClick={selectSelectTool}
44
-            isActive={activeTool === 'select'}
45
-          >
46
-            <CursorArrowIcon />
47
-          </SecondaryButton>
48
-        </FloatingContainer>
49
-      </LeftWrap>
50
-      <CenterWrap>
51
-        <BackToContent />
52
-        <FloatingContainer>
53
-          <PrimaryButton
54
-            kbd={'2'}
55
-            label={ShapeType.Draw}
56
-            onClick={selectDrawTool}
57
-            isActive={activeTool === ShapeType.Draw}
58
-          >
59
-            <Pencil1Icon />
60
-          </PrimaryButton>
61
-          <PrimaryButton
62
-            kbd={'3'}
63
-            label={ShapeType.Rectangle}
64
-            onClick={selectRectangleTool}
65
-            isActive={activeTool === ShapeType.Rectangle}
66
-          >
67
-            <SquareIcon />
68
-          </PrimaryButton>
69
-          <PrimaryButton
70
-            kbd={'4'}
71
-            label={ShapeType.Ellipse}
72
-            onClick={selectEllipseTool}
73
-            isActive={activeTool === ShapeType.Ellipse}
74
-          >
75
-            <CircleIcon />
76
-          </PrimaryButton>
77
-          <PrimaryButton
78
-            kbd={'5'}
79
-            label={ShapeType.Arrow}
80
-            onClick={selectArrowTool}
81
-            isActive={activeTool === ShapeType.Arrow}
82
-          >
83
-            <ArrowTopRightIcon />
84
-          </PrimaryButton>
85
-          <PrimaryButton
86
-            kbd={'6'}
87
-            label={ShapeType.Text}
88
-            onClick={selectTextTool}
89
-            isActive={activeTool === ShapeType.Text}
90
-          >
91
-            <TextIcon />
92
-          </PrimaryButton>
93
-        </FloatingContainer>
94
-      </CenterWrap>
95
-      <RightWrap size={{ '@initial': 'mobile', '@sm': 'small' }}>
96
-        <FloatingContainer>
97
-          <SecondaryButton
98
-            kbd={'7'}
99
-            label={'Lock Tool'}
100
-            onClick={toggleToolLock}
101
-            isActive={isToolLocked}
102
-          >
103
-            {isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
104
-          </SecondaryButton>
105
-        </FloatingContainer>
106
-        <UndoRedo />
107
-      </RightWrap>
108
-      <StatusWrap>
109
-        <StatusBar />
110
-      </StatusWrap>
111
-    </ToolsPanelContainer>
112
-  )
113
-}
114
-
115
-const ToolsPanelContainer = styled('div', {
116
-  position: 'fixed',
117
-  bottom: 0,
118
-  left: 0,
119
-  right: 0,
120
-  width: '100%',
121
-  minWidth: 0,
122
-  maxWidth: '100%',
123
-  display: 'grid',
124
-  gridTemplateColumns: '1fr auto 1fr',
125
-  padding: '0',
126
-  alignItems: 'flex-end',
127
-  zIndex: 200,
128
-  gridGap: '$4',
129
-  gridRowGap: '$4',
130
-})
131
-
132
-const CenterWrap = styled('div', {
133
-  gridRow: 1,
134
-  gridColumn: 2,
135
-  display: 'flex',
136
-  width: 'fit-content',
137
-  alignItems: 'center',
138
-  justifyContent: 'center',
139
-  flexDirection: 'column',
140
-  gap: 12,
141
-})
142
-
143
-const LeftWrap = styled('div', {
144
-  gridRow: 1,
145
-  gridColumn: 1,
146
-  display: 'flex',
147
-  paddingLeft: '$3',
148
-  variants: {
149
-    size: {
150
-      mobile: {
151
-        flexDirection: 'column',
152
-        justifyContent: 'flex-end',
153
-        alignItems: 'flex-start',
154
-        '& > *:nth-of-type(1)': {
155
-          marginBottom: '8px',
156
-        },
157
-      },
158
-      small: {
159
-        flexDirection: 'row',
160
-        alignItems: 'flex-end',
161
-        justifyContent: 'space-between',
162
-        '& > *:nth-of-type(1)': {
163
-          marginBottom: '0px',
164
-        },
165
-      },
166
-    },
167
-  },
168
-})
169
-
170
-const RightWrap = styled('div', {
171
-  gridRow: 1,
172
-  gridColumn: 3,
173
-  display: 'flex',
174
-  paddingRight: '$3',
175
-  variants: {
176
-    size: {
177
-      mobile: {
178
-        flexDirection: 'column-reverse',
179
-        justifyContent: 'flex-end',
180
-        alignItems: 'flex-end',
181
-        '& > *:nth-of-type(2)': {
182
-          marginBottom: '8px',
183
-        },
184
-      },
185
-      small: {
186
-        flexDirection: 'row',
187
-        alignItems: 'flex-end',
188
-        justifyContent: 'space-between',
189
-        '& > *:nth-of-type(2)': {
190
-          marginBottom: '0px',
191
-        },
192
-      },
193
-    },
194
-  },
195
-})
196
-
197
-const StatusWrap = styled('div', {
198
-  gridRow: 2,
199
-  gridColumn: '1 / span 3',
200
-})

+ 0
- 24
components/tools-panel/undo-redo.tsx View File

@@ -1,24 +0,0 @@
1
-import { TertiaryButton, TertiaryButtonsContainer } from './shared'
2
-import { Undo, Redo, Trash } from 'components/icons'
3
-import state from 'state'
4
-import { commandKey } from 'utils'
5
-
6
-const undo = () => state.send('UNDO')
7
-const redo = () => state.send('REDO')
8
-const clear = () => state.send('CLEARED_PAGE')
9
-
10
-export default function UndoRedo(): JSX.Element {
11
-  return (
12
-    <TertiaryButtonsContainer bp={{ '@initial': 'mobile', '@sm': 'small' }}>
13
-      <TertiaryButton label="Undo" kbd={`${commandKey()}Z`} onClick={undo}>
14
-        <Undo />
15
-      </TertiaryButton>
16
-      <TertiaryButton label="Redo" kbd={`${commandKey()}⇧Z`} onClick={redo}>
17
-        <Redo />
18
-      </TertiaryButton>
19
-      <TertiaryButton label="Delete" kbd="⌫" onClick={clear}>
20
-        <Trash />
21
-      </TertiaryButton>
22
-    </TertiaryButtonsContainer>
23
-  )
24
-}

+ 0
- 43
components/tools-panel/zoom.tsx View File

@@ -1,43 +0,0 @@
1
-import { ZoomInIcon, ZoomOutIcon } from '@radix-ui/react-icons'
2
-import { TertiaryButton, TertiaryButtonsContainer } from './shared'
3
-import state, { useSelector } from 'state'
4
-import tld from 'utils/tld'
5
-import { commandKey } from 'utils'
6
-
7
-const zoomIn = () => state.send('ZOOMED_IN')
8
-const zoomOut = () => state.send('ZOOMED_OUT')
9
-const zoomToFit = () => state.send('ZOOMED_TO_FIT')
10
-const zoomToActual = () => state.send('ZOOMED_TO_ACTUAL')
11
-
12
-export default function Zoom(): JSX.Element {
13
-  return (
14
-    <TertiaryButtonsContainer bp={{ '@initial': 'mobile', '@sm': 'small' }}>
15
-      <TertiaryButton
16
-        label="Zoom Out"
17
-        kbd={`${commandKey()}−`}
18
-        onClick={zoomOut}
19
-      >
20
-        <ZoomOutIcon />
21
-      </TertiaryButton>
22
-      <TertiaryButton label="Zoom In" kbd={`${commandKey()}+`} onClick={zoomIn}>
23
-        <ZoomInIcon />
24
-      </TertiaryButton>
25
-      <ZoomCounter />
26
-    </TertiaryButtonsContainer>
27
-  )
28
-}
29
-
30
-function ZoomCounter() {
31
-  const zoom = useSelector((s) => tld.getCurrentCamera(s.data).zoom)
32
-
33
-  return (
34
-    <TertiaryButton
35
-      label="Reset Zoom"
36
-      kbd="⇧0"
37
-      onClick={zoomToActual}
38
-      onDoubleClick={zoomToFit}
39
-    >
40
-      {Math.round(zoom * 100)}%
41
-    </TertiaryButton>
42
-  )
43
-}

+ 0
- 74
components/tooltip.tsx View File

@@ -1,74 +0,0 @@
1
-import * as _Tooltip from '@radix-ui/react-tooltip'
2
-import React from 'react'
3
-import styled from 'styles'
4
-
5
-interface TooltipProps {
6
-  children: React.ReactNode
7
-  label: string
8
-  kbd?: string
9
-  side?: 'bottom' | 'left' | 'right' | 'top'
10
-}
11
-
12
-export default function Tooltip({
13
-  children,
14
-  label,
15
-  kbd,
16
-  side = 'top',
17
-}: TooltipProps): JSX.Element {
18
-  return (
19
-    <_Tooltip.Root>
20
-      <_Tooltip.Trigger as="span">{children}</_Tooltip.Trigger>
21
-      <StyledContent side={side} sideOffset={8}>
22
-        {label}
23
-        {kbd ? (
24
-          <kbd>
25
-            {kbd.split('').map((k, i) => (
26
-              <span key={i}>{k}</span>
27
-            ))}
28
-          </kbd>
29
-        ) : null}
30
-        <StyledArrow />
31
-      </StyledContent>
32
-    </_Tooltip.Root>
33
-  )
34
-}
35
-
36
-const StyledContent = styled(_Tooltip.Content, {
37
-  borderRadius: 3,
38
-  padding: '$3 $3 $3 $3',
39
-  fontSize: '$1',
40
-  backgroundColor: '$tooltipBg',
41
-  color: '$tooltipText',
42
-  boxShadow: '$3',
43
-  display: 'flex',
44
-  alignItems: 'center',
45
-
46
-  '& kbd': {
47
-    marginLeft: '$3',
48
-    textShadow: '$2',
49
-    textAlign: 'center',
50
-    fontSize: '$1',
51
-    fontFamily: '$ui',
52
-    fontWeight: 400,
53
-    gap: '$1',
54
-    display: 'flex',
55
-    alignItems: 'center',
56
-
57
-    '& > span': {
58
-      padding: '$0',
59
-      borderRadius: '$0',
60
-      background: '$overlayContrast',
61
-      boxShadow: '$key',
62
-      width: '20px',
63
-      height: '20px',
64
-      display: 'flex',
65
-      alignItems: 'center',
66
-      justifyContent: 'center',
67
-    },
68
-  },
69
-})
70
-
71
-const StyledArrow = styled(_Tooltip.Arrow, {
72
-  fill: '$tooltipBg',
73
-  margin: '0 8px',
74
-})

+ 0
- 31
decs.d.ts View File

@@ -1,31 +0,0 @@
1
-type CSSOMString = string
2
-type FontFaceLoadStatus = 'unloaded' | 'loading' | 'loaded' | 'error'
3
-type FontFaceSetStatus = 'loading' | 'loaded'
4
-
5
-interface FontFace {
6
-  family: CSSOMString
7
-  style: CSSOMString
8
-  weight: CSSOMString
9
-  stretch: CSSOMString
10
-  unicodeRange: CSSOMString
11
-  variant: CSSOMString
12
-  featureSettings: CSSOMString
13
-  variationSettings: CSSOMString
14
-  display: CSSOMString
15
-  readonly status: FontFaceLoadStatus
16
-  readonly loaded: Promise<FontFace>
17
-  load(): Promise<FontFace>
18
-}
19
-
20
-interface FontFaceSet {
21
-  readonly status: FontFaceSetStatus
22
-  readonly ready: Promise<FontFaceSet>
23
-  check(font: string, text?: string): boolean
24
-  load(font: string, text?: string): Promise<FontFace[]>
25
-}
26
-
27
-declare global {
28
-  interface Document {
29
-    fonts: FontFaceSet
30
-  }
31
-}

+ 0
- 57
hooks/useBoundsEvents.ts View File

@@ -1,57 +0,0 @@
1
-/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2
-import { useCallback } from 'react'
3
-import { fastTransform } from 'state/hacks'
4
-import inputs from 'state/inputs'
5
-import { Edge, Corner } from 'types'
6
-
7
-import state from '../state'
8
-
9
-export default function useBoundsEvents(handle: Edge | Corner | 'rotate') {
10
-  const onPointerDown = useCallback(
11
-    (e) => {
12
-      if (!inputs.canAccept(e.pointerId)) return
13
-      e.stopPropagation()
14
-      e.currentTarget.setPointerCapture(e.pointerId)
15
-
16
-      if (e.button === 0) {
17
-        const info = inputs.pointerDown(e, handle)
18
-
19
-        if (inputs.isDoubleClick() && !(info.altKey || info.metaKey)) {
20
-          state.send('DOUBLE_POINTED_BOUNDS_HANDLE', info)
21
-        }
22
-
23
-        state.send('POINTED_BOUNDS_HANDLE', info)
24
-      }
25
-    },
26
-    [handle]
27
-  )
28
-
29
-  const onPointerMove = useCallback(
30
-    (e) => {
31
-      if (e.buttons !== 1) return
32
-      if (!inputs.canAccept(e.pointerId)) return
33
-      e.stopPropagation()
34
-
35
-      const info = inputs.pointerMove(e)
36
-
37
-      if (state.isIn('transformingSelection')) {
38
-        fastTransform(info)
39
-        return
40
-      }
41
-
42
-      state.send('MOVED_POINTER', info)
43
-    },
44
-    [handle]
45
-  )
46
-
47
-  const onPointerUp = useCallback((e) => {
48
-    if (e.buttons !== 1) return
49
-    if (!inputs.canAccept(e.pointerId)) return
50
-    e.stopPropagation()
51
-    e.currentTarget.releasePointerCapture(e.pointerId)
52
-    e.currentTarget.replaceWith(e.currentTarget)
53
-    state.send('STOPPED_POINTING', inputs.pointerUp(e, 'bounds'))
54
-  }, [])
55
-
56
-  return { onPointerDown, onPointerMove, onPointerUp }
57
-}

+ 0
- 34
hooks/useCamera.ts View File

@@ -1,34 +0,0 @@
1
-/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2
-import React, { useEffect } from 'react'
3
-import state from 'state'
4
-import storage from 'state/storage'
5
-import tld from 'utils/tld'
6
-
7
-/**
8
- * When the state's camera changes, update the transform of
9
- * the SVG group to reflect the correct zoom and pan.
10
- * @param ref
11
- */
12
-export default function useCamera(ref: React.MutableRefObject<SVGGElement>) {
13
-  useEffect(() => {
14
-    let prev = tld.getCurrentCamera(state.data)
15
-
16
-    return state.onUpdate(() => {
17
-      const g = ref.current
18
-      if (!g) return
19
-
20
-      const { point, zoom } = tld.getCurrentCamera(state.data)
21
-
22
-      if (point !== prev.point || zoom !== prev.zoom) {
23
-        g.setAttribute(
24
-          'transform',
25
-          `scale(${zoom}) translate(${point[0]} ${point[1]})`
26
-        )
27
-
28
-        storage.savePageState(state.data)
29
-
30
-        prev = tld.getCurrentCamera(state.data)
31
-      }
32
-    })
33
-  }, [state])
34
-}

+ 0
- 142
hooks/useCanvasEvents.ts View File

@@ -1,142 +0,0 @@
1
-/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2
-import { MutableRefObject, useCallback, useEffect } from 'react'
3
-import state from 'state'
4
-import {
5
-  fastBrushSelect,
6
-  fastDrawUpdate,
7
-  fastPanUpdate,
8
-  fastTransform,
9
-  fastTranslate,
10
-} from 'state/hacks'
11
-import inputs from 'state/inputs'
12
-import Vec from 'utils/vec'
13
-
14
-export default function useCanvasEvents(
15
-  rCanvas: MutableRefObject<SVGGElement>
16
-) {
17
-  const handlePointerDown = useCallback(
18
-    (e: React.PointerEvent<SVGSVGElement>) => {
19
-      if (!inputs.canAccept(e.pointerId)) return
20
-
21
-      rCanvas.current.setPointerCapture(e.pointerId)
22
-
23
-      const info = inputs.pointerDown(e, 'canvas')
24
-
25
-      if (e.button === 0) {
26
-        if (inputs.isDoubleClick() && !(info.altKey || info.metaKey)) {
27
-          state.send('DOUBLE_POINTED_CANVAS', info)
28
-        }
29
-
30
-        state.send('POINTED_CANVAS', info)
31
-      } else if (e.button === 2) {
32
-        state.send('RIGHT_POINTED', info)
33
-      }
34
-    },
35
-    []
36
-  )
37
-
38
-  const handlePointerMove = useCallback(
39
-    (e: React.PointerEvent<SVGSVGElement>) => {
40
-      if (!inputs.canAccept(e.pointerId)) return
41
-
42
-      const prev = inputs.pointer?.point
43
-      const info = inputs.pointerMove(e)
44
-
45
-      if (prev && state.isIn('selecting') && inputs.keys[' ']) {
46
-        const delta = Vec.sub(prev, info.point)
47
-        fastPanUpdate(delta)
48
-        state.send('KEYBOARD_PANNED_CAMERA', {
49
-          delta: Vec.sub(prev, info.point),
50
-        })
51
-        return
52
-      }
53
-
54
-      if (state.isIn('draw.editing')) {
55
-        fastDrawUpdate(info)
56
-      } else if (state.isIn('brushSelecting')) {
57
-        fastBrushSelect(info.point)
58
-      } else if (state.isIn('translatingSelection')) {
59
-        fastTranslate(info)
60
-      } else if (state.isIn('transformingSelection')) {
61
-        fastTransform(info)
62
-      }
63
-
64
-      state.send('MOVED_POINTER', info)
65
-    },
66
-    []
67
-  )
68
-
69
-  const handlePointerUp = useCallback(
70
-    (e: React.PointerEvent<SVGSVGElement>) => {
71
-      if (!inputs.canAccept(e.pointerId)) return
72
-
73
-      rCanvas.current.releasePointerCapture(e.pointerId)
74
-
75
-      state.send('STOPPED_POINTING', {
76
-        id: 'canvas',
77
-        ...inputs.pointerUp(e, 'canvas'),
78
-      })
79
-    },
80
-    []
81
-  )
82
-
83
-  const handleTouchStart = useCallback((e: React.TouchEvent<SVGSVGElement>) => {
84
-    if ('safari' in window) {
85
-      e.preventDefault()
86
-    }
87
-  }, [])
88
-
89
-  useEffect(() => {
90
-    const preventGestureNavigation = (event: TouchEvent) => {
91
-      event.preventDefault()
92
-    }
93
-
94
-    const preventNavigation = (event: TouchEvent) => {
95
-      // Center point of the touch area
96
-      const touchXPosition = event.touches[0].pageX
97
-      // Size of the touch area
98
-      const touchXRadius = event.touches[0].radiusX || 0
99
-
100
-      // We set a threshold (10px) on both sizes of the screen,
101
-      // if the touch area overlaps with the screen edges
102
-      // it's likely to trigger the navigation. We prevent the
103
-      // touchstart event in that case.
104
-      if (
105
-        touchXPosition - touchXRadius < 10 ||
106
-        touchXPosition + touchXRadius > window.innerWidth - 10
107
-      ) {
108
-        event.preventDefault()
109
-      }
110
-    }
111
-
112
-    rCanvas.current.addEventListener('gestureend', preventGestureNavigation)
113
-    rCanvas.current.addEventListener('gesturechange', preventGestureNavigation)
114
-    rCanvas.current.addEventListener('gesturestart', preventGestureNavigation)
115
-    rCanvas.current.addEventListener('touchstart', preventNavigation)
116
-
117
-    return () => {
118
-      if (rCanvas.current) {
119
-        rCanvas.current.removeEventListener(
120
-          'gestureend',
121
-          preventGestureNavigation
122
-        )
123
-        rCanvas.current.removeEventListener(
124
-          'gesturechange',
125
-          preventGestureNavigation
126
-        )
127
-        rCanvas.current.removeEventListener(
128
-          'gesturestart',
129
-          preventGestureNavigation
130
-        )
131
-        rCanvas.current.removeEventListener('touchstart', preventNavigation)
132
-      }
133
-    }
134
-  }, [])
135
-
136
-  return {
137
-    onPointerDown: handlePointerDown,
138
-    onPointerMove: handlePointerMove,
139
-    onPointerUp: handlePointerUp,
140
-    onTouchStart: handleTouchStart,
141
-  }
142
-}

+ 0
- 0
hooks/useHandleEvents.ts View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save