瀏覽代碼

Adds quick style selects, rewrites styling

main
Steve Ruiz 3 年之前
父節點
當前提交
7c768bddf5
共有 45 個檔案被更改,包括 855 行新增618 行删除
  1. 1
    1
      components/canvas/canvas.tsx
  2. 8
    2
      components/canvas/defs.tsx
  3. 23
    19
      components/canvas/selected.tsx
  4. 31
    81
      components/canvas/shape.tsx
  5. 15
    3
      components/code-panel/code-panel.tsx
  6. 30
    24
      components/controls-panel/controls-panel.tsx
  7. 1
    1
      components/panel.tsx
  8. 18
    15
      components/shared.tsx
  9. 34
    10
      components/style-panel/align-distribute.tsx
  10. 28
    0
      components/style-panel/color-content.tsx
  11. 14
    118
      components/style-panel/color-picker.tsx
  12. 26
    41
      components/style-panel/dash-picker.tsx
  13. 28
    0
      components/style-panel/is-filled-picker.tsx
  14. 21
    0
      components/style-panel/quick-color-select.tsx
  15. 52
    0
      components/style-panel/quick-dash-select.tsx
  16. 44
    0
      components/style-panel/quick-size-select.tsx
  17. 146
    3
      components/style-panel/shared.tsx
  18. 40
    0
      components/style-panel/size-picker.tsx
  19. 42
    89
      components/style-panel/style-panel.tsx
  20. 0
    30
      components/style-panel/width-picker.tsx
  21. 2
    2
      components/tools-panel/tools-panel.tsx
  22. 2
    5
      lib/code/circle.ts
  23. 5
    4
      lib/code/dot.ts
  24. 2
    5
      lib/code/ellipse.ts
  25. 5
    4
      lib/code/line.ts
  26. 2
    5
      lib/code/polyline.ts
  27. 5
    4
      lib/code/ray.ts
  28. 2
    5
      lib/code/rectangle.ts
  29. 0
    38
      lib/colors.ts
  30. 83
    0
      lib/shape-styles.ts
  31. 15
    5
      lib/shape-utils/arrow.tsx
  32. 6
    6
      lib/shape-utils/circle.tsx
  33. 5
    3
      lib/shape-utils/dot.tsx
  34. 18
    22
      lib/shape-utils/draw.tsx
  35. 5
    6
      lib/shape-utils/ellipse.tsx
  36. 1
    1
      lib/shape-utils/index.tsx
  37. 5
    3
      lib/shape-utils/line.tsx
  38. 2
    5
      lib/shape-utils/polyline.tsx
  39. 5
    4
      lib/shape-utils/ray.tsx
  40. 5
    6
      lib/shape-utils/rectangle.tsx
  41. 9
    9
      state/commands/style.ts
  42. 16
    18
      state/data.ts
  43. 2
    2
      state/sessions/draw-session.ts
  44. 8
    8
      state/state.ts
  45. 43
    11
      types.ts

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

75
         <g ref={rGroup}>
75
         <g ref={rGroup}>
76
           <BoundsBg />
76
           <BoundsBg />
77
           <Page />
77
           <Page />
78
-          <Selected />
78
+          {/* <Selected /> */}
79
           <Bounds />
79
           <Bounds />
80
           <Handles />
80
           <Handles />
81
           <Brush />
81
           <Brush />

+ 8
- 2
components/canvas/defs.tsx 查看文件

1
 import { getShapeUtils } from 'lib/shape-utils'
1
 import { getShapeUtils } from 'lib/shape-utils'
2
+import { memo } from 'react'
2
 import { useSelector } from 'state'
3
 import { useSelector } from 'state'
3
 import { deepCompareArrays, getPage } from 'utils/utils'
4
 import { deepCompareArrays, getPage } from 'utils/utils'
4
 
5
 
