Browse Source

adds tooltips, improves arrow

main
Steve Ruiz 4 years ago
parent
commit
34256f992a

+ 3
- 3
components/shared.tsx View File

@@ -31,7 +31,7 @@ export const IconButton = styled('button', {
31 31
   variants: {
32 32
     size: {
33 33
       small: {
34
-        '& > svg': {
34
+        '& svg': {
35 35
           height: '16px',
36 36
           width: '16px',
37 37
         },
@@ -39,7 +39,7 @@ export const IconButton = styled('button', {
39 39
       medium: {
40 40
         height: 44,
41 41
         width: 44,
42
-        '& > svg': {
42
+        '& svg': {
43 43
           height: '20px',
44 44
           width: '20px',
45 45
         },
@@ -47,7 +47,7 @@ export const IconButton = styled('button', {
47 47
       large: {
48 48
         height: 44,
49 49
         width: 44,
50
-        '& > svg': {
50
+        '& svg': {
51 51
           height: '24px',
52 52
           width: '24px',
53 53
         },

+ 5
- 2
components/style-panel/quick-color-select.tsx View File

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

+ 3
- 2
components/style-panel/quick-dash-select.tsx View File

@@ -1,5 +1,6 @@
1 1
 import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
2 2
 import { IconButton } from 'components/shared'
3
+import Tooltip from 'components/tooltip'
3 4
 import state, { useSelector } from 'state'
4 5
 import { DashStyle } from 'types'
5 6
 import {
@@ -21,8 +22,8 @@ export default function QuickdashSelect() {
21 22
 
22 23
   return (
23 24
     <DropdownMenu.Root>
24
-      <DropdownMenu.Trigger as={IconButton} title="dash">
25
-        {dashes[dash]}
25
+      <DropdownMenu.Trigger as={IconButton}>
26
+        <Tooltip label="Dash">{dashes[dash]}</Tooltip>
26 27
       </DropdownMenu.Trigger>
27 28
       <DropdownContent direction="vertical">
28 29
         <DashItem isActive={dash === DashStyle.Solid} dash={DashStyle.Solid} />

+ 5
- 2
components/style-panel/quick-size-select.tsx View File

@@ -1,5 +1,6 @@
1 1
 import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
2 2
 import { IconButton } from 'components/shared'
3
+import Tooltip from 'components/tooltip'
3 4
 import { Circle } from 'react-feather'
4 5
 import state, { useSelector } from 'state'
5 6
 import { SizeStyle } from 'types'
@@ -16,8 +17,10 @@ export default function QuickSizeSelect() {
16 17
 
17 18
   return (
18 19
     <DropdownMenu.Root>
19
-      <DropdownMenu.Trigger as={IconButton} title="size">
20
-        <Circle size={sizes[size]} stroke="none" fill="currentColor" />
20
+      <DropdownMenu.Trigger as={IconButton}>
21
+        <Tooltip label="Size">
22
+          <Circle size={sizes[size]} stroke="none" fill="currentColor" />
23
+        </Tooltip>
21 24
       </DropdownMenu.Trigger>
22 25
       <DropdownContent direction="vertical">
23 26
         <SizeItem isActive={size === SizeStyle.Small} size={SizeStyle.Small} />

+ 43
- 12
components/style-panel/style-panel.tsx View File

@@ -4,7 +4,7 @@ import * as Panel from 'components/panel'
4 4
 import { useRef } from 'react'
5 5
 import { IconButton } from 'components/shared'
6 6
 import * as Checkbox from '@radix-ui/react-checkbox'
7
-import { ChevronDown, Square, Trash2, X } from 'react-feather'
7
+import { ChevronDown, Square, Tool, Trash2, X } from 'react-feather'
8 8
 import { deepCompare, deepCompareArrays, getPage } from 'utils/utils'
9 9
 import { strokes } from 'lib/shape-styles'
10 10
 import AlignDistribute from './align-distribute'
@@ -35,6 +35,7 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
35 35
 import IsFilledPicker from './is-filled-picker'
36 36
 import QuickSizeSelect from './quick-size-select'
37 37
 import QuickdashSelect from './quick-dash-select'
38
+import Tooltip from 'components/tooltip'
38 39
 
39 40
 export default function StylePanel() {
40 41
   const rContainer = useRef<HTMLDivElement>(null)
@@ -54,7 +55,9 @@ export default function StylePanel() {
54 55
             size="small"
55 56
             onClick={() => state.send('TOGGLED_STYLE_PANEL_OPEN')}
56 57
           >
57
-            <ChevronDown />
58
+            <Tooltip label="More">
59
+              <ChevronDown />
60
+            </Tooltip>
58 61
           </IconButton>
59 62
         </>
60 63
       )}
@@ -125,35 +128,49 @@ function SelectedShapeStyles() {
125 128
             size="small"
126 129
             onClick={() => state.send('DUPLICATED')}
127 130
           >
128
-            <CopyIcon />
131
+            <Tooltip label="Duplicate">
132
+              <CopyIcon />
133
+            </Tooltip>
129 134
           </IconButton>
135
+
130 136
           <IconButton
131 137
             disabled={!hasSelection}
132 138
             size="small"
133 139
             onClick={() => state.send('ROTATED_CCW')}
134 140
           >
135
-            <RotateCounterClockwiseIcon />
141
+            <Tooltip label="Rotate">
142
+              <RotateCounterClockwiseIcon />
143
+            </Tooltip>
136 144
           </IconButton>
145
+
137 146
           <IconButton
138 147
             disabled={!hasSelection}
139 148
             size="small"
140 149
             onClick={() => state.send('TOGGLED_SHAPE_HIDE')}
141 150
           >
142
-            {isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
151
+            <Tooltip label="Toogle Hidden">
152
+              {isAllHidden ? <EyeClosedIcon /> : <EyeOpenIcon />}
153
+            </Tooltip>
143 154
           </IconButton>
155
+
144 156
           <IconButton
145 157
             disabled={!hasSelection}
146 158
             size="small"
147 159
             onClick={() => state.send('TOGGLED_SHAPE_LOCK')}
148 160
           >
149
-            {isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
161
+            <Tooltip label="Toogle Locked">
162
+              {isAllLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
163
+            </Tooltip>
150 164
           </IconButton>
165
+
151 166
           <IconButton
152 167
             disabled={!hasSelection}
153 168
             size="small"
154 169
             onClick={() => state.send('TOGGLED_SHAPE_ASPECT_LOCK')}
155 170
           >
156
-            {isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
171
+            <Tooltip label="Toogle Aspect Ratio Lock">
172
+              {isAllAspectLocked ? <AspectRatioIcon /> : <BoxIcon />}
173
+            </Tooltip>
157 174
           </IconButton>
158 175
         </ButtonsRow>
159 176
         <ButtonsRow>
@@ -162,35 +179,49 @@ function SelectedShapeStyles() {
162 179
             size="small"
163 180
             onClick={() => state.send('MOVED', { type: MoveType.ToBack })}
164 181
           >
165
-            <PinBottomIcon />
182
+            <Tooltip label="Move to Back">
183
+              <PinBottomIcon />
184
+            </Tooltip>
166 185
           </IconButton>
186
+
167 187
           <IconButton
168 188
             disabled={!hasSelection}
169 189
             size="small"
170 190
             onClick={() => state.send('MOVED', { type: MoveType.Backward })}
171 191
           >
172
-            <ArrowDownIcon />
192
+            <Tooltip label="Move Backward">
193
+              <ArrowDownIcon />
194
+            </Tooltip>
173 195
           </IconButton>
196
+
174 197
           <IconButton
175 198
             disabled={!hasSelection}
176 199
             size="small"
177 200
             onClick={() => state.send('MOVED', { type: MoveType.Forward })}
178 201
           >
179
-            <ArrowUpIcon />
202
+            <Tooltip label="Move Forward">
203
+              <ArrowUpIcon />
204
+            </Tooltip>
180 205
           </IconButton>
206
+
181 207
           <IconButton
182 208
             disabled={!hasSelection}
183 209
             size="small"
184 210
             onClick={() => state.send('MOVED', { type: MoveType.ToFront })}
185 211
           >
186
-            <PinTopIcon />
212
+            <Tooltip label="More to Front">
213
+              <PinTopIcon />
214
+            </Tooltip>
187 215
           </IconButton>
216
+
188 217
           <IconButton
189 218
             disabled={!hasSelection}
190 219
             size="small"
191 220
             onClick={() => state.send('DELETED')}
192 221
           >
193
-            <Trash2 />
222
+            <Tooltip label="Delete">
223
+              <Trash2 size="15" />
224
+            </Tooltip>
194 225
           </IconButton>
195 226
         </ButtonsRow>
196 227
         <AlignDistribute

+ 64
- 62
components/tools-panel/tools-panel.tsx View File

@@ -18,6 +18,7 @@ import styled from 'styles'
18 18
 import { ShapeType } from 'types'
19 19
 import UndoRedo from './undo-redo'
20 20
 import Zoom from './zoom'
21
+import Tooltip from '../tooltip'
21 22
 
22 23
 const selectArrowTool = () => state.send('SELECTED_ARROW_TOOL')
23 24
 const selectCircleTool = () => state.send('SELECTED_CIRCLE_TOOL')
@@ -32,20 +33,7 @@ const selectSelectTool = () => state.send('SELECTED_SELECT_TOOL')
32 33
 const selectToolLock = () => state.send('TOGGLED_TOOL_LOCK')
33 34
 
34 35
 export default function ToolsPanel() {
35
-  const activeTool = useSelector((state) =>
36
-    state.whenIn({
37
-      arrow: ShapeType.Arrow,
38
-      circle: ShapeType.Circle,
39
-      dot: ShapeType.Dot,
40
-      draw: ShapeType.Draw,
41
-      ellipse: ShapeType.Ellipse,
42
-      line: ShapeType.Line,
43
-      polyline: ShapeType.Polyline,
44
-      ray: ShapeType.Ray,
45
-      rectangle: ShapeType.Rectangle,
46
-      selecting: 'select',
47
-    })
48
-  )
36
+  const activeTool = useSelector((s) => s.data.activeTool)
49 37
 
50 38
   const isToolLocked = useSelector((s) => s.data.settings.isToolLocked)
51 39
 
@@ -56,48 +44,58 @@ export default function ToolsPanel() {
56 44
       <Zoom />
57 45
       <Flex size={{ '@sm': 'small' }}>
58 46
         <Container>
59
-          <IconButton
60
-            name="select"
61
-            size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
62
-            onClick={selectSelectTool}
63
-            isActive={activeTool === 'select'}
64
-          >
65
-            <CursorArrowIcon />
66
-          </IconButton>
47
+          <Tooltip label="Select">
48
+            <IconButton
49
+              name="select"
50
+              size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
51
+              onClick={selectSelectTool}
52
+              isActive={activeTool === 'select'}
53
+            >
54
+              <CursorArrowIcon />
55
+            </IconButton>
56
+          </Tooltip>
67 57
         </Container>
68 58
         <Container>
69
-          <IconButton
70
-            name={ShapeType.Draw}
71
-            size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
72
-            onClick={selectDrawTool}
73
-            isActive={activeTool === ShapeType.Draw}
74
-          >
75
-            <Pencil1Icon />
76
-          </IconButton>
77
-          <IconButton
78
-            name={ShapeType.Rectangle}
79
-            size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
80
-            onClick={selectRectangleTool}
81
-            isActive={activeTool === ShapeType.Rectangle}
82
-          >
83
-            <SquareIcon />
84
-          </IconButton>
85
-          <IconButton
86
-            name={ShapeType.Circle}
87
-            size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
88
-            onClick={selectEllipseTool}
89
-            isActive={activeTool === ShapeType.Ellipse}
90
-          >
91
-            <CircleIcon />
92
-          </IconButton>
93
-          <IconButton
94
-            name={ShapeType.Arrow}
95
-            size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
96
-            onClick={selectArrowTool}
97
-            isActive={activeTool === ShapeType.Arrow}
98
-          >
99
-            <ArrowTopRightIcon />
100
-          </IconButton>
59
+          <Tooltip label="Draw">
60
+            <IconButton
61
+              name={ShapeType.Draw}
62
+              size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
63
+              onClick={selectDrawTool}
64
+              isActive={activeTool === ShapeType.Draw}
65
+            >
66
+              <Pencil1Icon />
67
+            </IconButton>
68
+          </Tooltip>
69
+          <Tooltip label="Rectangle">
70
+            <IconButton
71
+              name={ShapeType.Rectangle}
72
+              size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
73
+              onClick={selectRectangleTool}
74
+              isActive={activeTool === ShapeType.Rectangle}
75
+            >
76
+              <SquareIcon />
77
+            </IconButton>
78
+          </Tooltip>
79
+          <Tooltip label="Ellipse">
80
+            <IconButton
81
+              name={ShapeType.Circle}
82
+              size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
83
+              onClick={selectEllipseTool}
84
+              isActive={activeTool === ShapeType.Ellipse}
85
+            >
86
+              <CircleIcon />
87
+            </IconButton>
88
+          </Tooltip>
89
+          <Tooltip label="Arrow">
90
+            <IconButton
91
+              name={ShapeType.Arrow}
92
+              size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
93
+              onClick={selectArrowTool}
94
+              isActive={activeTool === ShapeType.Arrow}
95
+            >
96
+              <ArrowTopRightIcon />
97
+            </IconButton>
98
+          </Tooltip>
101 99
           {/* <IconButton
102 100
             name={ShapeType.Circle}
103 101
             size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
@@ -132,19 +130,23 @@ export default function ToolsPanel() {
132 130
           </IconButton> */}
133 131
         </Container>
134 132
         <Container>
135
-          <IconButton
136
-            size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
137
-            onClick={selectToolLock}
138
-          >
139
-            {isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
140
-          </IconButton>
141
-          {isPenLocked && (
133
+          <Tooltip label="Lock Tool">
142 134
             <IconButton
143 135
               size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
144 136
               onClick={selectToolLock}
145 137
             >
146
-              <Pencil2Icon />
138
+              {isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
147 139
             </IconButton>
140
+          </Tooltip>
141
+          {isPenLocked && (
142
+            <Tooltip label="Unlock Pen">
143
+              <IconButton
144
+                size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
145
+                onClick={selectToolLock}
146
+              >
147
+                <Pencil2Icon />
148
+              </IconButton>
149
+            </Tooltip>
148 150
           )}
149 151
         </Container>
150 152
       </Flex>

+ 16
- 9
components/tools-panel/undo-redo.tsx View File

@@ -2,6 +2,7 @@ import { IconButton } from 'components/shared'
2 2
 import { RotateCcw, RotateCw, Trash2 } from 'react-feather'
3 3
 import state, { useSelector } from 'state'
4 4
 import styled from 'styles'
5
+import Tooltip from '../tooltip'
5 6
 
6 7
 const undo = () => state.send('UNDO')
7 8
 const redo = () => state.send('REDO')
@@ -10,15 +11,21 @@ const clear = () => state.send('CLEARED_PAGE')
10 11
 export default function UndoRedo() {
11 12
   return (
12 13
     <Container size={{ '@sm': 'small' }}>
13
-      <IconButton onClick={undo}>
14
-        <RotateCcw />
15
-      </IconButton>
16
-      <IconButton onClick={redo}>
17
-        <RotateCw />
18
-      </IconButton>
19
-      <IconButton onClick={clear}>
20
-        <Trash2 />
21
-      </IconButton>
14
+      <Tooltip label="Undo">
15
+        <IconButton onClick={undo}>
16
+          <RotateCcw />
17
+        </IconButton>
18
+      </Tooltip>
19
+      <Tooltip label="Redo">
20
+        <IconButton onClick={redo}>
21
+          <RotateCw />
22
+        </IconButton>
23
+      </Tooltip>
24
+      <Tooltip label="Clear Canvas">
25
+        <IconButton onClick={clear}>
26
+          <Trash2 />
27
+        </IconButton>
28
+      </Tooltip>
22 29
     </Container>
23 30
   )
24 31
 }

+ 14
- 7
components/tools-panel/zoom.tsx View File

@@ -2,6 +2,7 @@ import { ZoomInIcon, ZoomOutIcon } from '@radix-ui/react-icons'
2 2
 import { IconButton } from 'components/shared'
3 3
 import state, { useSelector } from 'state'
4 4
 import styled from 'styles'
5
+import Tooltip from '../tooltip'
5 6
 
6 7
 const zoomIn = () => state.send('ZOOMED_IN')
7 8
 const zoomOut = () => state.send('ZOOMED_OUT')
@@ -11,13 +12,19 @@ const zoomToActual = () => state.send('ZOOMED_TO_ACTUAL')
11 12
 export default function Zoom() {
12 13
   return (
13 14
     <Container size={{ '@sm': 'small' }}>
14
-      <IconButton onClick={zoomOut}>
15
-        <ZoomOutIcon />
16
-      </IconButton>
17
-      <IconButton onClick={zoomIn}>
18
-        <ZoomInIcon />
19
-      </IconButton>
20
-      <ZoomCounter />
15
+      <Tooltip label="Zoom Out">
16
+        <IconButton onClick={zoomOut}>
17
+          <ZoomOutIcon />
18
+        </IconButton>
19
+      </Tooltip>
20
+      <Tooltip label="Zoom In">
21
+        <IconButton onClick={zoomIn}>
22
+          <ZoomInIcon />
23
+        </IconButton>
24
+      </Tooltip>
25
+      <Tooltip label="Reset Zoom">
26
+        <ZoomCounter />
27
+      </Tooltip>
21 28
     </Container>
22 29
   )
23 30
 }

+ 36
- 0
components/tooltip.tsx View File

@@ -0,0 +1,36 @@
1
+import * as _Tooltip from '@radix-ui/react-tooltip'
2
+import React from 'react'
3
+import styled from 'styles'
4
+
5
+export default function Tooltip({
6
+  children,
7
+  label,
8
+  side = 'top',
9
+}: {
10
+  children: React.ReactNode
11
+  label: string
12
+  side?: 'bottom' | 'left' | 'right' | 'top'
13
+}) {
14
+  return (
15
+    <_Tooltip.Root>
16
+      <_Tooltip.Trigger as="span">{children}</_Tooltip.Trigger>
17
+      <StyledContent side={side} sideOffset={8}>
18
+        {label}
19
+        <StyledArrow />
20
+      </StyledContent>
21
+    </_Tooltip.Root>
22
+  )
23
+}
24
+
25
+const StyledContent = styled(_Tooltip.Content, {
26
+  borderRadius: 3,
27
+  padding: '6px 12px',
28
+  fontSize: '$1',
29
+  backgroundColor: '$text',
30
+  color: '$panel',
31
+})
32
+
33
+const StyledArrow = styled(_Tooltip.Arrow, {
34
+  fill: '$text',
35
+  margin: '0 8px',
36
+})

+ 1
- 9
lib/shape-styles.ts View File

@@ -6,15 +6,11 @@ export const strokes: Record<ColorStyle, string> = {
6 6
   [ColorStyle.LightGray]: 'rgba(224, 226, 230, 1.000)',
7 7
   [ColorStyle.Gray]: 'rgba(172, 181, 189, 1.000)',
8 8
   [ColorStyle.Black]: 'rgba(0,0,0, 1.000)',
9
-  [ColorStyle.Lime]: 'rgba(115, 184, 23, 1.000)',
10 9
   [ColorStyle.Green]: 'rgba(54, 178, 77, 1.000)',
11
-  [ColorStyle.Teal]: 'rgba(9, 167, 120, 1.000)',
12 10
   [ColorStyle.Cyan]: 'rgba(14, 152, 173, 1.000)',
13 11
   [ColorStyle.Blue]: 'rgba(28, 126, 214, 1.000)',
14 12
   [ColorStyle.Indigo]: 'rgba(66, 99, 235, 1.000)',
15 13
   [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 14
   [ColorStyle.Red]: 'rgba(240, 63, 63, 1.000)',
19 15
   [ColorStyle.Orange]: 'rgba(247, 103, 6, 1.000)',
20 16
   [ColorStyle.Yellow]: 'rgba(245, 159, 0, 1.000)',
@@ -24,16 +20,12 @@ export const fills = {
24 20
   [ColorStyle.White]: 'rgba(224, 226, 230, 1.000)',
25 21
   [ColorStyle.LightGray]: 'rgba(255, 255, 255, 1.000)',
26 22
   [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)',
23
+  [ColorStyle.Black]: 'rgba(255, 255, 255, 1.000)',
29 24
   [ColorStyle.Green]: 'rgba(235, 251, 238, 1.000)',
30
-  [ColorStyle.Teal]: 'rgba(230, 252, 245, 1.000)',
31 25
   [ColorStyle.Cyan]: 'rgba(227, 250, 251, 1.000)',
32 26
   [ColorStyle.Blue]: 'rgba(231, 245, 255, 1.000)',
33 27
   [ColorStyle.Indigo]: 'rgba(237, 242, 255, 1.000)',
34 28
   [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 29
   [ColorStyle.Red]: 'rgba(255, 245, 245, 1.000)',
38 30
   [ColorStyle.Orange]: 'rgba(255, 244, 229, 1.000)',
39 31
   [ColorStyle.Yellow]: 'rgba(255, 249, 219, 1.000)',

+ 45
- 29
lib/shape-utils/arrow.tsx View File

@@ -97,44 +97,60 @@ const arrow = registerShapeUtils<ArrowShape>({
97 97
     const { start, end, bend: _bend } = handles
98 98
 
99 99
     const arrowDist = vec.dist(start.point, end.point)
100
-    const bendDist = arrowDist * bend
101
-    const showCircle = Math.abs(bendDist) > 20
100
+    const showCircle = !vec.isEqual(
101
+      _bend.point,
102
+      vec.med(start.point, end.point)
103
+    )
102 104
 
103 105
     const style = getShapeStyle(shape.style)
104 106
 
107
+    let body: JSX.Element
108
+    let endAngle: number
109
+
110
+    if (showCircle) {
111
+      if (!ctpCache.has(handles)) {
112
+        ctpCache.set(
113
+          handles,
114
+          circleFromThreePoints(start.point, end.point, _bend.point)
115
+        )
116
+      }
117
+
118
+      const circle = getCtp(shape)
119
+
120
+      body = (
121
+        <path
122
+          d={getArrowArcPath(start, end, circle, bend)}
123
+          fill="none"
124
+          strokeLinecap="round"
125
+        />
126
+      )
127
+
128
+      const CE =
129
+        vec.angle([circle[0], circle[1]], end.point) -
130
+        vec.angle(start.point, end.point) +
131
+        (Math.PI / 2) * (bend > 0 ? 0.98 : -0.98)
132
+
133
+      endAngle = CE
134
+    } else {
135
+      body = (
136
+        <polyline
137
+          points={[start.point, end.point].join(' ')}
138
+          strokeLinecap="round"
139
+        />
140
+      )
141
+      endAngle = 0
142
+    }
143
+
105 144
     // Arrowhead
106 145
     const length = Math.min(arrowDist / 2, 16 + +style.strokeWidth * 2)
107
-    const angle = showCircle ? bend * (Math.PI * 0.48) : 0
108 146
     const u = vec.uni(vec.vec(start.point, end.point))
109
-    const v = vec.rot(vec.mul(vec.neg(u), length), angle)
147
+    const v = vec.rot(vec.mul(vec.neg(u), length), endAngle)
110 148
     const b = vec.add(points[1], vec.rot(v, Math.PI / 6))
111 149
     const c = vec.add(points[1], vec.rot(v, -(Math.PI / 6)))
112 150
 
113
-    if (showCircle && !ctpCache.has(handles)) {
114
-      ctpCache.set(
115
-        handles,
116
-        circleFromThreePoints(start.point, end.point, _bend.point)
117
-      )
118
-    }
119
-
120
-    const circle = showCircle && getCtp(shape)
121
-
122 151
     return (
123 152
       <g id={id}>
124
-        {circle ? (
125
-          <>
126
-            <path
127
-              d={getArrowArcPath(start, end, circle, bend)}
128
-              fill="none"
129
-              strokeLinecap="round"
130
-            />
131
-          </>
132
-        ) : (
133
-          <polyline
134
-            points={[start.point, end.point].join(' ')}
135
-            strokeLinecap="round"
136
-          />
137
-        )}
153
+        {body}
138 154
         <circle
139 155
           cx={start.point[0]}
140 156
           cy={start.point[1]}
@@ -301,11 +317,11 @@ function getArrowArcPath(
301 317
 }
302 318
 
303 319
 function getBendPoint(shape: ArrowShape) {
304
-  const { start, end, bend } = shape.handles
320
+  const { start, end } = shape.handles
305 321
 
306 322
   const dist = vec.dist(start.point, end.point)
307 323
   const midPoint = vec.med(start.point, end.point)
308
-  const bendDist = (dist / 2) * shape.bend
324
+  const bendDist = (dist / 2) * shape.bend * Math.min(1, dist / 128)
309 325
   const u = vec.uni(vec.vec(start.point, end.point))
310 326
 
311 327
   return Math.abs(bendDist) < 10

+ 22
- 19
lib/shape-utils/draw.tsx View File

@@ -1,6 +1,6 @@
1 1
 import { v4 as uuid } from 'uuid'
2 2
 import * as vec from 'utils/vec'
3
-import { DashStyle, DrawShape, ShapeType } from 'types'
3
+import { DashStyle, DrawShape, ShapeStyles, ShapeType } from 'types'
4 4
 import { registerShapeUtils } from './index'
5 5
 import { intersectPolylineBounds } from 'utils/intersections'
6 6
 import { boundsContainPolygon } from 'utils/bounds'
@@ -11,7 +11,6 @@ import {
11 11
   getSvgPathFromStroke,
12 12
   translateBounds,
13 13
 } from 'utils/utils'
14
-import styled from 'styles'
15 14
 import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
16 15
 
17 16
 const pathCache = new WeakMap<DrawShape['points'], string>([])
@@ -48,17 +47,7 @@ const draw = registerShapeUtils<DrawShape>({
48 47
     const styles = getShapeStyle(style)
49 48
 
50 49
     if (!pathCache.has(points)) {
51
-      pathCache.set(
52
-        points,
53
-        getSvgPathFromStroke(
54
-          getStroke(points, {
55
-            size: +styles.strokeWidth * 2,
56
-            thinning: 0.9,
57
-            end: { taper: 100 },
58
-            start: { taper: 40 },
59
-          })
60
-        )
61
-      )
50
+      renderPath(shape, style)
62 51
     }
63 52
 
64 53
     if (points.length < 2) {
@@ -155,9 +144,11 @@ const draw = registerShapeUtils<DrawShape>({
155 144
   },
156 145
 
157 146
   applyStyles(shape, style) {
158
-    Object.assign(shape.style, style)
159
-    shape.style.isFilled = false
160
-    shape.style.dash = DashStyle.Solid
147
+    const styles = { ...shape.style, ...style }
148
+    styles.isFilled = false
149
+    styles.dash = DashStyle.Solid
150
+    shape.style = styles
151
+    shape.points = [...shape.points]
161 152
     return this
162 153
   },
163 154
 
@@ -166,6 +157,18 @@ const draw = registerShapeUtils<DrawShape>({
166 157
 
167 158
 export default draw
168 159
 
169
-const DrawPath = styled('path', {
170
-  strokeWidth: 0,
171
-})
160
+function renderPath(shape: DrawShape, style: ShapeStyles) {
161
+  const styles = getShapeStyle(style)
162
+
163
+  pathCache.set(
164
+    shape.points,
165
+    getSvgPathFromStroke(
166
+      getStroke(shape.points, {
167
+        size: +styles.strokeWidth * 2,
168
+        thinning: 0.9,
169
+        end: { taper: 100 },
170
+        start: { taper: 40 },
171
+      })
172
+    )
173
+  )
174
+}

+ 1
- 0
package.json View File

@@ -13,6 +13,7 @@
13 13
     "@radix-ui/react-dropdown-menu": "^0.0.19",
14 14
     "@radix-ui/react-icons": "^1.0.3",
15 15
     "@radix-ui/react-radio-group": "^0.0.16",
16
+    "@radix-ui/react-tooltip": "^0.0.18",
16 17
     "@state-designer/react": "^1.7.1",
17 18
     "@stitches/react": "^0.1.9",
18 19
     "framer-motion": "^4.1.16",

+ 61
- 7
state/state.ts View File

@@ -58,6 +58,7 @@ const initialData: Data = {
58 58
     point: [0, 0],
59 59
     zoom: 1,
60 60
   },
61
+  activeTool: 'select',
61 62
   brush: undefined,
62 63
   boundsRotation: 0,
63 64
   pointedId: null,
@@ -142,6 +143,7 @@ const state = createState({
142 143
       initial: 'selecting',
143 144
       states: {
144 145
         selecting: {
146
+          onEnter: 'setActiveToolSelect',
145 147
           on: {
146 148
             SAVED: 'forceSave',
147 149
             UNDO: 'undo',
@@ -322,6 +324,7 @@ const state = createState({
322 324
           },
323 325
           states: {
324 326
             draw: {
327
+              onEnter: 'setActiveToolDraw',
325 328
               initial: 'creating',
326 329
               states: {
327 330
                 creating: {
@@ -361,6 +364,7 @@ const state = createState({
361 364
               },
362 365
             },
363 366
             dot: {
367
+              onEnter: 'setActiveToolDot',
364 368
               initial: 'creating',
365 369
               states: {
366 370
                 creating: {
@@ -417,6 +421,7 @@ const state = createState({
417 421
               },
418 422
             },
419 423
             arrow: {
424
+              onEnter: 'setActiveToolArrow',
420 425
               initial: 'creating',
421 426
               states: {
422 427
                 creating: {
@@ -462,6 +467,7 @@ const state = createState({
462 467
               },
463 468
             },
464 469
             circle: {
470
+              onEnter: 'setActiveToolCircle',
465 471
               initial: 'creating',
466 472
               states: {
467 473
                 creating: {
@@ -492,6 +498,7 @@ const state = createState({
492 498
               },
493 499
             },
494 500
             ellipse: {
501
+              onEnter: 'setActiveToolEllipse',
495 502
               initial: 'creating',
496 503
               states: {
497 504
                 creating: {
@@ -519,6 +526,7 @@ const state = createState({
519 526
               },
520 527
             },
521 528
             rectangle: {
529
+              onEnter: 'setActiveToolRectangle',
522 530
               initial: 'creating',
523 531
               states: {
524 532
                 creating: {
@@ -549,6 +557,7 @@ const state = createState({
549 557
               },
550 558
             },
551 559
             ray: {
560
+              onEnter: 'setActiveToolRay',
552 561
               initial: 'creating',
553 562
               states: {
554 563
                 creating: {
@@ -579,6 +588,7 @@ const state = createState({
579 588
               },
580 589
             },
581 590
             line: {
591
+              onEnter: 'setActiveToolLine',
582 592
               initial: 'creating',
583 593
               states: {
584 594
                 creating: {
@@ -608,7 +618,9 @@ const state = createState({
608 618
                 },
609 619
               },
610 620
             },
611
-            polyline: {},
621
+            polyline: {
622
+              onEnter: 'setActiveToolPolyline',
623
+            },
612 624
           },
613 625
         },
614 626
         drawingShape: {
@@ -1011,6 +1023,42 @@ const state = createState({
1011 1023
       commands.rotateCcw(data)
1012 1024
     },
1013 1025
 
1026
+    /* ---------------------- Tool ---------------------- */
1027
+
1028
+    setActiveTool(data, payload: { tool: ShapeType | 'select' }) {
1029
+      data.activeTool = payload.tool
1030
+    },
1031
+    setActiveToolSelect(data) {
1032
+      data.activeTool = 'select'
1033
+    },
1034
+    setActiveToolDraw(data) {
1035
+      data.activeTool = ShapeType.Draw
1036
+    },
1037
+    setActiveToolRectangle(data) {
1038
+      data.activeTool = ShapeType.Rectangle
1039
+    },
1040
+    setActiveToolEllipse(data) {
1041
+      data.activeTool = ShapeType.Ellipse
1042
+    },
1043
+    setActiveToolArrow(data) {
1044
+      data.activeTool = ShapeType.Arrow
1045
+    },
1046
+    setActiveToolDot(data) {
1047
+      data.activeTool = ShapeType.Dot
1048
+    },
1049
+    setActiveToolPolyline(data) {
1050
+      data.activeTool = ShapeType.Polyline
1051
+    },
1052
+    setActiveToolRay(data) {
1053
+      data.activeTool = ShapeType.Ray
1054
+    },
1055
+    setActiveToolCircle(data) {
1056
+      data.activeTool = ShapeType.Circle
1057
+    },
1058
+    setActiveToolLine(data) {
1059
+      data.activeTool = ShapeType.Line
1060
+    },
1061
+
1014 1062
     /* --------------------- Camera --------------------- */
1015 1063
 
1016 1064
     zoomIn(data) {
@@ -1019,7 +1067,7 @@ const state = createState({
1019 1067
       const center = [window.innerWidth / 2, window.innerHeight / 2]
1020 1068
 
1021 1069
       const p0 = screenToWorld(center, data)
1022
-      camera.zoom = Math.min(3, (i + 1) * 0.25)
1070
+      camera.zoom = getCameraZoom((i + 1) * 0.25)
1023 1071
       const p1 = screenToWorld(center, data)
1024 1072
       camera.point = vec.add(camera.point, vec.sub(p1, p0))
1025 1073
 
@@ -1031,7 +1079,7 @@ const state = createState({
1031 1079
       const center = [window.innerWidth / 2, window.innerHeight / 2]
1032 1080
 
1033 1081
       const p0 = screenToWorld(center, data)
1034
-      camera.zoom = Math.max(0.1, (i - 1) * 0.25)
1082
+      camera.zoom = getCameraZoom((i - 1) * 0.25)
1035 1083
       const p1 = screenToWorld(center, data)
1036 1084
       camera.point = vec.add(camera.point, vec.sub(p1, p0))
1037 1085
 
@@ -1067,10 +1115,11 @@ const state = createState({
1067 1115
 
1068 1116
       const bounds = getSelectedBounds(data)
1069 1117
 
1070
-      const zoom =
1118
+      const zoom = getCameraZoom(
1071 1119
         bounds.width > bounds.height
1072 1120
           ? (window.innerWidth - 128) / bounds.width
1073 1121
           : (window.innerHeight - 128) / bounds.height
1122
+      )
1074 1123
 
1075 1124
       const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
1076 1125
       const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
@@ -1096,10 +1145,11 @@ const state = createState({
1096 1145
         )
1097 1146
       )
1098 1147
 
1099
-      const zoom =
1148
+      const zoom = getCameraZoom(
1100 1149
         bounds.width > bounds.height
1101 1150
           ? (window.innerWidth - 128) / bounds.width
1102 1151
           : (window.innerHeight - 128) / bounds.height
1152
+      )
1103 1153
 
1104 1154
       const mx = (window.innerWidth - bounds.width * zoom) / 2 / zoom
1105 1155
       const my = (window.innerHeight - bounds.height * zoom) / 2 / zoom
@@ -1114,7 +1164,7 @@ const state = createState({
1114 1164
       const next = camera.zoom - (payload.delta / 100) * camera.zoom
1115 1165
 
1116 1166
       const p0 = screenToWorld(payload.point, data)
1117
-      camera.zoom = clamp(next, 0.1, 3)
1167
+      camera.zoom = getCameraZoom(next)
1118 1168
       const p1 = screenToWorld(payload.point, data)
1119 1169
       camera.point = vec.add(camera.point, vec.sub(p1, p0))
1120 1170
 
@@ -1140,7 +1190,7 @@ const state = createState({
1140 1190
       const next = camera.zoom - (payload.distanceDelta / 300) * camera.zoom
1141 1191
 
1142 1192
       const p0 = screenToWorld(payload.point, data)
1143
-      camera.zoom = clamp(next, 0.1, 3)
1193
+      camera.zoom = getCameraZoom(next)
1144 1194
       const p1 = screenToWorld(payload.point, data)
1145 1195
       camera.point = vec.add(camera.point, vec.sub(p1, p0))
1146 1196
 
@@ -1336,3 +1386,7 @@ let session: Sessions.BaseSession
1336 1386
 export default state
1337 1387
 
1338 1388
 export const useSelector = createSelectorHook(state)
1389
+
1390
+function getCameraZoom(zoom: number) {
1391
+  return clamp(zoom, 0.1, 5)
1392
+}

+ 1
- 4
types.ts View File

@@ -23,6 +23,7 @@ export interface Data {
23 23
     point: number[]
24 24
     zoom: number
25 25
   }
26
+  activeTool: ShapeType | 'select'
26 27
   brush?: Bounds
27 28
   boundsRotation: number
28 29
   selectedIds: Set<string>
@@ -72,15 +73,11 @@ export enum ColorStyle {
72 73
   LightGray = 'LightGray',
73 74
   Gray = 'Gray',
74 75
   Black = 'Black',
75
-  Lime = 'Lime',
76 76
   Green = 'Green',
77
-  Teal = 'Teal',
78 77
   Cyan = 'Cyan',
79 78
   Blue = 'Blue',
80 79
   Indigo = 'Indigo',
81 80
   Violet = 'Violet',
82
-  Grape = 'Grape',
83
-  Pink = 'Pink',
84 81
   Red = 'Red',
85 82
   Orange = 'Orange',
86 83
   Yellow = 'Yellow',

+ 39
- 0
yarn.lock View File

@@ -1500,6 +1500,29 @@
1500 1500
     "@radix-ui/primitive" "0.0.5"
1501 1501
     "@radix-ui/react-compose-refs" "0.0.5"
1502 1502
 
1503
+"@radix-ui/react-tooltip@^0.0.18":
1504
+  version "0.0.18"
1505
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.0.18.tgz#7594297dbc2acf101ac45fdb414c7bc0ac9426bc"
1506
+  integrity sha512-oYRAbbTZJ8zEokrrk5pe7QbzF+ZnzMby8mBplrkColi0ntToJ7RKzPgUs1OOFvmg/Nld0Iy2FufrTNlyyEI3kQ==
1507
+  dependencies:
1508
+    "@babel/runtime" "^7.13.10"
1509
+    "@radix-ui/primitive" "0.0.5"
1510
+    "@radix-ui/react-compose-refs" "0.0.5"
1511
+    "@radix-ui/react-context" "0.0.5"
1512
+    "@radix-ui/react-id" "0.0.6"
1513
+    "@radix-ui/react-polymorphic" "0.0.11"
1514
+    "@radix-ui/react-popper" "0.0.16"
1515
+    "@radix-ui/react-portal" "0.0.13"
1516
+    "@radix-ui/react-presence" "0.0.14"
1517
+    "@radix-ui/react-primitive" "0.0.13"
1518
+    "@radix-ui/react-slot" "0.0.10"
1519
+    "@radix-ui/react-use-controllable-state" "0.0.6"
1520
+    "@radix-ui/react-use-escape-keydown" "0.0.6"
1521
+    "@radix-ui/react-use-layout-effect" "0.0.5"
1522
+    "@radix-ui/react-use-previous" "0.0.5"
1523
+    "@radix-ui/react-use-rect" "0.0.7"
1524
+    "@radix-ui/react-visually-hidden" "0.0.13"
1525
+
1503 1526
 "@radix-ui/react-use-body-pointer-events@0.0.6":
1504 1527
   version "0.0.6"
1505 1528
   resolved "https://registry.yarnpkg.com/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.0.6.tgz#30b21301880417e7dbb345871ff5a83f2abe0d8d"
@@ -1538,6 +1561,13 @@
1538 1561
   dependencies:
1539 1562
     "@babel/runtime" "^7.13.10"
1540 1563
 
1564
+"@radix-ui/react-use-previous@0.0.5":
1565
+  version "0.0.5"
1566
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-0.0.5.tgz#75191d1fa0ac24c560fe8cfbaa2f1174858cbb2f"
1567
+  integrity sha512-GjtJlWlDAEMqCm2RDnVdWI6tk4/ZQfRq/VlP05Xy5rFZj6lD37VZWVWUELMBasRPzd2AS/9wPmphOgjH0VnE5A==
1568
+  dependencies:
1569
+    "@babel/runtime" "^7.13.10"
1570
+
1541 1571
 "@radix-ui/react-use-rect@0.0.7":
1542 1572
   version "0.0.7"
1543 1573
   resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-0.0.7.tgz#e3a55fa7183ef436042198787bf38f8c9befcc14"
@@ -1553,6 +1583,15 @@
1553 1583
   dependencies:
1554 1584
     "@babel/runtime" "^7.13.10"
1555 1585
 
1586
+"@radix-ui/react-visually-hidden@0.0.13":
1587
+  version "0.0.13"
1588
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.0.13.tgz#c7f69097eb7d796dcd9117cdd228d87991c08baf"
1589
+  integrity sha512-8VNuE4/3PnyrLv1je56fxaa5qka0Nb6/FlyQEDF2HCPpxVOWR4sxRfSBe8cjy+Me+pJN9ZoKBIuoFCVRk54xJA==
1590
+  dependencies:
1591
+    "@babel/runtime" "^7.13.10"
1592
+    "@radix-ui/react-polymorphic" "0.0.11"
1593
+    "@radix-ui/react-primitive" "0.0.13"
1594
+
1556 1595
 "@radix-ui/rect@0.0.5":
1557 1596
   version "0.0.5"
1558 1597
   resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-0.0.5.tgz#6000d8d800288114af4bbc5863e6b58755d7d978"

Loading…
Cancel
Save