Browse Source

fixes zstacking with text

main
Steve Ruiz 4 years ago
parent
commit
d0d24e9c71

+ 3
- 1
components/canvas/bounds/bounding-box.tsx View File

10
   getSelectedShapes,
10
   getSelectedShapes,
11
   isMobile,
11
   isMobile,
12
 } from 'utils/utils'
12
 } from 'utils/utils'
13
-
14
 import CenterHandle from './center-handle'
13
 import CenterHandle from './center-handle'
15
 import CornerHandle from './corner-handle'
14
 import CornerHandle from './corner-handle'
16
 import EdgeHandle from './edge-handle'
15
 import EdgeHandle from './edge-handle'
18
 
17
 
19
 export default function Bounds() {
18
 export default function Bounds() {
20
   const isBrushing = useSelector((s) => s.isIn('brushSelecting'))
19
   const isBrushing = useSelector((s) => s.isIn('brushSelecting'))
20
+
21
   const isSelecting = useSelector((s) => s.isIn('selecting'))
21
   const isSelecting = useSelector((s) => s.isIn('selecting'))
22
+
22
   const zoom = useSelector((s) => getCurrentCamera(s.data).zoom)
23
   const zoom = useSelector((s) => getCurrentCamera(s.data).zoom)
24
+
23
   const bounds = useSelector((s) => s.values.selectedBounds)
25
   const bounds = useSelector((s) => s.values.selectedBounds)
24
 
26
 
25
   const selectedIds = useSelector(
27
   const selectedIds = useSelector(

+ 1
- 1
components/canvas/canvas.tsx View File

33
           <g ref={rGroup}>
33
           <g ref={rGroup}>
34
             <BoundsBg />
34
             <BoundsBg />
35
             <Page />
35
             <Page />
36
-            <Selected />
36
+            {/* <Selected /> */}
37
             <Bounds />
37
             <Bounds />
38
             <Handles />
38
             <Handles />
39
             <Brush />
39
             <Brush />

+ 3
- 3
components/canvas/selected.tsx View File

46
   const center = getShapeUtils(shape).getCenter(shape)
46
   const center = getShapeUtils(shape).getCenter(shape)
47
 
47
 
48
   const transform = `
48
   const transform = `
49
-  rotate(${shape.rotation * (180 / Math.PI)}, ${center})
50
-  translate(${shape.point})
49
+    rotate(${shape.rotation * (180 / Math.PI)}, ${center})
50
+    translate(${shape.point})
51
   `
51
   `
52
 
52
 
53
   return (
53
   return (
68
   strokeLinejoin: 'round',
68
   strokeLinejoin: 'round',
69
   stroke: '$selected',
69
   stroke: '$selected',
70
   pointerEvents: 'none',
70
   pointerEvents: 'none',
71
-  fill: 'none',
71
+  fill: 'transparent',
72
 
72
 
73
   variants: {
73
   variants: {
74
     isLocked: {
74
     isLocked: {

+ 3
- 35
components/tools-panel/tools-panel.tsx View File

50
             <IconButton
50
             <IconButton
51
               name="select"
51
               name="select"
52
               bp={{ '@initial': 'mobile', '@sm': 'small' }}
52
               bp={{ '@initial': 'mobile', '@sm': 'small' }}
53
-              size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
53
+              size={{ '@initial': 'small', '@sm': 'small', '@md': 'medium' }}
54
               onClick={selectSelectTool}
54
               onClick={selectSelectTool}
55
               isActive={activeTool === 'select'}
55
               isActive={activeTool === 'select'}
56
             >
56
             >
114
               <TextIcon />
114
               <TextIcon />
115
             </IconButton>
115
             </IconButton>
116
           </Tooltip>
116
           </Tooltip>
117
-          {/* <IconButton
118
-            name={ShapeType.Circle}
119
-            size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
120
-            onClick={selectCircleTool}
121
-            isActive={activeTool === ShapeType.Circle}
122
-          >
123
-            <CircleIcon />
124
-          </IconButton> */}
125
-          {/* <IconButton
126
-            name={ShapeType.Line}
127
-            size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
128
-            onClick={selectLineTool}
129
-            isActive={activeTool === ShapeType.Line}
130
-          >
131
-            <DividerHorizontalIcon transform="rotate(-45)" />
132
-          </IconButton>
133
-          <IconButton
134
-            name={ShapeType.Ray}
135
-            size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
136
-            onClick={selectRayTool}
137
-            isActive={activeTool === ShapeType.Ray}
138
-          >
139
-            <SewingPinIcon transform="rotate(-135)" />
140
-          </IconButton>
141
-          <IconButton
142
-            name={ShapeType.Dot}
143
-            size={{ '@initial': 'medium', '@sm': 'small', '@md': 'large' }}
144
-            onClick={selectDotTool}
145
-            isActive={activeTool === ShapeType.Dot}
146
-          >
147
-            <DotIcon />
148
-          </IconButton> */}
149
         </Container>
117
         </Container>
150
         <Container>
118
         <Container>
151
           <Tooltip label="Lock Tool">
119
           <Tooltip label="Lock Tool">
152
             <IconButton
120
             <IconButton
153
               bp={{ '@initial': 'mobile', '@sm': 'small' }}
121
               bp={{ '@initial': 'mobile', '@sm': 'small' }}
154
-              size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
122
+              size={{ '@initial': 'small', '@sm': 'small', '@md': 'medium' }}
155
               onClick={selectToolLock}
123
               onClick={selectToolLock}
156
             >
124
             >
157
               {isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
125
               {isToolLocked ? <LockClosedIcon /> : <LockOpen1Icon />}
161
             <Tooltip label="Unlock Pen">
129
             <Tooltip label="Unlock Pen">
162
               <IconButton
130
               <IconButton
163
                 bp={{ '@initial': 'mobile', '@sm': 'small' }}
131
                 bp={{ '@initial': 'mobile', '@sm': 'small' }}
164
-                size={{ '@initial': 'small', '@sm': 'small', '@md': 'large' }}
132
+                size={{ '@initial': 'small', '@sm': 'small', '@md': 'medium' }}
165
                 onClick={selectToolLock}
133
                 onClick={selectToolLock}
166
               >
134
               >
167
                 <Pencil2Icon />
135
                 <Pencil2Icon />

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

70
 ) {
70
 ) {
71
   const fontSize = getFontSize(size)
71
   const fontSize = getFontSize(size)
72
 
72
 
73
-  return `${fontSize * scale}px Verveine Regular`
73
+  return `${fontSize * scale}px/1.4 Verveine Regular`
74
 }
74
 }
75
 
75
 
76
 export function getShapeStyle(
76
 export function getShapeStyle(

+ 75
- 23
lib/shape-utils/text.tsx View File

1
 import { uniqueId } from 'utils/utils'
1
 import { uniqueId } from 'utils/utils'
2
-import vec from 'utils/vec'
3
 import { TextShape, ShapeType, FontSize } from 'types'
2
 import { TextShape, ShapeType, FontSize } from 'types'
4
 import { registerShapeUtils } from './index'
3
 import { registerShapeUtils } from './index'
5
-import { defaultStyle, getFontStyle, getShapeStyle } from 'lib/shape-styles'
4
+import {
5
+  defaultStyle,
6
+  getFontSize,
7
+  getFontStyle,
8
+  getShapeStyle,
9
+} from 'lib/shape-styles'
6
 import styled from 'styles'
10
 import styled from 'styles'
7
 import state from 'state'
11
 import state from 'state'
8
-import { useEffect, useRef } from 'react'
9
-
10
-// A div used for measurement
12
+import React from 'react'
11
 
13
 
12
 if (document.getElementById('__textMeasure')) {
14
 if (document.getElementById('__textMeasure')) {
13
   document.getElementById('__textMeasure').remove()
15
   document.getElementById('__textMeasure').remove()
14
 }
16
 }
15
 
17
 
18
+// A div used for measurement
16
 const mdiv = document.createElement('pre')
19
 const mdiv = document.createElement('pre')
17
 mdiv.id = '__textMeasure'
20
 mdiv.id = '__textMeasure'
18
 mdiv.style.whiteSpace = 'pre'
21
 mdiv.style.whiteSpace = 'pre'
19
 mdiv.style.width = 'auto'
22
 mdiv.style.width = 'auto'
20
 mdiv.style.border = '1px solid red'
23
 mdiv.style.border = '1px solid red'
21
 mdiv.style.padding = '4px'
24
 mdiv.style.padding = '4px'
22
-mdiv.style.lineHeight = '1'
23
 mdiv.style.margin = '0px'
25
 mdiv.style.margin = '0px'
24
 mdiv.style.opacity = '0'
26
 mdiv.style.opacity = '0'
25
 mdiv.style.position = 'absolute'
27
 mdiv.style.position = 'absolute'
26
 mdiv.style.top = '-500px'
28
 mdiv.style.top = '-500px'
27
 mdiv.style.left = '0px'
29
 mdiv.style.left = '0px'
28
 mdiv.style.zIndex = '9999'
30
 mdiv.style.zIndex = '9999'
31
+mdiv.tabIndex = -1
29
 mdiv.setAttribute('readonly', 'true')
32
 mdiv.setAttribute('readonly', 'true')
30
 document.body.appendChild(mdiv)
33
 document.body.appendChild(mdiv)
31
 
34
 
35
+function normalizeText(text: string) {
36
+  return text.replace(/\t/g, '        ').replace(/\r?\n|\r/g, '\n')
37
+}
38
+
32
 const text = registerShapeUtils<TextShape>({
39
 const text = registerShapeUtils<TextShape>({
33
   isForeignObject: true,
40
   isForeignObject: true,
34
   canChangeAspectRatio: false,
41
   canChangeAspectRatio: false,
66
 
73
 
67
     const bounds = this.getBounds(shape)
74
     const bounds = this.getBounds(shape)
68
 
75
 
76
+    function handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
77
+      state.send('EDITED_SHAPE', {
78
+        change: { text: normalizeText(e.currentTarget.value) },
79
+      })
80
+    }
81
+
82
+    function handleKeyDown(e: React.KeyboardEvent) {
83
+      e.stopPropagation()
84
+      if (e.key === 'Tab') {
85
+        e.preventDefault()
86
+      }
87
+    }
88
+
89
+    function handleBlur() {
90
+      state.send('BLURRED_EDITING_SHAPE')
91
+    }
92
+
93
+    function handleFocus(e: React.FocusEvent<HTMLTextAreaElement>) {
94
+      e.currentTarget.select()
95
+      state.send('FOCUSED_EDITING_SHAPE')
96
+    }
97
+
98
+    const lineHeight = getFontSize(shape.fontSize) * shape.scale
99
+    const gap = lineHeight * 0.4
100
+
101
+    if (!isEditing) {
102
+      return (
103
+        <g id={id} pointerEvents="none">
104
+          {text.split('\n').map((str, i) => (
105
+            <text
106
+              key={i}
107
+              x={4}
108
+              y={4 + gap / 2 + i * (lineHeight + gap)}
109
+              style={{ font }}
110
+              width={bounds.width}
111
+              height={bounds.height}
112
+              dominant-baseline="hanging"
113
+            >
114
+              {str}
115
+            </text>
116
+          ))}
117
+        </g>
118
+      )
119
+    }
120
+
69
     return (
121
     return (
70
       <foreignObject
122
       <foreignObject
71
         id={id}
123
         id={id}
82
               font,
134
               font,
83
               color: styles.stroke,
135
               color: styles.stroke,
84
             }}
136
             }}
137
+            tabIndex={0}
85
             value={text}
138
             value={text}
86
             autoComplete="false"
139
             autoComplete="false"
87
             autoCapitalize="false"
140
             autoCapitalize="false"
88
             autoCorrect="false"
141
             autoCorrect="false"
89
-            onFocus={(e) => {
90
-              e.currentTarget.select()
91
-              state.send('FOCUSED_EDITING_SHAPE')
92
-            }}
93
-            onBlur={() => {
94
-              state.send('BLURRED_EDITING_SHAPE')
95
-            }}
96
-            onChange={(e) => {
97
-              state.send('EDITED_SHAPE', {
98
-                change: { text: e.currentTarget.value },
99
-              })
100
-            }}
142
+            spellCheck="false"
143
+            onFocus={handleFocus}
144
+            onBlur={handleBlur}
145
+            onKeyDown={handleKeyDown}
146
+            onChange={handleChange}
147
+            dir="auto"
101
           />
148
           />
102
         ) : (
149
         ) : (
103
           <StyledText
150
           <StyledText
115
 
162
 
116
   getBounds(shape) {
163
   getBounds(shape) {
117
     if (!this.boundsCache.has(shape)) {
164
     if (!this.boundsCache.has(shape)) {
118
-      mdiv.innerHTML = shape.text || ' ' // + '&nbsp;'
165
+      mdiv.innerHTML = shape.text + '&zwj;'
119
       mdiv.style.font = getFontStyle(shape.fontSize, shape.scale, shape.style)
166
       mdiv.style.font = getFontStyle(shape.fontSize, shape.scale, shape.style)
120
 
167
 
121
       const [minX, minY] = shape.point
168
       const [minX, minY] = shape.point
180
   border: 'none',
227
   border: 'none',
181
   padding: '4px',
228
   padding: '4px',
182
   whiteSpace: 'pre',
229
   whiteSpace: 'pre',
183
-  resize: 'none',
184
   minHeight: 1,
230
   minHeight: 1,
185
   minWidth: 1,
231
   minWidth: 1,
186
-  outline: 'none',
232
+  outline: 0,
187
   backgroundColor: 'transparent',
233
   backgroundColor: 'transparent',
188
   overflow: 'hidden',
234
   overflow: 'hidden',
189
   pointerEvents: 'none',
235
   pointerEvents: 'none',
190
   userSelect: 'none',
236
   userSelect: 'none',
237
+  backfaceVisibility: 'hidden',
238
+  display: 'inline-block',
239
+  position: 'relative',
240
+  zIndex: 0,
191
 })
241
 })
192
 
242
 
193
 const StyledTextArea = styled('textarea', {
243
 const StyledTextArea = styled('textarea', {
199
   resize: 'none',
249
   resize: 'none',
200
   minHeight: 1,
250
   minHeight: 1,
201
   minWidth: 1,
251
   minWidth: 1,
202
-  outline: 'none',
203
-  overflow: 'hidden',
252
+  outline: 0,
204
   backgroundColor: '$boundsBg',
253
   backgroundColor: '$boundsBg',
254
+  overflow: 'hidden',
205
   pointerEvents: 'all',
255
   pointerEvents: 'all',
256
+  backfaceVisibility: 'hidden',
257
+  display: 'inline-block',
206
 })
258
 })

+ 12
- 14
state/state.ts View File

155
         CREATED_PAGE: ['clearSelectedIds', 'createPage'],
155
         CREATED_PAGE: ['clearSelectedIds', 'createPage'],
156
         DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
156
         DELETED_PAGE: { unless: 'hasOnlyOnePage', do: 'deletePage' },
157
         LOADED_FROM_FILE: ['loadDocumentFromJson', 'resetHistory'],
157
         LOADED_FROM_FILE: ['loadDocumentFromJson', 'resetHistory'],
158
-        PANNED_CAMERA: {
159
-          do: 'panCamera',
160
-        },
158
+        PANNED_CAMERA: 'panCamera',
159
+        SELECTED_SELECT_TOOL: { to: 'selecting' },
160
+        SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
161
+        SELECTED_ARROW_TOOL: { unless: 'isReadOnly', to: 'arrow' },
162
+        SELECTED_DOT_TOOL: { unless: 'isReadOnly', to: 'dot' },
163
+        SELECTED_CIRCLE_TOOL: { unless: 'isReadOnly', to: 'circle' },
164
+        SELECTED_ELLIPSE_TOOL: { unless: 'isReadOnly', to: 'ellipse' },
165
+        SELECTED_RAY_TOOL: { unless: 'isReadOnly', to: 'ray' },
166
+        SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
167
+        SELECTED_POLYLINE_TOOL: { unless: 'isReadOnly', to: 'polyline' },
168
+        SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
169
+        SELECTED_TEXT_TOOL: { unless: 'isReadOnly', to: 'text' },
161
       },
170
       },
162
       initial: 'selecting',
171
       initial: 'selecting',
163
       states: {
172
       states: {
197
             },
206
             },
198
             SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
207
             SELECTED_ALL: { to: 'selecting', do: 'selectAll' },
199
             NUDGED: { do: 'nudgeSelection' },
208
             NUDGED: { do: 'nudgeSelection' },
200
-            SELECTED_SELECT_TOOL: { to: 'selecting' },
201
-            SELECTED_DRAW_TOOL: { unless: 'isReadOnly', to: 'draw' },
202
-            SELECTED_ARROW_TOOL: { unless: 'isReadOnly', to: 'arrow' },
203
-            SELECTED_DOT_TOOL: { unless: 'isReadOnly', to: 'dot' },
204
-            SELECTED_CIRCLE_TOOL: { unless: 'isReadOnly', to: 'circle' },
205
-            SELECTED_ELLIPSE_TOOL: { unless: 'isReadOnly', to: 'ellipse' },
206
-            SELECTED_RAY_TOOL: { unless: 'isReadOnly', to: 'ray' },
207
-            SELECTED_LINE_TOOL: { unless: 'isReadOnly', to: 'line' },
208
-            SELECTED_POLYLINE_TOOL: { unless: 'isReadOnly', to: 'polyline' },
209
-            SELECTED_RECTANGLE_TOOL: { unless: 'isReadOnly', to: 'rectangle' },
210
-            SELECTED_TEXT_TOOL: { unless: 'isReadOnly', to: 'text' },
211
             ZOOMED_CAMERA: {
209
             ZOOMED_CAMERA: {
212
               do: 'zoomCamera',
210
               do: 'zoomCamera',
213
             },
211
             },

Loading…
Cancel
Save