5
 export default function Defs() {
6
 export default function Defs() {
7
+  const zoom = useSelector((s) => s.data.camera.zoom)
8
+
6
   const currentPageShapeIds = useSelector(({ data }) => {
9
   const currentPageShapeIds = useSelector(({ data }) => {
7
     return Object.values(getPage(data).shapes)
10
     return Object.values(getPage(data).shapes)
8
       .sort((a, b) => a.childIndex - b.childIndex)
11
       .sort((a, b) => a.childIndex - b.childIndex)
14
       {currentPageShapeIds.map((id) => (
17
       {currentPageShapeIds.map((id) => (
15
         <Def key={id} id={id} />
18
         <Def key={id} id={id} />
16
       ))}
19
       ))}
20
+      <filter id="expand">
21
+        <feMorphology operator="dilate" radius={2 / zoom} />
22
+      </filter>
17
     </defs>
23
     </defs>
18
   )
24
   )
19
 }
25
 }
20
 
26
 
21
-export function Def({ id }: { id: string }) {
27
+const Def = memo(({ id }: { id: string }) => {
22
   const shape = useSelector(({ data }) => getPage(data).shapes[id])
28
   const shape = useSelector(({ data }) => getPage(data).shapes[id])
23
   if (!shape) return null
29
   if (!shape) return null
24
   return getShapeUtils(shape).render(shape)
30
   return getShapeUtils(shape).render(shape)
25
-}
31
+})

+ 23
- 19
components/canvas/selected.tsx 查看文件

3
 import { deepCompareArrays, getPage } from 'utils/utils'
3
 import { deepCompareArrays, getPage } from 'utils/utils'
4
 import { getShapeUtils } from 'lib/shape-utils'
4
 import { getShapeUtils } from 'lib/shape-utils'
5
 import useShapeEvents from 'hooks/useShapeEvents'
5
 import useShapeEvents from 'hooks/useShapeEvents'
6
-import { useRef } from 'react'
6
+import { memo, useRef } from 'react'
7
 
7
 
8
 export default function Selected() {
8
 export default function Selected() {
9
+  const selectedIds = useSelector((s) => s.data.selectedIds)
10
+
9
   const currentPageShapeIds = useSelector(({ data }) => {
11
   const currentPageShapeIds = useSelector(({ data }) => {
10
     return Array.from(data.selectedIds.values())
12
     return Array.from(data.selectedIds.values())
11
   }, deepCompareArrays)
13
   }, deepCompareArrays)
17
   return (
19
   return (
18
     <g>
20
     <g>
19
       {currentPageShapeIds.map((id) => (
21
       {currentPageShapeIds.map((id) => (
20
-        <ShapeOutline key={id} id={id} />
22
+        <ShapeOutline key={id} id={id} isSelected={selectedIds.has(id)} />
21
       ))}
23
       ))}
22
     </g>
24
     </g>
23
   )
25
   )
24
 }
26
 }
25
 
27
 
26
-export function ShapeOutline({ id }: { id: string }) {
27
-  const rIndicator = useRef<SVGUseElement>(null)
28
+export const ShapeOutline = memo(
29
+  ({ id, isSelected }: { id: string; isSelected: boolean }) => {
30
+    const rIndicator = useRef<SVGUseElement>(null)
28
 
31
 
29
-  const shape = useSelector(({ data }) => getPage(data).shapes[id])
32
+    const shape = useSelector(({ data }) => getPage(data).shapes[id])
30
 
33
 
31
-  const events = useShapeEvents(id, rIndicator)
34
+    const events = useShapeEvents(id, rIndicator)
32
 
35
 
33
-  if (!shape) return null
36
+    if (!shape) return null
34
 
37
 
35
-  const transform = `
38
+    const transform = `
36
     rotate(${shape.rotation * (180 / Math.PI)},
39
     rotate(${shape.rotation * (180 / Math.PI)},
37
     ${getShapeUtils(shape).getCenter(shape)})
40
     ${getShapeUtils(shape).getCenter(shape)})
38
     translate(${shape.point})
41
     translate(${shape.point})
39
   `
42
   `
40
 
43
 
41
-  return (
42
-    <SelectIndicator
43
-      ref={rIndicator}
44
-      as="use"
45
-      href={'#' + id}
46
-      transform={transform}
47
-      isLocked={shape.isLocked}
48
-      {...events}
49
-    />
50
-  )
51
-}
44
+    return (
45
+      <SelectIndicator
46
+        ref={rIndicator}
47
+        as="use"
48
+        href={'#' + id}
49
+        transform={transform}
50
+        isLocked={shape.isLocked}
51
+        {...events}
52
+      />
53
+    )
54
+  }
55
+)
52
 
56
 
53
 const SelectIndicator = styled('path', {
57
 const SelectIndicator = styled('path', {
54
   zStrokeWidth: 3,
58
   zStrokeWidth: 3,

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

5
 import { getPage } from 'utils/utils'
5
 import { getPage } from 'utils/utils'
6
 import { DashStyle, ShapeStyles } from 'types'
6
 import { DashStyle, ShapeStyles } from 'types'
7
 import useShapeEvents from 'hooks/useShapeEvents'
7
 import useShapeEvents from 'hooks/useShapeEvents'
8
-import { shades, strokes } from 'lib/colors'
8
+import { getShapeStyle } from 'lib/shape-styles'
9
 
9
 
10
 function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) {
10
 function Shape({ id, isSelecting }: { id: string; isSelecting: boolean }) {
11
-  const isHovered = useSelector((state) => state.data.hoveredId === id)
12
-
13
-  const isSelected = useSelector((state) => state.values.selectedIds.has(id))
11
+  const isSelected = useSelector((s) => s.values.selectedIds.has(id))
14
 
12
 
15
   const shape = useSelector(({ data }) => getPage(data).shapes[id])
13
   const shape = useSelector(({ data }) => getPage(data).shapes[id])
16
 
14
 
25
   if (!shape) return null
23
   if (!shape) return null
26
 
24
 
27
   const center = getShapeUtils(shape).getCenter(shape)
25
   const center = getShapeUtils(shape).getCenter(shape)
26
+
28
   const transform = `
27
   const transform = `
29
   rotate(${shape.rotation * (180 / Math.PI)}, ${center})
28
   rotate(${shape.rotation * (180 / Math.PI)}, ${center})
30
   translate(${shape.point})
29
   translate(${shape.point})
31
   `
30
   `
32
 
31
 
32
+  const style = getShapeStyle(shape.style)
33
+
33
   return (
34
   return (
34
-    <StyledGroup
35
-      ref={rGroup}
36
-      isHovered={isHovered}
37
-      isSelected={isSelected}
38
-      transform={transform}
39
-      stroke={'red'}
40
-      strokeWidth={10}
41
-    >
35
+    <StyledGroup ref={rGroup} isSelected={isSelected} transform={transform}>
42
       {isSelecting && (
36
       {isSelecting && (
43
         <HoverIndicator
37
         <HoverIndicator
44
           as="use"
38
           as="use"
45
           href={'#' + id}
39
           href={'#' + id}
46
-          strokeWidth={+shape.style.strokeWidth + 8}
47
-          variant={shape.style.fill === 'none' ? 'hollow' : 'filled'}
40
+          strokeWidth={+style.strokeWidth + 4}
41
+          variant={getShapeUtils(shape).canStyleFill ? 'filled' : 'hollow'}
48
           {...events}
42
           {...events}
49
         />
43
         />
50
       )}
44
       )}
51
-      {!shape.isHidden && (
52
-        <RealShape id={id} style={sanitizeStyle(shape.style)} />
53
-      )}
45
+      {!shape.isHidden && <RealShape id={id} style={style} />}
54
     </StyledGroup>
46
     </StyledGroup>
55
   )
47
   )
56
 }
48
 }
57
 
49
 
58
-const RealShape = memo(({ id, style }: { id: string; style: ShapeStyles }) => {
59
-  return (
60
-    <StyledShape
61
-      as="use"
62
-      href={'#' + id}
63
-      {...style}
64
-      strokeDasharray={getDash(style.dash, +style.strokeWidth)}
65
-    />
66
-  )
67
-})
50
+const RealShape = memo(
51
+  ({ id, style }: { id: string; style: ReturnType<typeof getShapeStyle> }) => {
52
+    return <StyledShape as="use" href={'#' + id} {...style} />
53
+  }
54
+)
68
 
55
 
69
 const StyledShape = styled('path', {
56
 const StyledShape = styled('path', {
70
   strokeLinecap: 'round',
57
   strokeLinecap: 'round',
71
   strokeLinejoin: 'round',
58
   strokeLinejoin: 'round',
59
+  pointerEvents: 'none',
72
 })
60
 })
73
 
61
 
74
 const HoverIndicator = styled('path', {
62
 const HoverIndicator = styled('path', {
75
-  fill: 'transparent',
76
-  stroke: 'transparent',
63
+  stroke: '$selected',
77
   strokeLinecap: 'round',
64
   strokeLinecap: 'round',
78
   strokeLinejoin: 'round',
65
   strokeLinejoin: 'round',
79
   transform: 'all .2s',
66
   transform: 'all .2s',
67
+  fill: 'transparent',
68
+  filter: 'url(#expand)',
80
   variants: {
69
   variants: {
81
     variant: {
70
     variant: {
82
       hollow: {
71
       hollow: {
90
 })
79
 })
91
 
80
 
92
 const StyledGroup = styled('g', {
81
 const StyledGroup = styled('g', {
93
-  pointerEvents: 'none',
94
   [`& ${HoverIndicator}`]: {
82
   [`& ${HoverIndicator}`]: {
95
     opacity: '0',
83
     opacity: '0',
96
   },
84
   },
97
   variants: {
85
   variants: {
98
     isSelected: {
86
     isSelected: {
99
-      true: {},
100
-      false: {},
101
-    },
102
-    isHovered: {
103
-      true: {},
104
-      false: {},
105
-    },
106
-  },
107
-  compoundVariants: [
108
-    {
109
-      isSelected: true,
110
-      isHovered: true,
111
-      css: {
87
+      true: {
112
         [`& ${HoverIndicator}`]: {
88
         [`& ${HoverIndicator}`]: {
113
-          opacity: '.4',
114
-          stroke: '$selected',
89
+          opacity: '0.2',
115
         },
90
         },
116
-      },
117
-    },
118
-    {
119
-      isSelected: true,
120
-      isHovered: false,
121
-      css: {
122
-        [`& ${HoverIndicator}`]: {
123
-          opacity: '.2',
124
-          stroke: '$selected',
91
+        [`&:hover ${HoverIndicator}`]: {
92
+          opacity: '0.3',
93
+        },
94
+        [`&:active ${HoverIndicator}`]: {
95
+          opacity: '0.3',
125
         },
96
         },
126
       },
97
       },
127
-    },
128
-    {
129
-      isSelected: false,
130
-      isHovered: true,
131
-      css: {
98
+      false: {
132
         [`& ${HoverIndicator}`]: {
99
         [`& ${HoverIndicator}`]: {
133
-          opacity: '.2',
134
-          stroke: '$selected',
100
+          opacity: '0',
101
+        },
102
+        [`&:hover ${HoverIndicator}`]: {
103
+          opacity: '0.16',
135
         },
104
         },
136
       },
105
       },
137
     },
106
     },
138
-  ],
107
+  },
139
 })
108
 })
140
 
109
 
141
 function Label({ text }: { text: string }) {
110
 function Label({ text }: { text: string }) {
154
   )
123
   )
155
 }
124
 }
156
 
125
 
157
-function getDash(dash: DashStyle, s: number) {
158
-  switch (dash) {
159
-    case DashStyle.Solid: {
160
-      return 'none'
161
-    }
162
-    case DashStyle.Dashed: {
163
-      return `${s} ${s * 2}`
164
-    }
165
-    case DashStyle.Dotted: {
166
-      return `0 ${s * 1.5}`
167
-    }
168
-  }
169
-}
170
-
171
-function sanitizeStyle(style: ShapeStyles) {
172
-  const next = { ...style }
173
-  return next
174
-}
175
-
176
 export { HoverIndicator }
126
 export { HoverIndicator }
177
 
127
 
178
 export default memo(Shape)
128
 export default memo(Shape)

+ 15
- 3
components/code-panel/code-panel.tsx 查看文件

119
       {isOpen ? (
119
       {isOpen ? (
120
         <Panel.Layout>
120
         <Panel.Layout>
121
           <Panel.Header side="left">
121
           <Panel.Header side="left">
122
-            <IconButton onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}>
122
+            <IconButton
123
+              size="small"
124
+              onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}
125
+            >
123
               <X />
126
               <X />
124
             </IconButton>
127
             </IconButton>
125
             <h3>Code</h3>
128
             <h3>Code</h3>
126
             <ButtonsGroup>
129
             <ButtonsGroup>
127
               <FontSizeButtons>
130
               <FontSizeButtons>
128
                 <IconButton
131
                 <IconButton
132
+                  size="small"
129
                   disabled={!local.isIn('editingCode')}
133
                   disabled={!local.isIn('editingCode')}
130
                   onClick={() => state.send('INCREASED_CODE_FONT_SIZE')}
134
                   onClick={() => state.send('INCREASED_CODE_FONT_SIZE')}
131
                 >
135
                 >
132
                   <ChevronUp />
136
                   <ChevronUp />
133
                 </IconButton>
137
                 </IconButton>
134
                 <IconButton
138
                 <IconButton
139
+                  size="small"
135
                   disabled={!local.isIn('editingCode')}
140
                   disabled={!local.isIn('editingCode')}
136
                   onClick={() => state.send('DECREASED_CODE_FONT_SIZE')}
141
                   onClick={() => state.send('DECREASED_CODE_FONT_SIZE')}
137
                 >
142
                 >
138
                   <ChevronDown />
143
                   <ChevronDown />
139
                 </IconButton>
144
                 </IconButton>
140
               </FontSizeButtons>
145
               </FontSizeButtons>
141
-              <IconButton onClick={() => local.send('TOGGLED_DOCS')}>
146
+              <IconButton
147
+                size="small"
148
+                onClick={() => local.send('TOGGLED_DOCS')}
149
+              >
142
                 <Info />
150
                 <Info />
143
               </IconButton>
151
               </IconButton>
144
               <IconButton
152
               <IconButton
153
+                size="small"
145
                 disabled={!local.isIn('editingCode')}
154
                 disabled={!local.isIn('editingCode')}
146
                 onClick={() => local.send('SAVED_CODE')}
155
                 onClick={() => local.send('SAVED_CODE')}
147
               >
156
               >
169
           </Panel.Footer>
178
           </Panel.Footer>
170
         </Panel.Layout>
179
         </Panel.Layout>
171
       ) : (
180
       ) : (
172
-        <IconButton onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}>
181
+        <IconButton
182
+          size="small"
183
+          onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}
184
+        >
173
           <Code />
185
           <Code />
174
         </IconButton>
186
         </IconButton>
175
       )}
187
       )}

+ 30
- 24
components/controls-panel/controls-panel.tsx 查看文件

1
 /* eslint-disable @typescript-eslint/ban-ts-comment */
1
 /* eslint-disable @typescript-eslint/ban-ts-comment */
2
-import styled from "styles"
3
-import React, { useEffect, useRef } from "react"
4
-import state, { useSelector } from "state"
5
-import { X, Code, PlayCircle } from "react-feather"
6
-import { IconButton } from "components/shared"
7
-import * as Panel from "../panel"
8
-import Control from "./control"
9
-import { deepCompareArrays } from "utils/utils"
2
+import styled from 'styles'
3
+import React, { useEffect, useRef } from 'react'
4
+import state, { useSelector } from 'state'
5
+import { X, Code, PlayCircle } from 'react-feather'
6
+import { IconButton } from 'components/shared'
7
+import * as Panel from '../panel'
8
+import Control from './control'
9
+import { deepCompareArrays } from 'utils/utils'
10
 
10
 
11
 export default function ControlPanel() {
11
 export default function ControlPanel() {
12
   const rContainer = useRef<HTMLDivElement>(null)
12
   const rContainer = useRef<HTMLDivElement>(null)
21
       {isOpen ? (
21
       {isOpen ? (
22
         <Panel.Layout>
22
         <Panel.Layout>
23
           <Panel.Header>
23
           <Panel.Header>
24
-            <IconButton onClick={() => state.send("CLOSED_CODE_PANEL")}>
24
+            <IconButton
25
+              size="small"
26
+              onClick={() => state.send('CLOSED_CODE_PANEL')}
27
+            >
25
               <X />
28
               <X />
26
             </IconButton>
29
             </IconButton>
27
             <h3>Controls</h3>
30
             <h3>Controls</h3>
33
           </ControlsList>
36
           </ControlsList>
34
         </Panel.Layout>
37
         </Panel.Layout>
35
       ) : (
38
       ) : (
36
-        <IconButton onClick={() => state.send("OPENED_CODE_PANEL")}>
39
+        <IconButton
40
+          size="small"
41
+          onClick={() => state.send('OPENED_CODE_PANEL')}
42
+        >
37
           <Code />
43
           <Code />
38
         </IconButton>
44
         </IconButton>
39
       )}
45
       )}
43
 
49
 
44
 const ControlsList = styled(Panel.Content, {
50
 const ControlsList = styled(Panel.Content, {
45
   padding: 12,
51
   padding: 12,
46
-  display: "grid",
47
-  gridTemplateColumns: "1fr 4fr",
48
-  gridAutoRows: "24px",
49
-  alignItems: "center",
50
-  gridColumnGap: "8px",
51
-  gridRowGap: "8px",
52
+  display: 'grid',
53
+  gridTemplateColumns: '1fr 4fr',
54
+  gridAutoRows: '24px',
55
+  alignItems: 'center',
56
+  gridColumnGap: '8px',
57
+  gridRowGap: '8px',
52
 
58
 
53
-  "& input": {
54
-    font: "$ui",
55
-    fontSize: "$1",
56
-    border: "1px solid $inputBorder",
57
-    backgroundColor: "$input",
58
-    color: "$text",
59
-    height: "100%",
60
-    padding: "0px 6px",
59
+  '& input': {
60
+    font: '$ui',
61
+    fontSize: '$1',
62
+    border: '1px solid $inputBorder',
63
+    backgroundColor: '$input',
64
+    color: '$text',
65
+    height: '100%',
66
+    padding: '0px 6px',
61
   },
67
   },
62
 })
68
 })

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

9
   userSelect: 'none',
9
   userSelect: 'none',
10
   zIndex: 200,
10
   zIndex: 200,
11
   border: '1px solid $panel',
11
   border: '1px solid $panel',
12
-  boxShadow: '0px 2px 4px rgba(0,0,0,.12)',
12
+  boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
13
 
13
 
14
   variants: {
14
   variants: {
15
     isOpen: {
15
     isOpen: {

+ 18
- 15
components/shared.tsx 查看文件

7
   borderRadius: '4px',
7
   borderRadius: '4px',
8
   padding: '0',
8
   padding: '0',
9
   margin: '0',
9
   margin: '0',
10
-  display: 'flex',
10
+  display: 'grid',
11
   alignItems: 'center',
11
   alignItems: 'center',
12
   justifyContent: 'center',
12
   justifyContent: 'center',
13
   outline: 'none',
13
   outline: 'none',
15
   pointerEvents: 'all',
15
   pointerEvents: 'all',
16
   cursor: 'pointer',
16
   cursor: 'pointer',
17
 
17
 
18
+  '& > *': {
19
+    gridRow: 1,
20
+    gridColumn: 1,
21
+  },
22
+
18
   '&:hover:not(:disabled)': {
23
   '&:hover:not(:disabled)': {
19
     backgroundColor: '$hover',
24
     backgroundColor: '$hover',
20
   },
25
   },
23
     opacity: '0.5',
28
     opacity: '0.5',
24
   },
29
   },
25
 
30
 
26
-  '& > svg': {
27
-    height: '16px',
28
-    width: '16px',
29
-  },
30
-
31
   variants: {
31
   variants: {
32
     size: {
32
     size: {
33
-      small: {},
33
+      small: {
34
+        '& > svg': {
35
+          height: '16px',
36
+          width: '16px',
37
+        },
38
+      },
34
       medium: {
39
       medium: {
35
         height: 44,
40
         height: 44,
36
         width: 44,
41
         width: 44,
37
-        '& svg': {
38
-          height: 16,
39
-          width: 16,
40
-          strokeWidth: 0,
42
+        '& > svg': {
43
+          height: '16px',
44
+          width: '16px',
41
         },
45
         },
42
       },
46
       },
43
       large: {
47
       large: {
44
         height: 44,
48
         height: 44,
45
         width: 44,
49
         width: 44,
46
-        '& svg': {
47
-          height: 24,
48
-          width: 24,
49
-          strokeWidth: 0,
50
+        '& > svg': {
51
+          height: '24px',
52
+          width: '24px',
50
         },
53
         },
51
       },
54
       },
52
     },
55
     },

+ 34
- 10
components/style-panel/align-distribute.tsx 查看文件

64
 }) {
64
 }) {
65
   return (
65
   return (
66
     <Container>
66
     <Container>
67
-      <IconButton disabled={!hasTwoOrMore} onClick={alignLeft}>
67
+      <IconButton size="small" disabled={!hasTwoOrMore} onClick={alignLeft}>
68
         <AlignLeftIcon />
68
         <AlignLeftIcon />
69
       </IconButton>
69
       </IconButton>
70
-      <IconButton disabled={!hasTwoOrMore} onClick={alignCenterHorizontal}>
70
+      <IconButton
71
+        size="small"
72
+        disabled={!hasTwoOrMore}
73
+        onClick={alignCenterHorizontal}
74
+      >
71
         <AlignCenterHorizontallyIcon />
75
         <AlignCenterHorizontallyIcon />
72
       </IconButton>
76
       </IconButton>
73
-      <IconButton disabled={!hasTwoOrMore} onClick={alignRight}>
77
+      <IconButton size="small" disabled={!hasTwoOrMore} onClick={alignRight}>
74
         <AlignRightIcon />
78
         <AlignRightIcon />
75
       </IconButton>
79
       </IconButton>
76
-      <IconButton disabled={!hasTwoOrMore} onClick={stretchHorizontally}>
80
+      <IconButton
81
+        size="small"
82
+        disabled={!hasTwoOrMore}
83
+        onClick={stretchHorizontally}
84
+      >
77
         <StretchHorizontallyIcon />
85
         <StretchHorizontallyIcon />
78
       </IconButton>
86
       </IconButton>
79
-      <IconButton disabled={!hasThreeOrMore} onClick={distributeHorizontally}>
87
+      <IconButton
88
+        size="small"
89
+        disabled={!hasThreeOrMore}
90
+        onClick={distributeHorizontally}
91
+      >
80
         <SpaceEvenlyHorizontallyIcon />
92
         <SpaceEvenlyHorizontallyIcon />
81
       </IconButton>
93
       </IconButton>
82
-      <IconButton disabled={!hasTwoOrMore} onClick={alignTop}>
94
+      <IconButton size="small" disabled={!hasTwoOrMore} onClick={alignTop}>
83
         <AlignTopIcon />
95
         <AlignTopIcon />
84
       </IconButton>
96
       </IconButton>
85
-      <IconButton disabled={!hasTwoOrMore} onClick={alignCenterVertical}>
97
+      <IconButton
98
+        size="small"
99
+        disabled={!hasTwoOrMore}
100
+        onClick={alignCenterVertical}
101
+      >
86
         <AlignCenterVerticallyIcon />
102
         <AlignCenterVerticallyIcon />
87
       </IconButton>
103
       </IconButton>
88
-      <IconButton disabled={!hasTwoOrMore} onClick={alignBottom}>
104
+      <IconButton size="small" disabled={!hasTwoOrMore} onClick={alignBottom}>
89
         <AlignBottomIcon />
105
         <AlignBottomIcon />
90
       </IconButton>
106
       </IconButton>
91
-      <IconButton disabled={!hasTwoOrMore} onClick={stretchVertically}>
107
+      <IconButton
108
+        size="small"
109
+        disabled={!hasTwoOrMore}
110
+        onClick={stretchVertically}
111
+      >
92
         <StretchVerticallyIcon />
112
         <StretchVerticallyIcon />
93
       </IconButton>
113
       </IconButton>
94
-      <IconButton disabled={!hasThreeOrMore} onClick={distributeVertically}>
114
+      <IconButton
115
+        size="small"
116
+        disabled={!hasThreeOrMore}
117
+        onClick={distributeVertically}
118
+      >
95
         <SpaceEvenlyVerticallyIcon />
119
         <SpaceEvenlyVerticallyIcon />
96
       </IconButton>
120
       </IconButton>
97
     </Container>
121
     </Container>

+ 28
- 0
components/style-panel/color-content.tsx 查看文件

1
+import { IconButton } from 'components/shared'
2
+import { strokes } from 'lib/shape-styles'
3
+import { ColorStyle } from 'types'
4
+import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
5
+import { Square } from 'react-feather'
6
+import styled from 'styles'
7
+import { DropdownContent } from './shared'
8
+
9
+export default function ColorContent({
10
+  onChange,
11
+}: {
12
+  onChange: (color: ColorStyle) => void
13
+}) {
14
+  return (
15
+    <DropdownContent sideOffset={0} side="bottom">
16
+      {Object.keys(strokes).map((color: ColorStyle) => (
17
+        <DropdownMenu.DropdownMenuItem
18
+          as={IconButton}
19
+          key={color}
20
+          title={color}
21
+          onSelect={() => onChange(color)}
22
+        >
23
+          <Square fill={strokes[color]} stroke="none" size="22" />
24
+        </DropdownMenu.DropdownMenuItem>
25
+      ))}
26
+    </DropdownContent>
27
+  )
28
+}

+ 14
- 118
components/style-panel/color-picker.tsx 查看文件

1
 import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
1
 import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
2
+import { strokes } from 'lib/shape-styles'
3
+import { ColorStyle } from 'types'
4
+import { IconWrapper, RowButton } from './shared'
2
 import { Square } from 'react-feather'
5
 import { Square } from 'react-feather'
3
-import styled from 'styles'
6
+import ColorContent from './color-content'
4
 
7
 
5
 interface Props {
8
 interface Props {
6
-  colors: Record<string, string>
7
-  onChange: (color: string) => void
8
-  children: React.ReactNode
9
+  color: ColorStyle
10
+  onChange: (color: ColorStyle) => void
9
 }
11
 }
10
 
12
 
11
-export default function ColorPicker({ colors, onChange, children }: Props) {
13
+export default function ColorPicker({ color, onChange }: Props) {
12
   return (
14
   return (
13
     <DropdownMenu.Root>
15
     <DropdownMenu.Root>
14
-      {children}
15
-      <Colors sideOffset={4}>
16
-        {Object.entries(colors).map(([name, color]) => (
17
-          <ColorButton key={name} title={name} onSelect={() => onChange(name)}>
18
-            <ColorIcon color={color} />
19
-          </ColorButton>
20
-        ))}
21
-      </Colors>
16
+      <DropdownMenu.Trigger as={RowButton}>
17
+        <label htmlFor="color">Color</label>
18
+        <IconWrapper>
19
+          <Square fill={strokes[color]} />
20
+        </IconWrapper>
21
+      </DropdownMenu.Trigger>
22
+      <ColorContent onChange={onChange} />
22
     </DropdownMenu.Root>
23
     </DropdownMenu.Root>
23
   )
24
   )
24
 }
25
 }
25
-
26
-export function ColorIcon({ color }: { color: string }) {
27
-  return (
28
-    <Square fill={color} strokeDasharray={color === 'none' ? '2, 3' : 'none'} />
29
-  )
30
-}
31
-
32
-export const Colors = styled(DropdownMenu.Content, {
33
-  display: 'grid',
34
-  padding: 4,
35
-  gridTemplateColumns: 'repeat(6, 1fr)',
36
-  border: '1px solid $border',
37
-  backgroundColor: '$panel',
38
-  borderRadius: 4,
39
-  boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
40
-})
41
-
42
-export const ColorButton = styled(DropdownMenu.Item, {
43
-  position: 'relative',
44
-  cursor: 'pointer',
45
-  height: 32,
46
-  width: 32,
47
-  border: 'none',
48
-  padding: 'none',
49
-  background: 'none',
50
-  display: 'flex',
51
-  alignItems: 'center',
52
-  justifyContent: 'center',
53
-
54
-  '&::before': {
55
-    content: "''",
56
-    position: 'absolute',
57
-    top: 4,
58
-    left: 4,
59
-    right: 4,
60
-    bottom: 4,
61
-    pointerEvents: 'none',
62
-    zIndex: 0,
63
-  },
64
-
65
-  '&:hover::before': {
66
-    backgroundColor: '$hover',
67
-    borderRadius: 4,
68
-  },
69
-
70
-  '& svg': {
71
-    position: 'relative',
72
-    stroke: 'rgba(0,0,0,.2)',
73
-    strokeWidth: 1,
74
-    zIndex: 1,
75
-  },
76
-})
77
-
78
-export const CurrentColor = styled(DropdownMenu.Trigger, {
79
-  position: 'relative',
80
-  display: 'flex',
81
-  width: '100%',
82
-  background: 'none',
83
-  border: 'none',
84
-  cursor: 'pointer',
85
-  outline: 'none',
86
-  alignItems: 'center',
87
-  justifyContent: 'space-between',
88
-  padding: '4px 6px 4px 12px',
89
-
90
-  '&::before': {
91
-    content: "''",
92
-    position: 'absolute',
93
-    top: 0,
94
-    left: 0,
95
-    right: 0,
96
-    bottom: 0,
97
-    pointerEvents: 'none',
98
-    zIndex: -1,
99
-  },
100
-
101
-  '&:hover::before': {
102
-    backgroundColor: '$hover',
103
-    borderRadius: 4,
104
-  },
105
-
106
-  '& label': {
107
-    fontFamily: '$ui',
108
-    fontSize: '$2',
109
-    fontWeight: '$1',
110
-    margin: 0,
111
-    padding: 0,
112
-  },
113
-
114
-  '& svg': {
115
-    position: 'relative',
116
-    stroke: 'rgba(0,0,0,.2)',
117
-    strokeWidth: 1,
118
-    zIndex: 1,
119
-  },
120
-
121
-  variants: {
122
-    size: {
123
-      icon: {
124
-        padding: '4px ',
125
-        width: 'auto',
126
-      },
127
-    },
128
-  },
129
-})

+ 26
- 41
components/style-panel/dash-picker.tsx 查看文件

1
-import { Group, RadioItem } from './shared'
1
+import {
2
+  Group,
3
+  Item,
4
+  DashDashedIcon,
5
+  DashDottedIcon,
6
+  DashSolidIcon,
7
+} from './shared'
8
+import * as RadioGroup from '@radix-ui/react-radio-group'
2
 import { DashStyle } from 'types'
9
 import { DashStyle } from 'types'
3
 import state from 'state'
10
 import state from 'state'
4
 import { ChangeEvent } from 'react'
11
 import { ChangeEvent } from 'react'
16
 export default function DashPicker({ dash }: Props) {
23
 export default function DashPicker({ dash }: Props) {
17
   return (
24
   return (
18
     <Group name="Dash" onValueChange={handleChange}>
25
     <Group name="Dash" onValueChange={handleChange}>
19
-      <RadioItem value={DashStyle.Solid} isActive={dash === DashStyle.Solid}>
26
+      <Item
27
+        as={RadioGroup.RadioGroupItem}
28
+        value={DashStyle.Solid}
29
+        isActive={dash === DashStyle.Solid}
30
+      >
20
         <DashSolidIcon />
31
         <DashSolidIcon />
21
-      </RadioItem>
22
-      <RadioItem value={DashStyle.Dashed} isActive={dash === DashStyle.Dashed}>
32
+      </Item>
33
+      <Item
34
+        as={RadioGroup.RadioGroupItem}
35
+        value={DashStyle.Dashed}
36
+        isActive={dash === DashStyle.Dashed}
37
+      >
23
         <DashDashedIcon />
38
         <DashDashedIcon />
24
-      </RadioItem>
25
-      <RadioItem value={DashStyle.Dotted} isActive={dash === DashStyle.Dotted}>
39
+      </Item>
40
+      <Item
41
+        as={RadioGroup.RadioGroupItem}
42
+        value={DashStyle.Dotted}
43
+        isActive={dash === DashStyle.Dotted}
44
+      >
26
         <DashDottedIcon />
45
         <DashDottedIcon />
27
-      </RadioItem>
46
+      </Item>
28
     </Group>
47
     </Group>
29
   )
48
   )
30
 }
49
 }
31
-
32
-function DashSolidIcon() {
33
-  return (
34
-    <svg width="16" height="16">
35
-      <path d="M 3,8 L 13,8" strokeWidth={3} strokeLinecap="round" />
36
-    </svg>
37
-  )
38
-}
39
-
40
-function DashDashedIcon() {
41
-  return (
42
-    <svg width="16" height="16">
43
-      <path
44
-        d="M 2,8 L 14,8"
45
-        strokeWidth={3}
46
-        strokeLinecap="round"
47
-        strokeDasharray="4 4"
48
-      />
49
-    </svg>
50
-  )
51
-}
52
-
53
-function DashDottedIcon() {
54
-  return (
55
-    <svg width="16" height="16">
56
-      <path
57
-        d="M 3,8 L 14,8"
58
-        strokeWidth={3}
59
-        strokeLinecap="round"
60
-        strokeDasharray="1 4"
61
-      />
62
-    </svg>
63
-  )
64
-}

+ 28
- 0
components/style-panel/is-filled-picker.tsx 查看文件

1
+import * as Checkbox from '@radix-ui/react-checkbox'
2
+import { CheckIcon } from '@radix-ui/react-icons'
3
+import { strokes } from 'lib/shape-styles'
4
+import { Square } from 'react-feather'
5
+import { IconWrapper, RowButton } from './shared'
6
+
7
+interface Props {
8
+  isFilled: boolean
9
+  onChange: (isFilled: boolean) => void
10
+}
11
+
12
+export default function IsFilledPicker({ isFilled, onChange }: Props) {
13
+  return (
14
+    <Checkbox.Root
15
+      as={RowButton}
16
+      checked={isFilled}
17
+      onCheckedChange={(e: React.ChangeEvent<HTMLInputElement>) =>
18
+        onChange(e.currentTarget.checked)
19
+      }
20
+    >
21
+      <label htmlFor="fill">Fill</label>
22
+      <IconWrapper>
23
+        {isFilled || <Square stroke={strokes.Black} />}
24
+        <Checkbox.Indicator as={CheckIcon} />
25
+      </IconWrapper>
26
+    </Checkbox.Root>
27
+  )
28
+}

+ 21
- 0
components/style-panel/quick-color-select.tsx 查看文件

1
+import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
2
+import { IconButton } from 'components/shared'
3
+import { strokes } from 'lib/shape-styles'
4
+import { Square } from 'react-feather'
5
+import state, { useSelector } from 'state'
6
+import ColorContent from './color-content'
7
+
8
+export default function QuickColorSelect() {
9
+  const color = useSelector((s) => s.values.selectedStyle.color)
10
+
11
+  return (
12
+    <DropdownMenu.Root>
13
+      <DropdownMenu.Trigger as={IconButton} title="color">
14
+        <Square fill={strokes[color]} stroke={strokes[color]} />
15
+      </DropdownMenu.Trigger>
16
+      <ColorContent
17
+        onChange={(color) => state.send('CHANGED_STYLE', { color })}
18
+      />
19
+    </DropdownMenu.Root>
20
+  )
21
+}

+ 52
- 0
components/style-panel/quick-dash-select.tsx 查看文件

1
+import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
2
+import { IconButton } from 'components/shared'
3
+import state, { useSelector } from 'state'
4
+import { DashStyle } from 'types'
5
+import {
6
+  DropdownContent,
7
+  Item,
8
+  DashDottedIcon,
9
+  DashSolidIcon,
10
+  DashDashedIcon,
11
+} from './shared'
12
+
13
+const dashes = {
14
+  [DashStyle.Solid]: <DashSolidIcon />,
15
+  [DashStyle.Dashed]: <DashDashedIcon />,
16
+  [DashStyle.Dotted]: <DashDottedIcon />,
17
+}
18
+
19
+export default function QuickdashSelect() {
20
+  const dash = useSelector((s) => s.values.selectedStyle.dash)
21
+
22
+  return (
23
+    <DropdownMenu.Root>
24
+      <DropdownMenu.Trigger as={IconButton} title="dash">
25
+        {dashes[dash]}
26
+      </DropdownMenu.Trigger>
27
+      <DropdownContent direction="vertical">
28
+        <DashItem isActive={dash === DashStyle.Solid} dash={DashStyle.Solid} />
29
+        <DashItem
30
+          isActive={dash === DashStyle.Dashed}
31
+          dash={DashStyle.Dashed}
32
+        />
33
+        <DashItem
34
+          isActive={dash === DashStyle.Dotted}
35
+          dash={DashStyle.Dotted}
36
+        />
37
+      </DropdownContent>
38
+    </DropdownMenu.Root>
39
+  )
40
+}
41
+
42
+function DashItem({ dash, isActive }: { isActive: boolean; dash: DashStyle }) {
43
+  return (
44
+    <Item
45
+      as={DropdownMenu.DropdownMenuItem}
46
+      isActive={isActive}
47
+      onSelect={() => state.send('CHANGED_STYLE', { dash })}
48
+    >
49
+      {dashes[dash]}
50
+    </Item>
51
+  )
52
+}

+ 44
- 0
components/style-panel/quick-size-select.tsx 查看文件

1
+import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
2
+import { IconButton } from 'components/shared'
3
+import { Circle } from 'react-feather'
4
+import state, { useSelector } from 'state'
5
+import { SizeStyle } from 'types'
6
+import { DropdownContent, Item } from './shared'
7
+
8
+const sizes = {
9
+  [SizeStyle.Small]: 6,
10
+  [SizeStyle.Medium]: 12,
11
+  [SizeStyle.Large]: 22,
12
+}
13
+
14
+export default function QuickSizeSelect() {
15
+  const size = useSelector((s) => s.values.selectedStyle.size)
16
+
17
+  return (
18
+    <DropdownMenu.Root>
19
+      <DropdownMenu.Trigger as={IconButton} title="size">
20
+        <Circle size={sizes[size]} stroke="none" fill="currentColor" />
21
+      </DropdownMenu.Trigger>
22
+      <DropdownContent direction="vertical">
23
+        <SizeItem isActive={size === SizeStyle.Small} size={SizeStyle.Small} />
24
+        <SizeItem
25
+          isActive={size === SizeStyle.Medium}
26
+          size={SizeStyle.Medium}
27
+        />
28
+        <SizeItem isActive={size === SizeStyle.Large} size={SizeStyle.Large} />
29
+      </DropdownContent>
30
+    </DropdownMenu.Root>
31
+  )
32
+}
33
+
34
+function SizeItem({ size, isActive }: { isActive: boolean; size: SizeStyle }) {
35
+  return (
36
+    <Item
37
+      as={DropdownMenu.DropdownMenuItem}
38
+      isActive={isActive}
39
+      onSelect={() => state.send('CHANGED_STYLE', { size })}
40
+    >
41
+      <Circle size={sizes[size]} />
42
+    </Item>
43
+  )
44
+}

+ 146
- 3
components/style-panel/shared.tsx 查看文件

1
+import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
1
 import * as RadioGroup from '@radix-ui/react-radio-group'
2
 import * as RadioGroup from '@radix-ui/react-radio-group'
2
 import * as Panel from '../panel'
3
 import * as Panel from '../panel'
3
 import styled from 'styles'
4
 import styled from 'styles'
27
   display: 'flex',
28
   display: 'flex',
28
 })
29
 })
29
 
30
 
30
-export const RadioItem = styled(RadioGroup.Item, {
31
+export const Item = styled('button', {
31
   height: '32px',
32
   height: '32px',
32
   width: '32px',
33
   width: '32px',
33
   backgroundColor: '$panel',
34
   backgroundColor: '$panel',
61
         '& svg': {
62
         '& svg': {
62
           fill: '$text',
63
           fill: '$text',
63
           stroke: '$text',
64
           stroke: '$text',
64
-          strokeWidth: '0',
65
         },
65
         },
66
       },
66
       },
67
       false: {
67
       false: {
68
         '& svg': {
68
         '& svg': {
69
           fill: '$inactive',
69
           fill: '$inactive',
70
           stroke: '$inactive',
70
           stroke: '$inactive',
71
-          strokeWidth: '0',
72
         },
71
         },
73
       },
72
       },
74
     },
73
     },
75
   },
74
   },
76
 })
75
 })
76
+
77
+export const RowButton = styled('button', {
78
+  position: 'relative',
79
+  display: 'flex',
80
+  width: '100%',
81
+  background: 'none',
82
+  border: 'none',
83
+  cursor: 'pointer',
84
+  outline: 'none',
85
+  alignItems: 'center',
86
+  justifyContent: 'space-between',
87
+  padding: '4px 6px 4px 12px',
88
+
89
+  '&::before': {
90
+    content: "''",
91
+    position: 'absolute',
92
+    top: 0,
93
+    left: 0,
94
+    right: 0,
95
+    bottom: 0,
96
+    pointerEvents: 'none',
97
+    zIndex: -1,
98
+  },
99
+
100
+  '&:hover::before': {
101
+    backgroundColor: '$hover',
102
+    borderRadius: 4,
103
+  },
104
+
105
+  '& label': {
106
+    fontFamily: '$ui',
107
+    fontSize: '$2',
108
+    fontWeight: '$1',
109
+    margin: 0,
110
+    padding: 0,
111
+  },
112
+
113
+  '& svg': {
114
+    position: 'relative',
115
+    stroke: 'rgba(0,0,0,.2)',
116
+    strokeWidth: 1,
117
+    zIndex: 1,
118
+  },
119
+
120
+  variants: {
121
+    size: {
122
+      icon: {
123
+        padding: '4px ',
124
+        width: 'auto',
125
+      },
126
+    },
127
+  },
128
+})
129
+
130
+export const IconWrapper = styled('div', {
131
+  height: '100%',
132
+  borderRadius: '4px',
133
+  marginRight: '1px',
134
+  display: 'grid',
135
+  alignItems: 'center',
136
+  justifyContent: 'center',
137
+  outline: 'none',
138
+  border: 'none',
139
+  pointerEvents: 'all',
140
+  cursor: 'pointer',
141
+
142
+  '& svg': {
143
+    height: 22,
144
+    width: 22,
145
+    strokeWidth: 1,
146
+  },
147
+
148
+  '& > *': {
149
+    gridRow: 1,
150
+    gridColumn: 1,
151
+  },
152
+})
153
+
154
+export const DropdownContent = styled(DropdownMenu.Content, {
155
+  display: 'grid',
156
+  padding: 4,
157
+  gridTemplateColumns: 'repeat(4, 1fr)',
158
+  backgroundColor: '$panel',
159
+  borderRadius: 4,
160
+  border: '1px solid $panel',
161
+  boxShadow: '0px 2px 4px rgba(0,0,0,.28)',
162
+
163
+  variants: {
164
+    direction: {
165
+      vertical: {
166
+        gridTemplateColumns: '1fr',
167
+      },
168
+    },
169
+  },
170
+})
171
+
172
+export function DashSolidIcon() {
173
+  return (
174
+    <svg width="24" height="24" stroke="currentColor">
175
+      <circle
176
+        cx={12}
177
+        cy={12}
178
+        r={8}
179
+        fill="none"
180
+        strokeWidth={2}
181
+        strokeLinecap="round"
182
+      />
183
+    </svg>
184
+  )
185
+}
186
+
187
+export function DashDashedIcon() {
188
+  return (
189
+    <svg width="24" height="24" stroke="currentColor">
190
+      <circle
191
+        cx={12}
192
+        cy={12}
193
+        r={8}
194
+        fill="none"
195
+        strokeWidth={2.5}
196
+        strokeLinecap="round"
197
+        strokeDasharray={50.26548 * 0.1}
198
+      />
199
+    </svg>
200
+  )
201
+}
202
+
203
+const dottedDasharray = `${50.26548 * 0.025} ${50.26548 * 0.1}`
204
+
205
+export function DashDottedIcon() {
206
+  return (
207
+    <svg width="24" height="24" stroke="currentColor">
208
+      <circle
209
+        cx={12}
210
+        cy={12}
211
+        r={8}
212
+        fill="none"
213
+        strokeWidth={2.5}
214
+        strokeLinecap="round"
215
+        strokeDasharray={dottedDasharray}
216
+      />
217
+    </svg>
218
+  )
219
+}

+ 40
- 0
components/style-panel/size-picker.tsx 查看文件

1
+import { Group, Item } from './shared'
2
+import * as RadioGroup from '@radix-ui/react-radio-group'
3
+import { ChangeEvent } from 'react'
4
+import { Circle } from 'react-feather'
5
+import state from 'state'
6
+import { SizeStyle } from 'types'
7
+
8
+function handleChange(e: ChangeEvent<HTMLInputElement>) {
9
+  state.send('CHANGED_STYLE', {
10
+    size: e.currentTarget.value as SizeStyle,
11
+  })
12
+}
13
+
14
+export default function SizePicker({ size }: { size: SizeStyle }) {
15
+  return (
16
+    <Group name="width" onValueChange={handleChange}>
17
+      <Item
18
+        as={RadioGroup.Item}
19
+        value={SizeStyle.Small}
20
+        isActive={size === SizeStyle.Small}
21
+      >
22
+        <Circle size={6} />
23
+      </Item>
24
+      <Item
25
+        as={RadioGroup.Item}
26
+        value={SizeStyle.Medium}
27
+        isActive={size === SizeStyle.Medium}
28
+      >
29
+        <Circle size={12} />
30
+      </Item>
31
+      <Item
32
+        as={RadioGroup.Item}
33
+        value={SizeStyle.Large}
34
+        isActive={size === SizeStyle.Large}
35
+      >
36
+        <Circle size={22} />
37
+      </Item>
38
+    </Group>
39
+  )
40
+}

+ 42
- 89
components/style-panel/style-panel.tsx 查看文件

4
 import { useRef } from 'react'
4
 import { useRef } from 'react'
5
 import { IconButton } from 'components/shared'
5
 import { IconButton } from 'components/shared'
6
 import * as Checkbox from '@radix-ui/react-checkbox'
6
 import * as Checkbox from '@radix-ui/react-checkbox'
7
-import { Trash2, X } from 'react-feather'
7
+import { ChevronDown, Square, Trash2, X } from 'react-feather'
8
 import { deepCompare, deepCompareArrays, getPage } from 'utils/utils'
8
 import { deepCompare, deepCompareArrays, getPage } from 'utils/utils'
9
-import { shades, fills, strokes } from 'lib/colors'
10
-import ColorPicker, { ColorIcon, CurrentColor } from './color-picker'
9
+import { strokes } from 'lib/shape-styles'
11
 import AlignDistribute from './align-distribute'
10
 import AlignDistribute from './align-distribute'
12
-import { MoveType, ShapeStyles } from 'types'
13
-import WidthPicker from './width-picker'
11
+import { MoveType } from 'types'
12
+import SizePicker from './size-picker'
14
 import {
13
 import {
15
   ArrowDownIcon,
14
   ArrowDownIcon,
16
   ArrowUpIcon,
15
   ArrowUpIcon,
28
   RotateCounterClockwiseIcon,
27
   RotateCounterClockwiseIcon,
29
 } from '@radix-ui/react-icons'
28
 } from '@radix-ui/react-icons'
30
 import DashPicker from './dash-picker'
29
 import DashPicker from './dash-picker'
31
-
32
-const fillColors = { ...shades, ...fills }
33
-const strokeColors = { ...shades, ...strokes }
34
-const getFillColor = (color: string) => {
35
-  if (shades[color]) {
36
-    return '#fff'
37
-  }
38
-  return fillColors[color]
39
-}
30
+import QuickColorSelect from './quick-color-select'
31
+import ColorContent from './color-content'
32
+import { RowButton, IconWrapper } from './shared'
33
+import ColorPicker from './color-picker'
34
+import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
35
+import IsFilledPicker from './is-filled-picker'
36
+import QuickSizeSelect from './quick-size-select'
37
+import QuickdashSelect from './quick-dash-select'
40
 
38
 
41
 export default function StylePanel() {
39
 export default function StylePanel() {
42
   const rContainer = useRef<HTMLDivElement>(null)
40
   const rContainer = useRef<HTMLDivElement>(null)
48
         <SelectedShapeStyles />
46
         <SelectedShapeStyles />
49
       ) : (
47
       ) : (
50
         <>
48
         <>
51
-          <QuickColorSelect prop="stroke" colors={strokeColors} />
49
+          <QuickColorSelect />
50
+          <QuickSizeSelect />
51
+          <QuickdashSelect />
52
           <IconButton
52
           <IconButton
53
             title="Style"
53
             title="Style"
54
+            size="small"
54
             onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}
55
             onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}
55
           >
56
           >
56
-            <DotsVerticalIcon />
57
+            <ChevronDown />
57
           </IconButton>
58
           </IconButton>
58
         </>
59
         </>
59
       )}
