瀏覽代碼

Adds alignment to context menu

main
Steve Ruiz 4 年之前
父節點
當前提交
946fdbab4c

+ 1
- 1
components/canvas/canvas.tsx 查看文件

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

components/context-menu.tsx → components/canvas/context-menu/context-menu.tsx 查看文件

@@ -1,7 +1,11 @@
1 1
 import * as _ContextMenu from '@radix-ui/react-context-menu'
2 2
 import * as _Dropdown from '@radix-ui/react-dropdown-menu'
3 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 9
 import {
6 10
   commandKey,
7 11
   deepCompareArrays,
@@ -9,9 +13,67 @@ import {
9 13
   isMobile,
10 14
 } from 'utils/utils'
11 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 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 78
 export default function ContextMenu({
17 79
   children,
@@ -26,7 +88,8 @@ export default function ContextMenu({
26 88
   const rContent = useRef<HTMLDivElement>(null)
27 89
 
28 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 94
   return (
32 95
     <_ContextMenu.Root>
@@ -58,7 +121,7 @@ export default function ContextMenu({
58 121
             </Button>
59 122
             <StyledDivider />
60 123
             {hasGroupSelectd ||
61
-              (hasMultipleSelected && (
124
+              (hasTwoOrMore && (
62 125
                 <>
63 126
                   {hasGroupSelectd && (
64 127
                     <Button onSelect={() => state.send('UNGROUPED')}>
@@ -70,7 +133,7 @@ export default function ContextMenu({
70 133
                       </kbd>
71 134
                     </Button>
72 135
                   )}
73
-                  {hasMultipleSelected && (
136
+                  {hasTwoOrMore && (
74 137
                     <Button onSelect={() => state.send('GROUPED')}>
75 138
                       <span>Group</span>
76 139
                       <kbd>
@@ -138,6 +201,12 @@ export default function ContextMenu({
138 201
                 </kbd>
139 202
               </Button>
140 203
             </SubMenu>
204
+            {hasTwoOrMore && (
205
+              <AlignDistributeSubMenu
206
+                hasTwoOrMore={hasTwoOrMore}
207
+                hasThreeOrMore={hasThreeOrMore}
208
+              />
209
+            )}
141 210
             <MoveToPageMenu />
142 211
             <StyledDivider />
143 212
             <Button onSelect={() => state.send('DELETED')}>
@@ -232,6 +301,27 @@ function Button({
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 325
 function SubMenu({
236 326
   children,
237 327
   label,
@@ -258,6 +348,85 @@ function SubMenu({
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 430
 function MoveToPageMenu() {
262 431
   const documentPages = useSelector((s) => s.data.document.pages)
263 432
   const currentPageId = useSelector((s) => s.data.currentPageId)
@@ -268,6 +437,8 @@ function MoveToPageMenu() {
268 437
     .sort((a, b) => a.childIndex - b.childIndex)
269 438
     .filter((a) => a.id !== currentPageId)
270 439
 
440
+  if (sorted.length === 0) return null
441
+
271 442
   return (
272 443
     <_ContextMenu.Root>
273 444
       <_ContextMenu.TriggerItem

+ 1
- 1
components/canvas/shape.tsx 查看文件

@@ -7,7 +7,7 @@ import { ShapeStyles, ShapeType } from 'types'
7 7
 import useShapeEvents from 'hooks/useShapeEvents'
8 8
 import vec from 'utils/vec'
9 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 12
 interface ShapeProps {
13 13
   id: string

+ 1
- 1
components/editor.tsx 查看文件

@@ -9,7 +9,7 @@ import StylePanel from './style-panel/style-panel'
9 9
 import { useSelector } from 'state'
10 10
 import styled from 'styles'
11 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 14
 export default function Editor() {
15 15
   useKeyboardEvents()

+ 0
- 1
components/shared.tsx 查看文件

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

+ 10
- 2
state/commands/create-page.ts 查看文件

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

+ 2
- 2
state/state.ts 查看文件

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

Loading…
取消
儲存