@svgedit/svgcanvas 7.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/blur-event.js +156 -0
- package/clear.js +43 -0
- package/coords.js +298 -0
- package/copy-elem.js +45 -0
- package/dataStorage.js +28 -0
- package/dist/svgcanvas.js +515 -0
- package/dist/svgcanvas.js.map +1 -0
- package/draw.js +1064 -0
- package/elem-get-set.js +1077 -0
- package/event.js +1388 -0
- package/history.js +619 -0
- package/historyrecording.js +161 -0
- package/json.js +110 -0
- package/layer.js +228 -0
- package/math.js +221 -0
- package/namespaces.js +40 -0
- package/package.json +54 -0
- package/paint.js +88 -0
- package/paste-elem.js +127 -0
- package/path-actions.js +1237 -0
- package/path-method.js +1012 -0
- package/path.js +781 -0
- package/recalculate.js +794 -0
- package/rollup.config.js +40 -0
- package/sanitize.js +252 -0
- package/select.js +543 -0
- package/selected-elem.js +1297 -0
- package/selection.js +482 -0
- package/svg-exec.js +1289 -0
- package/svgcanvas.js +1347 -0
- package/svgroot.js +36 -0
- package/text-actions.js +530 -0
- package/touch.js +51 -0
- package/undo.js +279 -0
- package/utilities.js +1214 -0
package/select.js
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM element selection box tools.
|
|
3
|
+
* @module select
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { isWebkit } from '../../src/common/browser.js'
|
|
10
|
+
import { getRotationAngle, getBBox, getStrokedBBox } from './utilities.js'
|
|
11
|
+
import { transformListToTransform, transformBox, transformPoint } from './math.js'
|
|
12
|
+
|
|
13
|
+
let svgCanvas
|
|
14
|
+
let selectorManager_ // A Singleton
|
|
15
|
+
// change radius if touch screen
|
|
16
|
+
const gripRadius = window.ontouchstart ? 10 : 4
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Private class for DOM element selection boxes.
|
|
20
|
+
*/
|
|
21
|
+
export class Selector {
|
|
22
|
+
/**
|
|
23
|
+
* @param {Integer} id - Internally identify the selector
|
|
24
|
+
* @param {Element} elem - DOM element associated with this selector
|
|
25
|
+
* @param {module:utilities.BBoxObject} [bbox] - Optional bbox to use for initialization (prevents duplicate `getBBox` call).
|
|
26
|
+
*/
|
|
27
|
+
constructor (id, elem, bbox) {
|
|
28
|
+
// this is the selector's unique number
|
|
29
|
+
this.id = id
|
|
30
|
+
|
|
31
|
+
// this holds a reference to the element for which this selector is being used
|
|
32
|
+
this.selectedElement = elem
|
|
33
|
+
|
|
34
|
+
// this is a flag used internally to track whether the selector is being used or not
|
|
35
|
+
this.locked = true
|
|
36
|
+
|
|
37
|
+
// this holds a reference to the <g> element that holds all visual elements of the selector
|
|
38
|
+
this.selectorGroup = svgCanvas.createSVGElement({
|
|
39
|
+
element: 'g',
|
|
40
|
+
attr: { id: ('selectorGroup' + this.id) }
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// this holds a reference to the path rect
|
|
44
|
+
this.selectorRect = svgCanvas.createSVGElement({
|
|
45
|
+
element: 'path',
|
|
46
|
+
attr: {
|
|
47
|
+
id: ('selectedBox' + this.id),
|
|
48
|
+
fill: 'none',
|
|
49
|
+
stroke: '#22C',
|
|
50
|
+
'stroke-width': '1',
|
|
51
|
+
'stroke-dasharray': '5,5',
|
|
52
|
+
// need to specify this so that the rect is not selectable
|
|
53
|
+
style: 'pointer-events:none'
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
this.selectorGroup.append(this.selectorRect)
|
|
57
|
+
|
|
58
|
+
// this holds a reference to the grip coordinates for this selector
|
|
59
|
+
this.gripCoords = {
|
|
60
|
+
nw: null,
|
|
61
|
+
n: null,
|
|
62
|
+
ne: null,
|
|
63
|
+
e: null,
|
|
64
|
+
se: null,
|
|
65
|
+
s: null,
|
|
66
|
+
sw: null,
|
|
67
|
+
w: null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.reset(this.selectedElement, bbox)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Used to reset the id and element that the selector is attached to.
|
|
75
|
+
* @param {Element} e - DOM element associated with this selector
|
|
76
|
+
* @param {module:utilities.BBoxObject} bbox - Optional bbox to use for reset (prevents duplicate getBBox call).
|
|
77
|
+
* @returns {void}
|
|
78
|
+
*/
|
|
79
|
+
reset (e, bbox) {
|
|
80
|
+
this.locked = true
|
|
81
|
+
this.selectedElement = e
|
|
82
|
+
this.resize(bbox)
|
|
83
|
+
this.selectorGroup.setAttribute('display', 'inline')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Show the resize grips of this selector.
|
|
88
|
+
* @param {boolean} show - Indicates whether grips should be shown or not
|
|
89
|
+
* @returns {void}
|
|
90
|
+
*/
|
|
91
|
+
showGrips (show) {
|
|
92
|
+
const bShow = show ? 'inline' : 'none'
|
|
93
|
+
selectorManager_.selectorGripsGroup.setAttribute('display', bShow)
|
|
94
|
+
const elem = this.selectedElement
|
|
95
|
+
this.hasGrips = show
|
|
96
|
+
if (elem && show) {
|
|
97
|
+
this.selectorGroup.append(selectorManager_.selectorGripsGroup)
|
|
98
|
+
Selector.updateGripCursors(getRotationAngle(elem))
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Updates the selector to match the element's size.
|
|
104
|
+
* @param {module:utilities.BBoxObject} [bbox] - BBox to use for resize (prevents duplicate getBBox call).
|
|
105
|
+
* @returns {void}
|
|
106
|
+
*/
|
|
107
|
+
resize (bbox) {
|
|
108
|
+
const dataStorage = svgCanvas.getDataStorage()
|
|
109
|
+
const selectedBox = this.selectorRect
|
|
110
|
+
const mgr = selectorManager_
|
|
111
|
+
const selectedGrips = mgr.selectorGrips
|
|
112
|
+
const selected = this.selectedElement
|
|
113
|
+
const zoom = svgCanvas.getZoom()
|
|
114
|
+
let offset = 1 / zoom
|
|
115
|
+
const sw = selected.getAttribute('stroke-width')
|
|
116
|
+
if (selected.getAttribute('stroke') !== 'none' && !isNaN(sw)) {
|
|
117
|
+
offset += (sw / 2)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const { tagName } = selected
|
|
121
|
+
if (tagName === 'text') {
|
|
122
|
+
offset += 2 / zoom
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// loop and transform our bounding box until we reach our first rotation
|
|
126
|
+
const tlist = selected.transform.baseVal
|
|
127
|
+
const m = transformListToTransform(tlist).matrix
|
|
128
|
+
|
|
129
|
+
// This should probably be handled somewhere else, but for now
|
|
130
|
+
// it keeps the selection box correctly positioned when zoomed
|
|
131
|
+
m.e *= zoom
|
|
132
|
+
m.f *= zoom
|
|
133
|
+
|
|
134
|
+
if (!bbox) {
|
|
135
|
+
bbox = getBBox(selected)
|
|
136
|
+
}
|
|
137
|
+
// TODO: getBBox (previous line) already knows to call getStrokedBBox when tagName === 'g'. Remove this?
|
|
138
|
+
// TODO: getBBox doesn't exclude 'gsvg' and calls getStrokedBBox for any 'g'. Should getBBox be updated?
|
|
139
|
+
if (tagName === 'g' && !dataStorage.has(selected, 'gsvg')) {
|
|
140
|
+
// The bbox for a group does not include stroke vals, so we
|
|
141
|
+
// get the bbox based on its children.
|
|
142
|
+
const strokedBbox = getStrokedBBox([selected.childNodes])
|
|
143
|
+
if (strokedBbox) {
|
|
144
|
+
bbox = strokedBbox
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// apply the transforms
|
|
149
|
+
const l = bbox.x; const t = bbox.y; const w = bbox.width; const h = bbox.height
|
|
150
|
+
// bbox = {x: l, y: t, width: w, height: h}; // Not in use
|
|
151
|
+
|
|
152
|
+
// we need to handle temporary transforms too
|
|
153
|
+
// if skewed, get its transformed box, then find its axis-aligned bbox
|
|
154
|
+
|
|
155
|
+
// *
|
|
156
|
+
offset *= zoom
|
|
157
|
+
|
|
158
|
+
const nbox = transformBox(l * zoom, t * zoom, w * zoom, h * zoom, m)
|
|
159
|
+
const { aabox } = nbox
|
|
160
|
+
let nbax = aabox.x - offset
|
|
161
|
+
let nbay = aabox.y - offset
|
|
162
|
+
let nbaw = aabox.width + (offset * 2)
|
|
163
|
+
let nbah = aabox.height + (offset * 2)
|
|
164
|
+
|
|
165
|
+
// now if the shape is rotated, un-rotate it
|
|
166
|
+
const cx = nbax + nbaw / 2
|
|
167
|
+
const cy = nbay + nbah / 2
|
|
168
|
+
|
|
169
|
+
const angle = getRotationAngle(selected)
|
|
170
|
+
if (angle) {
|
|
171
|
+
const rot = svgCanvas.getSvgRoot().createSVGTransform()
|
|
172
|
+
rot.setRotate(-angle, cx, cy)
|
|
173
|
+
const rotm = rot.matrix
|
|
174
|
+
nbox.tl = transformPoint(nbox.tl.x, nbox.tl.y, rotm)
|
|
175
|
+
nbox.tr = transformPoint(nbox.tr.x, nbox.tr.y, rotm)
|
|
176
|
+
nbox.bl = transformPoint(nbox.bl.x, nbox.bl.y, rotm)
|
|
177
|
+
nbox.br = transformPoint(nbox.br.x, nbox.br.y, rotm)
|
|
178
|
+
|
|
179
|
+
// calculate the axis-aligned bbox
|
|
180
|
+
const { tl } = nbox
|
|
181
|
+
let minx = tl.x
|
|
182
|
+
let miny = tl.y
|
|
183
|
+
let maxx = tl.x
|
|
184
|
+
let maxy = tl.y
|
|
185
|
+
|
|
186
|
+
const { min, max } = Math
|
|
187
|
+
|
|
188
|
+
minx = min(minx, min(nbox.tr.x, min(nbox.bl.x, nbox.br.x))) - offset
|
|
189
|
+
miny = min(miny, min(nbox.tr.y, min(nbox.bl.y, nbox.br.y))) - offset
|
|
190
|
+
maxx = max(maxx, max(nbox.tr.x, max(nbox.bl.x, nbox.br.x))) + offset
|
|
191
|
+
maxy = max(maxy, max(nbox.tr.y, max(nbox.bl.y, nbox.br.y))) + offset
|
|
192
|
+
|
|
193
|
+
nbax = minx
|
|
194
|
+
nbay = miny
|
|
195
|
+
nbaw = (maxx - minx)
|
|
196
|
+
nbah = (maxy - miny)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const dstr = 'M' + nbax + ',' + nbay +
|
|
200
|
+
' L' + (nbax + nbaw) + ',' + nbay +
|
|
201
|
+
' ' + (nbax + nbaw) + ',' + (nbay + nbah) +
|
|
202
|
+
' ' + nbax + ',' + (nbay + nbah) + 'z'
|
|
203
|
+
|
|
204
|
+
const xform = angle ? 'rotate(' + [angle, cx, cy].join(',') + ')' : ''
|
|
205
|
+
|
|
206
|
+
// TODO(codedread): Is this needed?
|
|
207
|
+
// if (selected === selectedElements[0]) {
|
|
208
|
+
this.gripCoords = {
|
|
209
|
+
nw: [nbax, nbay],
|
|
210
|
+
ne: [nbax + nbaw, nbay],
|
|
211
|
+
sw: [nbax, nbay + nbah],
|
|
212
|
+
se: [nbax + nbaw, nbay + nbah],
|
|
213
|
+
n: [nbax + (nbaw) / 2, nbay],
|
|
214
|
+
w: [nbax, nbay + (nbah) / 2],
|
|
215
|
+
e: [nbax + nbaw, nbay + (nbah) / 2],
|
|
216
|
+
s: [nbax + (nbaw) / 2, nbay + nbah]
|
|
217
|
+
}
|
|
218
|
+
selectedBox.setAttribute('d', dstr)
|
|
219
|
+
this.selectorGroup.setAttribute('transform', xform)
|
|
220
|
+
Object.entries(this.gripCoords).forEach(([dir, coords]) => {
|
|
221
|
+
selectedGrips[dir].setAttribute('cx', coords[0])
|
|
222
|
+
selectedGrips[dir].setAttribute('cy', coords[1])
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
// we want to go 20 pixels in the negative transformed y direction, ignoring scale
|
|
226
|
+
mgr.rotateGripConnector.setAttribute('x1', nbax + (nbaw) / 2)
|
|
227
|
+
mgr.rotateGripConnector.setAttribute('y1', nbay)
|
|
228
|
+
mgr.rotateGripConnector.setAttribute('x2', nbax + (nbaw) / 2)
|
|
229
|
+
mgr.rotateGripConnector.setAttribute('y2', nbay - (gripRadius * 5))
|
|
230
|
+
|
|
231
|
+
mgr.rotateGrip.setAttribute('cx', nbax + (nbaw) / 2)
|
|
232
|
+
mgr.rotateGrip.setAttribute('cy', nbay - (gripRadius * 5))
|
|
233
|
+
// }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// STATIC methods
|
|
237
|
+
/**
|
|
238
|
+
* Updates cursors for corner grips on rotation so arrows point the right way.
|
|
239
|
+
* @param {Float} angle - Current rotation angle in degrees
|
|
240
|
+
* @returns {void}
|
|
241
|
+
*/
|
|
242
|
+
static updateGripCursors (angle) {
|
|
243
|
+
const dirArr = Object.keys(selectorManager_.selectorGrips)
|
|
244
|
+
let steps = Math.round(angle / 45)
|
|
245
|
+
if (steps < 0) { steps += 8 }
|
|
246
|
+
while (steps > 0) {
|
|
247
|
+
dirArr.push(dirArr.shift())
|
|
248
|
+
steps--
|
|
249
|
+
}
|
|
250
|
+
Object.values(selectorManager_.selectorGrips).forEach((gripElement, i) => {
|
|
251
|
+
gripElement.setAttribute('style', ('cursor:' + dirArr[i] + '-resize'))
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Manage all selector objects (selection boxes).
|
|
258
|
+
*/
|
|
259
|
+
export class SelectorManager {
|
|
260
|
+
/**
|
|
261
|
+
* Sets up properties and calls `initGroup`.
|
|
262
|
+
*/
|
|
263
|
+
constructor () {
|
|
264
|
+
// this will hold the <g> element that contains all selector rects/grips
|
|
265
|
+
this.selectorParentGroup = null
|
|
266
|
+
|
|
267
|
+
// this is a special rect that is used for multi-select
|
|
268
|
+
this.rubberBandBox = null
|
|
269
|
+
|
|
270
|
+
// this will hold objects of type Selector (see above)
|
|
271
|
+
this.selectors = []
|
|
272
|
+
|
|
273
|
+
// this holds a map of SVG elements to their Selector object
|
|
274
|
+
this.selectorMap = {}
|
|
275
|
+
|
|
276
|
+
// this holds a reference to the grip elements
|
|
277
|
+
this.selectorGrips = {
|
|
278
|
+
nw: null,
|
|
279
|
+
n: null,
|
|
280
|
+
ne: null,
|
|
281
|
+
e: null,
|
|
282
|
+
se: null,
|
|
283
|
+
s: null,
|
|
284
|
+
sw: null,
|
|
285
|
+
w: null
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
this.selectorGripsGroup = null
|
|
289
|
+
this.rotateGripConnector = null
|
|
290
|
+
this.rotateGrip = null
|
|
291
|
+
|
|
292
|
+
this.initGroup()
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Resets the parent selector group element.
|
|
297
|
+
* @returns {void}
|
|
298
|
+
*/
|
|
299
|
+
initGroup () {
|
|
300
|
+
const dataStorage = svgCanvas.getDataStorage()
|
|
301
|
+
// remove old selector parent group if it existed
|
|
302
|
+
if (this.selectorParentGroup?.parentNode) {
|
|
303
|
+
this.selectorParentGroup.remove()
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// create parent selector group and add it to svgroot
|
|
307
|
+
this.selectorParentGroup = svgCanvas.createSVGElement({
|
|
308
|
+
element: 'g',
|
|
309
|
+
attr: { id: 'selectorParentGroup' }
|
|
310
|
+
})
|
|
311
|
+
this.selectorGripsGroup = svgCanvas.createSVGElement({
|
|
312
|
+
element: 'g',
|
|
313
|
+
attr: { display: 'none' }
|
|
314
|
+
})
|
|
315
|
+
this.selectorParentGroup.append(this.selectorGripsGroup)
|
|
316
|
+
svgCanvas.getSvgRoot().append(this.selectorParentGroup)
|
|
317
|
+
|
|
318
|
+
this.selectorMap = {}
|
|
319
|
+
this.selectors = []
|
|
320
|
+
this.rubberBandBox = null
|
|
321
|
+
|
|
322
|
+
// add the corner grips
|
|
323
|
+
Object.keys(this.selectorGrips).forEach((dir) => {
|
|
324
|
+
const grip = svgCanvas.createSVGElement({
|
|
325
|
+
element: 'circle',
|
|
326
|
+
attr: {
|
|
327
|
+
id: ('selectorGrip_resize_' + dir),
|
|
328
|
+
fill: '#22C',
|
|
329
|
+
r: gripRadius,
|
|
330
|
+
style: ('cursor:' + dir + '-resize'),
|
|
331
|
+
// This expands the mouse-able area of the grips making them
|
|
332
|
+
// easier to grab with the mouse.
|
|
333
|
+
// This works in Opera and WebKit, but does not work in Firefox
|
|
334
|
+
// see https://bugzilla.mozilla.org/show_bug.cgi?id=500174
|
|
335
|
+
'stroke-width': 2,
|
|
336
|
+
'pointer-events': 'all'
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
dataStorage.put(grip, 'dir', dir)
|
|
341
|
+
dataStorage.put(grip, 'type', 'resize')
|
|
342
|
+
this.selectorGrips[dir] = grip
|
|
343
|
+
this.selectorGripsGroup.append(grip)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
// add rotator elems
|
|
347
|
+
this.rotateGripConnector =
|
|
348
|
+
svgCanvas.createSVGElement({
|
|
349
|
+
element: 'line',
|
|
350
|
+
attr: {
|
|
351
|
+
id: ('selectorGrip_rotateconnector'),
|
|
352
|
+
stroke: '#22C',
|
|
353
|
+
'stroke-width': '1'
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
this.selectorGripsGroup.append(this.rotateGripConnector)
|
|
357
|
+
|
|
358
|
+
this.rotateGrip =
|
|
359
|
+
svgCanvas.createSVGElement({
|
|
360
|
+
element: 'circle',
|
|
361
|
+
attr: {
|
|
362
|
+
id: 'selectorGrip_rotate',
|
|
363
|
+
fill: 'lime',
|
|
364
|
+
r: gripRadius,
|
|
365
|
+
stroke: '#22C',
|
|
366
|
+
'stroke-width': 2,
|
|
367
|
+
style: `cursor:url(${svgCanvas.curConfig.imgPath}/rotate.svg) 12 12, auto;`
|
|
368
|
+
}
|
|
369
|
+
})
|
|
370
|
+
this.selectorGripsGroup.append(this.rotateGrip)
|
|
371
|
+
dataStorage.put(this.rotateGrip, 'type', 'rotate')
|
|
372
|
+
|
|
373
|
+
if (document.getElementById('canvasBackground')) { return }
|
|
374
|
+
|
|
375
|
+
const [width, height] = svgCanvas.curConfig.dimensions
|
|
376
|
+
const canvasbg = svgCanvas.createSVGElement({
|
|
377
|
+
element: 'svg',
|
|
378
|
+
attr: {
|
|
379
|
+
id: 'canvasBackground',
|
|
380
|
+
width,
|
|
381
|
+
height,
|
|
382
|
+
x: 0,
|
|
383
|
+
y: 0,
|
|
384
|
+
overflow: (isWebkit() ? 'none' : 'visible'), // Chrome 7 has a problem with this when zooming out
|
|
385
|
+
style: 'pointer-events:none'
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
const rect = svgCanvas.createSVGElement({
|
|
390
|
+
element: 'rect',
|
|
391
|
+
attr: {
|
|
392
|
+
width: '100%',
|
|
393
|
+
height: '100%',
|
|
394
|
+
x: 0,
|
|
395
|
+
y: 0,
|
|
396
|
+
'stroke-width': 1,
|
|
397
|
+
stroke: '#000',
|
|
398
|
+
fill: '#FFF',
|
|
399
|
+
style: 'pointer-events:none'
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
canvasbg.append(rect)
|
|
403
|
+
svgCanvas.getSvgRoot().insertBefore(canvasbg, svgCanvas.getSvgContent())
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
*
|
|
408
|
+
* @param {Element} elem - DOM element to get the selector for
|
|
409
|
+
* @param {module:utilities.BBoxObject} [bbox] - Optional bbox to use for reset (prevents duplicate getBBox call).
|
|
410
|
+
* @returns {Selector} The selector based on the given element
|
|
411
|
+
*/
|
|
412
|
+
requestSelector (elem, bbox) {
|
|
413
|
+
if (!elem) { return null }
|
|
414
|
+
|
|
415
|
+
const N = this.selectors.length
|
|
416
|
+
// If we've already acquired one for this element, return it.
|
|
417
|
+
if (typeof this.selectorMap[elem.id] === 'object') {
|
|
418
|
+
this.selectorMap[elem.id].locked = true
|
|
419
|
+
return this.selectorMap[elem.id]
|
|
420
|
+
}
|
|
421
|
+
for (let i = 0; i < N; ++i) {
|
|
422
|
+
if (!this.selectors[i]?.locked) {
|
|
423
|
+
this.selectors[i].locked = true
|
|
424
|
+
this.selectors[i].reset(elem, bbox)
|
|
425
|
+
this.selectorMap[elem.id] = this.selectors[i]
|
|
426
|
+
return this.selectors[i]
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// if we reached here, no available selectors were found, we create one
|
|
430
|
+
this.selectors[N] = new Selector(N, elem, bbox)
|
|
431
|
+
this.selectorParentGroup.append(this.selectors[N].selectorGroup)
|
|
432
|
+
this.selectorMap[elem.id] = this.selectors[N]
|
|
433
|
+
return this.selectors[N]
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Removes the selector of the given element (hides selection box).
|
|
438
|
+
*
|
|
439
|
+
* @param {Element} elem - DOM element to remove the selector for
|
|
440
|
+
* @returns {void}
|
|
441
|
+
*/
|
|
442
|
+
releaseSelector (elem) {
|
|
443
|
+
if (!elem) { return }
|
|
444
|
+
const N = this.selectors.length
|
|
445
|
+
const sel = this.selectorMap[elem.id]
|
|
446
|
+
if (!sel?.locked) {
|
|
447
|
+
// TODO(codedread): Ensure this exists in this module.
|
|
448
|
+
console.warn('WARNING! selector was released but was already unlocked')
|
|
449
|
+
}
|
|
450
|
+
for (let i = 0; i < N; ++i) {
|
|
451
|
+
if (this.selectors[i] && this.selectors[i] === sel) {
|
|
452
|
+
delete this.selectorMap[elem.id]
|
|
453
|
+
sel.locked = false
|
|
454
|
+
sel.selectedElement = null
|
|
455
|
+
sel.showGrips(false)
|
|
456
|
+
|
|
457
|
+
// remove from DOM and store reference in JS but only if it exists in the DOM
|
|
458
|
+
try {
|
|
459
|
+
sel.selectorGroup.setAttribute('display', 'none')
|
|
460
|
+
} catch (e) { /* empty fn */ }
|
|
461
|
+
|
|
462
|
+
break
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* @returns {SVGRectElement} The rubberBandBox DOM element. This is the rectangle drawn by
|
|
469
|
+
* the user for selecting/zooming
|
|
470
|
+
*/
|
|
471
|
+
getRubberBandBox () {
|
|
472
|
+
if (!this.rubberBandBox) {
|
|
473
|
+
this.rubberBandBox =
|
|
474
|
+
svgCanvas.createSVGElement({
|
|
475
|
+
element: 'rect',
|
|
476
|
+
attr: {
|
|
477
|
+
id: 'selectorRubberBand',
|
|
478
|
+
fill: '#22C',
|
|
479
|
+
'fill-opacity': 0.15,
|
|
480
|
+
stroke: '#22C',
|
|
481
|
+
'stroke-width': 0.5,
|
|
482
|
+
display: 'none',
|
|
483
|
+
style: 'pointer-events:none'
|
|
484
|
+
}
|
|
485
|
+
})
|
|
486
|
+
this.selectorParentGroup.append(this.rubberBandBox)
|
|
487
|
+
}
|
|
488
|
+
return this.rubberBandBox
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* An object that creates SVG elements for the canvas.
|
|
494
|
+
*
|
|
495
|
+
* @interface module:select.SVGFactory
|
|
496
|
+
*/
|
|
497
|
+
/**
|
|
498
|
+
* @function module:select.SVGFactory#createSVGElement
|
|
499
|
+
* @param {module:utilities.EditorContext#addSVGElementsFromJson} jsonMap
|
|
500
|
+
* @returns {SVGElement}
|
|
501
|
+
*/
|
|
502
|
+
/**
|
|
503
|
+
* @function module:select.SVGFactory#svgRoot
|
|
504
|
+
* @returns {SVGSVGElement}
|
|
505
|
+
*/
|
|
506
|
+
/**
|
|
507
|
+
* @function module:select.SVGFactory#svgContent
|
|
508
|
+
* @returns {SVGSVGElement}
|
|
509
|
+
*/
|
|
510
|
+
/**
|
|
511
|
+
* @function module:select.SVGFactory#getZoom
|
|
512
|
+
* @returns {Float} The current zoom level
|
|
513
|
+
*/
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* @typedef {GenericArray} module:select.Dimensions
|
|
517
|
+
* @property {Integer} length 2
|
|
518
|
+
* @property {Float} 0 Width
|
|
519
|
+
* @property {Float} 1 Height
|
|
520
|
+
*/
|
|
521
|
+
/**
|
|
522
|
+
* @typedef {PlainObject} module:select.Config
|
|
523
|
+
* @property {string} imgPath
|
|
524
|
+
* @property {module:select.Dimensions} dimensions
|
|
525
|
+
*/
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Initializes this module.
|
|
529
|
+
* @function module:select.init
|
|
530
|
+
* @param {module:select.Config} config - An object containing configurable parameters (imgPath)
|
|
531
|
+
* @param {module:select.SVGFactory} svgFactory - An object implementing the SVGFactory interface.
|
|
532
|
+
* @returns {void}
|
|
533
|
+
*/
|
|
534
|
+
export const init = (canvas) => {
|
|
535
|
+
svgCanvas = canvas
|
|
536
|
+
selectorManager_ = new SelectorManager()
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* @function module:select.getSelectorManager
|
|
541
|
+
* @returns {module:select.SelectorManager} The SelectorManager instance.
|
|
542
|
+
*/
|
|
543
|
+
export const getSelectorManager = () => selectorManager_
|