60
       )}
61
   )
62
   )
62
 }
63
 }
63
 
64
 
64
-function QuickColorSelect({
65
-  prop,
66
-  colors,
67
-}: {
68
-  prop: ShapeStyles['fill'] | ShapeStyles['stroke']
69
-  colors: Record<string, string>
70
-}) {
71
-  const value = useSelector((s) => s.values.selectedStyle[prop])
72
-
73
-  return (
74
-    <ColorPicker
75
-      colors={colors}
76
-      onChange={(color) => state.send('CHANGED_STYLE', { [prop]: color })}
77
-    >
78
-      <CurrentColor size="icon" title={prop}>
79
-        <ColorIcon color={value} />
80
-      </CurrentColor>
81
-    </ColorPicker>
82
-  )
83
-}
84
-
85
 // This panel is going to be hard to keep cool, as we're selecting computed
65
 // This panel is going to be hard to keep cool, as we're selecting computed
86
 // information, based on the user's current selection. We might have to keep
66
 // information, based on the user's current selection. We might have to keep
87
 // track of this data manually within our state.
67
 // track of this data manually within our state.
88
 
68
 
89
-function SelectedShapeStyles({}: {}) {
69
+function SelectedShapeStyles() {
90
   const selectedIds = useSelector(
70
   const selectedIds = useSelector(
91
     (s) => Array.from(s.data.selectedIds.values()),
71
     (s) => Array.from(s.data.selectedIds.values()),
92
     deepCompareArrays
72
     deepCompareArrays
115
     <Panel.Layout>
95
     <Panel.Layout>
116
       <Panel.Header side="right">
96
       <Panel.Header side="right">
117
         <h3>Style</h3>
97
         <h3>Style</h3>
118
-        <IconButton onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}>
98
+        <IconButton
99
+          size="small"
100
+          onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}
101
+        >
119
           <X />
102
           <X />
120
         </IconButton>
103
         </IconButton>
121
       </Panel.Header>
104
       </Panel.Header>
122
       <Content>
105
       <Content>
123
         <ColorPicker
106
         <ColorPicker
124
-          colors={strokeColors}
125
-          onChange={(color) =>
126
-            state.send('CHANGED_STYLE', {
127
-              stroke: strokeColors[color],
128
-              fill: getFillColor(color),
129
-            })
130
-          }
131
-        >
132
-          <CurrentColor>
133
-            <label>Color</label>
134
-            <ColorIcon color={commonStyle.stroke} />
135
-          </CurrentColor>
136
-        </ColorPicker>
137
-        {/* <Row>
138
-          <label htmlFor="filled">Filled</label>
139
-          <StyledCheckbox
140
-            checked={commonStyle.isFilled}
141
-            onCheckedChange={(e: React.ChangeEvent<HTMLInputElement>) => {
142
-              console.log(e.target.value)
143
-              state.send('CHANGED_STYLE', {
144
-                isFilled: e.target.value === 'on',
145
-              })
146
-            }}
147
-          >
148
-            <Checkbox.Indicator as={CheckIcon} />
149
-          </StyledCheckbox> 
150
-        </Row>*/}
107
+          color={commonStyle.color}
108
+          onChange={(color) => state.send('CHANGED_STYLE', { color })}
109
+        />
110
+        <IsFilledPicker
111
+          isFilled={commonStyle.isFilled}
112
+          onChange={(isFilled) => state.send('CHANGED_STYLE', { isFilled })}
113
+        />
151
         <Row>
114
         <Row>
152
-          <label htmlFor="width">Width</label>
153
-          <WidthPicker strokeWidth={Number(commonStyle.strokeWidth)} />
115
+          <label htmlFor="size">Size</label>
116
+          <SizePicker size={commonStyle.size} />
154
         </Row>
117
         </Row>
155
         <Row>
118
         <Row>
156
           <label htmlFor="dash">Dash</label>
119
           <label htmlFor="dash">Dash</label>
159
         <ButtonsRow>
122
         <ButtonsRow>
160
           <IconButton
123
           <IconButton
161
             disabled={!hasSelection}
124
             disabled={!hasSelection}
125
+            size="small"
162
             onClick={() => state.send('DUPLICATED')}
126
             onClick={() => state.send('DUPLICATED')}
163
           >
127
           >
164
             <CopyIcon />
128
             <CopyIcon />
165
           </IconButton>
129
           </IconButton>
166
           <IconButton
130
           <IconButton
167
             disabled={!hasSelection}
131
             disabled={!hasSelection}
132
+            size="small"
168
             onClick={() => state.send('ROTATED_CCW')}
133
             onClick={() => state.send('ROTATED_CCW')}
169
           >
134
           >
170
             <RotateCounterClockwiseIcon />
135
             <RotateCounterClockwiseIcon />
171
           </IconButton>
136
           </IconButton>
172
           <IconButton
137
           <IconButton
173
             disabled={!hasSelection}
138
             disabled={!hasSelection}
139
+            size="small"
174
             onClick={() => state.send('TOGGLED_SHAPE_HIDE')}
140
             onClick={() => state.send('TOGGLED_SHAPE_HIDE')}
175
           >
141
           >
176
             {isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
142
             {isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
177
           </IconButton>
143
           </IconButton>
178
           <IconButton
144
           <IconButton
179
             disabled={!hasSelection}
145
             disabled={!hasSelection}
146
+            size="small"
180
             onClick={() => state.send('TOGGLED_SHAPE_LOCK')}
147
             onClick={() => state.send('TOGGLED_SHAPE_LOCK')}
181
           >
148
           >
182
             {isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
149
             {isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
183
           </IconButton>
150
           </IconButton>
184
           <IconButton
151
           <IconButton
185
             disabled={!hasSelection}
152
             disabled={!hasSelection}
153
+            size="small"
186
             onClick={() => state.send('TOGGLED_SHAPE_ASPECT_LOCK')}
154
             onClick={() => state.send('TOGGLED_SHAPE_ASPECT_LOCK')}
187
           >
155
           >
188
             {isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
156
             {isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
191
         <ButtonsRow>
159
         <ButtonsRow>
192
           <IconButton
160
           <IconButton
193
             disabled={!hasSelection}
161
             disabled={!hasSelection}
162
+            size="small"
194
             onClick={() => state.send('MOVED', { type: MoveType.ToBack })}
163
             onClick={() => state.send('MOVED', { type: MoveType.ToBack })}
195
           >
164
           >
196
             <PinBottomIcon />
165
             <PinBottomIcon />
197
           </IconButton>
166
           </IconButton>
198
           <IconButton
167
           <IconButton
199
             disabled={!hasSelection}
168
             disabled={!hasSelection}
169
+            size="small"
200
             onClick={() => state.send('MOVED', { type: MoveType.Backward })}
170
             onClick={() => state.send('MOVED', { type: MoveType.Backward })}
201
           >
171
           >
202
             <ArrowDownIcon />
172
             <ArrowDownIcon />
203
           </IconButton>
173
           </IconButton>
204
           <IconButton
174
           <IconButton
205
             disabled={!hasSelection}
175
             disabled={!hasSelection}
176
+            size="small"
206
             onClick={() => state.send('MOVED', { type: MoveType.Forward })}
177
             onClick={() => state.send('MOVED', { type: MoveType.Forward })}
207
           >
178
           >
208
             <ArrowUpIcon />
179
             <ArrowUpIcon />
209
           </IconButton>
180
           </IconButton>
210
           <IconButton
181
           <IconButton
211
             disabled={!hasSelection}
182
             disabled={!hasSelection}
183
+            size="small"
212
             onClick={() => state.send('MOVED', { type: MoveType.ToFront })}
184
             onClick={() => state.send('MOVED', { type: MoveType.ToFront })}
213
           >
185
           >
214
             <PinTopIcon />
186
             <PinTopIcon />
215
           </IconButton>
187
           </IconButton>
216
           <IconButton
188
           <IconButton
217
             disabled={!hasSelection}
189
             disabled={!hasSelection}
190
+            size="small"
218
             onClick={() => state.send('DELETED')}
191
             onClick={() => state.send('DELETED')}
219
           >
192
           >
220
             <Trash2 />
193
             <Trash2 />
236
   overflow: 'hidden',
209
   overflow: 'hidden',
237
   position: 'relative',
210
   position: 'relative',
238
   border: '1px solid $panel',
211
   border: '1px solid $panel',
239
-  boxShadow: '0px 2px 4px rgba(0,0,0,.12)',
212
+  boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
240
   display: 'flex',
213
   display: 'flex',
241
   alignItems: 'center',
214
   alignItems: 'center',
242
   pointerEvents: 'all',
215
   pointerEvents: 'all',
262
   width: '100%',
235
   width: '100%',
263
   background: 'none',
236
   background: 'none',
264
   border: 'none',
237
   border: 'none',
265
-  cursor: 'pointer',
266
   outline: 'none',
238
   outline: 'none',
267
   alignItems: 'center',
239
   alignItems: 'center',
268
   justifyContent: 'space-between',
240
   justifyContent: 'space-between',
293
   justifyContent: 'flex-start',
265
   justifyContent: 'flex-start',
294
   padding: 4,
266
   padding: 4,
295
 })
267
 })
296
-
297
-const StyledCheckbox = styled(Checkbox.Root, {
298
-  appearance: 'none',
299
-  backgroundColor: 'transparent',
300
-  border: 'none',
301
-  padding: 0,
302
-  boxShadow: 'inset 0 0 0 1px gainsboro',
303
-  width: 15,
304
-  height: 15,
305
-  borderRadius: 2,
306
-  display: 'flex',
307
-  alignItems: 'center',
308
-  justifyContent: 'center',
309
-
310
-  '&:focus': {
311
-    outline: 'none',
312
-    boxShadow: 'inset 0 0 0 1px dodgerblue, 0 0 0 1px dodgerblue',
313
-  },
314
-})

+ 0
- 30
components/style-panel/width-picker.tsx 查看文件

1
-import { Group, RadioItem } from './shared'
2
-import { ChangeEvent } from 'react'
3
-import { Circle } from 'react-feather'
4
-import state from 'state'
5
-
6
-function handleChange(e: ChangeEvent<HTMLInputElement>) {
7
-  state.send('CHANGED_STYLE', {
8
-    strokeWidth: Number(e.currentTarget.value),
9
-  })
10
-}
11
-
12
-export default function WidthPicker({
13
-  strokeWidth = 2,
14
-}: {
15
-  strokeWidth?: number
16
-}) {
17
-  return (
18
-    <Group name="width" onValueChange={handleChange}>
19
-      <RadioItem value="2" isActive={strokeWidth === 2}>
20
-        <Circle size={6} />
21
-      </RadioItem>
22
-      <RadioItem value="4" isActive={strokeWidth === 4}>
23
-        <Circle size={12} />
24
-      </RadioItem>
25
-      <RadioItem value="8" isActive={strokeWidth === 8}>
26
-        <Circle size={22} />
27
-      </RadioItem>
28
-    </Group>
29
-  )
30
-}

+ 2
- 2
components/tools-panel/tools-panel.tsx 查看文件

106
           >
106
           >
107
             <CircleIcon />
107
             <CircleIcon />
108
           </IconButton> */}
108
           </IconButton> */}
109
-          <IconButton
109
+          {/* <IconButton
110
             name={ShapeType.Line}
110
             name={ShapeType.Line}
111
             size={{ '@sm': 'small', '@md': 'large' }}
111
             size={{ '@sm': 'small', '@md': 'large' }}
112
             onClick={selectLineTool}
112
             onClick={selectLineTool}
129
             isActive={activeTool === ShapeType.Dot}
129
             isActive={activeTool === ShapeType.Dot}
130
           >
130
           >
131
             <DotIcon />
131
             <DotIcon />
132
-          </IconButton>
132
+          </IconButton> */}
133
         </Container>
133
         </Container>
134
         <Container>
134
         <Container>
135
           <IconButton
135
           <IconButton

+ 2
- 5
lib/code/circle.ts 查看文件

2
 import { v4 as uuid } from 'uuid'
2
 import { v4 as uuid } from 'uuid'
3
 import { CircleShape, ShapeType } from 'types'
3
 import { CircleShape, ShapeType } from 'types'
4
 import { vectorToPoint } from 'utils/utils'
4
 import { vectorToPoint } from 'utils/utils'
5
+import { defaultStyle } from 'lib/shape-styles'
5
 
6
 
6
 export default class Circle extends CodeShape<CircleShape> {
7
 export default class Circle extends CodeShape<CircleShape> {
7
   constructor(props = {} as Partial<CircleShape>) {
8
   constructor(props = {} as Partial<CircleShape>) {
20
       isAspectRatioLocked: false,
21
       isAspectRatioLocked: false,
21
       isLocked: false,
22
       isLocked: false,
22
       isHidden: false,
23
       isHidden: false,
23
-      style: {
24
-        fill: '#c6cacb',
25
-        stroke: '#000',
26
-        strokeWidth: 1,
27
-      },
24
+      style: defaultStyle,
28
       ...props,
25
       ...props,
29
     })
26
     })
30
   }
27
   }

