@gregoriusrippenstein/node-red-contrib-introspection 0.0.3 → 0.0.5
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/README.md +10 -7
- package/examples/trigger-and-save-screenshot.json +155 -0
- package/nodes/15-screenshot.html +223 -188
- package/nodes/15-screenshot.js +8 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,25 +34,28 @@ Double click on the top-level node and all nodes in the pathway are highlighted.
|
|
|
34
34
|
|
|
35
35
|
Drag the node into a flow, double-click on the node and the tray opens -depending on the size of the flow, this might take a moment. Once the tray is opened, the SVG is shown in the editor window (I took the code from the template node hence the syntax dropdown). Below the editor window is the download button. Pressing that will download the SVG as it is in the editor window, so making changes in that window will be reflected in the downloaded content.
|
|
36
36
|
|
|
37
|
-
The SVG code can also be copied and pasted into something like [drawsvg.org](https://drawsvg.org/drawsvg.html) hence select-all-copy-paste is
|
|
37
|
+
The SVG code can also be copied and pasted into something like [drawsvg.org](https://drawsvg.org/drawsvg.html) hence select-all-copy-paste is provided by having the editor window.
|
|
38
38
|
|
|
39
39
|
Improvements over [svgexport.io](https://svgexport.io) browser plugin:
|
|
40
40
|
|
|
41
|
-
- Respects the 'hide' CSS class that is used to hide specific elements, this the svgexport plugin cannot know.
|
|
41
|
+
- Respects the 'hide' CSS class that is used to hide specific elements, this is something that the svgexport plugin cannot know.
|
|
42
42
|
- Compliments the setting of visibility on the 'g' element with opacity=0. The SVG Standard ignores visibility on 'g' elements but browsers don't seem to care, Inkscape does care.
|
|
43
43
|
- Image data (i.e. icons) is inlined using `data:image/XXXX;base64,` - this is good for things such as Inkscape that can't resolve relative urls. PNG, JPEG and SVG are supported.
|
|
44
44
|
- It also retrieves font details and includes them in the SVG. This makes the font italic, even if it's not the correct font family (because font might not be installed on system).
|
|
45
45
|
|
|
46
46
|
Disappointments:
|
|
47
47
|
|
|
48
|
-
- Font-awesome icons, because they use the font-awesome font, aren't inlined and therefore aren't available in external tools
|
|
49
|
-
-
|
|
50
|
-
- Limited testing: on Firefox (on mac), your mileage might vary
|
|
48
|
+
- Font-awesome icons, because they use the font-awesome font, aren't inlined and therefore aren't available in external tools. If the font-awesome fonts are installed on the system icons do work in Inkscape and browser.
|
|
49
|
+
- Limited testing: Firefox & Opera (on mac), your mileage might vary
|
|
51
50
|
- No error checking - network requests are assumed to work
|
|
52
51
|
|
|
52
|
+
Since version 0.0.5 the Screenshot node takes one input. That input will trigger the node to take a screenshot *without double clicking to open the tray*. The intention is to have an inject trigger regularly so that screenshots are made automagically and flows can have highlights. Screenshots are then posted to the endpoint `/screenshot` (this can be captured with a http-in node) and data is post as a json object `{ d: <svgdata> }`.
|
|
53
|
+
|
|
54
|
+
There is an example [flow](/examples/trigger-and-save-screenshot.json) that demonstrates this feature. The author is aware that this is dangerously close to enabling spyware for flow modification, please see the [LICENSE](/LICENSE) and behaviour.
|
|
55
|
+
|
|
53
56
|
## Examples
|
|
54
57
|
|
|
55
|
-
There
|
|
58
|
+
There are some example flow contained in the package. There are also examples to be found online:
|
|
56
59
|
|
|
57
60
|
- [Orphans](https://demo.openmindmap.org/omm/#flow/3ebb65fdbecb182e) - node is top left of flow or search for `type:Orphans`
|
|
58
61
|
- [Seeker](https://demo.openmindmap.org/omm/#flow/40ea5f2aea6592ae) - top left and the [Sink](https://demo.openmindmap.org/omm/#flow/459c271a96458c7c) - top right
|
|
@@ -64,7 +67,7 @@ Example screenshot:
|
|
|
64
67
|
|
|
65
68
|
## License
|
|
66
69
|
|
|
67
|
-
[Do whatever but don't do evil](/LICENSE)
|
|
70
|
+
[*Do whatever but don't do evil* license](/LICENSE)
|
|
68
71
|
|
|
69
72
|
## Contribution & Ideas
|
|
70
73
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "e815c52d1e6a0d25",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "Flow 3",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "",
|
|
8
|
+
"env": []
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "03f322255fd8cad1",
|
|
12
|
+
"type": "http in",
|
|
13
|
+
"z": "e815c52d1e6a0d25",
|
|
14
|
+
"name": "[POST] screenshot",
|
|
15
|
+
"url": "/screenshot",
|
|
16
|
+
"method": "post",
|
|
17
|
+
"upload": false,
|
|
18
|
+
"swaggerDoc": "",
|
|
19
|
+
"x": 388,
|
|
20
|
+
"y": 305,
|
|
21
|
+
"wires": [
|
|
22
|
+
[
|
|
23
|
+
"ddabce426445e225",
|
|
24
|
+
"a9f9ce494ec4edc1",
|
|
25
|
+
"c81d27b03cf13c69"
|
|
26
|
+
]
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "ddabce426445e225",
|
|
31
|
+
"type": "debug",
|
|
32
|
+
"z": "e815c52d1e6a0d25",
|
|
33
|
+
"name": "debug 1",
|
|
34
|
+
"active": false,
|
|
35
|
+
"tosidebar": true,
|
|
36
|
+
"console": false,
|
|
37
|
+
"tostatus": false,
|
|
38
|
+
"complete": "true",
|
|
39
|
+
"targetType": "full",
|
|
40
|
+
"statusVal": "",
|
|
41
|
+
"statusType": "auto",
|
|
42
|
+
"x": 1175,
|
|
43
|
+
"y": 307,
|
|
44
|
+
"wires": []
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "e62ae897ba4c777f",
|
|
48
|
+
"type": "http response",
|
|
49
|
+
"z": "e815c52d1e6a0d25",
|
|
50
|
+
"name": "",
|
|
51
|
+
"statusCode": "",
|
|
52
|
+
"headers": {
|
|
53
|
+
"content-type": "application/json"
|
|
54
|
+
},
|
|
55
|
+
"x": 817,
|
|
56
|
+
"y": 624,
|
|
57
|
+
"wires": []
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "a9f9ce494ec4edc1",
|
|
61
|
+
"type": "function",
|
|
62
|
+
"z": "e815c52d1e6a0d25",
|
|
63
|
+
"name": "respond with ok",
|
|
64
|
+
"func": "msg.payload = {\n status: \"ok\",\n data: {\n }\n};\nmsg.statusCode = 200;\nreturn msg;\n",
|
|
65
|
+
"outputs": 1,
|
|
66
|
+
"noerr": 0,
|
|
67
|
+
"initialize": "",
|
|
68
|
+
"finalize": "",
|
|
69
|
+
"libs": [],
|
|
70
|
+
"x": 622,
|
|
71
|
+
"y": 517,
|
|
72
|
+
"wires": [
|
|
73
|
+
[
|
|
74
|
+
"e62ae897ba4c777f"
|
|
75
|
+
]
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "c81d27b03cf13c69",
|
|
80
|
+
"type": "function",
|
|
81
|
+
"z": "e815c52d1e6a0d25",
|
|
82
|
+
"name": "set filename & payload",
|
|
83
|
+
"func": "msg.filename = \"/data/content/screenshot-\" + Date.now() + \".svg\";\n\nmsg.payload = msg.payload[\"d\"];\n\nreturn msg;",
|
|
84
|
+
"outputs": 1,
|
|
85
|
+
"noerr": 0,
|
|
86
|
+
"initialize": "",
|
|
87
|
+
"finalize": "",
|
|
88
|
+
"libs": [],
|
|
89
|
+
"x": 744,
|
|
90
|
+
"y": 410,
|
|
91
|
+
"wires": [
|
|
92
|
+
[
|
|
93
|
+
"00c967aaf678c503"
|
|
94
|
+
]
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "00c967aaf678c503",
|
|
99
|
+
"type": "file",
|
|
100
|
+
"z": "e815c52d1e6a0d25",
|
|
101
|
+
"name": "",
|
|
102
|
+
"filename": "filename",
|
|
103
|
+
"filenameType": "msg",
|
|
104
|
+
"appendNewline": false,
|
|
105
|
+
"createDir": true,
|
|
106
|
+
"overwriteFile": "true",
|
|
107
|
+
"encoding": "none",
|
|
108
|
+
"x": 1012,
|
|
109
|
+
"y": 505,
|
|
110
|
+
"wires": [
|
|
111
|
+
[
|
|
112
|
+
"ddabce426445e225"
|
|
113
|
+
]
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "32cde1648d31e9b4",
|
|
118
|
+
"type": "Screenshot",
|
|
119
|
+
"z": "e815c52d1e6a0d25",
|
|
120
|
+
"name": "",
|
|
121
|
+
"screenshot": "",
|
|
122
|
+
"x": 461,
|
|
123
|
+
"y": 108,
|
|
124
|
+
"wires": []
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": "3957fb7da9d89393",
|
|
128
|
+
"type": "inject",
|
|
129
|
+
"z": "e815c52d1e6a0d25",
|
|
130
|
+
"name": "",
|
|
131
|
+
"props": [
|
|
132
|
+
{
|
|
133
|
+
"p": "payload"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"p": "topic",
|
|
137
|
+
"vt": "str"
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
"repeat": "600",
|
|
141
|
+
"crontab": "",
|
|
142
|
+
"once": true,
|
|
143
|
+
"onceDelay": "2",
|
|
144
|
+
"topic": "",
|
|
145
|
+
"payload": "",
|
|
146
|
+
"payloadType": "date",
|
|
147
|
+
"x": 213,
|
|
148
|
+
"y": 108,
|
|
149
|
+
"wires": [
|
|
150
|
+
[
|
|
151
|
+
"32cde1648d31e9b4"
|
|
152
|
+
]
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
]
|
package/nodes/15-screenshot.html
CHANGED
|
@@ -1,4 +1,196 @@
|
|
|
1
1
|
<script type="text/javascript">
|
|
2
|
+
function nr_intro_generate_svg( callbackWithSvgCode ) {
|
|
3
|
+
//
|
|
4
|
+
// begin the SVG conversion code.
|
|
5
|
+
//
|
|
6
|
+
var origSvg = $($('svg')[0]);
|
|
7
|
+
|
|
8
|
+
var hwAttrs = (
|
|
9
|
+
'width="' + origSvg.attr('width') + '" height="' +
|
|
10
|
+
origSvg.attr('height') + '"'
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
var svgHeader = (
|
|
14
|
+
'<?xml version="1.0" standalone="no"?>\r\n' +
|
|
15
|
+
'<svg ' + hwAttrs + ' pointer-events="all" style="cursor: crosshair; '+
|
|
16
|
+
'touch-action: none;" xmlns="http://www.w3.org/2000/svg" '+
|
|
17
|
+
'xmlns:xlink="http://www.w3.org/1999/xlink">\r\n'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
var svgBody = origSvg.html();
|
|
21
|
+
|
|
22
|
+
var parser = new DOMParser();
|
|
23
|
+
var doc = parser.parseFromString(svgHeader + svgBody + '\r\n</svg>',
|
|
24
|
+
"image/svg+xml");
|
|
25
|
+
|
|
26
|
+
// SVG has very specific requirements for colour specification, baseline
|
|
27
|
+
// all colors to #rrggbb. This converts: 'rgb(rrr, ggg, bbb)' and '#rgb'.
|
|
28
|
+
var rgb2hex = (rgb) => {
|
|
29
|
+
if ( !rgb ) { return "none" }
|
|
30
|
+
if ( rgb === null ) { return "none" }
|
|
31
|
+
|
|
32
|
+
rgb3 = rgb.match(/^#(.)(.)(.)$/);
|
|
33
|
+
if ( rgb3 ) {
|
|
34
|
+
return ("#" +rgb3[1]+rgb3[1]+rgb3[2]+rgb3[2]+rgb3[3]+rgb3[3]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
rgb3 = rgb.match(/^#......$/);
|
|
38
|
+
if ( rgb3 ) { return rgb; }
|
|
39
|
+
|
|
40
|
+
rgb3 = rgb.match(/^rgb\(([0-9]+),\s+([0-9]+),\s+([0-9]+)/);
|
|
41
|
+
if ( rgb3 === null ) { return rgb; }
|
|
42
|
+
|
|
43
|
+
return "#" +
|
|
44
|
+
("0" + parseInt(rgb3[1],10).toString(16)).slice(-2) +
|
|
45
|
+
("0" + parseInt(rgb3[2],10).toString(16)).slice(-2) +
|
|
46
|
+
("0" + parseInt(rgb3[3],10).toString(16)).slice(-2);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Set everything that might have been set via CSS directly on the
|
|
50
|
+
// element. All styling values must be defined as attributes on the
|
|
51
|
+
// SVG element.
|
|
52
|
+
var convertCssToAttr = function( collection, origElems ) {
|
|
53
|
+
for ( var idx = 0; idx < collection.length; idx++ ) {
|
|
54
|
+
var elem = collection.item(idx);
|
|
55
|
+
var origElem = origElems[idx];
|
|
56
|
+
|
|
57
|
+
[
|
|
58
|
+
"fill", "stroke"
|
|
59
|
+
].forEach( function(attrname) {
|
|
60
|
+
elem.setAttribute(attrname,
|
|
61
|
+
rgb2hex($(origElem).attr(attrname) ||
|
|
62
|
+
$(origElem).css(attrname) ));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
[
|
|
66
|
+
"stroke-width","fill-opacity","stroke-opacity","opacity"
|
|
67
|
+
].forEach(function(attrname) {
|
|
68
|
+
elem.setAttribute(attrname,
|
|
69
|
+
$(origElem).attr(attrname) ||
|
|
70
|
+
$(origElem).css(attrname));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if ( $(origElem).hasClass('hide') ) {
|
|
74
|
+
if ( elem.tagName == "g" ) {
|
|
75
|
+
// according to the SVG standard, visibility cannot be applied
|
|
76
|
+
// (or better said: has no effect) on 'g' (group) elements.
|
|
77
|
+
// Inkscape is rather pedantic about this and will show the
|
|
78
|
+
// group, so hack-it by using opacity. Browsers (firefox) doesn't
|
|
79
|
+
// seem to care and will apply visibility to 'g' elements.
|
|
80
|
+
elem.setAttribute("opacity", "0");
|
|
81
|
+
}
|
|
82
|
+
elem.setAttribute("visibility", "hidden");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// include font details on the text elements.
|
|
88
|
+
var convertFontsToAttr = function( collection, origElems ) {
|
|
89
|
+
for ( var idx = 0; idx < collection.length; idx++ ) {
|
|
90
|
+
var elem = collection.item(idx);
|
|
91
|
+
var origElem = origElems[idx];
|
|
92
|
+
|
|
93
|
+
["font-family", "font-size", "font-size-adjust", "font-stretch",
|
|
94
|
+
"font-style", "font-variant", "font-weight"
|
|
95
|
+
].forEach( function(attrname) {
|
|
96
|
+
elem.setAttribute(attrname,
|
|
97
|
+
$(origElem).attr(attrname) ||
|
|
98
|
+
$(origElem).css(attrname) );
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// probably missed some elements ...
|
|
104
|
+
var tagnames = [ "g", "rect", "line", "path", "circle", "image" ];
|
|
105
|
+
tagnames.forEach( function(tagname) {
|
|
106
|
+
convertCssToAttr(doc.getElementsByTagName(tagname),
|
|
107
|
+
origSvg.find(tagname));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
[ "text" ].forEach( function(tagname) {
|
|
111
|
+
convertCssToAttr(doc.getElementsByTagName(tagname),
|
|
112
|
+
origSvg.find(tagname));
|
|
113
|
+
|
|
114
|
+
convertFontsToAttr(doc.getElementsByTagName(tagname),
|
|
115
|
+
origSvg.find(tagname));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// inline image data. Image types supported: Jpeg, Png and Svg.
|
|
119
|
+
var imageColl = doc.getElementsByTagName("image");
|
|
120
|
+
|
|
121
|
+
var getDataAndCallbackWhenDone = (elem, cntr, cb) => {
|
|
122
|
+
var postfix = elem.getAttribute("xlink:href").substr(-4,4).toLowerCase();
|
|
123
|
+
|
|
124
|
+
switch(postfix){
|
|
125
|
+
case ".jpg":
|
|
126
|
+
case "jpeg":
|
|
127
|
+
case ".png":
|
|
128
|
+
var fileType = {
|
|
129
|
+
".jpg": "jpeg",
|
|
130
|
+
"jpeg": "jpeg",
|
|
131
|
+
".png": "png"
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
var oReq = new XMLHttpRequest();
|
|
135
|
+
oReq.open("GET", elem.getAttribute( "xlink:href" ), true);
|
|
136
|
+
oReq.responseType = "arraybuffer";
|
|
137
|
+
|
|
138
|
+
var arrayBufferToBase64 = ( buffer ) => {
|
|
139
|
+
var binary = '';
|
|
140
|
+
var bytes = new Uint8Array( buffer );
|
|
141
|
+
var len = bytes.byteLength;
|
|
142
|
+
for (var i = 0; i < len; i++) {
|
|
143
|
+
binary += String.fromCharCode( bytes[ i ] );
|
|
144
|
+
}
|
|
145
|
+
return window.btoa( binary );
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
oReq.onload = function (oEvent) {
|
|
149
|
+
var arrayBuffer = oReq.response; // Note: not oReq.responseText
|
|
150
|
+
if (arrayBuffer) {
|
|
151
|
+
elem.setAttribute("xlink:href",
|
|
152
|
+
"data:image/"+fileType[postfix]+";base64," +
|
|
153
|
+
arrayBufferToBase64(arrayBuffer));
|
|
154
|
+
cb(cntr-1)
|
|
155
|
+
} else {
|
|
156
|
+
cb(cntr-1)
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
oReq.send(null);
|
|
161
|
+
break;
|
|
162
|
+
|
|
163
|
+
case ".svg":
|
|
164
|
+
$.get( elem.getAttribute( "xlink:href" ), function(data) {
|
|
165
|
+
const serializer = new XMLSerializer();
|
|
166
|
+
elem.setAttribute( "xlink:href",
|
|
167
|
+
"data:image/svg+xml;base64," +
|
|
168
|
+
btoa(serializer.serializeToString(data)));
|
|
169
|
+
cb(cntr-1)
|
|
170
|
+
});
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
var cb = (cntr) => {
|
|
176
|
+
if ( cntr < 0 ) {
|
|
177
|
+
const serializer = new XMLSerializer();
|
|
178
|
+
callbackWithSvgCode( serializer.serializeToString(doc) );
|
|
179
|
+
} else {
|
|
180
|
+
getDataAndCallbackWhenDone( imageColl.item(cntr), cntr, cb );
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
if ( imageColl.length > 0 ) {
|
|
185
|
+
getDataAndCallbackWhenDone( imageColl.item(imageColl.length-1),
|
|
186
|
+
imageColl.length-1,
|
|
187
|
+
cb );
|
|
188
|
+
} else {
|
|
189
|
+
const serializer = new XMLSerializer();
|
|
190
|
+
callbackWithSvgCode( serializer.serializeToString(doc) );
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
2
194
|
RED.nodes.registerType('Screenshot',{
|
|
3
195
|
color: '#e5e4ef',
|
|
4
196
|
icon: "font-awesome/fa-camera",
|
|
@@ -12,7 +204,7 @@
|
|
|
12
204
|
value:""
|
|
13
205
|
},
|
|
14
206
|
},
|
|
15
|
-
inputs:
|
|
207
|
+
inputs:1,
|
|
16
208
|
outputs:0,
|
|
17
209
|
|
|
18
210
|
label: function() {
|
|
@@ -20,6 +212,34 @@
|
|
|
20
212
|
},
|
|
21
213
|
|
|
22
214
|
labelStyle: function() {
|
|
215
|
+
/*
|
|
216
|
+
because there is not a better hook to connect up the event
|
|
217
|
+
listener, misuse the label styling for this. Also don't register a
|
|
218
|
+
new listener each time, maintain only one listener by removing and
|
|
219
|
+
readding.
|
|
220
|
+
*/
|
|
221
|
+
var that = this;
|
|
222
|
+
var functName = "introspect:" + that.id;
|
|
223
|
+
|
|
224
|
+
if ( !window[functName] ) {
|
|
225
|
+
window[functName] = (e,m) => {
|
|
226
|
+
if ( m.msg == "timer-tripped" ) {
|
|
227
|
+
nr_intro_generate_svg( (svgdata) => {
|
|
228
|
+
$.ajax({
|
|
229
|
+
type: "POST",
|
|
230
|
+
url: "/screenshot",
|
|
231
|
+
data: { d: svgdata },
|
|
232
|
+
success: (data) => {},
|
|
233
|
+
dataType: "image/svg+xml;charset=utf-8"
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
RED.comms.unsubscribe(functName, window[functName]);
|
|
241
|
+
RED.comms.subscribe(functName, window[functName] );
|
|
242
|
+
|
|
23
243
|
return this.name?"node_label_italic":"";
|
|
24
244
|
},
|
|
25
245
|
|
|
@@ -54,193 +274,10 @@
|
|
|
54
274
|
|
|
55
275
|
this.editor.setValue( "Please wait, screenshot being prepared ..." );
|
|
56
276
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
var origSvg = $($('svg')[0]);
|
|
61
|
-
|
|
62
|
-
var hwAttrs = (
|
|
63
|
-
'width="' + origSvg.attr('width') + '" height="' +
|
|
64
|
-
origSvg.attr('height') + '"'
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
var svgHeader = (
|
|
68
|
-
'<?xml version="1.0" standalone="no"?>\r\n' +
|
|
69
|
-
'<svg ' + hwAttrs + ' pointer-events="all" style="cursor: crosshair; '+
|
|
70
|
-
'touch-action: none;" xmlns="http://www.w3.org/2000/svg" '+
|
|
71
|
-
'xmlns:xlink="http://www.w3.org/1999/xlink">\r\n'
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
var svgBody = origSvg.html();
|
|
75
|
-
|
|
76
|
-
var parser = new DOMParser();
|
|
77
|
-
var doc = parser.parseFromString(svgHeader + svgBody + '\r\n</svg>',
|
|
78
|
-
"image/svg+xml");
|
|
79
|
-
|
|
80
|
-
// SVG has very specific requirements for colour specification, baseline
|
|
81
|
-
// all colors to #rrggbb. This converts: 'rgb(rrr, ggg, bbb)' and '#rgb'.
|
|
82
|
-
var rgb2hex = (rgb) => {
|
|
83
|
-
if ( !rgb ) { return "none" }
|
|
84
|
-
if ( rgb === null ) { return "none" }
|
|
85
|
-
|
|
86
|
-
rgb3 = rgb.match(/^#(.)(.)(.)$/);
|
|
87
|
-
if ( rgb3 ) {
|
|
88
|
-
return ("#" +rgb3[1]+rgb3[1]+rgb3[2]+rgb3[2]+rgb3[3]+rgb3[3]);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
rgb3 = rgb.match(/^#......$/);
|
|
92
|
-
if ( rgb3 ) { return rgb; }
|
|
93
|
-
|
|
94
|
-
rgb3 = rgb.match(/^rgb\(([0-9]+),\s+([0-9]+),\s+([0-9]+)/);
|
|
95
|
-
if ( rgb3 === null ) { return rgb; }
|
|
96
|
-
|
|
97
|
-
return "#" +
|
|
98
|
-
("0" + parseInt(rgb3[1],10).toString(16)).slice(-2) +
|
|
99
|
-
("0" + parseInt(rgb3[2],10).toString(16)).slice(-2) +
|
|
100
|
-
("0" + parseInt(rgb3[3],10).toString(16)).slice(-2);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Set everything that might have been set via CSS directly on the
|
|
104
|
-
// element. All styling values must be defined as attributes on the
|
|
105
|
-
// SVG element.
|
|
106
|
-
var convertCssToAttr = function( collection, origElems ) {
|
|
107
|
-
for ( var idx = 0; idx < collection.length; idx++ ) {
|
|
108
|
-
var elem = collection.item(idx);
|
|
109
|
-
var origElem = origElems[idx];
|
|
110
|
-
|
|
111
|
-
[
|
|
112
|
-
"fill", "stroke"
|
|
113
|
-
].forEach( function(attrname) {
|
|
114
|
-
elem.setAttribute(attrname,
|
|
115
|
-
rgb2hex($(origElem).attr(attrname) ||
|
|
116
|
-
$(origElem).css(attrname) ));
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
[
|
|
120
|
-
"stroke-width","fill-opacity","stroke-opacity","opacity"
|
|
121
|
-
].forEach(function(attrname) {
|
|
122
|
-
elem.setAttribute(attrname,
|
|
123
|
-
$(origElem).attr(attrname) ||
|
|
124
|
-
$(origElem).css(attrname));
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
if ( $(origElem).hasClass('hide') ) {
|
|
128
|
-
if ( elem.tagName == "g" ) {
|
|
129
|
-
// according to the SVG standard, visibility cannot be applied
|
|
130
|
-
// (or better said: has no effect) on 'g' (group) elements.
|
|
131
|
-
// Inkscape is rather pedantic about this and will show the
|
|
132
|
-
// group, so hack-it by using opacity. Browsers (firefox) doesn't
|
|
133
|
-
// seem to care and will apply visibility to 'g' elements.
|
|
134
|
-
elem.setAttribute("opacity", "0");
|
|
135
|
-
}
|
|
136
|
-
elem.setAttribute("visibility", "hidden");
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// include font details on the text elements.
|
|
142
|
-
var convertFontsToAttr = function( collection, origElems ) {
|
|
143
|
-
for ( var idx = 0; idx < collection.length; idx++ ) {
|
|
144
|
-
var elem = collection.item(idx);
|
|
145
|
-
var origElem = origElems[idx];
|
|
146
|
-
|
|
147
|
-
["font-family", "font-size", "font-size-adjust", "font-stretch",
|
|
148
|
-
"font-style", "font-variant", "font-weight"
|
|
149
|
-
].forEach( function(attrname) {
|
|
150
|
-
elem.setAttribute(attrname,
|
|
151
|
-
$(origElem).attr(attrname) ||
|
|
152
|
-
$(origElem).css(attrname) );
|
|
153
|
-
})
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// probably missed some elements ...
|
|
158
|
-
var tagnames = [ "g", "rect", "line", "path", "circle", "image" ];
|
|
159
|
-
tagnames.forEach( function(tagname) {
|
|
160
|
-
convertCssToAttr(doc.getElementsByTagName(tagname),
|
|
161
|
-
origSvg.find(tagname));
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
[ "text" ].forEach( function(tagname) {
|
|
165
|
-
convertCssToAttr(doc.getElementsByTagName(tagname),
|
|
166
|
-
origSvg.find(tagname));
|
|
167
|
-
|
|
168
|
-
convertFontsToAttr(doc.getElementsByTagName(tagname),
|
|
169
|
-
origSvg.find(tagname));
|
|
277
|
+
nr_intro_generate_svg( (svgdata) => {
|
|
278
|
+
that.editor.setValue( svgdata );
|
|
170
279
|
});
|
|
171
280
|
|
|
172
|
-
// inline image data. Image types supported: Jpeg, Png and Svg.
|
|
173
|
-
var imageColl = doc.getElementsByTagName("image");
|
|
174
|
-
|
|
175
|
-
var getDataAndCallbackWhenDone = (elem, cntr, cb) => {
|
|
176
|
-
var postfix = elem.getAttribute("xlink:href").substr(-4,4).toLowerCase();
|
|
177
|
-
|
|
178
|
-
switch(postfix){
|
|
179
|
-
case ".jpg":
|
|
180
|
-
case "jpeg":
|
|
181
|
-
case ".png":
|
|
182
|
-
var fileType = {
|
|
183
|
-
".jpg": "jpeg",
|
|
184
|
-
"jpeg": "jpeg",
|
|
185
|
-
".png": "png"
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
var oReq = new XMLHttpRequest();
|
|
189
|
-
oReq.open("GET", elem.getAttribute( "xlink:href" ), true);
|
|
190
|
-
oReq.responseType = "arraybuffer";
|
|
191
|
-
|
|
192
|
-
var arrayBufferToBase64 = ( buffer ) => {
|
|
193
|
-
var binary = '';
|
|
194
|
-
var bytes = new Uint8Array( buffer );
|
|
195
|
-
var len = bytes.byteLength;
|
|
196
|
-
for (var i = 0; i < len; i++) {
|
|
197
|
-
binary += String.fromCharCode( bytes[ i ] );
|
|
198
|
-
}
|
|
199
|
-
return window.btoa( binary );
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
oReq.onload = function (oEvent) {
|
|
203
|
-
var arrayBuffer = oReq.response; // Note: not oReq.responseText
|
|
204
|
-
if (arrayBuffer) {
|
|
205
|
-
elem.setAttribute("xlink:href",
|
|
206
|
-
"data:image/"+fileType[postfix]+";base64," +
|
|
207
|
-
arrayBufferToBase64(arrayBuffer));
|
|
208
|
-
cb(cntr-1)
|
|
209
|
-
} else {
|
|
210
|
-
cb(cntr-1)
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
oReq.send(null);
|
|
215
|
-
break;
|
|
216
|
-
|
|
217
|
-
case ".svg":
|
|
218
|
-
$.get( elem.getAttribute( "xlink:href" ), function(data) {
|
|
219
|
-
const serializer = new XMLSerializer();
|
|
220
|
-
elem.setAttribute( "xlink:href",
|
|
221
|
-
"data:image/svg+xml;base64," +
|
|
222
|
-
btoa(serializer.serializeToString(data)));
|
|
223
|
-
cb(cntr-1)
|
|
224
|
-
});
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
var cb = (cntr) => {
|
|
230
|
-
if ( cntr < 0 ) {
|
|
231
|
-
const serializer = new XMLSerializer();
|
|
232
|
-
that.editor.setValue( serializer.serializeToString(doc) );
|
|
233
|
-
} else {
|
|
234
|
-
getDataAndCallbackWhenDone( imageColl.item(cntr), cntr, cb );
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
if ( imageColl.length > 0 ) {
|
|
239
|
-
getDataAndCallbackWhenDone( imageColl.item(imageColl.length-1),
|
|
240
|
-
imageColl.length-1,
|
|
241
|
-
cb );
|
|
242
|
-
}
|
|
243
|
-
|
|
244
281
|
// handle the download button under the editor window.
|
|
245
282
|
$('#node-screenshot-download-svg').on("click", function (e) {
|
|
246
283
|
e.preventDefault();
|
|
@@ -254,8 +291,6 @@
|
|
|
254
291
|
document.body.removeChild(downloadLink);
|
|
255
292
|
});
|
|
256
293
|
|
|
257
|
-
// normality resumes here, all the SVG handling is done.
|
|
258
|
-
|
|
259
294
|
$("#node-input-format").on("change", function() {
|
|
260
295
|
var mod = "ace/mode/"+$("#node-input-format").val();
|
|
261
296
|
that.editor.getSession().setMode({
|
package/nodes/15-screenshot.js
CHANGED
|
@@ -9,7 +9,14 @@ module.exports = function(RED) {
|
|
|
9
9
|
node.status({});
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
-
node.on("input",function(msg, send, done) {
|
|
12
|
+
node.on("input", function(msg, send, done) {
|
|
13
|
+
RED.comms.publish("introspect:" + node.id, RED.util.encodeObject({
|
|
14
|
+
msg: "timer-tripped",
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
node.status({ fill: "green", shape: "dot", text:"Taking screenshot" });
|
|
18
|
+
setTimeout( () => { node.status({}) }, 1432 );
|
|
19
|
+
|
|
13
20
|
send(msg);
|
|
14
21
|
});
|
|
15
22
|
}
|