Bläddra i källkod

Adds copy to svg

main
Steve Ruiz 4 år sedan
förälder
incheckning
fc2e3b3c4c

+ 1
- 2
components/canvas/canvas.tsx Visa fil

@@ -30,10 +30,9 @@ export default function Canvas() {
30 30
       <MainSVG ref={rCanvas} {...events}>
31 31
         <Defs />
32 32
         {isReady && (
33
-          <g ref={rGroup}>
33
+          <g ref={rGroup} id="shapes">
34 34
             <BoundsBg />
35 35
             <Page />
36
-            {/* <Selected /> */}
37 36
             <Bounds />
38 37
             <Handles />
39 38
             <Brush />

+ 8
- 0
components/canvas/context-menu/context-menu.tsx Visa fil

@@ -208,6 +208,14 @@ export default function ContextMenu({
208 208
               />
209 209
             )}
210 210
             <MoveToPageMenu />
211
+            <Button onSelect={() => state.send('COPIED_TO_SVG')}>
212
+              <span>Copy to SVG</span>
213
+              <kbd>
214
+                <span>{commandKey()}</span>
215
+                <span>⇧</span>
216
+                <span>C</span>
217
+              </kbd>
218
+            </Button>
211 219
             <StyledDivider />
212 220
             <Button onSelect={() => state.send('DELETED')}>
213 221
               <span>Delete</span>

+ 9
- 2
components/canvas/defs.tsx Visa fil

@@ -1,5 +1,6 @@
1
+import { getShapeStyle } from 'lib/shape-styles'
1 2
 import { getShapeUtils } from 'lib/shape-utils'
2
-import { memo } from 'react'
3
+import React, { memo } from 'react'
3 4
 import { useSelector } from 'state'
4 5
 import { deepCompareArrays, getCurrentCamera, getPage } from 'utils/utils'
5 6
 import { DotCircle, Handle } from './misc'
@@ -33,5 +34,11 @@ const Def = memo(function Def({ id }: { id: string }) {
33 34
   const shape = useSelector((s) => getPage(s.data).shapes[id])
34 35
 
35 36
   if (!shape) return null
36
-  return getShapeUtils(shape).render(shape, { isEditing: false })
37
+
38
+  const style = getShapeStyle(shape.style)
39
+
40
+  return React.cloneElement(
41
+    getShapeUtils(shape).render(shape, { isEditing: false }),
42
+    { ...style }
43
+  )
37 44
 })

+ 8
- 7
components/canvas/selected.tsx Visa fil

@@ -32,11 +32,11 @@ export default function Selected() {
32 32
 }
33 33
 
34 34
 export const ShapeOutline = memo(function ShapeOutline({ id }: { id: string }) {
35
-  const rIndicator = useRef<SVGUseElement>(null)
35
+  // const rIndicator = useRef<SVGUseElement>(null)
36 36
 
37 37
   const shape = useSelector((s) => getPage(s.data).shapes[id])
38 38
 
39
-  const events = useShapeEvents(id, shape?.type === ShapeType.Group, rIndicator)
39
+  // const events = useShapeEvents(id, shape?.type === ShapeType.Group, rIndicator)
40 40
 
41 41
   if (!shape) return null
42 42
 
@@ -52,23 +52,24 @@ export const ShapeOutline = memo(function ShapeOutline({ id }: { id: string }) {
52 52
 
53 53
   return (
54 54
     <SelectIndicator
55
-      ref={rIndicator}
55
+      // ref={rIndicator}
56 56
       as="use"
57 57
       href={'#' + id}
58 58
       transform={transform}
59 59
       isLocked={shape.isLocked}
60
-      {...events}
60
+      // {...events}
61 61
     />
62 62
   )
63 63
 })
64 64
 
65 65
 const SelectIndicator = styled('path', {
66
-  zStrokeWidth: 2,
66
+  // zStrokeWidth: 2,
67 67
   strokeLineCap: 'round',
68 68
   strokeLinejoin: 'round',
69
-  stroke: '$selected',
69
+  stroke: 'red',
70
+  strokeWidth: '10',
70 71
   pointerEvents: 'none',
71
-  fill: 'transparent',
72
+  fill: 'red',
72 73
 
73 74
   variants: {
74 75
     isLocked: {

+ 6
- 2
components/canvas/shape.tsx Visa fil

@@ -3,7 +3,7 @@ import state, { useSelector } from 'state'
3 3
 import styled from 'styles'
4 4
 import { getShapeUtils } from 'lib/shape-utils'
5 5
 import { getBoundsCenter, getPage, isMobile } from 'utils/utils'
6
-import { ShapeStyles, ShapeType } from 'types'
6
+import { ShapeStyles, ShapeType, Shape as _Shape } from 'types'
7 7
 import useShapeEvents from 'hooks/useShapeEvents'
8 8
 import vec from 'utils/vec'
9 9
 import { getShapeStyle } from 'lib/shape-styles'
@@ -60,6 +60,7 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
60 60
 
61 61
   return (
62 62
     <StyledGroup
63
+      id={id + '-group'}
63 64
       ref={rGroup}
64 65
       transform={transform}
65 66
       device={isMobileDevice ? 'mobile' : 'desktop'}
@@ -94,6 +95,7 @@ function Shape({ id, isSelecting, parentPoint }: ShapeProps) {
94 95
           <RealShape
95 96
             isParent={isParent}
96 97
             id={id}
98
+            shape={shape}
97 99
             style={style}
98 100
             isEditing={isEditing}
99 101
           />
@@ -116,15 +118,17 @@ interface RealShapeProps {
116 118
   id: string
117 119
   style: Partial<React.SVGProps<SVGUseElement>>
118 120
   isParent: boolean
121
+  shape: _Shape
119 122
   isEditing: boolean
120 123
 }
121 124
 
122 125
 const RealShape = memo(function RealShape({
123 126
   id,
127
+  shape,
124 128
   style,
125 129
   isParent,
126 130
 }: RealShapeProps) {
127
-  return <StyledShape as="use" data-shy={isParent} href={'#' + id} {...style} />
131
+  return <StyledShape as="use" data-shy={isParent} href={'#' + id} />
128 132
 })
129 133
 
130 134
 const StyledShape = styled('path', {

+ 5
- 1
hooks/useKeyboardEvents.ts Visa fil

@@ -192,7 +192,11 @@ export default function useKeyboardEvents() {
192 192
         }
193 193
         case 'c': {
194 194
           if (metaKey(e)) {
195
-            state.send('COPIED', getKeyboardEventInfo(e))
195
+            if (e.shiftKey) {
196
+              state.send('COPIED_TO_SVG', getKeyboardEventInfo(e))
197
+            } else {
198
+              state.send('COPIED', getKeyboardEventInfo(e))
199
+            }
196 200
           } else {
197 201
             state.send('SELECTED_ELLIPSE_TOOL', getKeyboardEventInfo(e))
198 202
           }

+ 16
- 0
pages/shhh.tsx Visa fil

@@ -0,0 +1,16 @@
1
+// import Editor from "components/editor"
2
+import Head from 'next/head'
3
+import dynamic from 'next/dynamic'
4
+
5
+const Editor = dynamic(() => import('components/editor'), { ssr: false })
6
+
7
+export default function Home() {
8
+  return (
9
+    <>
10
+      <Head>
11
+        <title>tldraw</title>
12
+      </Head>
13
+      <Editor />
14
+    </>
15
+  )
16
+}

+ 90
- 0
state/clipboard.ts Visa fil

@@ -1,4 +1,6 @@
1
+import { getShapeUtils } from 'lib/shape-utils'
1 2
 import { Data, Shape } from 'types'
3
+import { getCommonBounds, getSelectedIds, getSelectedShapes } from 'utils/utils'
2 4
 import state from './state'
3 5
 
4 6
 class Clipboard {
@@ -43,6 +45,94 @@ class Clipboard {
43 45
   clear = () => {
44 46
     this.current = undefined
45 47
   }
48
+
49
+  copySelectionToSvg(data: Data) {
50
+    const shapes = getSelectedShapes(data)
51
+
52
+    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
53
+
54
+    shapes
55
+      .sort((a, b) => a.childIndex - b.childIndex)
56
+      .forEach((shape) => {
57
+        const group = document.getElementById(shape.id + '-group')
58
+        const node = document.getElementById(shape.id)
59
+
60
+        const groupClone = group.cloneNode()
61
+        groupClone.appendChild(node.cloneNode(true))
62
+
63
+        svg.appendChild(groupClone)
64
+      })
65
+
66
+    const bounds = getCommonBounds(
67
+      ...shapes.map((shape) => getShapeUtils(shape).getBounds(shape))
68
+    )
69
+
70
+    // No content
71
+    if (!bounds) return
72
+
73
+    const padding = 16
74
+
75
+    // Resize the element to the bounding box
76
+    svg.setAttribute(
77
+      'viewBox',
78
+      [
79
+        bounds.minX - padding,
80
+        bounds.minY - padding,
81
+        bounds.width + padding * 2,
82
+        bounds.height + padding * 2,
83
+      ].join(' ')
84
+    )
85
+
86
+    svg.setAttribute('width', String(bounds.width))
87
+    svg.setAttribute('height', String(bounds.height))
88
+
89
+    // Take a snapshot of the element
90
+    const s = new XMLSerializer()
91
+    const svgString = s.serializeToString(svg)
92
+
93
+    // Copy to clipboard!
94
+    try {
95
+      navigator.clipboard.writeText(svgString)
96
+    } catch (e) {
97
+      Clipboard.copyStringToClipboard(svgString)
98
+    }
99
+  }
100
+
101
+  static copyStringToClipboard(string: string) {
102
+    let textarea: HTMLTextAreaElement
103
+    let result: boolean | null
104
+
105
+    textarea = document.createElement('textarea')
106
+    textarea.setAttribute('position', 'fixed')
107
+    textarea.setAttribute('top', '0')
108
+    textarea.setAttribute('readonly', 'true')
109
+    textarea.setAttribute('contenteditable', 'true')
110
+    textarea.style.position = 'fixed'
111
+    textarea.value = string
112
+    document.body.appendChild(textarea)
113
+    textarea.focus()
114
+    textarea.select()
115
+
116
+    try {
117
+      const range = document.createRange()
118
+      range.selectNodeContents(textarea)
119
+
120
+      const sel = window.getSelection()
121
+      sel.removeAllRanges()
122
+      sel.addRange(range)
123
+
124
+      textarea.setSelectionRange(0, textarea.value.length)
125
+      result = document.execCommand('copy')
126
+    } catch (err) {
127
+      result = null
128
+    } finally {
129
+      document.body.removeChild(textarea)
130
+    }
131
+
132
+    if (!result) return false
133
+
134
+    return true
135
+  }
46 136
 }
47 137
 
48 138
 export default new Clipboard()

+ 5
- 0
state/state.ts Visa fil

@@ -226,6 +226,7 @@ const state = createState({
226 226
             ZOOMED_IN: 'zoomIn',
227 227
             ZOOMED_OUT: 'zoomOut',
228 228
             RESET_CAMERA: 'resetCamera',
229
+            COPIED_TO_SVG: 'copyToSvg',
229 230
           },
230 231
           initial: 'notPointing',
231 232
           states: {
@@ -1640,6 +1641,10 @@ const state = createState({
1640 1641
 
1641 1642
     /* ---------------------- Data ---------------------- */
1642 1643
 
1644
+    copyToSvg(data) {
1645
+      clipboard.copySelectionToSvg(data)
1646
+    },
1647
+
1643 1648
     copyToClipboard(data) {
1644 1649
       clipboard.copy(getSelectedShapes(data))
1645 1650
     },

Laddar…
Avbryt
Spara