+ 5
- 4
lib/code/dot.ts 查看文件

2
 import { v4 as uuid } from 'uuid'
2
 import { v4 as uuid } from 'uuid'
3
 import { DotShape, ShapeType } from 'types'
3
 import { DotShape, ShapeType } from 'types'
4
 import { vectorToPoint } from 'utils/utils'
4
 import { vectorToPoint } from 'utils/utils'
5
+import { defaultStyle } from 'lib/shape-styles'
5
 
6
 
6
 export default class Dot extends CodeShape<DotShape> {
7
 export default class Dot extends CodeShape<DotShape> {
7
   constructor(props = {} as Partial<DotShape>) {
8
   constructor(props = {} as Partial<DotShape>) {
19
       isAspectRatioLocked: false,
20
       isAspectRatioLocked: false,
20
       isLocked: false,
21
       isLocked: false,
21
       isHidden: false,
22
       isHidden: false,
23
+      ...props,
22
       style: {
24
       style: {
23
-        fill: '#c6cacb',
24
-        stroke: '#000',
25
-        strokeWidth: 1,
25
+        ...defaultStyle,
26
+        ...props.style,
27
+        isFilled: false,
26
       },
28
       },
27
-      ...props,
28
     })
29
     })
29
   }
30
   }
30
 
31
 

