Ver código fonte

Adds alignment to context menu

main
Steve Ruiz 4 anos atrás
pai
commit
946fdbab4c

+ 1
- 1
components/canvas/canvas.tsx Ver arquivo

11
 import Selected from './selected'
11
 import Selected from './selected'
12
 import Handles from './bounds/handles'
12
 import Handles from './bounds/handles'
13
 import useCanvasEvents from 'hooks/useCanvasEvents'
13
 import useCanvasEvents from 'hooks/useCanvasEvents'
14
-import ContextMenu from 'components/context-menu'
14
+import ContextMenu from './context-menu/context-menu'
15
 
15
 
16
 export default function Canvas() {
16
 export default function Canvas() {
17
   const rCanvas = useRef<SVGSVGElement>(null)
17
   const rCanvas = useRef<SVGSVGElement>(null)

components/context-menu.tsx → components/canvas/context-menu/context-menu.tsx Ver arquivo

1
 import * as _ContextMenu from '@radix-ui/react-context-menu'
1
 import * as _ContextMenu from '@radix-ui/react-context-menu'
2
 import * as _Dropdown from '@radix-ui/react-dropdown-menu'
2
 import * as _Dropdown from '@radix-ui/react-dropdown-menu'
3
 import styled from 'styles'
3
 import styled from 'styles'
4
-import { IconWrapper, RowButton } from './shared'
4
+import {
5
+  IconWrapper,
6
+  IconButton as _IconButton,
7
+  RowButton,
8
+} from 'components/shared'
5
 import {
9
 import {
6
   commandKey,
10
   commandKey,
7
   deepCompareArrays,
11
   deepCompareArrays,
9
   isMobile,
13
   isMobile,
10
 } from 'utils/utils'
14
 } from 'utils/utils'
11
 import state, { useSelector } from 'state'
15
 import state, { useSelector } from 'state'
12
-import { MoveType, ShapeType } from 'types'
16
+import {
17
+  AlignType,
18
+  DistributeType,
19
+  MoveType,
20
+  ShapeType,
21
+  StretchType,
22
+} from 'types'
13
 import React, { useRef } from 'react'
23
 import React, { useRef } from 'react'
14
-import { ChevronRightIcon } from '@radix-ui/react-icons'
24
+import {
25
+  ChevronRightIcon,
26
+  AlignBottomIcon,
27
+  AlignCenterHorizontallyIcon,
28
+  AlignCenterVerticallyIcon,
29
+  AlignLeftIcon,
30
+  AlignRightIcon,
31
+  AlignTopIcon,
32
+  SpaceEvenlyHorizontallyIcon,
33
+  SpaceEvenlyVerticallyIcon,
34
+  StretchHorizontallyIcon,
35
+  StretchVerticallyIcon,
36
+} from '@radix-ui/react-icons'
37
+
38
+function alignTop() {
39
+  state.send('ALIGNED', { type: AlignType.Top })
40
+}
41
+
42
+function alignCenterVertical() {
43
+  state.send('ALIGNED', { type: AlignType.CenterVertical })
44
+}
45
+
46
+function alignBottom() {
47
+  state.send('ALIGNED', { type: AlignType.Bottom })
48
+}
49
+
50
+function stretchVertically() {
51
+  state.send('STRETCHED', { type: StretchType.Vertical })
52
+}
53
+
54
+function distributeVertically() {
55
+  state.send('DISTRIBUTED', { type: DistributeType.Vertical })
56
+}
57
+
58
+function alignLeft() {
59
+  state.send('ALIGNED', { type: AlignType.Left })
60
+}
61
+
62
+function alignCenterHorizontal() {
63
+  state.send('ALIGNED', { type: AlignType.CenterHorizontal })
64
+}
65
+
66
+function alignRight() {
67
+  state.send('ALIGNED', { type: AlignType.Right })
68
+}
69
+
70
+function stretchHorizontally() {
71
+  state.send('STRETCHED', { type: StretchType.Horizontal })
72
+}
73
+
74
+function distributeHorizontally() {
75
+  state.send('DISTRIBUTED', { type: DistributeType.Horizontal })
76
+}
15
 
77
 
16
 export default function ContextMenu({
78
 export default function ContextMenu({
17
   children,
79
   children,
26
   const rContent = useRef<HTMLDivElement>(null)
88
   const rContent = useRef<HTMLDivElement>(null)
27
 
89
 
28
   const hasGroupSelectd = selectedShapes.some((s) => s.type === ShapeType.Group)
90
   const hasGroupSelectd = selectedShapes.some((s) => s.type === ShapeType.Group)
29
-  const hasMultipleSelected = selectedShapes.length > 1
91
+  const hasTwoOrMore = selectedShapes.length > 1
92
+  const hasThreeOrMore = selectedShapes.length > 2
30
 
93
 
31
   return (
94
   return (
32
     <_ContextMenu.Root>
95
     <_ContextMenu.Root>
58
             </Button>
121
             </Button>
59
             <StyledDivider />
122
             <StyledDivider />
60
             {hasGroupSelectd ||
123
             {hasGroupSelectd ||
61
-              (hasMultipleSelected && (
124
+              (hasTwoOrMore && (
62
                 <>
125
                 <>
63
                   {hasGroupSelectd && (
126
                   {hasGroupSelectd && (
64
                     <Button onSelect={() => state.send('UNGROUPED')}>
127
                     <Button onSelect={() => state.send('UNGROUPED')}>
70
                       </kbd>
133
                       </kbd>
71
                     </Button>
134
                     </Button>
72
                   )}
135
                   )}
73
-                  {hasMultipleSelected && (
136
+                  {hasTwoOrMore && (
74
                     <Button onSelect={() => state.send('GROUPED')}>
137
                     <Button onSelect={() => state.send('GROUPED')}>
75
                       <span>Group</span>
138
                       <span>Group</span>
76
                       <kbd>
139
                       <kbd>
138
                 </kbd>
201
                 </kbd>
139
               </Button>
202
               </Button>
140
             </SubMenu>
203
             </SubMenu>
204
+            {hasTwoOrMore && (
205
+              <AlignDistributeSubMenu
206
+                hasTwoOrMore={hasTwoOrMore}
207
+                hasThreeOrMore={hasThreeOrMore}
208
+              />
209
+            )}
141
             <MoveToPageMenu />
210
             <MoveToPageMenu />
142
             <StyledDivider />
211
             <StyledDivider />
143
             <Button onSelect={() => state.send('DELETED')}>
212
             <Button onSelect={() => state.send('DELETED')}>
232
   )
301
   )
233
 }
302
 }
234
 
303
 
304
+function IconButton({
305
+  onSelect,
306
+  children,
307
+  disabled = false,
308
+}: {
309
+  onSelect: () => void
310
+  disabled?: boolean
311
+  children: React.ReactNode
312
+}) {
313
+  return (
314
+    <_ContextMenu.Item
315
+      as={_IconButton}
316
+      bp={{ '@initial': 'mobile', '@sm': 'small' }}
317
+      disabled={disabled}
318
+      onSelect={onSelect}
319
+    >
320
+      {children}
321
+    </_ContextMenu.Item>
322
+  )
323
+}
324
+
235
 function SubMenu({
325
 function SubMenu({
236
   children,
326
   children,
237
   label,
327
   label,
258
   )
348
   )
259
 }
349
 }
260
 
350
 
351
+function AlignDistributeSubMenu({
352
+  hasTwoOrMore,
353
+  hasThreeOrMore,
354
+}: {
355
+  hasTwoOrMore: boolean
356
+  hasThreeOrMore: boolean
357
+}) {
358
+  return (
359
+    <_ContextMenu.Root>
360
+      <_ContextMenu.TriggerItem
361
+        as={RowButton}
362
+        bp={{ '@initial': 'mobile', '@sm': 'small' }}
363
+      >
364
+        <span>Align / Distribute</span>
365
+        <IconWrapper size="small">
366
+          <ChevronRightIcon />
367
+        </IconWrapper>
368
+      </_ContextMenu.TriggerItem>
369
+      <StyledGrid
370
+        sideOffset={2}
371
+        alignOffset={-2}
372
+        isMobile={isMobile()}
373
+        selectedStyle={hasThreeOrMore ? 'threeOrMore' : 'twoOrMore'}
374
+      >
375
+        <IconButton onSelect={alignLeft}>
376
+          <AlignLeftIcon />
377
+        </IconButton>
378
+        <IconButton onSelect={alignCenterHorizontal}>
379
+          <AlignCenterHorizontallyIcon />
380
+        </IconButton>
381
+        <IconButton onSelect={alignRight}>
382
+          <AlignRightIcon />
383
+        </IconButton>
384
+        <IconButton onSelect={stretchHorizontally}>
385
+          <StretchHorizontallyIcon />
386
+        </IconButton>
387
+        {hasThreeOrMore && (
388
+          <IconButton onSelect={distributeHorizontally}>
389
+            <SpaceEvenlyHorizontallyIcon />
390
+          </IconButton>
391
+        )}
392
+
393
+        <IconButton onSelect={alignTop}>
394
+          <AlignTopIcon />
395
+        </IconButton>
396
+        <IconButton onSelect={alignCenterVertical}>
397
+          <AlignCenterVerticallyIcon />
398
+        </IconButton>
399
+        <IconButton onSelect={alignBottom}>
400
+          <AlignBottomIcon />
401
+        </IconButton>
402
+        <IconButton onSelect={stretchVertically}>
403
+          <StretchVerticallyIcon />
404
+        </IconButton>
405
+        {hasThreeOrMore && (
406
+          <IconButton onSelect={distributeVertically}>
407
+            <SpaceEvenlyVerticallyIcon />
408
+          </IconButton>
409
+        )}
410
+        <StyledArrow offset={13} />
411
+      </StyledGrid>
412
+    </_ContextMenu.Root>
413
+  )
414
+}
415
+
416
+const StyledGrid = styled(StyledContent, {
417
+  display: 'grid',
418
+  variants: {
419
+    selectedStyle: {
420
+      threeOrMore: {
421
+        gridTemplateColumns: 'repeat(5, auto)',
422
+      },
423
+      twoOrMore: {
424
+        gridTemplateColumns: 'repeat(4, auto)',
425
+      },
426
+    },
427
+  },
428
+})
429
+
261
 function MoveToPageMenu() {
430
 function MoveToPageMenu() {
262
   const documentPages = useSelector((s) => s.data.document.pages)
431
   const documentPages = useSelector((s) => s.data.document.pages)
263
   const currentPageId = useSelector((s) => s.data.currentPageId)
432
   const currentPageId = useSelector((s) => s.data.currentPageId)
268
     .sort((a, b) => a.childIndex - b.childIndex)
437
     .sort((a, b) => a.childIndex - b.childIndex)
269
     .filter((a) => a.id !== currentPageId)
438
     .filter((a) => a.id !== currentPageId)
270
 
439
 
440
+  if (sorted.length === 0) return null
441
+
271
   return (
442
   return (
272
     <_ContextMenu.Root>
443
     <_ContextMenu.Root>
273
       <_ContextMenu.TriggerItem
444
       <_ContextMenu.TriggerItem

+ 1
- 1
components/canvas/shape.tsx Ver arquivo

7
 import useShapeEvents from 'hooks/useShapeEvents'
7
 import useShapeEvents from 'hooks/useShapeEvents'
8
 import vec from 'utils/vec'
8
 import vec from 'utils/vec'
9
 import { getShapeStyle } from 'lib/shape-styles'
9
 import { getShapeStyle } from 'lib/shape-styles'
10
-import ContextMenu from 'components/context-menu'
10
+import ContextMenu from 'components/canvas/context-menu/context-menu'
11
 
11
 
12
 interface ShapeProps {
12
 interface ShapeProps {
13
   id: string
13
   id: string

+ 1
- 1
components/editor.tsx Ver arquivo

9
 import { useSelector } from 'state'
9
 import { useSelector } from 'state'
10
 import styled from 'styles'
10
 import styled from 'styles'
11
 import PagePanel from './page-panel/page-panel'
11
 import PagePanel from './page-panel/page-panel'
12
-import ContextMenu from './context-menu'
12
+import ContextMenu from './canvas/context-menu/context-menu'
13
 
13
 
14
 export default function Editor() {
14
 export default function Editor() {
15
   useKeyboardEvents()
15
   useKeyboardEvents()

+ 0
- 1
components/shared.tsx Ver arquivo

2
 import * as RadioGroup from '@radix-ui/react-radio-group'
2
 import * as RadioGroup from '@radix-ui/react-radio-group'
3
 import * as Panel from './panel'
3
 import * as Panel from './panel'
4
 import styled from 'styles'
4
 import styled from 'styles'
5
-import Tooltip from './tooltip'
6
 
5
 
7
 export const IconButton = styled('button', {
6
 export const IconButton = styled('button', {
8
   height: '32px',
7
   height: '32px',

+ 10
- 2
state/commands/create-page.ts Ver arquivo

5
 import { current } from 'immer'
5
 import { current } from 'immer'
6
 import storage from 'state/storage'
6
 import storage from 'state/storage'
7
 
7
 
8
-export default function createPage(data: Data) {
8
+export default function createPage(data: Data, goToPage = true) {
9
   const snapshot = getSnapshot(data)
9
   const snapshot = getSnapshot(data)
10
 
10
 
11
   history.execute(
11
   history.execute(
14
       name: 'change_page',
14
       name: 'change_page',
15
       category: 'canvas',
15
       category: 'canvas',
16
       do(data) {
16
       do(data) {
17
-        const { page, pageState } = snapshot
17
+        const { page, pageState, currentPageId } = snapshot
18
         data.document.pages[page.id] = page
18
         data.document.pages[page.id] = page
19
         data.pageStates[page.id] = pageState
19
         data.pageStates[page.id] = pageState
20
+
20
         data.currentPageId = page.id
21
         data.currentPageId = page.id
21
         storage.savePage(data, data.document.id, page.id)
22
         storage.savePage(data, data.document.id, page.id)
22
         storage.saveDocumentToLocalStorage(data)
23
         storage.saveDocumentToLocalStorage(data)
24
+
25
+        if (goToPage) {
26
+          data.currentPageId = page.id
27
+        } else {
28
+          data.currentPageId = currentPageId
29
+        }
23
       },
30
       },
24
       undo(data) {
31
       undo(data) {
25
         const { page, currentPageId } = snapshot
32
         const { page, currentPageId } = snapshot
46
     childIndex: pages.length,
53
     childIndex: pages.length,
47
     shapes: {},
54
     shapes: {},
48
   }
55
   }
56
+
49
   const pageState: PageState = {
57
   const pageState: PageState = {
50
     id,
58
     id,
51
     selectedIds: new Set([]),
59
     selectedIds: new Set([]),

+ 2
- 2
state/state.ts Ver arquivo

26
   setSelectedIds,
26
   setSelectedIds,
27
   getPageState,
27
   getPageState,
28
   getShapes,
28
   getShapes,
29
+  setToArray,
29
 } from 'utils/utils'
30
 } from 'utils/utils'
30
 import {
31
 import {
31
   Data,
32
   Data,
1001
       commands.changePage(data, payload.id)
1002
       commands.changePage(data, payload.id)
1002
     },
1003
     },
1003
     createPage(data) {
1004
     createPage(data) {
1004
-      commands.createPage(data)
1005
+      commands.createPage(data, true)
1005
     },
1006
     },
1006
     deletePage(data, payload: { id: string }) {
1007
     deletePage(data, payload: { id: string }) {
1007
       commands.deletePage(data, payload.id)
1008
       commands.deletePage(data, payload.id)
1008
     },
1009
     },
1009
-
1010
     /* --------------------- Shapes --------------------- */
1010
     /* --------------------- Shapes --------------------- */
1011
     resetShapes(data) {
1011
     resetShapes(data) {
1012
       const page = getPage(data)
1012
       const page = getPage(data)

Carregando…
Cancelar
Salvar