@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.
@@ -0,0 +1,161 @@
1
+ /**
2
+ * HistoryRecordingService component of history.
3
+ * @module history
4
+ * @license MIT
5
+ * @copyright 2016 Flint O'Brien
6
+ */
7
+
8
+ import {
9
+ BatchCommand, MoveElementCommand, InsertElementCommand, RemoveElementCommand,
10
+ ChangeElementCommand
11
+ } from './history.js'
12
+
13
+ /**
14
+ * History recording service.
15
+ *
16
+ * A self-contained service interface for recording history. Once injected, no other dependencies
17
+ * or globals are required (example: UndoManager, command types, etc.). Easy to mock for unit tests.
18
+ * Built on top of history classes in history.js.
19
+ *
20
+ * There is a simple start/end interface for batch commands.
21
+ *
22
+ * HistoryRecordingService.NO_HISTORY is a singleton that can be passed in to functions
23
+ * that record history. This helps when the caller requires that no history be recorded.
24
+ *
25
+ * The following will record history: insert, batch, insert.
26
+ * @example
27
+ * hrService = new HistoryRecordingService(this.undoMgr);
28
+ * hrService.insertElement(elem, text); // add simple command to history.
29
+ * hrService.startBatchCommand('create two elements');
30
+ * hrService.changeElement(elem, attrs, text); // add to batchCommand
31
+ * hrService.changeElement(elem, attrs2, text); // add to batchCommand
32
+ * hrService.endBatchCommand(); // add batch command with two change commands to history.
33
+ * hrService.insertElement(elem, text); // add simple command to history.
34
+ *
35
+ * @example
36
+ * // Note that all functions return this, so commands can be chained, like so:
37
+ * hrService
38
+ * .startBatchCommand('create two elements')
39
+ * .insertElement(elem, text)
40
+ * .changeElement(elem, attrs, text)
41
+ * .endBatchCommand();
42
+ *
43
+ * @memberof module:history
44
+ */
45
+ class HistoryRecordingService {
46
+ /**
47
+ * @param {history.UndoManager|null} undoManager - The undo manager.
48
+ * A value of `null` is valid for cases where no history recording is required.
49
+ * See singleton: {@link module:history.HistoryRecordingService.HistoryRecordingService.NO_HISTORY}
50
+ */
51
+ constructor (undoManager) {
52
+ this.undoManager_ = undoManager
53
+ this.currentBatchCommand_ = null
54
+ this.batchCommandStack_ = []
55
+ }
56
+
57
+ /**
58
+ * Start a batch command so multiple commands can recorded as a single history command.
59
+ * Requires a corresponding call to endBatchCommand. Start and end commands can be nested.
60
+ *
61
+ * @param {string} text - Optional string describing the batch command.
62
+ * @returns {module:history.HistoryRecordingService}
63
+ */
64
+ startBatchCommand (text) {
65
+ if (!this.undoManager_) { return this }
66
+ this.currentBatchCommand_ = new BatchCommand(text)
67
+ this.batchCommandStack_.push(this.currentBatchCommand_)
68
+ return this
69
+ }
70
+
71
+ /**
72
+ * End a batch command and add it to the history or a parent batch command.
73
+ * @returns {module:history.HistoryRecordingService}
74
+ */
75
+ endBatchCommand () {
76
+ if (!this.undoManager_) { return this }
77
+ if (this.currentBatchCommand_) {
78
+ const batchCommand = this.currentBatchCommand_
79
+ this.batchCommandStack_.pop()
80
+ const { length: len } = this.batchCommandStack_
81
+ this.currentBatchCommand_ = len ? this.batchCommandStack_[len - 1] : null
82
+ this.addCommand_(batchCommand)
83
+ }
84
+ return this
85
+ }
86
+
87
+ /**
88
+ * Add a `MoveElementCommand` to the history or current batch command.
89
+ * @param {Element} elem - The DOM element that was moved
90
+ * @param {Element} oldNextSibling - The element's next sibling before it was moved
91
+ * @param {Element} oldParent - The element's parent before it was moved
92
+ * @param {string} [text] - An optional string visible to user related to this change
93
+ * @returns {module:history.HistoryRecordingService}
94
+ */
95
+ moveElement (elem, oldNextSibling, oldParent, text) {
96
+ if (!this.undoManager_) { return this }
97
+ this.addCommand_(new MoveElementCommand(elem, oldNextSibling, oldParent, text))
98
+ return this
99
+ }
100
+
101
+ /**
102
+ * Add an `InsertElementCommand` to the history or current batch command.
103
+ * @param {Element} elem - The DOM element that was added
104
+ * @param {string} [text] - An optional string visible to user related to this change
105
+ * @returns {module:history.HistoryRecordingService}
106
+ */
107
+ insertElement (elem, text) {
108
+ if (!this.undoManager_) { return this }
109
+ this.addCommand_(new InsertElementCommand(elem, text))
110
+ return this
111
+ }
112
+
113
+ /**
114
+ * Add a `RemoveElementCommand` to the history or current batch command.
115
+ * @param {Element} elem - The DOM element that was removed
116
+ * @param {Element} oldNextSibling - The element's next sibling before it was removed
117
+ * @param {Element} oldParent - The element's parent before it was removed
118
+ * @param {string} [text] - An optional string visible to user related to this change
119
+ * @returns {module:history.HistoryRecordingService}
120
+ */
121
+ removeElement (elem, oldNextSibling, oldParent, text) {
122
+ if (!this.undoManager_) { return this }
123
+ this.addCommand_(new RemoveElementCommand(elem, oldNextSibling, oldParent, text))
124
+ return this
125
+ }
126
+
127
+ /**
128
+ * Add a `ChangeElementCommand` to the history or current batch command.
129
+ * @param {Element} elem - The DOM element that was changed
130
+ * @param {module:history.CommandAttributes} attrs - An object with the attributes to be changed and the values they had *before* the change
131
+ * @param {string} [text] - An optional string visible to user related to this change
132
+ * @returns {module:history.HistoryRecordingService}
133
+ */
134
+ changeElement (elem, attrs, text) {
135
+ if (!this.undoManager_) { return this }
136
+ this.addCommand_(new ChangeElementCommand(elem, attrs, text))
137
+ return this
138
+ }
139
+
140
+ /**
141
+ * Private function to add a command to the history or current batch command.
142
+ * @private
143
+ * @param {Command} cmd
144
+ * @returns {module:history.HistoryRecordingService|void}
145
+ */
146
+ addCommand_ (cmd) {
147
+ if (!this.undoManager_) { return this }
148
+ if (this.currentBatchCommand_) {
149
+ this.currentBatchCommand_.addSubCommand(cmd)
150
+ } else {
151
+ this.undoManager_.addCommandToHistory(cmd)
152
+ }
153
+ return undefined
154
+ }
155
+ }
156
+ /**
157
+ * @memberof module:history.HistoryRecordingService
158
+ * @property {module:history.HistoryRecordingService} NO_HISTORY - Singleton that can be passed to functions that record history, but the caller requires that no history be recorded.
159
+ */
160
+ HistoryRecordingService.NO_HISTORY = new HistoryRecordingService()
161
+ export default HistoryRecordingService
package/json.js ADDED
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Tools for SVG handle on JSON format.
3
+ * @module svgcanvas
4
+ * @license MIT
5
+ *
6
+ * @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
7
+ */
8
+ import { getElement, assignAttributes, cleanupElement } from './utilities.js'
9
+ import { NS } from './namespaces.js'
10
+
11
+ let svgCanvas = null
12
+ let svgdoc_ = null
13
+
14
+ /**
15
+ * @function module:json.jsonContext#getSelectedElements
16
+ * @returns {Element[]} the array with selected DOM elements
17
+ */
18
+ /**
19
+ * @function module:json.jsonContext#getDOMDocument
20
+ * @returns {HTMLDocument}
21
+ */
22
+
23
+ /**
24
+ * @function module:json.init
25
+ * @param {module:json.jsonContext} jsonContext
26
+ * @returns {void}
27
+ */
28
+ export const init = (canvas) => {
29
+ svgCanvas = canvas
30
+ svgdoc_ = canvas.getDOMDocument()
31
+ }
32
+ /**
33
+ * @function module:json.getJsonFromSvgElements Iterate element and return json format
34
+ * @param {ArgumentsArray} data - element
35
+ * @returns {svgRootElement}
36
+ */
37
+ export const getJsonFromSvgElements = (data) => {
38
+ // Text node
39
+ if (data.nodeType === 3) return data.nodeValue
40
+
41
+ const retval = {
42
+ element: data.tagName,
43
+ // namespace: nsMap[data.namespaceURI],
44
+ attr: {},
45
+ children: []
46
+ }
47
+
48
+ // Iterate attributes
49
+ for (let i = 0, attr; (attr = data.attributes[i]); i++) {
50
+ retval.attr[attr.name] = attr.value
51
+ }
52
+
53
+ // Iterate children
54
+ for (let i = 0, node; (node = data.childNodes[i]); i++) {
55
+ retval.children[i] = getJsonFromSvgElements(node)
56
+ }
57
+
58
+ return retval
59
+ }
60
+
61
+ /**
62
+ * This should really be an intersection implementing all rather than a union.
63
+ * @name module:json.addSVGElementsFromJson
64
+ * @type {module:utilities.EditorContext#addSVGElementsFromJson|module:path.EditorContext#addSVGElementsFromJson}
65
+ */
66
+
67
+ export const addSVGElementsFromJson = (data) => {
68
+ if (typeof data === 'string') return svgdoc_.createTextNode(data)
69
+
70
+ let shape = getElement(data.attr.id)
71
+ // if shape is a path but we need to create a rect/ellipse, then remove the path
72
+ const currentLayer = svgCanvas.getDrawing().getCurrentLayer()
73
+ if (shape && data.element !== shape.tagName) {
74
+ shape.remove()
75
+ shape = null
76
+ }
77
+ if (!shape) {
78
+ const ns = data.namespace || NS.SVG
79
+ shape = svgdoc_.createElementNS(ns, data.element)
80
+ if (currentLayer) {
81
+ (svgCanvas.getCurrentGroup() || currentLayer).append(shape)
82
+ }
83
+ }
84
+ const curShape = svgCanvas.getCurShape()
85
+ if (data.curStyles) {
86
+ assignAttributes(shape, {
87
+ fill: curShape.fill,
88
+ stroke: curShape.stroke,
89
+ 'stroke-width': curShape.strokeWidth,
90
+ 'stroke-dasharray': curShape.stroke_dasharray,
91
+ 'stroke-linejoin': curShape.stroke_linejoin,
92
+ 'stroke-linecap': curShape.stroke_linecap,
93
+ 'stroke-opacity': curShape.stroke_opacity,
94
+ 'fill-opacity': curShape.fill_opacity,
95
+ opacity: curShape.opacity / 2,
96
+ style: 'pointer-events:inherit'
97
+ }, 100)
98
+ }
99
+ assignAttributes(shape, data.attr, 100)
100
+ cleanupElement(shape)
101
+
102
+ // Children
103
+ if (data.children) {
104
+ data.children.forEach((child) => {
105
+ shape.append(addSVGElementsFromJson(child))
106
+ })
107
+ }
108
+
109
+ return shape
110
+ }
package/layer.js ADDED
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Provides tools for the layer concept.
3
+ * @module layer
4
+ * @license MIT
5
+ *
6
+ * @copyright 2011 Jeff Schiller, 2016 Flint O'Brien
7
+ */
8
+
9
+ import { NS } from './namespaces.js'
10
+ import { toXml, walkTree } from './utilities.js'
11
+
12
+ /**
13
+ * This class encapsulates the concept of a layer in the drawing. It can be constructed with
14
+ * an existing group element or, with three parameters, will create a new layer group element.
15
+ *
16
+ * @example
17
+ * const l1 = new Layer('name', group); // Use the existing group for this layer.
18
+ * const l2 = new Layer('name', group, svgElem); // Create a new group and add it to the DOM after group.
19
+ * const l3 = new Layer('name', null, svgElem); // Create a new group and add it to the DOM as the last layer.
20
+ * @memberof module:layer
21
+ */
22
+ class Layer {
23
+ /**
24
+ * @param {string} name - Layer name
25
+ * @param {SVGGElement|null} group - An existing SVG group element or null.
26
+ * If group and no svgElem, use group for this layer.
27
+ * If group and svgElem, create a new group element and insert it in the DOM after group.
28
+ * If no group and svgElem, create a new group element and insert it in the DOM as the last layer.
29
+ * @param {SVGGElement} [svgElem] - The SVG DOM element. If defined, use this to add
30
+ * a new layer to the document.
31
+ */
32
+ constructor (name, group, svgElem) {
33
+ this.name_ = name
34
+ this.group_ = svgElem ? null : group
35
+
36
+ if (svgElem) {
37
+ // Create a group element with title and add it to the DOM.
38
+ const svgdoc = svgElem.ownerDocument
39
+ this.group_ = svgdoc.createElementNS(NS.SVG, 'g')
40
+ const layerTitle = svgdoc.createElementNS(NS.SVG, 'title')
41
+ layerTitle.textContent = name
42
+ this.group_.append(layerTitle)
43
+ if (group) {
44
+ group.insertAdjacentElement('afterend', this.group_)
45
+ } else {
46
+ svgElem.append(this.group_)
47
+ }
48
+ }
49
+
50
+ addLayerClass(this.group_)
51
+ walkTree(this.group_, function (e) {
52
+ e.setAttribute('style', 'pointer-events:inherit')
53
+ })
54
+
55
+ this.group_.setAttribute('style', svgElem ? 'pointer-events:all' : 'pointer-events:none')
56
+ }
57
+
58
+ /**
59
+ * Get the layer's name.
60
+ * @returns {string} The layer name
61
+ */
62
+ getName () {
63
+ return this.name_
64
+ }
65
+
66
+ /**
67
+ * Get the group element for this layer.
68
+ * @returns {SVGGElement} The layer SVG group
69
+ */
70
+ getGroup () {
71
+ return this.group_
72
+ }
73
+
74
+ /**
75
+ * Active this layer so it takes pointer events.
76
+ * @returns {void}
77
+ */
78
+ activate () {
79
+ this.group_.setAttribute('style', 'pointer-events:all')
80
+ }
81
+
82
+ /**
83
+ * Deactive this layer so it does NOT take pointer events.
84
+ * @returns {void}
85
+ */
86
+ deactivate () {
87
+ this.group_.setAttribute('style', 'pointer-events:none')
88
+ }
89
+
90
+ /**
91
+ * Set this layer visible or hidden based on 'visible' parameter.
92
+ * @param {boolean} visible - If true, make visible; otherwise, hide it.
93
+ * @returns {void}
94
+ */
95
+ setVisible (visible) {
96
+ const expected = visible === undefined || visible ? 'inline' : 'none'
97
+ const oldDisplay = this.group_.getAttribute('display')
98
+ if (oldDisplay !== expected) {
99
+ this.group_.setAttribute('display', expected)
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Is this layer visible?
105
+ * @returns {boolean} True if visible.
106
+ */
107
+ isVisible () {
108
+ return this.group_.getAttribute('display') !== 'none'
109
+ }
110
+
111
+ /**
112
+ * Get layer opacity.
113
+ * @returns {Float} Opacity value.
114
+ */
115
+ getOpacity () {
116
+ const opacity = this.group_.getAttribute('opacity')
117
+ if (!opacity) {
118
+ return 1
119
+ }
120
+ return Number.parseFloat(opacity)
121
+ }
122
+
123
+ /**
124
+ * Sets the opacity of this layer. If opacity is not a value between 0.0 and 1.0,
125
+ * nothing happens.
126
+ * @param {Float} opacity - A float value in the range 0.0-1.0
127
+ * @returns {void}
128
+ */
129
+ setOpacity (opacity) {
130
+ if (typeof opacity === 'number' && opacity >= 0.0 && opacity <= 1.0) {
131
+ this.group_.setAttribute('opacity', opacity)
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Append children to this layer.
137
+ * @param {SVGGElement} children - The children to append to this layer.
138
+ * @returns {void}
139
+ */
140
+ appendChildren (children) {
141
+ for (const child of children) {
142
+ this.group_.append(child)
143
+ }
144
+ }
145
+
146
+ /**
147
+ * @returns {SVGTitleElement|null}
148
+ */
149
+ getTitleElement () {
150
+ const len = this.group_.childNodes.length
151
+ for (let i = 0; i < len; ++i) {
152
+ const child = this.group_.childNodes.item(i)
153
+ if (child?.tagName === 'title') {
154
+ return child
155
+ }
156
+ }
157
+ return null
158
+ }
159
+
160
+ /**
161
+ * Set the name of this layer.
162
+ * @param {string} name - The new name.
163
+ * @param {module:history.HistoryRecordingService} hrService - History recording service
164
+ * @returns {string|null} The new name if changed; otherwise, null.
165
+ */
166
+ setName (name, hrService) {
167
+ const previousName = this.name_
168
+ name = toXml(name)
169
+ // now change the underlying title element contents
170
+ const title = this.getTitleElement()
171
+ if (title) {
172
+ while (title.firstChild) { title.removeChild(title.firstChild) }
173
+ title.textContent = name
174
+ this.name_ = name
175
+ if (hrService) {
176
+ hrService.changeElement(title, { '#text': previousName })
177
+ }
178
+ return this.name_
179
+ }
180
+ return null
181
+ }
182
+
183
+ /**
184
+ * Remove this layer's group from the DOM. No more functions on group can be called after this.
185
+ * @returns {SVGGElement} The layer SVG group that was just removed.
186
+ */
187
+ removeGroup () {
188
+ const group = this.group_
189
+ this.group_.remove()
190
+ this.group_ = undefined
191
+ return group
192
+ }
193
+
194
+ /**
195
+ * Test whether an element is a layer or not.
196
+ * @param {SVGGElement} elem - The SVGGElement to test.
197
+ * @returns {boolean} True if the element is a layer
198
+ */
199
+ static isLayer (elem) {
200
+ return elem && elem.tagName === 'g' && Layer.CLASS_REGEX.test(elem.getAttribute('class'))
201
+ }
202
+ }
203
+ /**
204
+ * @property {string} CLASS_NAME - class attribute assigned to all layer groups.
205
+ */
206
+ Layer.CLASS_NAME = 'layer'
207
+
208
+ /**
209
+ * @property {RegExp} CLASS_REGEX - Used to test presence of class Layer.CLASS_NAME
210
+ */
211
+ Layer.CLASS_REGEX = new RegExp('(\\s|^)' + Layer.CLASS_NAME + '(\\s|$)')
212
+
213
+ /**
214
+ * Add class `Layer.CLASS_NAME` to the element (usually `class='layer'`).
215
+ *
216
+ * @param {SVGGElement} elem - The SVG element to update
217
+ * @returns {void}
218
+ */
219
+ function addLayerClass (elem) {
220
+ const classes = elem.getAttribute('class')
221
+ if (!classes || !classes.length) {
222
+ elem.setAttribute('class', Layer.CLASS_NAME)
223
+ } else if (!Layer.CLASS_REGEX.test(classes)) {
224
+ elem.setAttribute('class', classes + ' ' + Layer.CLASS_NAME)
225
+ }
226
+ }
227
+
228
+ export default Layer