+ 2
- 5
lib/code/ellipse.ts 查看文件

2
 import { v4 as uuid } from 'uuid'
2
 import { v4 as uuid } from 'uuid'
3
 import { EllipseShape, ShapeType } from 'types'
3
 import { EllipseShape, ShapeType } from 'types'
4
 import { vectorToPoint } from 'utils/utils'
4
 import { vectorToPoint } from 'utils/utils'
5
+import { defaultStyle } from 'lib/shape-styles'
5
 
6
 
6
 export default class Ellipse extends CodeShape<EllipseShape> {
7
 export default class Ellipse extends CodeShape<EllipseShape> {
7
   constructor(props = {} as Partial<EllipseShape>) {
8
   constructor(props = {} as Partial<EllipseShape>) {
21
       isAspectRatioLocked: false,
22
       isAspectRatioLocked: false,
22
       isLocked: false,
23
       isLocked: false,
23
       isHidden: false,
24
       isHidden: false,
24
-      style: {
25
-        fill: '#c6cacb',
26
-        stroke: '#000',
27
-        strokeWidth: 1,
28
-      },
25
+      style: defaultStyle,
29
       ...props,
26
       ...props,
30
     })
27
     })
31
   }
28
   }

+ 5
- 4
lib/code/line.ts 查看文件

