@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/draw.js
ADDED
|
@@ -0,0 +1,1064 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools for drawing.
|
|
3
|
+
* @module draw
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright 2011 Jeff Schiller
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import Layer from './layer.js'
|
|
9
|
+
import HistoryRecordingService from './historyrecording.js'
|
|
10
|
+
|
|
11
|
+
import { NS } from './namespaces.js'
|
|
12
|
+
import {
|
|
13
|
+
toXml, getElement
|
|
14
|
+
} from './utilities.js'
|
|
15
|
+
import {
|
|
16
|
+
copyElem as utilCopyElem
|
|
17
|
+
} from './copy-elem.js'
|
|
18
|
+
import { getParentsUntil } from '../../src/common/util.js'
|
|
19
|
+
|
|
20
|
+
const visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use'.split(',')
|
|
21
|
+
|
|
22
|
+
const RandomizeModes = {
|
|
23
|
+
LET_DOCUMENT_DECIDE: 0,
|
|
24
|
+
ALWAYS_RANDOMIZE: 1,
|
|
25
|
+
NEVER_RANDOMIZE: 2
|
|
26
|
+
}
|
|
27
|
+
let randIds = RandomizeModes.LET_DOCUMENT_DECIDE
|
|
28
|
+
// Array with current disabled elements (for in-group editing)
|
|
29
|
+
let disabledElems = []
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get a HistoryRecordingService.
|
|
33
|
+
* @param {module:history.HistoryRecordingService} [hrService] - if exists, return it instead of creating a new service.
|
|
34
|
+
* @returns {module:history.HistoryRecordingService}
|
|
35
|
+
*/
|
|
36
|
+
function historyRecordingService (hrService) {
|
|
37
|
+
return hrService || new HistoryRecordingService(svgCanvas.undoMgr)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Find the layer name in a group element.
|
|
42
|
+
* @param {Element} group The group element to search in.
|
|
43
|
+
* @returns {string} The layer name or empty string.
|
|
44
|
+
*/
|
|
45
|
+
function findLayerNameInGroup (group) {
|
|
46
|
+
const sel = group.querySelector('title')
|
|
47
|
+
return sel ? sel.textContent : ''
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Given a set of names, return a new unique name.
|
|
52
|
+
* @param {string[]} existingLayerNames - Existing layer names.
|
|
53
|
+
* @returns {string} - The new name.
|
|
54
|
+
*/
|
|
55
|
+
function getNewLayerName (existingLayerNames) {
|
|
56
|
+
let i = 1
|
|
57
|
+
// TODO(codedread): What about internationalization of "Layer"?
|
|
58
|
+
while (existingLayerNames.includes(('Layer ' + i))) { i++ }
|
|
59
|
+
return 'Layer ' + i
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* This class encapsulates the concept of a SVG-edit drawing.
|
|
64
|
+
*/
|
|
65
|
+
export class Drawing {
|
|
66
|
+
/**
|
|
67
|
+
* @param {SVGSVGElement} svgElem - The SVG DOM Element that this JS object
|
|
68
|
+
* encapsulates. If the svgElem has a se:nonce attribute on it, then
|
|
69
|
+
* IDs will use the nonce as they are generated.
|
|
70
|
+
* @param {string} [optIdPrefix=svg_] - The ID prefix to use.
|
|
71
|
+
* @throws {Error} If not initialized with an SVG element
|
|
72
|
+
*/
|
|
73
|
+
constructor (svgElem, optIdPrefix) {
|
|
74
|
+
if (!svgElem || !svgElem.tagName || !svgElem.namespaceURI ||
|
|
75
|
+
svgElem.tagName !== 'svg' || svgElem.namespaceURI !== NS.SVG) {
|
|
76
|
+
throw new Error('Error: svgedit.draw.Drawing instance initialized without a <svg> element')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The SVG DOM Element that represents this drawing.
|
|
81
|
+
* @type {SVGSVGElement}
|
|
82
|
+
*/
|
|
83
|
+
this.svgElem_ = svgElem
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The latest object number used in this drawing.
|
|
87
|
+
* @type {Integer}
|
|
88
|
+
*/
|
|
89
|
+
this.obj_num = 0
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The prefix to prepend to each element id in the drawing.
|
|
93
|
+
* @type {string}
|
|
94
|
+
*/
|
|
95
|
+
this.idPrefix = optIdPrefix || 'svg_'
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* An array of released element ids to immediately reuse.
|
|
99
|
+
* @type {Integer[]}
|
|
100
|
+
*/
|
|
101
|
+
this.releasedNums = []
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The z-ordered array of Layer objects. Each layer has a name
|
|
105
|
+
* and group element.
|
|
106
|
+
* The first layer is the one at the bottom of the rendering.
|
|
107
|
+
* @type {Layer[]}
|
|
108
|
+
*/
|
|
109
|
+
this.all_layers = []
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Map of all_layers by name.
|
|
113
|
+
*
|
|
114
|
+
* Note: Layers are ordered, but referenced externally by name; so, we need both container
|
|
115
|
+
* types depending on which function is called (i.e. all_layers and layer_map).
|
|
116
|
+
*
|
|
117
|
+
* @type {PlainObject<string, Layer>}
|
|
118
|
+
*/
|
|
119
|
+
this.layer_map = {}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The current layer being used.
|
|
123
|
+
* @type {Layer}
|
|
124
|
+
*/
|
|
125
|
+
this.current_layer = null
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The nonce to use to uniquely identify elements across drawings.
|
|
129
|
+
* @type {!string}
|
|
130
|
+
*/
|
|
131
|
+
this.nonce_ = ''
|
|
132
|
+
const n = this.svgElem_.getAttributeNS(NS.SE, 'nonce')
|
|
133
|
+
// If already set in the DOM, use the nonce throughout the document
|
|
134
|
+
// else, if randomizeIds(true) has been called, create and set the nonce.
|
|
135
|
+
if (n && randIds !== RandomizeModes.NEVER_RANDOMIZE) {
|
|
136
|
+
this.nonce_ = n
|
|
137
|
+
} else if (randIds === RandomizeModes.ALWAYS_RANDOMIZE) {
|
|
138
|
+
this.setNonce(Math.floor(Math.random() * 100001))
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @param {string} id Element ID to retrieve
|
|
144
|
+
* @returns {Element} SVG element within the root SVGSVGElement
|
|
145
|
+
*/
|
|
146
|
+
getElem_ (id) {
|
|
147
|
+
if (this.svgElem_.querySelector) {
|
|
148
|
+
// querySelector lookup
|
|
149
|
+
return this.svgElem_.querySelector('#' + id)
|
|
150
|
+
}
|
|
151
|
+
// jQuery lookup: twice as slow as xpath in FF
|
|
152
|
+
return this.svgElem_.querySelector('[id=' + id + ']')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @returns {SVGSVGElement}
|
|
157
|
+
*/
|
|
158
|
+
getSvgElem () {
|
|
159
|
+
return this.svgElem_
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @returns {!(string|Integer)} The previously set nonce
|
|
164
|
+
*/
|
|
165
|
+
getNonce () {
|
|
166
|
+
return this.nonce_
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @param {!(string|Integer)} n The nonce to set
|
|
171
|
+
* @returns {void}
|
|
172
|
+
*/
|
|
173
|
+
setNonce (n) {
|
|
174
|
+
this.svgElem_.setAttributeNS(NS.XMLNS, 'xmlns:se', NS.SE)
|
|
175
|
+
this.svgElem_.setAttributeNS(NS.SE, 'se:nonce', n)
|
|
176
|
+
this.nonce_ = n
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Clears any previously set nonce.
|
|
181
|
+
* @returns {void}
|
|
182
|
+
*/
|
|
183
|
+
clearNonce () {
|
|
184
|
+
// We deliberately leave any se:nonce attributes alone,
|
|
185
|
+
// we just don't use it to randomize ids.
|
|
186
|
+
this.nonce_ = ''
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Returns the latest object id as a string.
|
|
191
|
+
* @returns {string} The latest object Id.
|
|
192
|
+
*/
|
|
193
|
+
getId () {
|
|
194
|
+
return this.nonce_
|
|
195
|
+
? this.idPrefix + this.nonce_ + '_' + this.obj_num
|
|
196
|
+
: this.idPrefix + this.obj_num
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Returns the next object Id as a string.
|
|
201
|
+
* @returns {string} The next object Id to use.
|
|
202
|
+
*/
|
|
203
|
+
getNextId () {
|
|
204
|
+
const oldObjNum = this.obj_num
|
|
205
|
+
let restoreOldObjNum = false
|
|
206
|
+
|
|
207
|
+
// If there are any released numbers in the release stack,
|
|
208
|
+
// use the last one instead of the next obj_num.
|
|
209
|
+
// We need to temporarily use obj_num as that is what getId() depends on.
|
|
210
|
+
if (this.releasedNums.length > 0) {
|
|
211
|
+
this.obj_num = this.releasedNums.pop()
|
|
212
|
+
restoreOldObjNum = true
|
|
213
|
+
} else {
|
|
214
|
+
// If we are not using a released id, then increment the obj_num.
|
|
215
|
+
this.obj_num++
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Ensure the ID does not exist.
|
|
219
|
+
let id = this.getId()
|
|
220
|
+
while (this.getElem_(id)) {
|
|
221
|
+
if (restoreOldObjNum) {
|
|
222
|
+
this.obj_num = oldObjNum
|
|
223
|
+
restoreOldObjNum = false
|
|
224
|
+
}
|
|
225
|
+
this.obj_num++
|
|
226
|
+
id = this.getId()
|
|
227
|
+
}
|
|
228
|
+
// Restore the old object number if required.
|
|
229
|
+
if (restoreOldObjNum) {
|
|
230
|
+
this.obj_num = oldObjNum
|
|
231
|
+
}
|
|
232
|
+
return id
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Releases the object Id, letting it be used as the next id in getNextId().
|
|
237
|
+
* This method DOES NOT remove any elements from the DOM, it is expected
|
|
238
|
+
* that client code will do this.
|
|
239
|
+
* @param {string} id - The id to release.
|
|
240
|
+
* @returns {boolean} True if the id was valid to be released, false otherwise.
|
|
241
|
+
*/
|
|
242
|
+
releaseId (id) {
|
|
243
|
+
// confirm if this is a valid id for this Document, else return false
|
|
244
|
+
const front = this.idPrefix + (this.nonce_ ? this.nonce_ + '_' : '')
|
|
245
|
+
if (typeof id !== 'string' || !id.startsWith(front)) {
|
|
246
|
+
return false
|
|
247
|
+
}
|
|
248
|
+
// extract the obj_num of this id
|
|
249
|
+
const num = Number.parseInt(id.substr(front.length))
|
|
250
|
+
|
|
251
|
+
// if we didn't get a positive number or we already released this number
|
|
252
|
+
// then return false.
|
|
253
|
+
if (typeof num !== 'number' || num <= 0 || this.releasedNums.includes(num)) {
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// push the released number into the released queue
|
|
258
|
+
this.releasedNums.push(num)
|
|
259
|
+
|
|
260
|
+
return true
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Returns the number of layers in the current drawing.
|
|
265
|
+
* @returns {Integer} The number of layers in the current drawing.
|
|
266
|
+
*/
|
|
267
|
+
getNumLayers () {
|
|
268
|
+
return this.all_layers.length
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Check if layer with given name already exists.
|
|
273
|
+
* @param {string} name - The layer name to check
|
|
274
|
+
* @returns {boolean}
|
|
275
|
+
*/
|
|
276
|
+
hasLayer (name) {
|
|
277
|
+
return this.layer_map[name] !== undefined
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Returns the name of the ith layer. If the index is out of range, an empty string is returned.
|
|
282
|
+
* @param {Integer} i - The zero-based index of the layer you are querying.
|
|
283
|
+
* @returns {string} The name of the ith layer (or the empty string if none found)
|
|
284
|
+
*/
|
|
285
|
+
getLayerName (i) {
|
|
286
|
+
return i >= 0 && i < this.getNumLayers() ? this.all_layers[i].getName() : ''
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @returns {SVGGElement|null} The SVGGElement representing the current layer.
|
|
291
|
+
*/
|
|
292
|
+
getCurrentLayer () {
|
|
293
|
+
return this.current_layer ? this.current_layer.getGroup() : null
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get a layer by name.
|
|
298
|
+
* @param {string} name
|
|
299
|
+
* @returns {SVGGElement} The SVGGElement representing the named layer or null.
|
|
300
|
+
*/
|
|
301
|
+
getLayerByName (name) {
|
|
302
|
+
const layer = this.layer_map[name]
|
|
303
|
+
return layer ? layer.getGroup() : null
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Returns the name of the currently selected layer. If an error occurs, an empty string
|
|
308
|
+
* is returned.
|
|
309
|
+
* @returns {string} The name of the currently active layer (or the empty string if none found).
|
|
310
|
+
*/
|
|
311
|
+
getCurrentLayerName () {
|
|
312
|
+
return this.current_layer ? this.current_layer.getName() : ''
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Set the current layer's name.
|
|
317
|
+
* @param {string} name - The new name.
|
|
318
|
+
* @param {module:history.HistoryRecordingService} hrService - History recording service
|
|
319
|
+
* @returns {string|null} The new name if changed; otherwise, null.
|
|
320
|
+
*/
|
|
321
|
+
setCurrentLayerName (name, hrService) {
|
|
322
|
+
let finalName = null
|
|
323
|
+
if (this.current_layer) {
|
|
324
|
+
const oldName = this.current_layer.getName()
|
|
325
|
+
finalName = this.current_layer.setName(name, hrService)
|
|
326
|
+
if (finalName) {
|
|
327
|
+
delete this.layer_map[oldName]
|
|
328
|
+
this.layer_map[finalName] = this.current_layer
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return finalName
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Set the current layer's position.
|
|
336
|
+
* @param {Integer} newpos - The zero-based index of the new position of the layer. Range should be 0 to layers-1
|
|
337
|
+
* @returns {{title: SVGGElement, previousName: string}|null} If the name was changed, returns {title:SVGGElement, previousName:string}; otherwise null.
|
|
338
|
+
*/
|
|
339
|
+
setCurrentLayerPosition (newpos) {
|
|
340
|
+
const layerCount = this.getNumLayers()
|
|
341
|
+
if (!this.current_layer || newpos < 0 || newpos >= layerCount) {
|
|
342
|
+
return null
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const oldpos = this.indexCurrentLayer()
|
|
346
|
+
if ((oldpos === -1) || (oldpos === newpos)) { return null }
|
|
347
|
+
|
|
348
|
+
// if our new position is below us, we need to insert before the node after newpos
|
|
349
|
+
const currentGroup = this.current_layer.getGroup()
|
|
350
|
+
const oldNextSibling = currentGroup.nextSibling
|
|
351
|
+
|
|
352
|
+
let refGroup = null
|
|
353
|
+
if (newpos > oldpos) {
|
|
354
|
+
if (newpos < layerCount - 1) {
|
|
355
|
+
refGroup = this.all_layers[newpos + 1].getGroup()
|
|
356
|
+
}
|
|
357
|
+
// if our new position is above us, we need to insert before the node at newpos
|
|
358
|
+
} else {
|
|
359
|
+
refGroup = this.all_layers[newpos].getGroup()
|
|
360
|
+
}
|
|
361
|
+
this.svgElem_.insertBefore(currentGroup, refGroup) // Ok to replace with `refGroup.before(currentGroup);`?
|
|
362
|
+
|
|
363
|
+
this.identifyLayers()
|
|
364
|
+
this.setCurrentLayer(this.getLayerName(newpos))
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
currentGroup,
|
|
368
|
+
oldNextSibling
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* @param {module:history.HistoryRecordingService} hrService
|
|
374
|
+
* @returns {void}
|
|
375
|
+
*/
|
|
376
|
+
mergeLayer (hrService) {
|
|
377
|
+
const currentGroup = this.current_layer.getGroup()
|
|
378
|
+
const prevGroup = currentGroup.previousElementSibling
|
|
379
|
+
if (!prevGroup) { return }
|
|
380
|
+
|
|
381
|
+
hrService.startBatchCommand('Merge Layer')
|
|
382
|
+
|
|
383
|
+
const layerNextSibling = currentGroup.nextSibling
|
|
384
|
+
hrService.removeElement(currentGroup, layerNextSibling, this.svgElem_)
|
|
385
|
+
|
|
386
|
+
while (currentGroup.firstChild) {
|
|
387
|
+
const child = currentGroup.firstChild
|
|
388
|
+
if (child.localName === 'title') {
|
|
389
|
+
hrService.removeElement(child, child.nextSibling, currentGroup)
|
|
390
|
+
child.remove()
|
|
391
|
+
continue
|
|
392
|
+
}
|
|
393
|
+
const oldNextSibling = child.nextSibling
|
|
394
|
+
prevGroup.append(child)
|
|
395
|
+
hrService.moveElement(child, oldNextSibling, currentGroup)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Remove current layer's group
|
|
399
|
+
this.current_layer.removeGroup()
|
|
400
|
+
// Remove the current layer and set the previous layer as the new current layer
|
|
401
|
+
const index = this.indexCurrentLayer()
|
|
402
|
+
if (index > 0) {
|
|
403
|
+
const name = this.current_layer.getName()
|
|
404
|
+
this.current_layer = this.all_layers[index - 1]
|
|
405
|
+
this.all_layers.splice(index, 1)
|
|
406
|
+
delete this.layer_map[name]
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
hrService.endBatchCommand()
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* @param {module:history.HistoryRecordingService} hrService
|
|
414
|
+
* @returns {void}
|
|
415
|
+
*/
|
|
416
|
+
mergeAllLayers (hrService) {
|
|
417
|
+
// Set the current layer to the last layer.
|
|
418
|
+
this.current_layer = this.all_layers[this.all_layers.length - 1]
|
|
419
|
+
|
|
420
|
+
hrService.startBatchCommand('Merge all Layers')
|
|
421
|
+
while (this.all_layers.length > 1) {
|
|
422
|
+
this.mergeLayer(hrService)
|
|
423
|
+
}
|
|
424
|
+
hrService.endBatchCommand()
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Sets the current layer. If the name is not a valid layer name, then this
|
|
429
|
+
* function returns `false`. Otherwise it returns `true`. This is not an
|
|
430
|
+
* undo-able action.
|
|
431
|
+
* @param {string} name - The name of the layer you want to switch to.
|
|
432
|
+
* @returns {boolean} `true` if the current layer was switched, otherwise `false`
|
|
433
|
+
*/
|
|
434
|
+
setCurrentLayer (name) {
|
|
435
|
+
const layer = this.layer_map[name]
|
|
436
|
+
if (layer) {
|
|
437
|
+
if (this.current_layer) {
|
|
438
|
+
this.current_layer.deactivate()
|
|
439
|
+
}
|
|
440
|
+
this.current_layer = layer
|
|
441
|
+
this.current_layer.activate()
|
|
442
|
+
return true
|
|
443
|
+
}
|
|
444
|
+
return false
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Sets the current layer. If the name is not a valid layer name, then this
|
|
449
|
+
* function returns `false`. Otherwise it returns `true`. This is not an
|
|
450
|
+
* undo-able action.
|
|
451
|
+
* @param {string} name - The name of the layer you want to switch to.
|
|
452
|
+
* @returns {boolean} `true` if the current layer was switched, otherwise `false`
|
|
453
|
+
*/
|
|
454
|
+
indexCurrentLayer () {
|
|
455
|
+
return this.all_layers.indexOf(this.current_layer)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Deletes the current layer from the drawing and then clears the selection.
|
|
460
|
+
* This function then calls the 'changed' handler. This is an undoable action.
|
|
461
|
+
* @todo Does this actually call the 'changed' handler?
|
|
462
|
+
* @returns {SVGGElement} The SVGGElement of the layer removed or null.
|
|
463
|
+
*/
|
|
464
|
+
deleteCurrentLayer () {
|
|
465
|
+
if (this.current_layer && this.getNumLayers() > 1) {
|
|
466
|
+
const oldLayerGroup = this.current_layer.removeGroup()
|
|
467
|
+
this.identifyLayers()
|
|
468
|
+
return oldLayerGroup
|
|
469
|
+
}
|
|
470
|
+
return null
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Updates layer system and sets the current layer to the
|
|
475
|
+
* top-most layer (last `<g>` child of this drawing).
|
|
476
|
+
* @returns {void}
|
|
477
|
+
*/
|
|
478
|
+
identifyLayers () {
|
|
479
|
+
this.all_layers = []
|
|
480
|
+
this.layer_map = {}
|
|
481
|
+
const numchildren = this.svgElem_.childNodes.length
|
|
482
|
+
// loop through all children of SVG element
|
|
483
|
+
const orphans = []; const layernames = []
|
|
484
|
+
let layer = null
|
|
485
|
+
let childgroups = false
|
|
486
|
+
for (let i = 0; i < numchildren; ++i) {
|
|
487
|
+
const child = this.svgElem_.childNodes.item(i)
|
|
488
|
+
// for each g, find its layer name
|
|
489
|
+
if (child?.nodeType === 1) {
|
|
490
|
+
if (child.tagName === 'g') {
|
|
491
|
+
childgroups = true
|
|
492
|
+
const name = findLayerNameInGroup(child)
|
|
493
|
+
if (name) {
|
|
494
|
+
layernames.push(name)
|
|
495
|
+
layer = new Layer(name, child)
|
|
496
|
+
this.all_layers.push(layer)
|
|
497
|
+
this.layer_map[name] = layer
|
|
498
|
+
} else {
|
|
499
|
+
// if group did not have a name, it is an orphan
|
|
500
|
+
orphans.push(child)
|
|
501
|
+
}
|
|
502
|
+
} else if (visElems.includes(child.nodeName)) {
|
|
503
|
+
// Child is "visible" (i.e. not a <title> or <defs> element), so it is an orphan
|
|
504
|
+
orphans.push(child)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// If orphans or no layers found, create a new layer and add all the orphans to it
|
|
510
|
+
if (orphans.length > 0 || !childgroups) {
|
|
511
|
+
layer = new Layer(getNewLayerName(layernames), null, this.svgElem_)
|
|
512
|
+
layer.appendChildren(orphans)
|
|
513
|
+
this.all_layers.push(layer)
|
|
514
|
+
this.layer_map[name] = layer
|
|
515
|
+
} else {
|
|
516
|
+
layer.activate()
|
|
517
|
+
}
|
|
518
|
+
this.current_layer = layer
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Creates a new top-level layer in the drawing with the given name and
|
|
523
|
+
* makes it the current layer.
|
|
524
|
+
* @param {string} name - The given name. If the layer name exists, a new name will be generated.
|
|
525
|
+
* @param {module:history.HistoryRecordingService} hrService - History recording service
|
|
526
|
+
* @returns {SVGGElement} The SVGGElement of the new layer, which is
|
|
527
|
+
* also the current layer of this drawing.
|
|
528
|
+
*/
|
|
529
|
+
createLayer (name, hrService) {
|
|
530
|
+
if (this.current_layer) {
|
|
531
|
+
this.current_layer.deactivate()
|
|
532
|
+
}
|
|
533
|
+
// Check for duplicate name.
|
|
534
|
+
if (name === undefined || name === null || name === '' || this.layer_map[name]) {
|
|
535
|
+
name = getNewLayerName(Object.keys(this.layer_map))
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Crate new layer and add to DOM as last layer
|
|
539
|
+
const layer = new Layer(name, null, this.svgElem_)
|
|
540
|
+
// Like to assume hrService exists, but this is backwards compatible with old version of createLayer.
|
|
541
|
+
if (hrService) {
|
|
542
|
+
hrService.startBatchCommand('Create Layer')
|
|
543
|
+
hrService.insertElement(layer.getGroup())
|
|
544
|
+
hrService.endBatchCommand()
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
this.all_layers.push(layer)
|
|
548
|
+
this.layer_map[name] = layer
|
|
549
|
+
this.current_layer = layer
|
|
550
|
+
return layer.getGroup()
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Creates a copy of the current layer with the given name and makes it the current layer.
|
|
555
|
+
* @param {string} name - The given name. If the layer name exists, a new name will be generated.
|
|
556
|
+
* @param {module:history.HistoryRecordingService} hrService - History recording service
|
|
557
|
+
* @returns {SVGGElement} The SVGGElement of the new layer, which is
|
|
558
|
+
* also the current layer of this drawing.
|
|
559
|
+
*/
|
|
560
|
+
cloneLayer (name, hrService) {
|
|
561
|
+
if (!this.current_layer) { return null }
|
|
562
|
+
this.current_layer.deactivate()
|
|
563
|
+
// Check for duplicate name.
|
|
564
|
+
if (name === undefined || name === null || name === '' || this.layer_map[name]) {
|
|
565
|
+
name = getNewLayerName(Object.keys(this.layer_map))
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Create new group and add to DOM just after current_layer
|
|
569
|
+
const currentGroup = this.current_layer.getGroup()
|
|
570
|
+
const layer = new Layer(name, currentGroup, this.svgElem_)
|
|
571
|
+
const group = layer.getGroup()
|
|
572
|
+
|
|
573
|
+
// Clone children
|
|
574
|
+
const children = [...currentGroup.childNodes]
|
|
575
|
+
children.forEach((child) => {
|
|
576
|
+
if (child.localName === 'title') { return }
|
|
577
|
+
group.append(this.copyElem(child))
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
if (hrService) {
|
|
581
|
+
hrService.startBatchCommand('Duplicate Layer')
|
|
582
|
+
hrService.insertElement(group)
|
|
583
|
+
hrService.endBatchCommand()
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Update layer containers and current_layer.
|
|
587
|
+
const index = this.indexCurrentLayer()
|
|
588
|
+
if (index >= 0) {
|
|
589
|
+
this.all_layers.splice(index + 1, 0, layer)
|
|
590
|
+
} else {
|
|
591
|
+
this.all_layers.push(layer)
|
|
592
|
+
}
|
|
593
|
+
this.layer_map[name] = layer
|
|
594
|
+
this.current_layer = layer
|
|
595
|
+
return group
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Returns whether the layer is visible. If the layer name is not valid,
|
|
600
|
+
* then this function returns `false`.
|
|
601
|
+
* @param {string} layerName - The name of the layer which you want to query.
|
|
602
|
+
* @returns {boolean} The visibility state of the layer, or `false` if the layer name was invalid.
|
|
603
|
+
*/
|
|
604
|
+
getLayerVisibility (layerName) {
|
|
605
|
+
const layer = this.layer_map[layerName]
|
|
606
|
+
return layer ? layer.isVisible() : false
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Sets the visibility of the layer. If the layer name is not valid, this
|
|
611
|
+
* function returns `null`, otherwise it returns the `SVGElement` representing
|
|
612
|
+
* the layer. This is an undo-able action.
|
|
613
|
+
* @param {string} layerName - The name of the layer to change the visibility
|
|
614
|
+
* @param {boolean} bVisible - Whether the layer should be visible
|
|
615
|
+
* @returns {?SVGGElement} The SVGGElement representing the layer if the
|
|
616
|
+
* `layerName` was valid, otherwise `null`.
|
|
617
|
+
*/
|
|
618
|
+
setLayerVisibility (layerName, bVisible) {
|
|
619
|
+
if (typeof bVisible !== 'boolean') {
|
|
620
|
+
return null
|
|
621
|
+
}
|
|
622
|
+
const layer = this.layer_map[layerName]
|
|
623
|
+
if (!layer) { return null }
|
|
624
|
+
layer.setVisible(bVisible)
|
|
625
|
+
return layer.getGroup()
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Returns the opacity of the given layer. If the input name is not a layer, `null` is returned.
|
|
630
|
+
* @param {string} layerName - name of the layer on which to get the opacity
|
|
631
|
+
* @returns {?Float} The opacity value of the given layer. This will be a value between 0.0 and 1.0, or `null`
|
|
632
|
+
* if `layerName` is not a valid layer
|
|
633
|
+
*/
|
|
634
|
+
getLayerOpacity (layerName) {
|
|
635
|
+
const layer = this.layer_map[layerName]
|
|
636
|
+
if (!layer) { return null }
|
|
637
|
+
return layer.getOpacity()
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Sets the opacity of the given layer. If the input name is not a layer,
|
|
642
|
+
* nothing happens. If opacity is not a value between 0.0 and 1.0, then
|
|
643
|
+
* nothing happens.
|
|
644
|
+
* NOTE: this function exists solely to apply a highlighting/de-emphasis
|
|
645
|
+
* effect to a layer. When it is possible for a user to affect the opacity
|
|
646
|
+
* of a layer, we will need to allow this function to produce an undo-able
|
|
647
|
+
* action.
|
|
648
|
+
* @param {string} layerName - Name of the layer on which to set the opacity
|
|
649
|
+
* @param {Float} opacity - A float value in the range 0.0-1.0
|
|
650
|
+
* @returns {void}
|
|
651
|
+
*/
|
|
652
|
+
setLayerOpacity (layerName, opacity) {
|
|
653
|
+
if (typeof opacity !== 'number' || opacity < 0.0 || opacity > 1.0) {
|
|
654
|
+
return
|
|
655
|
+
}
|
|
656
|
+
const layer = this.layer_map[layerName]
|
|
657
|
+
if (layer) {
|
|
658
|
+
layer.setOpacity(opacity)
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Create a clone of an element, updating its ID and its children's IDs when needed.
|
|
664
|
+
* @param {Element} el - DOM element to clone
|
|
665
|
+
* @returns {Element}
|
|
666
|
+
*/
|
|
667
|
+
copyElem (el) {
|
|
668
|
+
const that = this
|
|
669
|
+
const getNextIdClosure = function () { return that.getNextId() }
|
|
670
|
+
return utilCopyElem(el, getNextIdClosure)
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Called to ensure that drawings will or will not have randomized ids.
|
|
676
|
+
* The currentDrawing will have its nonce set if it doesn't already.
|
|
677
|
+
* @function module:draw.randomizeIds
|
|
678
|
+
* @param {boolean} enableRandomization - flag indicating if documents should have randomized ids
|
|
679
|
+
* @param {draw.Drawing} currentDrawing
|
|
680
|
+
* @returns {void}
|
|
681
|
+
*/
|
|
682
|
+
export const randomizeIds = function (enableRandomization, currentDrawing) {
|
|
683
|
+
randIds = enableRandomization === false
|
|
684
|
+
? RandomizeModes.NEVER_RANDOMIZE
|
|
685
|
+
: RandomizeModes.ALWAYS_RANDOMIZE
|
|
686
|
+
|
|
687
|
+
if (randIds === RandomizeModes.ALWAYS_RANDOMIZE && !currentDrawing.getNonce()) {
|
|
688
|
+
currentDrawing.setNonce(Math.floor(Math.random() * 100001))
|
|
689
|
+
} else if (randIds === RandomizeModes.NEVER_RANDOMIZE && currentDrawing.getNonce()) {
|
|
690
|
+
currentDrawing.clearNonce()
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Layer API Functions
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Group: Layers.
|
|
698
|
+
*/
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* @see {@link https://api.jquery.com/jQuery.data/}
|
|
702
|
+
* @name external:jQuery.data
|
|
703
|
+
*/
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* @interface module:draw.DrawCanvasInit
|
|
707
|
+
* @property {module:path.pathActions} pathActions
|
|
708
|
+
* @property {module:history.UndoManager} undoMgr
|
|
709
|
+
*/
|
|
710
|
+
/**
|
|
711
|
+
* @function module:draw.DrawCanvasInit#getCurrentGroup
|
|
712
|
+
* @returns {Element}
|
|
713
|
+
*/
|
|
714
|
+
/**
|
|
715
|
+
* @function module:draw.DrawCanvasInit#setCurrentGroup
|
|
716
|
+
* @param {Element} cg
|
|
717
|
+
* @returns {void}
|
|
718
|
+
*/
|
|
719
|
+
/**
|
|
720
|
+
* @function module:draw.DrawCanvasInit#getSelectedElements
|
|
721
|
+
* @returns {Element[]} the array with selected DOM elements
|
|
722
|
+
*/
|
|
723
|
+
/**
|
|
724
|
+
* @function module:draw.DrawCanvasInit#getSvgContent
|
|
725
|
+
* @returns {SVGSVGElement}
|
|
726
|
+
*/
|
|
727
|
+
/**
|
|
728
|
+
* @function module:draw.DrawCanvasInit#getCurrentDrawing
|
|
729
|
+
* @returns {module:draw.Drawing}
|
|
730
|
+
*/
|
|
731
|
+
/**
|
|
732
|
+
* @function module:draw.DrawCanvasInit#clearSelection
|
|
733
|
+
* @param {boolean} [noCall] - When `true`, does not call the "selected" handler
|
|
734
|
+
* @returns {void}
|
|
735
|
+
*/
|
|
736
|
+
/**
|
|
737
|
+
* Run the callback function associated with the given event.
|
|
738
|
+
* @function module:draw.DrawCanvasInit#call
|
|
739
|
+
* @param {"changed"|"contextset"} ev - String with the event name
|
|
740
|
+
* @param {module:svgcanvas.SvgCanvas#event:changed|module:svgcanvas.SvgCanvas#event:contextset} arg - Argument to pass through to the callback
|
|
741
|
+
* function. If the event is "changed", a (single-item) array of `Element`s is
|
|
742
|
+
* passed. If the event is "contextset", the arg is `null` or `Element`.
|
|
743
|
+
* @returns {void}
|
|
744
|
+
*/
|
|
745
|
+
/**
|
|
746
|
+
* @function module:draw.DrawCanvasInit#addCommandToHistory
|
|
747
|
+
* @param {Command} cmd
|
|
748
|
+
* @returns {void}
|
|
749
|
+
*/
|
|
750
|
+
/**
|
|
751
|
+
* @function module:draw.DrawCanvasInit#changeSvgContent
|
|
752
|
+
* @returns {void}
|
|
753
|
+
*/
|
|
754
|
+
|
|
755
|
+
let svgCanvas
|
|
756
|
+
/**
|
|
757
|
+
* @function module:draw.init
|
|
758
|
+
* @param {module:draw.DrawCanvasInit} canvas
|
|
759
|
+
* @returns {void}
|
|
760
|
+
*/
|
|
761
|
+
export const init = (canvas) => {
|
|
762
|
+
svgCanvas = canvas
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Updates layer system.
|
|
767
|
+
* @function module:draw.identifyLayers
|
|
768
|
+
* @returns {void}
|
|
769
|
+
*/
|
|
770
|
+
export const identifyLayers = () => {
|
|
771
|
+
leaveContext()
|
|
772
|
+
svgCanvas.getCurrentDrawing().identifyLayers()
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* get current index
|
|
777
|
+
* @function module:draw.identifyLayers
|
|
778
|
+
* @returns {void}
|
|
779
|
+
*/
|
|
780
|
+
export const indexCurrentLayer = () => {
|
|
781
|
+
return svgCanvas.getCurrentDrawing().indexCurrentLayer()
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Creates a new top-level layer in the drawing with the given name, sets the current layer
|
|
786
|
+
* to it, and then clears the selection. This function then calls the 'changed' handler.
|
|
787
|
+
* This is an undoable action.
|
|
788
|
+
* @function module:draw.createLayer
|
|
789
|
+
* @param {string} name - The given name
|
|
790
|
+
* @param {module:history.HistoryRecordingService} hrService
|
|
791
|
+
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
792
|
+
* @returns {void}
|
|
793
|
+
*/
|
|
794
|
+
export const createLayer = (name, hrService) => {
|
|
795
|
+
const newLayer = svgCanvas.getCurrentDrawing().createLayer(
|
|
796
|
+
name,
|
|
797
|
+
historyRecordingService(hrService)
|
|
798
|
+
)
|
|
799
|
+
svgCanvas.clearSelection()
|
|
800
|
+
svgCanvas.call('changed', [newLayer])
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Creates a new top-level layer in the drawing with the given name, copies all the current layer's contents
|
|
805
|
+
* to it, and then clears the selection. This function then calls the 'changed' handler.
|
|
806
|
+
* This is an undoable action.
|
|
807
|
+
* @function module:draw.cloneLayer
|
|
808
|
+
* @param {string} name - The given name. If the layer name exists, a new name will be generated.
|
|
809
|
+
* @param {module:history.HistoryRecordingService} hrService - History recording service
|
|
810
|
+
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
811
|
+
* @returns {void}
|
|
812
|
+
*/
|
|
813
|
+
export const cloneLayer = (name, hrService) => {
|
|
814
|
+
// Clone the current layer and make the cloned layer the new current layer
|
|
815
|
+
const newLayer = svgCanvas.getCurrentDrawing().cloneLayer(name, historyRecordingService(hrService))
|
|
816
|
+
|
|
817
|
+
svgCanvas.clearSelection()
|
|
818
|
+
leaveContext()
|
|
819
|
+
svgCanvas.call('changed', [newLayer])
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Deletes the current layer from the drawing and then clears the selection. This function
|
|
824
|
+
* then calls the 'changed' handler. This is an undoable action.
|
|
825
|
+
* @function module:draw.deleteCurrentLayer
|
|
826
|
+
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
827
|
+
* @returns {boolean} `true` if an old layer group was found to delete
|
|
828
|
+
*/
|
|
829
|
+
export const deleteCurrentLayer = () => {
|
|
830
|
+
const { BatchCommand, RemoveElementCommand } = svgCanvas.history
|
|
831
|
+
let currentLayer = svgCanvas.getCurrentDrawing().getCurrentLayer()
|
|
832
|
+
const { nextSibling } = currentLayer
|
|
833
|
+
const parent = currentLayer.parentNode
|
|
834
|
+
currentLayer = svgCanvas.getCurrentDrawing().deleteCurrentLayer()
|
|
835
|
+
if (currentLayer) {
|
|
836
|
+
const batchCmd = new BatchCommand('Delete Layer')
|
|
837
|
+
// store in our Undo History
|
|
838
|
+
batchCmd.addSubCommand(new RemoveElementCommand(currentLayer, nextSibling, parent))
|
|
839
|
+
svgCanvas.addCommandToHistory(batchCmd)
|
|
840
|
+
svgCanvas.clearSelection()
|
|
841
|
+
svgCanvas.call('changed', [parent])
|
|
842
|
+
return true
|
|
843
|
+
}
|
|
844
|
+
return false
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Sets the current layer. If the name is not a valid layer name, then this function returns
|
|
849
|
+
* false. Otherwise it returns true. This is not an undo-able action.
|
|
850
|
+
* @function module:draw.setCurrentLayer
|
|
851
|
+
* @param {string} name - The name of the layer you want to switch to.
|
|
852
|
+
* @returns {boolean} true if the current layer was switched, otherwise false
|
|
853
|
+
*/
|
|
854
|
+
export const setCurrentLayer = (name) => {
|
|
855
|
+
const result = svgCanvas.getCurrentDrawing().setCurrentLayer(toXml(name))
|
|
856
|
+
if (result) {
|
|
857
|
+
svgCanvas.clearSelection()
|
|
858
|
+
}
|
|
859
|
+
return result
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Renames the current layer. If the layer name is not valid (i.e. unique), then this function
|
|
864
|
+
* does nothing and returns `false`, otherwise it returns `true`. This is an undo-able action.
|
|
865
|
+
* @function module:draw.renameCurrentLayer
|
|
866
|
+
* @param {string} newName - the new name you want to give the current layer. This name must
|
|
867
|
+
* be unique among all layer names.
|
|
868
|
+
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
869
|
+
* @returns {boolean} Whether the rename succeeded
|
|
870
|
+
*/
|
|
871
|
+
export const renameCurrentLayer = (newName) => {
|
|
872
|
+
const drawing = svgCanvas.getCurrentDrawing()
|
|
873
|
+
const layer = drawing.getCurrentLayer()
|
|
874
|
+
if (layer) {
|
|
875
|
+
const result = drawing.setCurrentLayerName(newName, historyRecordingService())
|
|
876
|
+
if (result) {
|
|
877
|
+
svgCanvas.call('changed', [layer])
|
|
878
|
+
return true
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return false
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Changes the position of the current layer to the new value. If the new index is not valid,
|
|
886
|
+
* this function does nothing and returns false, otherwise it returns true. This is an
|
|
887
|
+
* undo-able action.
|
|
888
|
+
* @function module:draw.setCurrentLayerPosition
|
|
889
|
+
* @param {Integer} newPos - The zero-based index of the new position of the layer. This should be between
|
|
890
|
+
* 0 and (number of layers - 1)
|
|
891
|
+
* @returns {boolean} `true` if the current layer position was changed, `false` otherwise.
|
|
892
|
+
*/
|
|
893
|
+
export const setCurrentLayerPosition = (newPos) => {
|
|
894
|
+
const { MoveElementCommand } = svgCanvas.history
|
|
895
|
+
const drawing = svgCanvas.getCurrentDrawing()
|
|
896
|
+
const result = drawing.setCurrentLayerPosition(newPos)
|
|
897
|
+
if (result) {
|
|
898
|
+
svgCanvas.addCommandToHistory(new MoveElementCommand(result.currentGroup, result.oldNextSibling, svgCanvas.getSvgContent()))
|
|
899
|
+
return true
|
|
900
|
+
}
|
|
901
|
+
return false
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Sets the visibility of the layer. If the layer name is not valid, this function return
|
|
906
|
+
* `false`, otherwise it returns `true`. This is an undo-able action.
|
|
907
|
+
* @function module:draw.setLayerVisibility
|
|
908
|
+
* @param {string} layerName - The name of the layer to change the visibility
|
|
909
|
+
* @param {boolean} bVisible - Whether the layer should be visible
|
|
910
|
+
* @returns {boolean} true if the layer's visibility was set, false otherwise
|
|
911
|
+
*/
|
|
912
|
+
export const setLayerVisibility = (layerName, bVisible) => {
|
|
913
|
+
const { ChangeElementCommand } = svgCanvas.history
|
|
914
|
+
const drawing = svgCanvas.getCurrentDrawing()
|
|
915
|
+
const prevVisibility = drawing.getLayerVisibility(layerName)
|
|
916
|
+
const layer = drawing.setLayerVisibility(layerName, bVisible)
|
|
917
|
+
if (layer) {
|
|
918
|
+
const oldDisplay = prevVisibility ? 'inline' : 'none'
|
|
919
|
+
svgCanvas.addCommandToHistory(new ChangeElementCommand(layer, { display: oldDisplay }, 'Layer Visibility'))
|
|
920
|
+
} else {
|
|
921
|
+
return false
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if (layer === drawing.getCurrentLayer()) {
|
|
925
|
+
svgCanvas.clearSelection()
|
|
926
|
+
svgCanvas.pathActions.clear()
|
|
927
|
+
}
|
|
928
|
+
// call('changed', [selected]);
|
|
929
|
+
return true
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Moves the selected elements to layerName. If the name is not a valid layer name, then `false`
|
|
934
|
+
* is returned. Otherwise it returns `true`. This is an undo-able action.
|
|
935
|
+
* @function module:draw.moveSelectedToLayer
|
|
936
|
+
* @param {string} layerName - The name of the layer you want to which you want to move the selected elements
|
|
937
|
+
* @returns {boolean} Whether the selected elements were moved to the layer.
|
|
938
|
+
*/
|
|
939
|
+
export const moveSelectedToLayer = (layerName) => {
|
|
940
|
+
const { BatchCommand, MoveElementCommand } = svgCanvas.history
|
|
941
|
+
// find the layer
|
|
942
|
+
const drawing = svgCanvas.getCurrentDrawing()
|
|
943
|
+
const layer = drawing.getLayerByName(layerName)
|
|
944
|
+
if (!layer) { return false }
|
|
945
|
+
|
|
946
|
+
const batchCmd = new BatchCommand('Move Elements to Layer')
|
|
947
|
+
|
|
948
|
+
// loop for each selected element and move it
|
|
949
|
+
const selElems = svgCanvas.getSelectedElements()
|
|
950
|
+
let i = selElems.length
|
|
951
|
+
while (i--) {
|
|
952
|
+
const elem = selElems[i]
|
|
953
|
+
if (!elem) { continue }
|
|
954
|
+
const oldNextSibling = elem.nextSibling
|
|
955
|
+
// TODO: this is pretty brittle!
|
|
956
|
+
const oldLayer = elem.parentNode
|
|
957
|
+
layer.append(elem)
|
|
958
|
+
batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldLayer))
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
svgCanvas.addCommandToHistory(batchCmd)
|
|
962
|
+
|
|
963
|
+
return true
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* @function module:draw.mergeLayer
|
|
968
|
+
* @param {module:history.HistoryRecordingService} hrService
|
|
969
|
+
* @returns {void}
|
|
970
|
+
*/
|
|
971
|
+
export const mergeLayer = (hrService) => {
|
|
972
|
+
svgCanvas.getCurrentDrawing().mergeLayer(historyRecordingService(hrService))
|
|
973
|
+
svgCanvas.clearSelection()
|
|
974
|
+
leaveContext()
|
|
975
|
+
svgCanvas.changeSvgContent()
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* @function module:draw.mergeAllLayers
|
|
980
|
+
* @param {module:history.HistoryRecordingService} hrService
|
|
981
|
+
* @returns {void}
|
|
982
|
+
*/
|
|
983
|
+
export const mergeAllLayers = (hrService) => {
|
|
984
|
+
svgCanvas.getCurrentDrawing().mergeAllLayers(historyRecordingService(hrService))
|
|
985
|
+
svgCanvas.clearSelection()
|
|
986
|
+
leaveContext()
|
|
987
|
+
svgCanvas.changeSvgContent()
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Return from a group context to the regular kind, make any previously
|
|
992
|
+
* disabled elements enabled again.
|
|
993
|
+
* @function module:draw.leaveContext
|
|
994
|
+
* @fires module:svgcanvas.SvgCanvas#event:contextset
|
|
995
|
+
* @returns {void}
|
|
996
|
+
*/
|
|
997
|
+
export const leaveContext = () => {
|
|
998
|
+
const len = disabledElems.length
|
|
999
|
+
const dataStorage = svgCanvas.getDataStorage()
|
|
1000
|
+
if (len) {
|
|
1001
|
+
for (let i = 0; i < len; i++) {
|
|
1002
|
+
const elem = disabledElems[i]
|
|
1003
|
+
const orig = dataStorage.get(elem, 'orig_opac')
|
|
1004
|
+
if (orig !== 1) {
|
|
1005
|
+
elem.setAttribute('opacity', orig)
|
|
1006
|
+
} else {
|
|
1007
|
+
elem.removeAttribute('opacity')
|
|
1008
|
+
}
|
|
1009
|
+
elem.setAttribute('style', 'pointer-events: inherit')
|
|
1010
|
+
}
|
|
1011
|
+
disabledElems = []
|
|
1012
|
+
svgCanvas.clearSelection(true)
|
|
1013
|
+
svgCanvas.call('contextset', null)
|
|
1014
|
+
}
|
|
1015
|
+
svgCanvas.setCurrentGroup(null)
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* Set the current context (for in-group editing).
|
|
1020
|
+
* @function module:draw.setContext
|
|
1021
|
+
* @param {Element} elem
|
|
1022
|
+
* @fires module:svgcanvas.SvgCanvas#event:contextset
|
|
1023
|
+
* @returns {void}
|
|
1024
|
+
*/
|
|
1025
|
+
export const setContext = (elem) => {
|
|
1026
|
+
const dataStorage = svgCanvas.getDataStorage()
|
|
1027
|
+
leaveContext()
|
|
1028
|
+
if (typeof elem === 'string') {
|
|
1029
|
+
elem = getElement(elem)
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// Edit inside this group
|
|
1033
|
+
svgCanvas.setCurrentGroup(elem)
|
|
1034
|
+
|
|
1035
|
+
// Disable other elements
|
|
1036
|
+
const parentsUntil = getParentsUntil(elem, '#svgcontent')
|
|
1037
|
+
const siblings = []
|
|
1038
|
+
parentsUntil.forEach(function (parent) {
|
|
1039
|
+
const elements = Array.prototype.filter.call(parent.parentNode.children, function (child) {
|
|
1040
|
+
return child !== parent
|
|
1041
|
+
})
|
|
1042
|
+
elements.forEach(function (element) {
|
|
1043
|
+
siblings.push(element)
|
|
1044
|
+
})
|
|
1045
|
+
})
|
|
1046
|
+
|
|
1047
|
+
siblings.forEach(function (curthis) {
|
|
1048
|
+
const opac = curthis.getAttribute('opacity') || 1
|
|
1049
|
+
// Store the original's opacity
|
|
1050
|
+
dataStorage.put(curthis, 'orig_opac', opac)
|
|
1051
|
+
curthis.setAttribute('opacity', opac * 0.33)
|
|
1052
|
+
curthis.setAttribute('style', 'pointer-events: none')
|
|
1053
|
+
disabledElems.push(curthis)
|
|
1054
|
+
})
|
|
1055
|
+
svgCanvas.clearSelection()
|
|
1056
|
+
svgCanvas.call('contextset', svgCanvas.getCurrentGroup())
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* @memberof module:draw
|
|
1061
|
+
* @class Layer
|
|
1062
|
+
* @see {@link module:layer.Layer}
|
|
1063
|
+
*/
|
|
1064
|
+
export { Layer }
|