2
 import { v4 as uuid } from 'uuid'
2
 import { v4 as uuid } from 'uuid'
3
 import { LineShape, ShapeType } from 'types'
3
 import { LineShape, ShapeType } from 'types'
4
 import { vectorToPoint } from 'utils/utils'
4
 import { vectorToPoint } from 'utils/utils'
5
+import { defaultStyle } from 'lib/shape-styles'
5
 
6
 
6
 export default class Line extends CodeShape<LineShape> {
7
 export default class Line extends CodeShape<LineShape> {
7
   constructor(props = {} as Partial<LineShape>) {
8
   constructor(props = {} as Partial<LineShape>) {
21
       isAspectRatioLocked: false,
22
       isAspectRatioLocked: false,
22
       isLocked: false,
23
       isLocked: false,
23
       isHidden: false,
24
       isHidden: false,
25
+      ...props,
24
       style: {
26
       style: {
25
-        fill: '#c6cacb',
26
-        stroke: '#000',
27
-        strokeWidth: 1,
27
+        ...defaultStyle,
28
+        ...props.style,
29
+        isFilled: false,
28
       },
30
       },
29
-      ...props,
30
     })
31
     })
31
   }
32
   }
32
 
33
 

+ 2
- 5
lib/code/polyline.ts 查看文件

2
 import { v4 as uuid } from 'uuid'
2
 import { v4 as uuid } from 'uuid'
3
 import { PolylineShape, ShapeType } from 'types'
3
 import { PolylineShape, ShapeType } from 'types'
4
 import { vectorToPoint } from 'utils/utils'
4
 import { vectorToPoint } from 'utils/utils'
5
+import { defaultStyle } from 'lib/shape-styles'
5
 
6
 
6
 export default class Polyline extends CodeShape<PolylineShape> {
7
 export default class Polyline extends CodeShape<PolylineShape> {
7
   constructor(props = {} as Partial<PolylineShape>) {
8
   constructor(props = {} as Partial<PolylineShape>) {
21
       isAspectRatioLocked: false,
22
       isAspectRatioLocked: false,
22
       isLocked: false,
23
       isLocked: false,
23
       isHidden: false,
24
       isHidden: false,
24
-      style: {
25
-        fill: 'none',
26
-        stroke: '#000',
27
-        strokeWidth: 1,
28
-      },
25
+      style: defaultStyle,
29
       ...props,
26
       ...props,
30
     })
27
     })
31
   }
28
   }

+ 5
- 4
lib/code/ray.ts 查看文件

2
 import { v4 as uuid } from 'uuid'
2
 import { v4 as uuid } from 'uuid'
3
 import { RayShape, ShapeType } from 'types'
3
 import { RayShape, ShapeType } from 'types'
4
 import { vectorToPoint } from 'utils/utils'
4
 import { vectorToPoint } from 'utils/utils'
5
+import { defaultStyle } from 'lib/shape-styles'
5
 
6
 
6
 export default class Ray extends CodeShape<RayShape> {
7
 export default class Ray extends CodeShape<RayShape> {
7
   constructor(props = {} as Partial<RayShape>) {
8
   constructor(props = {} as Partial<RayShape>) {
21
       isAspectRatioLocked: false,
22
       isAspectRatioLocked: false,
22
       isLocked: false,
23
       isLocked: false,
23
       isHidden: false,
24
       isHidden: false,
25
+      ...props,
24
       style: {
26
       style: {
25
-        fill: '#c6cacb',
26
-        stroke: '#000',
27
-        strokeWidth: 1,
27
+        ...defaultStyle,
28
+        ...props.style,
29
+        isFilled: false,
28
       },
30
       },
29
-      ...props,
30
     })
31
     })
31
   }
32
   }
32
 
33
 

+ 2
- 5
lib/code/rectangle.ts 查看文件

2
 import { v4 as uuid } from 'uuid'
2
 import { v4 as uuid } from 'uuid'
3
 import { RectangleShape, ShapeType } from 'types'
3
 import { RectangleShape, ShapeType } from 'types'
4
 import { vectorToPoint } from 'utils/utils'
4
 import { vectorToPoint } from 'utils/utils'
5
+import { defaultStyle } from 'lib/shape-styles'
5
 
6
 
6
 export default class Rectangle extends CodeShape<RectangleShape> {
7
 export default class Rectangle extends CodeShape<RectangleShape> {
7
   constructor(props = {} as Partial<RectangleShape>) {
8
   constructor(props = {} as Partial<RectangleShape>) {
22
       isAspectRatioLocked: false,
23
       isAspectRatioLocked: false,
23
       isLocked: false,
24
       isLocked: false,
24
       isHidden: false,
25
       isHidden: false,
25
-      style: {
26
-        fill: '#c6cacb',
27
-        stroke: '#000',
28
-        strokeWidth: 1,
29
-      },
26
+      style: defaultStyle,
30
       ...props,
27
       ...props,
31
     })
28
     })
32
   }
29
   }

+ 0
- 38
lib/colors.ts 查看文件

1
-export const shades = {
2
-  none: 'none',
3
-  white: 'rgba(248, 249, 250, 1.000)',
4
-  lightGray: 'rgba(224, 226, 230, 1.000)',
5
-  gray: 'rgba(172, 181, 189, 1.000)',
6
-  darkGray: 'rgba(52, 58, 64, 1.000)',
7
-  black: 'rgba(0,0,0, 1.000)',
8
-}
9
-
10
-export const strokes = {
11
-  lime: 'rgba(115, 184, 23, 1.000)',
12
-  green: 'rgba(54, 178, 77, 1.000)',
13
-  teal: 'rgba(9, 167, 120, 1.000)',
14
-  cyan: 'rgba(14, 152, 173, 1.000)',
15
-  blue: 'rgba(28, 126, 214, 1.000)',
16
-  indigo: 'rgba(66, 99, 235, 1.000)',
17
-  violet: 'rgba(112, 72, 232, 1.000)',
18
-  grape: 'rgba(174, 62, 200, 1.000)',
19
-  pink: 'rgba(214, 51, 108, 1.000)',
20
-  red: 'rgba(240, 63, 63, 1.000)',
21
-  orange: 'rgba(247, 103, 6, 1.000)',
22
-  yellow: 'rgba(245, 159, 0, 1.000)',
23
-}
24
-
25
-export const fills = {
26
-  lime: 'rgba(243, 252, 227, 1.000)',
27
-  green: 'rgba(235, 251, 238, 1.000)',
28
-  teal: 'rgba(230, 252, 245, 1.000)',
29
-  cyan: 'rgba(227, 250, 251, 1.000)',
30
-  blue: 'rgba(231, 245, 255, 1.000)',
31
-  indigo: 'rgba(237, 242, 255, 1.000)',
32
-  violet: 'rgba(242, 240, 255, 1.000)',
33
-  grape: 'rgba(249, 240, 252, 1.000)',
34
-  pink: 'rgba(254, 241, 246, 1.000)',
35
-  red: 'rgba(255, 245, 245, 1.000)',
36
-  orange: 'rgba(255, 244, 229, 1.000)',
37
-  yellow: 'rgba(255, 249, 219, 1.000)',
38
-}

+ 83
- 0
lib/shape-styles.ts 查看文件

1
+import { SVGProps } from 'react'
2
+import { ColorStyle, DashStyle, Shape, ShapeStyles, SizeStyle } from 'types'
3
+
4
+export const strokes: Record<ColorStyle, string> = {
5
+  [ColorStyle.White]: 'rgba(248, 249, 250, 1.000)',
6
+  [ColorStyle.LightGray]: 'rgba(224, 226, 230, 1.000)',
7
+  [ColorStyle.Gray]: 'rgba(172, 181, 189, 1.000)',
8
+  [ColorStyle.Black]: 'rgba(0,0,0, 1.000)',
9
+  [ColorStyle.Lime]: 'rgba(115, 184, 23, 1.000)',
10
+  [ColorStyle.Green]: 'rgba(54, 178, 77, 1.000)',
11
+  [ColorStyle.Teal]: 'rgba(9, 167, 120, 1.000)',
12
+  [ColorStyle.Cyan]: 'rgba(14, 152, 173, 1.000)',
13
+  [ColorStyle.Blue]: 'rgba(28, 126, 214, 1.000)',
14
+  [ColorStyle.Indigo]: 'rgba(66, 99, 235, 1.000)',
15
+  [ColorStyle.Violet]: 'rgba(112, 72, 232, 1.000)',
16
+  [ColorStyle.Grape]: 'rgba(174, 62, 200, 1.000)',
17
+  [ColorStyle.Pink]: 'rgba(214, 51, 108, 1.000)',
18
+  [ColorStyle.Red]: 'rgba(240, 63, 63, 1.000)',
19
+  [ColorStyle.Orange]: 'rgba(247, 103, 6, 1.000)',
20
+  [ColorStyle.Yellow]: 'rgba(245, 159, 0, 1.000)',
21
+}
22
+
23
+export const fills = {
24
+  [ColorStyle.White]: 'rgba(224, 226, 230, 1.000)',
25
+  [ColorStyle.LightGray]: 'rgba(255, 255, 255, 1.000)',
26
+  [ColorStyle.Gray]: 'rgba(224, 226, 230, 1.000)',
27
+  [ColorStyle.Black]: 'rgba(224, 226, 230, 1.000)',
28
+  [ColorStyle.Lime]: 'rgba(243, 252, 227, 1.000)',
29
+  [ColorStyle.Green]: 'rgba(235, 251, 238, 1.000)',
30
+  [ColorStyle.Teal]: 'rgba(230, 252, 245, 1.000)',
31
+  [ColorStyle.Cyan]: 'rgba(227, 250, 251, 1.000)',
32
+  [ColorStyle.Blue]: 'rgba(231, 245, 255, 1.000)',
33
+  [ColorStyle.Indigo]: 'rgba(237, 242, 255, 1.000)',
34
+  [ColorStyle.Violet]: 'rgba(242, 240, 255, 1.000)',
35
+  [ColorStyle.Grape]: 'rgba(249, 240, 252, 1.000)',
36
+  [ColorStyle.Pink]: 'rgba(254, 241, 246, 1.000)',
37
+  [ColorStyle.Red]: 'rgba(255, 245, 245, 1.000)',
38
+  [ColorStyle.Orange]: 'rgba(255, 244, 229, 1.000)',
39
+  [ColorStyle.Yellow]: 'rgba(255, 249, 219, 1.000)',
40
+}
41
+
42
+const strokeWidths = {
43
+  [SizeStyle.Small]: 2,
44
+  [SizeStyle.Medium]: 4,
45
+  [SizeStyle.Large]: 8,
46
+}
47
+
48
+const dashArrays = {
49
+  [DashStyle.Solid]: () => 'none',
50
+  [DashStyle.Dashed]: (sw: number) => `${sw} ${sw * 2}`,
51
+  [DashStyle.Dotted]: (sw: number) => `0 ${sw * 1.5}`,
52
+}
53
+
54
+function getStrokeWidth(size: SizeStyle) {
55
+  return strokeWidths[size]
56
+}
57
+
58
+function getStrokeDashArray(dash: DashStyle, strokeWidth: number) {
59
+  return dashArrays[dash](strokeWidth)
60
+}
61
+
62
+export function getShapeStyle(
63
+  style: ShapeStyles
64
+): Partial<SVGProps<SVGUseElement>> {
65
+  const { color, size, dash, isFilled } = style
66
+
67
+  const strokeWidth = getStrokeWidth(size)
68
+  const strokeDasharray = getStrokeDashArray(dash, strokeWidth)
69
+
70
+  return {
71
+    stroke: strokes[color],
72
+    fill: isFilled ? fills[color] : 'none',
73
+    strokeWidth,
74
+    strokeDasharray,
75
+  }
76
+}
77
+
78
+export const defaultStyle = {
79
+  color: ColorStyle.Black,
80
+  size: SizeStyle.Medium,
81
+  isFilled: false,
82
+  dash: DashStyle.Solid,
83
+}

+ 15
- 5
lib/shape-utils/arrow.tsx 查看文件

1
 import { v4 as uuid } from 'uuid'
1
 import { v4 as uuid } from 'uuid'
2
 import * as vec from 'utils/vec'
2
 import * as vec from 'utils/vec'
3
 import * as svg from 'utils/svg'
3
 import * as svg from 'utils/svg'
4
-import { ArrowShape, ShapeHandle, ShapeType } from 'types'
4
+import {
5
+  ArrowShape,
6
+  ColorStyle,
7
+  DashStyle,
8
+  ShapeHandle,
9
+  ShapeType,
10
+  SizeStyle,
11
+} from 'types'
5
 import { registerShapeUtils } from './index'
12
 import { registerShapeUtils } from './index'
6
 import { circleFromThreePoints, clamp, isAngleBetween } from 'utils/utils'
13
 import { circleFromThreePoints, clamp, isAngleBetween } from 'utils/utils'
7
 import { pointInBounds } from 'utils/bounds'
14
 import { pointInBounds } from 'utils/bounds'
11
 } from 'utils/intersections'
18
 } from 'utils/intersections'
12
 import { getBoundsFromPoints, translateBounds } from 'utils/utils'
19
 import { getBoundsFromPoints, translateBounds } from 'utils/utils'
13
 import { pointInCircle } from 'utils/hitTests'
20
 import { pointInCircle } from 'utils/hitTests'
21
+import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
14
 
22
 
15
 const ctpCache = new WeakMap<ArrowShape['handles'], number[]>()
23
 const ctpCache = new WeakMap<ArrowShape['handles'], number[]>()
16
 
24
 
77
       },
85
       },
78
       ...props,
86
       ...props,
79
       style: {
87
       style: {
80
-        strokeWidth: 2,
88
+        ...defaultStyle,
81
         ...props.style,
89
         ...props.style,
82
-        fill: 'none',
90
+        isFilled: false,
83
       },
91
       },
84
     }
92
     }
85
   },
93
   },
86
 
94
 
87
   render(shape) {
95
   render(shape) {
88
-    const { id, bend, points, handles, style } = shape
96
+    const { id, bend, points, handles } = shape
89
     const { start, end, bend: _bend } = handles
97
     const { start, end, bend: _bend } = handles
90
 
98
 
91
     const arrowDist = vec.dist(start.point, end.point)
99
     const arrowDist = vec.dist(start.point, end.point)
92
     const bendDist = arrowDist * bend
100
     const bendDist = arrowDist * bend
93
     const showCircle = Math.abs(bendDist) > 20
101
     const showCircle = Math.abs(bendDist) > 20
94
 
102
 
103
+    const style = getShapeStyle(shape.style)
104
+
95
     // Arrowhead
105
     // Arrowhead
96
     const length = Math.min(arrowDist / 2, 16 + +style.strokeWidth * 2)
106
     const length = Math.min(arrowDist / 2, 16 + +style.strokeWidth * 2)
97
     const angle = showCircle ? bend * (Math.PI * 0.48) : 0
107
     const angle = showCircle ? bend * (Math.PI * 0.48) : 0
145
 
155
 
146
   applyStyles(shape, style) {
156
   applyStyles(shape, style) {
147
     Object.assign(shape.style, style)
157
     Object.assign(shape.style, style)
148
-    shape.style.fill = 'none'
158
+    shape.style.isFilled = false
149
     return this
159
     return this
150
   },
160
   },
151
 
161
 

+ 6
- 6
lib/shape-utils/circle.tsx 查看文件

1
 import { v4 as uuid } from 'uuid'
1
 import { v4 as uuid } from 'uuid'
2
 import * as vec from 'utils/vec'
2
 import * as vec from 'utils/vec'
3
-import { CircleShape, ShapeType } from 'types'
3
+import { CircleShape, ColorStyle, DashStyle, ShapeType, SizeStyle } from 'types'
4
 import { registerShapeUtils } from './index'
4
 import { registerShapeUtils } from './index'
5
 import { boundsContained } from 'utils/bounds'
5
 import { boundsContained } from 'utils/bounds'
6
 import { intersectCircleBounds } from 'utils/intersections'
6
 import { intersectCircleBounds } from 'utils/intersections'
7
 import { pointInCircle } from 'utils/hitTests'
7
 import { pointInCircle } from 'utils/hitTests'
8
 import { translateBounds } from 'utils/utils'
8
 import { translateBounds } from 'utils/utils'
9
+import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
9
 
10
 
10
 const circle = registerShapeUtils<CircleShape>({
11
 const circle = registerShapeUtils<CircleShape>({
11
   boundsCache: new WeakMap([]),
12
   boundsCache: new WeakMap([]),
24
       isAspectRatioLocked: false,
25
       isAspectRatioLocked: false,
25
       isLocked: false,
26
       isLocked: false,
26
       isHidden: false,
27
       isHidden: false,
27
-      style: {
28
-        fill: '#c6cacb',
29
-        stroke: '#000',
30
-      },
28
+      style: defaultStyle,
31
       ...props,
29
       ...props,
32
     }
30
     }
33
   },
31
   },
34
 
32
 
35
   render({ id, radius, style }) {
33
   render({ id, radius, style }) {
34
+    const styles = getShapeStyle(style)
35
+
36
     return (
36
     return (
37
       <circle
37
       <circle
38
         id={id}
38
         id={id}
39
         cx={radius}
39
         cx={radius}
40
         cy={radius}
40
         cy={radius}
41
-        r={Math.max(0, radius - Number(style.strokeWidth) / 2)}
41
+        r={Math.max(0, radius - Number(styles.strokeWidth) / 2)}
42
       />
42
       />
43
     )
43
     )
44
   },
44
   },

+ 5
- 3
lib/shape-utils/dot.tsx 查看文件

6
 import { intersectCircleBounds } from 'utils/intersections'
6
 import { intersectCircleBounds } from 'utils/intersections'
7
 import { DotCircle } from 'components/canvas/misc'
7
 import { DotCircle } from 'components/canvas/misc'
8
 import { translateBounds } from 'utils/utils'
8
 import { translateBounds } from 'utils/utils'
9
+import { defaultStyle } from 'lib/shape-styles'
9
 
10
 
10
 const dot = registerShapeUtils<DotShape>({
11
 const dot = registerShapeUtils<DotShape>({
11
   boundsCache: new WeakMap([]),
12
   boundsCache: new WeakMap([]),
23
       isAspectRatioLocked: false,
24
       isAspectRatioLocked: false,
24
       isLocked: false,
25
       isLocked: false,
25
       isHidden: false,
26
       isHidden: false,
27
+      ...props,
26
       style: {
28
       style: {
27
-        fill: '#c6cacb',
28
-        strokeWidth: '0',
29
+        ...defaultStyle,
30
+        ...props.style,
31
+        isFilled: false,
29
       },
32
       },
30
-      ...props,
31
     }
33
     }
32
   },
34
   },
33
 
35
 

+ 18
- 22
lib/shape-utils/draw.tsx 查看文件

1
 import { v4 as uuid } from 'uuid'
1
 import { v4 as uuid } from 'uuid'
2
 import * as vec from 'utils/vec'
2
 import * as vec from 'utils/vec'
3
-import { DrawShape, ShapeType } from 'types'
3
+import { DashStyle, DrawShape, ShapeType } from 'types'
4
 import { registerShapeUtils } from './index'
4
 import { registerShapeUtils } from './index'
5
 import { intersectPolylineBounds } from 'utils/intersections'
5
 import { intersectPolylineBounds } from 'utils/intersections'
6
 import { boundsContainPolygon } from 'utils/bounds'
6
 import { boundsContainPolygon } from 'utils/bounds'
12
   translateBounds,
12
   translateBounds,
13
 } from 'utils/utils'
13
 } from 'utils/utils'
14
 import styled from 'styles'
14
 import styled from 'styles'
15
+import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
15
 
16
 
16
 const pathCache = new WeakMap<DrawShape['points'], string>([])
17
 const pathCache = new WeakMap<DrawShape['points'], string>([])
17
 
18
 
34
       isHidden: false,
35
       isHidden: false,
35
       ...props,
36
       ...props,
36
       style: {
37
       style: {
37
-        strokeWidth: 2,
38
-        strokeLinecap: 'round',
39
-        strokeLinejoin: 'round',
38
+        ...defaultStyle,
40
         ...props.style,
39
         ...props.style,
41
-        fill: props.style.stroke,
40
+        isFilled: false,
42
       },
41
       },
43
     }
42
     }
44
   },
43
   },
46
   render(shape) {
45
   render(shape) {
47
     const { id, points, style } = shape
46
     const { id, points, style } = shape
48
 
47
 
48
+    const styles = getShapeStyle(style)
49
+
49
     if (!pathCache.has(points)) {
50
     if (!pathCache.has(points)) {
50
       pathCache.set(
51
       pathCache.set(
51
         points,
52
         points,
52
         getSvgPathFromStroke(
53
         getSvgPathFromStroke(
53
           getStroke(points, {
54
           getStroke(points, {
54
-            size: +style.strokeWidth * 2,
55
+            size: +styles.strokeWidth * 2,
55
             thinning: 0.9,
56
             thinning: 0.9,
56
             end: { taper: 100 },
57
             end: { taper: 100 },
57
             start: { taper: 40 },
58
             start: { taper: 40 },
61
     }
62
     }
62
 
63
 
63
     if (points.length < 2) {
64
     if (points.length < 2) {
64
-      return <circle id={id} r={+style.strokeWidth * 0.618} />
65
+      return (
66
+        <circle id={id} r={+styles.strokeWidth * 0.618} fill={styles.stroke} />
67
+      )
65
     }
68
     }
66
 
69
 
67
-    return <path id={id} d={pathCache.get(points)} />
70
+    return <path id={id} d={pathCache.get(points)} fill={styles.stroke} />
68
   },
71
   },
69
 
72
 
70
   applyStyles(shape, style) {
73
   applyStyles(shape, style) {
71
     Object.assign(shape.style, style)
74
     Object.assign(shape.style, style)
72
-    shape.style.fill = shape.style.stroke
75
+    shape.style.isFilled = false
76
+    shape.style.dash = DashStyle.Solid
73
     return this
77
     return this
74
   },
78
   },
75
 
79
 
106
 
110
 
107
   hitTest(shape, point) {
111
   hitTest(shape, point) {
108
     let pt = vec.sub(point, shape.point)
112
     let pt = vec.sub(point, shape.point)
109
-    let prev = shape.points[0]
110
-
111
-    for (let i = 1; i < shape.points.length; i++) {
112
-      let curr = shape.points[i]
113
-      if (
114
-        vec.distanceToLineSegment(prev, curr, pt) < +shape.style.strokeWidth
115
-      ) {
116
-        return true
117
-      }
118
-      prev = curr
119
-    }
120
-
121
-    return false
113
+    const min = +getShapeStyle(shape.style).strokeWidth
114
+    return shape.points.some(
115
+      (curr, i) =>
116
+        i > 0 && vec.distanceToLineSegment(shape.points[i - 1], curr, pt) < min
117
+    )
122
   },
118
   },
123
 
119
 
124
   hitTestBounds(this, shape, brushBounds) {
120
   hitTestBounds(this, shape, brushBounds) {

+ 5
- 6
lib/shape-utils/ellipse.tsx 查看文件

11
   rotateBounds,
11
   rotateBounds,
12
   translateBounds,
12
   translateBounds,
13
 } from 'utils/utils'
13
 } from 'utils/utils'
14
+import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
14
 
15
 
15
 const ellipse = registerShapeUtils<EllipseShape>({
16
 const ellipse = registerShapeUtils<EllipseShape>({
16
   boundsCache: new WeakMap([]),
17
   boundsCache: new WeakMap([]),
30
       isAspectRatioLocked: false,
31
       isAspectRatioLocked: false,
31
       isLocked: false,
32
       isLocked: false,
32
       isHidden: false,
33
       isHidden: false,
33
-      style: {
34
-        fill: '#c6cacb',
35
-        stroke: '#000',
36
-      },
34
+      style: defaultStyle,
37
       ...props,
35
       ...props,
38
     }
36
     }
39
   },
37
   },
40
 
38
 
41
   render({ id, radiusX, radiusY, style }) {
39
   render({ id, radiusX, radiusY, style }) {
40
+    const styles = getShapeStyle(style)
42
     return (
41
     return (
43
       <ellipse
42
       <ellipse
44
         id={id}
43
         id={id}
45
         cx={radiusX}
44
         cx={radiusX}
46
         cy={radiusY}
45
         cy={radiusY}
47
-        rx={Math.max(0, radiusX - Number(style.strokeWidth) / 2)}
48
-        ry={Math.max(0, radiusY - Number(style.strokeWidth) / 2)}
46
+        rx={Math.max(0, radiusX - Number(styles.strokeWidth) / 2)}
47
+        ry={Math.max(0, radiusY - Number(styles.strokeWidth) / 2)}
49
       />
48
       />
50
     )
49
     )
51
   },
50
   },

+ 1
- 1
lib/shape-utils/index.tsx 查看文件

48
   applyStyles(
48
   applyStyles(
49
     this: ShapeUtility<K>,
49
     this: ShapeUtility<K>,
50
     shape: K,
50
     shape: K,
51
-    style: ShapeStyles
51
+    style: Partial<ShapeStyles>
52
   ): ShapeUtility<K>
52
   ): ShapeUtility<K>
53
 
53
 
54
   // Set the shape's point.
54
   // Set the shape's point.

+ 5
- 3
lib/shape-utils/line.tsx 查看文件

7
 import { DotCircle, ThinLine } from 'components/canvas/misc'
7
 import { DotCircle, ThinLine } from 'components/canvas/misc'
8
 import { translateBounds } from 'utils/utils'
8
 import { translateBounds } from 'utils/utils'
9
 import styled from 'styles'
9
 import styled from 'styles'
10
+import { defaultStyle } from 'lib/shape-styles'
10
 
11
 
11
 const line = registerShapeUtils<LineShape>({
12
 const line = registerShapeUtils<LineShape>({
12
   boundsCache: new WeakMap([]),
13
   boundsCache: new WeakMap([]),
25
       isAspectRatioLocked: false,
26
       isAspectRatioLocked: false,
26
       isLocked: false,
27
       isLocked: false,
27
       isHidden: false,
28
       isHidden: false,
29
+      ...props,
28
       style: {
30
       style: {
29
-        fill: '#c6cacb',
30
-        stroke: '#000',
31
+        ...defaultStyle,
32
+        ...props.style,
33
+        isFilled: false,
31
       },
34
       },
32
-      ...props,
33
     }
35
     }
34
   },
36
   },
35
 
37
 

+ 2
- 5
lib/shape-utils/polyline.tsx 查看文件

5
 import { intersectPolylineBounds } from 'utils/intersections'
5
 import { intersectPolylineBounds } from 'utils/intersections'
6
 import { boundsContainPolygon } from 'utils/bounds'
6
 import { boundsContainPolygon } from 'utils/bounds'
7
 import { getBoundsFromPoints, translateBounds } from 'utils/utils'
7
 import { getBoundsFromPoints, translateBounds } from 'utils/utils'
8
+import { defaultStyle } from 'lib/shape-styles'
8
 
9
 
9
 const polyline = registerShapeUtils<PolylineShape>({
10
 const polyline = registerShapeUtils<PolylineShape>({
10
   boundsCache: new WeakMap([]),
11
   boundsCache: new WeakMap([]),
23
       isAspectRatioLocked: false,
24
       isAspectRatioLocked: false,
24
       isLocked: false,
25
       isLocked: false,
25
       isHidden: false,
26
       isHidden: false,
26
-      style: {
27
-        strokeWidth: 2,
28
-        strokeLinecap: 'round',
29
-        strokeLinejoin: 'round',
30
-      },
27
+      style: defaultStyle,
31
       ...props,
28
       ...props,
32
     }
29
     }
33
   },
30
   },

+ 5
- 4
lib/shape-utils/ray.tsx 查看文件

6
 import { intersectCircleBounds } from 'utils/intersections'
6
 import { intersectCircleBounds } from 'utils/intersections'
7
 import { DotCircle, ThinLine } from 'components/canvas/misc'
7
 import { DotCircle, ThinLine } from 'components/canvas/misc'
8
 import { translateBounds } from 'utils/utils'
8
 import { translateBounds } from 'utils/utils'
9
+import { defaultStyle } from 'lib/shape-styles'
9
 
10
 
10
 const ray = registerShapeUtils<RayShape>({
11
 const ray = registerShapeUtils<RayShape>({
11
   boundsCache: new WeakMap([]),
12
   boundsCache: new WeakMap([]),
24
       isAspectRatioLocked: false,
25
       isAspectRatioLocked: false,
25
       isLocked: false,
26
       isLocked: false,
26
       isHidden: false,
27
       isHidden: false,
28
+      ...props,
27
       style: {
29
       style: {
28
-        fill: '#c6cacb',
29
-        stroke: '#000',
30
-        strokeWidth: 1,
30
+        ...defaultStyle,
31
+        ...props.style,
32
+        isFilled: false,
31
       },
33
       },
32
-      ...props,
33
     }
34
     }
34
   },
35
   },
35
 
36
 

+ 5
- 6
lib/shape-utils/rectangle.tsx 查看文件

8
   getRotatedCorners,
8
   getRotatedCorners,
9
   translateBounds,
9
   translateBounds,
10
 } from 'utils/utils'
10
 } from 'utils/utils'
11
+import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
11
 
12
 
12
 const rectangle = registerShapeUtils<RectangleShape>({
13
 const rectangle = registerShapeUtils<RectangleShape>({
13
   boundsCache: new WeakMap([]),
14
   boundsCache: new WeakMap([]),
27
       isAspectRatioLocked: false,
28
       isAspectRatioLocked: false,
28
       isLocked: false,
29
       isLocked: false,
29
       isHidden: false,
30
       isHidden: false,
30
-      style: {
31
-        fill: '#c6cacb',
32
-        stroke: '#000',
33
-      },
31
+      style: defaultStyle,
34
       ...props,
32
       ...props,
35
     }
33
     }
36
   },
34
   },
37
 
35
 
38
   render({ id, size, radius, style }) {
36
   render({ id, size, radius, style }) {
37
+    const styles = getShapeStyle(style)
39
     return (
38
     return (
40
       <g id={id}>
39
       <g id={id}>
41
         <rect
40
         <rect
42
           id={id}
41
           id={id}
43
           rx={radius}
42
           rx={radius}
44
           ry={radius}
43
           ry={radius}
45
-          width={Math.max(0, size[0] - Number(style.strokeWidth) / 2)}
46
-          height={Math.max(0, size[1] - Number(style.strokeWidth) / 2)}
44
+          width={Math.max(0, size[0] - Number(styles.strokeWidth) / 2)}
45
+          height={Math.max(0, size[1] - Number(styles.strokeWidth) / 2)}
47
         />
46
         />
48
       </g>
47
       </g>
49
     )
48
     )

+ 9
- 9
state/commands/style.ts 查看文件

1
-import Command from "./command"
2
-import history from "../history"
3
-import { Data, ShapeStyles } from "types"
4
-import { getPage, getSelectedShapes } from "utils/utils"
5
-import { getShapeUtils } from "lib/shape-utils"
6
-import { current } from "immer"
1
+import Command from './command'
2
+import history from '../history'
3
+import { Data, ShapeStyles } from 'types'
4
+import { getPage, getSelectedShapes } from 'utils/utils'
5
+import { getShapeUtils } from 'lib/shape-utils'
6
+import { current } from 'immer'
7
 
7
 
8
-export default function styleCommand(data: Data, styles: ShapeStyles) {
8
+export default function styleCommand(data: Data, styles: Partial<ShapeStyles>) {
9
   const { currentPageId } = data
9
   const { currentPageId } = data
10
   const initialShapes = getSelectedShapes(current(data))
10
   const initialShapes = getSelectedShapes(current(data))
11
 
11
 
12
   history.execute(
12
   history.execute(
13
     data,
13
     data,
14
     new Command({
14
     new Command({
15
-      name: "changed_style",
16
-      category: "canvas",
15
+      name: 'changed_style',
16
+      category: 'canvas',
17
       manualSelection: true,
17
       manualSelection: true,
18
       do(data) {
18
       do(data) {
19
         const { shapes } = getPage(data, currentPageId)
19
         const { shapes } = getPage(data, currentPageId)

+ 16
- 18
state/data.ts 查看文件

1
 import { Data, ShapeType } from 'types'
1
 import { Data, ShapeType } from 'types'
2
 import shapeUtils from 'lib/shape-utils'
2
 import shapeUtils from 'lib/shape-utils'
3
-import { shades } from 'lib/colors'
4
 
3
 
5
 export const defaultDocument: Data['document'] = {
4
 export const defaultDocument: Data['document'] = {
6
   pages: {
5
   pages: {
10
       name: 'Page 0',
9
       name: 'Page 0',
11
       childIndex: 0,
10
       childIndex: 0,
12
       shapes: {
11
       shapes: {
13
-        arrowShape0: shapeUtils[ShapeType.Arrow].create({
14
-          id: 'arrowShape0',
15
-          point: [200, 200],
16
-          points: [
17
-            [0, 0],
18
-            [200, 200],
19
-          ],
20
-        }),
21
-        arrowShape1: shapeUtils[ShapeType.Arrow].create({
22
-          id: 'arrowShape1',
23
-          point: [100, 100],
24
-          points: [
25
-            [0, 0],
26
-            [300, 0],
27
-          ],
28
-        }),
29
-
12
+        // arrowShape0: shapeUtils[ShapeType.Arrow].create({
13
+        //   id: 'arrowShape0',
14
+        //   point: [200, 200],
15
+        //   points: [
16
+        //     [0, 0],
17
+        //     [200, 200],
18
+        //   ],
19
+        // }),
20
+        // arrowShape1: shapeUtils[ShapeType.Arrow].create({
21
+        //   id: 'arrowShape1',
22
+        //   point: [100, 100],
23
+        //   points: [
24
+        //     [0, 0],
25
+        //     [300, 0],
26
+        //   ],
27
+        // }),
30
         // shape3: shapeUtils[ShapeType.Dot].create({
28
         // shape3: shapeUtils[ShapeType.Dot].create({
31
         //   id: 'shape3',
29
         //   id: 'shape3',
32
         //   name: 'Shape 3',
30
         //   name: 'Shape 3',

+ 2
- 2
state/sessions/draw-session.ts 查看文件

117
 
117
 
118
 export function getDrawSnapshot(data: Data, shapeId: string) {
118
 export function getDrawSnapshot(data: Data, shapeId: string) {
119
   const page = getPage(current(data))
119
   const page = getPage(current(data))
120
-  const { points, style } = page.shapes[shapeId] as DrawShape
120
+  const { points } = page.shapes[shapeId] as DrawShape
121
+
121
   return {
122
   return {
122
     id: shapeId,
123
     id: shapeId,
123
     points,
124
     points,
124
-    strokeWidth: style.strokeWidth,
125
   }
125
   }
126
 }
126
 }
127
 
127
 

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

2
 import * as vec from 'utils/vec'
2
 import * as vec from 'utils/vec'
3
 import inputs from './inputs'
3
 import inputs from './inputs'
4
 import { defaultDocument } from './data'
4
 import { defaultDocument } from './data'
5
-import { shades } from 'lib/colors'
6
 import { createShape, getShapeUtils } from 'lib/shape-utils'
5
 import { createShape, getShapeUtils } from 'lib/shape-utils'
7
 import history from 'state/history'
6
 import history from 'state/history'
8
 import * as Sessions from './sessions'
7
 import * as Sessions from './sessions'
15
   getCurrent,
14
   getCurrent,
16
   getPage,
15
   getPage,
17
   getSelectedBounds,
16
   getSelectedBounds,
18
-  getSelectedShapes,
19
   getShape,
17
   getShape,
20
   screenToWorld,
18
   screenToWorld,
21
   setZoomCSS,
19
   setZoomCSS,
34
   AlignType,
32
   AlignType,
35
   StretchType,
33
   StretchType,
36
   DashStyle,
34
   DashStyle,
35
+  SizeStyle,
36
+  ColorStyle,
37
 } from 'types'
37
 } from 'types'
38
 
38
 
39
 const initialData: Data = {
39
 const initialData: Data = {
49
     nudgeDistanceSmall: 1,
49
     nudgeDistanceSmall: 1,
50
   },
50
   },
51
   currentStyle: {
51
   currentStyle: {
52
-    fill: shades.lightGray,
53
-    stroke: shades.darkGray,
54
-    strokeWidth: 2,
52
+    size: SizeStyle.Medium,
53
+    color: ColorStyle.Black,
55
     dash: DashStyle.Solid,
54
     dash: DashStyle.Solid,
55
+    isFilled: false,
56
   },
56
   },
57
   camera: {
57
   camera: {
58
     point: [0, 0],
58
     point: [0, 0],
131
         SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
131
         SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
132
         TOGGLED_CODE_PANEL_OPEN: 'toggleCodePanel',
132
         TOGGLED_CODE_PANEL_OPEN: 'toggleCodePanel',
133
         TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel',
133
         TOGGLED_STYLE_PANEL_OPEN: 'toggleStylePanel',
134
-        TOUCHED_CANVAS: 'closeStylePanel',
134
+        POINTED_CANVAS: 'closeStylePanel',
135
         CHANGED_STYLE: ['updateStyles', 'applyStylesToSelection'],
135
         CHANGED_STYLE: ['updateStyles', 'applyStylesToSelection'],
136
         SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
136
         SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
137
         NUDGED: { do: 'nudgeSelection' },
137
         NUDGED: { do: 'nudgeSelection' },
1261
     },
1261
     },
1262
 
1262
 
1263
     restoreSavedData(data) {
1263
     restoreSavedData(data) {
1264
-      history.load(data)
1264
+      // history.load(data)
1265
     },
1265
     },
1266
 
1266
 
1267
     clearBoundsRotation(data) {
1267
     clearBoundsRotation(data) {
1309
       const page = getPage(data)
1309
       const page = getPage(data)
1310
       const shapeStyles = selectedIds.map((id) => page.shapes[id].style)
1310
       const shapeStyles = selectedIds.map((id) => page.shapes[id].style)
1311
 
1311
 
1312
-      const commonStyle: Partial<ShapeStyles> = {}
1312
+      const commonStyle: ShapeStyles = {} as ShapeStyles
1313
 
1313
 
1314
       const overrides = new Set<string>([])
1314
       const overrides = new Set<string>([])
1315
 
1315
 

+ 43
- 11
types.ts 查看文件

67
 // Cubic = "cubic",
67
 // Cubic = "cubic",
68
 // Conic = "conic",
68
 // Conic = "conic",
69
 
69
 
70
-export type ShapeStyles = Partial<
71
-  React.SVGProps<SVGUseElement> & {
72
-    dash: DashStyle
73
-  }
74
->
70
+export enum ColorStyle {
71
+  White = 'White',
72
+  LightGray = 'LightGray',
73
+  Gray = 'Gray',
74
+  Black = 'Black',
75
+  Lime = 'Lime',
76
+  Green = 'Green',
77
+  Teal = 'Teal',
78
+  Cyan = 'Cyan',
79
+  Blue = 'Blue',
80
+  Indigo = 'Indigo',
81
+  Violet = 'Violet',
82
+  Grape = 'Grape',
83
+  Pink = 'Pink',
84
+  Red = 'Red',
85
+  Orange = 'Orange',
86
+  Yellow = 'Yellow',
87
+}
88
+
89
+export enum SizeStyle {
90
+  Small = 'Small',
91
+  Medium = 'Medium',
92
+  Large = 'Large',
93
+}
94
+
95
+export enum DashStyle {
96
+  Solid = 'Solid',
97
+  Dashed = 'Dashed',
98
+  Dotted = 'Dotted',
99
+}
100
+
101
+export type ShapeStyles = {
102
+  color: ColorStyle
103
+  size: SizeStyle
104
+  dash: DashStyle
105
+  isFilled: boolean
106
+}
107
+
108
+// export type ShapeStyles = Partial<
109
+//   React.SVGProps<SVGUseElement> & {
110
+//     dash: DashStyle
111
+//   }
112
+// >
75
 
113
 
76
 export interface BaseShape {
114
 export interface BaseShape {
77
   id: string
115
   id: string
180
   Arrow = 'Arrow',
218
   Arrow = 'Arrow',
181
 }
219
 }
182
 
220
 
183
-export enum DashStyle {
184
-  Solid = 'Solid',
185
-  Dashed = 'Dashed',
186
-  Dotted = 'Dotted',
187
-}
188
-
189
 export interface ShapeBinding {
221
 export interface ShapeBinding {
190
   id: string
222
   id: string
191
   index: number
223
   index: number

Loading…
取消
儲存