@dualbox/editor 1.0.1 → 1.0.3
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/js/src/GraphEditor.js +159 -0
- package/js/src/c/GraphController.js +646 -0
- package/js/src/libs/CodeMirror.js +8 -0
- package/js/src/libs/fontawesome.js +1 -0
- package/js/src/libs/jsoneditor.css +2 -0
- package/js/src/libs/jsoneditor.js +4 -0
- package/js/src/m/DualboxUtils.js +35 -0
- package/js/src/m/GraphModel.js +2167 -0
- package/js/src/m/History.js +94 -0
- package/js/src/m/Merger.js +357 -0
- package/js/src/v/AppManager.js +61 -0
- package/js/src/v/CanvasSizeHandler.js +136 -0
- package/js/src/v/ContextMenu.css +45 -0
- package/js/src/v/ContextMenu.js +239 -0
- package/js/src/v/GraphView.js +928 -0
- package/js/src/v/PlumbStyle.js +254 -0
- package/js/src/v/Selector.js +239 -0
- package/js/src/v/TemplateManager.js +79 -0
- package/js/src/v/Translater.js +174 -0
- package/js/src/v/Utils.js +7 -0
- package/js/src/v/Zoomer.js +201 -0
- package/js/src/v/templates/addNode.css +45 -0
- package/js/src/v/templates/addNode.html +62 -0
- package/js/src/v/templates/addNode.js +34 -0
- package/js/src/v/templates/debugNodeInfos.css +5 -0
- package/js/src/v/templates/debugNodeInfos.html +336 -0
- package/js/src/v/templates/debugNodeInfos.js +31 -0
- package/js/src/v/templates/editMainSettings.css +67 -0
- package/js/src/v/templates/editMainSettings.html +265 -0
- package/js/src/v/templates/editMainSettings.js +240 -0
- package/js/src/v/templates/editNodeSettings.css +86 -0
- package/js/src/v/templates/editNodeSettings.html +539 -0
- package/js/src/v/templates/editNodeSettings.js +356 -0
- package/js/src/v/templates/graphNode.css +333 -0
- package/js/src/v/templates/graphNode.html +227 -0
- package/js/src/v/templates/graphNode.js +412 -0
- package/js/src/v/templates/main.css +353 -0
- package/js/src/v/templates/main.html +149 -0
- package/js/src/v/templates/main.js +511 -0
- package/js/src/v/templates/searchResults.css +50 -0
- package/js/src/v/templates/searchResults.html +46 -0
- package/js/src/v/templates/searchResults.js +176 -0
- package/package.json +3 -2
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Object to manage ctrl-z/ctrl-y
|
|
3
|
+
* Manages the different states of the model and is able to restore them
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const _ = require('lodash');
|
|
7
|
+
|
|
8
|
+
class History {
|
|
9
|
+
constructor(model) {
|
|
10
|
+
this.m = model;
|
|
11
|
+
|
|
12
|
+
this.states = []; // all saved states
|
|
13
|
+
|
|
14
|
+
this._holdSaving = false; // if true, save() will have no effect
|
|
15
|
+
this.maxStates = 30; // to be changed accordingly
|
|
16
|
+
|
|
17
|
+
// info needed if we cycle through the array
|
|
18
|
+
this.reachedMaxStates = false;
|
|
19
|
+
this.oldestStateIndex = 0; // pointer to the oldest state
|
|
20
|
+
|
|
21
|
+
this.current = -1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// clean all states and set this one as a start
|
|
25
|
+
reset() {
|
|
26
|
+
this.states = [];
|
|
27
|
+
this.current = -1;
|
|
28
|
+
this.save();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pushState( state ) {
|
|
32
|
+
this.states.push(state);
|
|
33
|
+
if( this.states.length > this.maxStates ) {
|
|
34
|
+
this.states.shift();
|
|
35
|
+
this.current--;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
save() {
|
|
40
|
+
if( !this._holdSaving ) {
|
|
41
|
+
var state = _.cloneDeep(this.m.data); // deep clone
|
|
42
|
+
state.metadata = { savedAt : new Date() };
|
|
43
|
+
this.pushState(state);
|
|
44
|
+
this.current++;
|
|
45
|
+
console.log("state saved");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
undo() {
|
|
50
|
+
if( this.current > 0 ) {
|
|
51
|
+
this.current--;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if( this.current >= 0 ) {
|
|
55
|
+
console.log('undo');
|
|
56
|
+
this.setState( this.states[this.current] );
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
redo() {
|
|
61
|
+
if( this.current < this.states.length - 1 ) {
|
|
62
|
+
this.current++;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if( this.current >= 0 ) {
|
|
66
|
+
console.log('redo');
|
|
67
|
+
this.setState( this.states[this.current] );
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setState(state) {
|
|
72
|
+
this.m.data = state;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
holdSaving(b) {
|
|
76
|
+
this._holdSaving = b;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// execute the callback and save the history as 1 change
|
|
80
|
+
batch( cb ) {
|
|
81
|
+
this.holdSaving(true);
|
|
82
|
+
cb();
|
|
83
|
+
this.holdSaving(false);
|
|
84
|
+
this.save();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
ignore( cb ) {
|
|
88
|
+
this.holdSaving(true);
|
|
89
|
+
cb();
|
|
90
|
+
this.holdSaving(false);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = History;
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Object to perform a "good-enough" merge
|
|
3
|
+
* merges a set of boxes into a metanode with the minimal set of input/output
|
|
4
|
+
* needed to preserve the actual app behavior
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const _ = require('lodash');
|
|
8
|
+
const utils = require('./DualboxUtils');
|
|
9
|
+
|
|
10
|
+
class Merger {
|
|
11
|
+
constructor(model, ids) {
|
|
12
|
+
this.m = model;
|
|
13
|
+
this.ids = ids;
|
|
14
|
+
|
|
15
|
+
// the future metabox definition
|
|
16
|
+
this.def = {
|
|
17
|
+
desc: "",
|
|
18
|
+
metanodes: {},
|
|
19
|
+
input: {},
|
|
20
|
+
output: {},
|
|
21
|
+
modules: {},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.nodes = [];
|
|
25
|
+
_.each(ids, (id) => {
|
|
26
|
+
this.nodes.push( this.m.getNode(id) );
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this.id = null;
|
|
30
|
+
|
|
31
|
+
// links from and to outside
|
|
32
|
+
this.inboundLinksFromOutside = [];
|
|
33
|
+
this.outboundLinksToOutside = [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// merge all divs into a metabox
|
|
37
|
+
// ids: array of ids from the boxes to be merged
|
|
38
|
+
// metaboxName: name of the new metabox
|
|
39
|
+
// metaboxDesc: description of the new metabox
|
|
40
|
+
merge( metaboxName, metaboxDesc ) {
|
|
41
|
+
this.def.desc = metaboxDesc;
|
|
42
|
+
|
|
43
|
+
this.m.history.batch( () => {
|
|
44
|
+
// 0. create a new ID for our metanode
|
|
45
|
+
this.id = metaboxName + '-' + utils.randomString(8);
|
|
46
|
+
|
|
47
|
+
// 0. set a better position for the nodes
|
|
48
|
+
var bbox = this._computeBbox();
|
|
49
|
+
this._repositionNodes();
|
|
50
|
+
|
|
51
|
+
// 1. collect and detach the links from and to outside the metanode
|
|
52
|
+
this._collectAndDetachExternalLinks();
|
|
53
|
+
|
|
54
|
+
// 2. create the metabox inputs and outputs, and update the links definitions
|
|
55
|
+
this._createInputs();
|
|
56
|
+
this._createOutputs();
|
|
57
|
+
|
|
58
|
+
// 3. move the nodes to the new structure
|
|
59
|
+
this._moveNodes();
|
|
60
|
+
|
|
61
|
+
// 4. bind our nodes to the new inputs and outputs
|
|
62
|
+
this._bindInputs();
|
|
63
|
+
this._bindOutputs();
|
|
64
|
+
|
|
65
|
+
// 5. Our metanode definition is ready, add it to the app
|
|
66
|
+
this.m.data.app.metanodes = this.m.data.app.metanodes || {};
|
|
67
|
+
this.m.data.app.metanodes[ metaboxName ] = this.def;
|
|
68
|
+
|
|
69
|
+
// 6. Create a new node from this def
|
|
70
|
+
this.m.data.app.modules[this.id] = {
|
|
71
|
+
"module" : metaboxName,
|
|
72
|
+
"version": "*",
|
|
73
|
+
"graph" : {
|
|
74
|
+
"position" : {
|
|
75
|
+
top : (bbox.top + bbox.bottom) / 2,
|
|
76
|
+
left : (bbox.left + bbox.right) / 2
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// 7. link our node to the rest of the graph
|
|
82
|
+
this._bindMetanodeInputs();
|
|
83
|
+
this._bindMetanodeOutputs();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_debug() {
|
|
88
|
+
console.log( this.def );
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_isInMetanode( id ) {
|
|
92
|
+
var inGroup = false;
|
|
93
|
+
_.each( this.nodes, (node) => {
|
|
94
|
+
if( node.id === id ) {
|
|
95
|
+
inGroup = true;
|
|
96
|
+
return false; // eol
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
return inGroup;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// find the links from and to outside the metanode
|
|
103
|
+
_collectAndDetachExternalLinks() {
|
|
104
|
+
_.each( this.nodes, (node) => {
|
|
105
|
+
var links = node.getInboundLinks();
|
|
106
|
+
_.each( links, (link) => {
|
|
107
|
+
if( !this._isInMetanode(link.sourceId) ) {
|
|
108
|
+
link.detach(); // we don't need this link anymore
|
|
109
|
+
this.inboundLinksFromOutside.push( link );
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
var links = node.getOutboundLinks();
|
|
114
|
+
_.each( links, (link) => {
|
|
115
|
+
if( !this._isInMetanode(link.targetId) ) {
|
|
116
|
+
link.detach();
|
|
117
|
+
this.outboundLinksToOutside.push( link );
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// transform "cond" into "cond2", or "cond3" if cond2 is taken, etc.
|
|
124
|
+
_nextName( type, name ) {
|
|
125
|
+
// find the appropriate number end (that is not taken already)
|
|
126
|
+
var ending = 2;
|
|
127
|
+
if( type == "input" ) {
|
|
128
|
+
while( this.def.input[ name + "-" + ending ] ) ending++;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
while( this.def.output[ name + "-" + ending ] ) ending++;
|
|
132
|
+
}
|
|
133
|
+
return name+"-"+ending;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// find and create the metabox inputs
|
|
137
|
+
_createInputs() {
|
|
138
|
+
// dic of input -> link (that created this input)
|
|
139
|
+
var inputToLinks = {};
|
|
140
|
+
|
|
141
|
+
// function to find the inputName corresponding to this link's source
|
|
142
|
+
// (if it has been created already)
|
|
143
|
+
var getInputNameForThisLink = function(l) {
|
|
144
|
+
var r = null;
|
|
145
|
+
_.each(inputToLinks, (link, inputName) => {
|
|
146
|
+
if( link.sourceId == l.sourceId && link.sourceOutput == l.sourceOutput ) {
|
|
147
|
+
r = inputName;
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
return r;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// the inputs are determined from the inbound links of the metanode
|
|
155
|
+
_.each(this.inboundLinksFromOutside, (link, index) => {
|
|
156
|
+
var targetNode = link.getTargetNode();
|
|
157
|
+
|
|
158
|
+
// get the original input name, rename if necessary
|
|
159
|
+
var inputName = link.targetInput;
|
|
160
|
+
if( this.def.input[inputName] ) {
|
|
161
|
+
// the input already exists. Check if the link that led to the creation of this
|
|
162
|
+
// input have the same source that 'link'. Otherwise, we create another input.
|
|
163
|
+
var l = inputToLinks[inputName];
|
|
164
|
+
|
|
165
|
+
// check if an input for this source has already been created
|
|
166
|
+
// otherwise, create a new one
|
|
167
|
+
var lookupInputName = getInputNameForThisLink(link);
|
|
168
|
+
inputName = lookupInputName ? lookupInputName : this._nextName("input", inputName);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this.def.input[ inputName ] = {
|
|
172
|
+
type: targetNode.getInputType( link.targetInput ),
|
|
173
|
+
graph: {
|
|
174
|
+
position: this._computeInputSmartPosition(index)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// update the link with the new target, but don't attach it yet
|
|
179
|
+
// (the metanode isnt created yet)
|
|
180
|
+
link.nextTargetId = this.id;
|
|
181
|
+
link.nextTargetInput = inputName;
|
|
182
|
+
|
|
183
|
+
// save our link in the dictionary
|
|
184
|
+
inputToLinks[ inputName ] = link;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// create the metabox outputs
|
|
189
|
+
_createOutputs() {
|
|
190
|
+
// the inputs are determined from the inbound links of the metanode
|
|
191
|
+
_.each(this.outboundLinksToOutside, (link, index) => {
|
|
192
|
+
var sourceNode = link.getSourceNode();
|
|
193
|
+
|
|
194
|
+
var outputName = link.sourceOutput;
|
|
195
|
+
if( this.def.output[ outputName ] ) {
|
|
196
|
+
// check if the source of the output is the same as this link
|
|
197
|
+
var outputLink = this.def.output[outputName].link;
|
|
198
|
+
var sourceId = _.keys(outputLink)[0];
|
|
199
|
+
var sourceOutput = outputLink[sourceId];
|
|
200
|
+
|
|
201
|
+
if( !(link.sourceId == sourceId && link.sourceOutput == sourceOutput) ) {
|
|
202
|
+
// the output already exists. we need to create another one.
|
|
203
|
+
outputName = this._nextName("output", outputName);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
var l = {};
|
|
208
|
+
l[link.sourceId] = link.sourceOutput;
|
|
209
|
+
this.def.output[ outputName ] = {
|
|
210
|
+
type: sourceNode.getOutputType( link.sourceOutput ),
|
|
211
|
+
graph: {
|
|
212
|
+
position: this._computeOutputSmartPosition(index)
|
|
213
|
+
},
|
|
214
|
+
link: l
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// update the link with the new target, but don't attach it yet
|
|
218
|
+
// (the metanode isnt created yet)
|
|
219
|
+
link.nextSourceId = this.id;
|
|
220
|
+
link.nextSourceOutput = outputName;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
_repositionNodes() {
|
|
225
|
+
// find the lowest left, top coordinates to reposition nodes from there
|
|
226
|
+
var top = Number.MAX_SAFE_INTEGER;
|
|
227
|
+
var left = Number.MAX_SAFE_INTEGER;
|
|
228
|
+
_.each(this.nodes, (node) => {
|
|
229
|
+
var pos = node.getPosition();
|
|
230
|
+
if( pos.top < top ) top = pos.top;
|
|
231
|
+
if( pos.left < left ) left = pos.left;
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// we want to put theses node to 50px top and 50px left, so compute the diff
|
|
235
|
+
var topdiff = top - 50;
|
|
236
|
+
var leftdiff = left - 250;
|
|
237
|
+
if( topdiff < 0 ) topdiff = 0;
|
|
238
|
+
if( leftdiff < 0 ) leftdiff = 0;
|
|
239
|
+
|
|
240
|
+
// reposition all nodes
|
|
241
|
+
_.each(this.nodes, (node) => {
|
|
242
|
+
var pos = node.getPosition();
|
|
243
|
+
pos.left = pos.left - leftdiff;
|
|
244
|
+
pos.top = pos.top - topdiff;
|
|
245
|
+
node.setPosition(pos);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
_moveNodes() {
|
|
250
|
+
// move the nodes into the Metamodule def
|
|
251
|
+
_.each(this.nodes, (node) => {
|
|
252
|
+
if( node.isMetanode() ) {
|
|
253
|
+
// move the metanode definition too
|
|
254
|
+
this.def.metanodes[ node.def.module ] = node.getMetanodeDefinition();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// move the node module definition (preserving the links between metabox's node)
|
|
258
|
+
this.def.modules[node.id] = this.m.data.app.modules[ node.id ];
|
|
259
|
+
delete this.m.data.app.modules[node.id];
|
|
260
|
+
|
|
261
|
+
if( node.isMetanode() ) {
|
|
262
|
+
// if the metanode isn't still in use in the main app, remove def from here
|
|
263
|
+
if( !this.m.isMetanodeUsed(node.def.module) ) {
|
|
264
|
+
delete this.m.data.app.metanodes[node.def.module];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
_bindInputs() {
|
|
271
|
+
_.each(this.inboundLinksFromOutside, (link) => {
|
|
272
|
+
// we created an input with the same name, bind it to it
|
|
273
|
+
this.def.modules[link.targetId].links[link.targetInput] = { "input" : link.nextTargetInput };
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
_bindOutputs() {
|
|
278
|
+
_.each(this.outboundLinksToOutside, (link) => {
|
|
279
|
+
// we created an output with the same name, bind it to it
|
|
280
|
+
this.def.output[link.nextSourceOutput].link = {};
|
|
281
|
+
this.def.output[link.nextSourceOutput].link[link.sourceId] = link.sourceOutput;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
_bindMetanodeInputs() {
|
|
286
|
+
_.each(this.inboundLinksFromOutside, (link) => {
|
|
287
|
+
link.targetId = link.nextTargetId;
|
|
288
|
+
link.targetInput = link.nextTargetInput;
|
|
289
|
+
link.attach();
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
_bindMetanodeOutputs() {
|
|
294
|
+
_.each(this.outboundLinksToOutside, (link) => {
|
|
295
|
+
link.sourceId = link.nextSourceId;
|
|
296
|
+
link.sourceOutput = link.nextSourceOutput;
|
|
297
|
+
link.attach();
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
// compute the bbox of all divs beeing merged, position the new div in the middle
|
|
303
|
+
_computeBbox() {
|
|
304
|
+
var most = {
|
|
305
|
+
top: +Infinity,
|
|
306
|
+
bottom: -Infinity,
|
|
307
|
+
left: +Infinity,
|
|
308
|
+
right: -Infinity
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
_.each( this.nodes, (node) => {
|
|
312
|
+
var pos = node.getPosition();
|
|
313
|
+
if( pos.top < most.top ) most.top = pos.top;
|
|
314
|
+
if( pos.top > most.bottom ) most.bottom = pos.top;
|
|
315
|
+
if( pos.left < most.left ) most.left = pos.left;
|
|
316
|
+
if( pos.left > most.right ) most.right = pos.left;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return most;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
_getPositioningValues() {
|
|
323
|
+
// todo: get a better system with the real bbox
|
|
324
|
+
return {
|
|
325
|
+
width : 100,
|
|
326
|
+
height : 80,
|
|
327
|
+
hMargin : 80,
|
|
328
|
+
vMargin : 20,
|
|
329
|
+
divAvgWidth : 150
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// compute the "smart" position of the 'i'th input
|
|
334
|
+
_computeInputSmartPosition(i) {
|
|
335
|
+
var most = this._computeBbox();
|
|
336
|
+
var v = this._getPositioningValues();
|
|
337
|
+
return {
|
|
338
|
+
top: most.top + (v.height + v.vMargin) * i,
|
|
339
|
+
left: most.left - (v.width + v.hMargin),
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// compute the "smart" position of the 'i'th output
|
|
344
|
+
_computeOutputSmartPosition(i) {
|
|
345
|
+
var most = this._computeBbox();
|
|
346
|
+
var v = this._getPositioningValues();
|
|
347
|
+
return {
|
|
348
|
+
top: most.top + (v.height + v.vMargin) * i,
|
|
349
|
+
left: most.right + v.divAvgWidth + (v.width + v.hMargin),
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
module.exports = Merger;
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This object is in charge of managing the application state
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const _ = require('lodash');
|
|
6
|
+
|
|
7
|
+
class AppManager {
|
|
8
|
+
constructor(view, div) {
|
|
9
|
+
this.div = div;
|
|
10
|
+
this.app = null;
|
|
11
|
+
this.loaded = false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
kill() {
|
|
15
|
+
if( this.app ) {
|
|
16
|
+
this.app.kill();
|
|
17
|
+
this.app = null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
load(cb) {
|
|
22
|
+
if( this.loaded ) {
|
|
23
|
+
cb();
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
/* todo: load dynamically .js ressources from the app */
|
|
27
|
+
this.loaded = true;
|
|
28
|
+
cb();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
run(json, options, cb) {
|
|
33
|
+
this.kill(); // ensure we don't run it twice
|
|
34
|
+
if( typeof DualBox != "undefined" ) {
|
|
35
|
+
options.json = json;
|
|
36
|
+
options.translations = null;
|
|
37
|
+
options.lang = null;
|
|
38
|
+
options.div = this.div;
|
|
39
|
+
options.profiler = options.profiler || false;
|
|
40
|
+
options.logLevel = options.logLevel || false;
|
|
41
|
+
options.options = options.options || {};
|
|
42
|
+
options.options.NoVersionCheck = options.options.NoVersionCheck || true;
|
|
43
|
+
options.options.debug = options.options.debug || false;
|
|
44
|
+
options.options.debug.makeSynchrone = options.options.debug.makeSynchrone || false;
|
|
45
|
+
options.options.debug.removeTryCatch = options.options.debug.removeTryCatch || false;
|
|
46
|
+
options.options.debug.record = options.options.debug.record || false;
|
|
47
|
+
|
|
48
|
+
this.app = DualBox.start(options);
|
|
49
|
+
this.app.start(cb);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
setTimeout( this.run.bind(this, json, options, cb), 1000 );
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getSnapshot() {
|
|
57
|
+
return this.app.getSnapshot();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = AppManager;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This object is in charge of keeping the canvas to the right size
|
|
3
|
+
* when called with the dragging() method
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const _ = require('lodash');
|
|
7
|
+
|
|
8
|
+
class CanvasSizeHandler {
|
|
9
|
+
constructor(view, canvas) {
|
|
10
|
+
this.view = view;
|
|
11
|
+
this.canvas = canvas;
|
|
12
|
+
this.minWidth = this.width = this.canvas.width();
|
|
13
|
+
this.minHeight = this.height = this.canvas.height();
|
|
14
|
+
|
|
15
|
+
// arbitrary: number of pixels to expand/shrink the canvas for 1 step
|
|
16
|
+
// if a div is less than 1 step away from the border of the canvas, we expand
|
|
17
|
+
this.treshold = 50;
|
|
18
|
+
this.shiftTreshold = 200;
|
|
19
|
+
|
|
20
|
+
// debounced resize
|
|
21
|
+
this.debouncedResize = _.debounce(this.resize.bind(this), 100);
|
|
22
|
+
|
|
23
|
+
this.z = 0; // current zoom
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
refreshZoom( z ) {
|
|
27
|
+
this.z = z;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// set the current width and height to the new canvas
|
|
31
|
+
apply() {
|
|
32
|
+
if( this.canvas.width() !== this.width ) {
|
|
33
|
+
console.log('setting canvas width: %s -> %s', this.canvas.width(), this.width);
|
|
34
|
+
this.canvas.width(this.width);
|
|
35
|
+
}
|
|
36
|
+
if( this.canvas.height !== this.height ) {
|
|
37
|
+
console.log('setting canvas height: %s -> %s', this.canvas.height(), this.height);
|
|
38
|
+
this.canvas.height(this.height);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// method to call when an element is positioned or dragged
|
|
43
|
+
// el: div element
|
|
44
|
+
resize() {
|
|
45
|
+
// dont trigger save
|
|
46
|
+
this.view.m.history.ignore(() => {
|
|
47
|
+
// refresh zoom value, zoom affects width, height and offset calculations
|
|
48
|
+
this.refreshZoom( this.view.zoomer.getZoom() );
|
|
49
|
+
|
|
50
|
+
// find the coord of the 2d box bounding all divs
|
|
51
|
+
var most = {
|
|
52
|
+
top: +Infinity,
|
|
53
|
+
bottom: -Infinity,
|
|
54
|
+
left: +Infinity,
|
|
55
|
+
right: -Infinity
|
|
56
|
+
};
|
|
57
|
+
this.canvas.find('.card').each(function() {
|
|
58
|
+
var rect = this.getBoundingClientRect();
|
|
59
|
+
if( rect.top < most.top ) most.top = Math.floor(rect.top);
|
|
60
|
+
if( rect.bottom > most.bottom ) most.bottom = Math.ceil(rect.bottom);
|
|
61
|
+
if( rect.left < most.left ) most.left = Math.floor(rect.left);
|
|
62
|
+
if( rect.right > most.right ) most.right = Math.ceil(rect.right);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// compare to the canvas coord
|
|
66
|
+
var canvasRect = this.canvas[0].getBoundingClientRect();
|
|
67
|
+
|
|
68
|
+
var r = {
|
|
69
|
+
top: canvasRect.top - most.top + this.treshold * this.z,
|
|
70
|
+
bottom: most.bottom - canvasRect.bottom + this.treshold * this.z,
|
|
71
|
+
left: canvasRect.left - most.left + this.treshold * this.z,
|
|
72
|
+
right: most.right - canvasRect.right + this.treshold * this.z
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// avoid to resize for some pixel roundup
|
|
76
|
+
if( Math.abs(r.top) > 2 || Math.abs(r.left) > 2 || Math.abs(r.bottom) > 2 || Math.abs(r.right) > 2 ) {
|
|
77
|
+
// resize width and height
|
|
78
|
+
this.width += r.left + r.right;
|
|
79
|
+
this.height += r.top + r.bottom;
|
|
80
|
+
if( this.width < this.minWidth ) this.width = this.minWidth;
|
|
81
|
+
if( this.height < this.minHeight ) this.height = this.minHeight;
|
|
82
|
+
this.apply();
|
|
83
|
+
|
|
84
|
+
// if we're expanding to the top or shrinking back, move the non-selected divs
|
|
85
|
+
// in the opposite direction
|
|
86
|
+
if( r.top > 0 || (r.top < 0 && this.height > this.minHeight) ) {
|
|
87
|
+
// move the non-selected divs to the bottom
|
|
88
|
+
this.canvas.find('.card:not(.selected)').each(function() {
|
|
89
|
+
var offset = $(this).offset();
|
|
90
|
+
offset.top += r.top;
|
|
91
|
+
$(this).offset(offset);
|
|
92
|
+
$(this).savePosition();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
this.view.translater.translate({
|
|
96
|
+
top: -r.top,
|
|
97
|
+
left: 0,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// same for left
|
|
102
|
+
if( r.left > 0 || (r.left < 0 && this.width > this.minWidth) ) {
|
|
103
|
+
// move the non-selected divs to the right
|
|
104
|
+
this.canvas.find('.card:not(.selected)').each(function() {
|
|
105
|
+
var offset = $(this).offset();
|
|
106
|
+
offset.left += r.left;
|
|
107
|
+
$(this).offset(offset);
|
|
108
|
+
$(this).savePosition();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
this.view.translater.translate({
|
|
112
|
+
top: 0,
|
|
113
|
+
left: -r.left,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.view.jsPlumbInstance.repaintEverything();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// return a Promise if we need to wait for this call
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
this.canvas.ready(resolve);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getIncreasedWidth() {
|
|
128
|
+
return this.width - this.minWidth;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getIncreasedHeight() {
|
|
132
|
+
return this.height - this.minHeight;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = CanvasSizeHandler;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
.ContextMenu {
|
|
2
|
+
display: none;
|
|
3
|
+
list-style: none;
|
|
4
|
+
margin: 0;
|
|
5
|
+
max-width: 250px;
|
|
6
|
+
min-width: 125px;
|
|
7
|
+
padding: 0;
|
|
8
|
+
position: absolute;
|
|
9
|
+
user-select: none;
|
|
10
|
+
}
|
|
11
|
+
.ContextMenu--theme-default {
|
|
12
|
+
background-color: #fff;
|
|
13
|
+
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
14
|
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
|
|
15
|
+
font-size: 13px;
|
|
16
|
+
outline: 0;
|
|
17
|
+
padding: 2px 0;
|
|
18
|
+
}
|
|
19
|
+
.ContextMenu--theme-default .ContextMenu-item {
|
|
20
|
+
padding: 6px 12px;
|
|
21
|
+
}
|
|
22
|
+
.ContextMenu--theme-default .ContextMenu-item:hover,
|
|
23
|
+
.ContextMenu--theme-default .ContextMenu-item:focus {
|
|
24
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
25
|
+
}
|
|
26
|
+
.ContextMenu--theme-default .ContextMenu-item:focus {
|
|
27
|
+
outline: 0;
|
|
28
|
+
}
|
|
29
|
+
.ContextMenu--theme-default .ContextMenu-divider {
|
|
30
|
+
background-color: rgba(0, 0, 0, 0.15);
|
|
31
|
+
}
|
|
32
|
+
.ContextMenu.is-open {
|
|
33
|
+
display: block;
|
|
34
|
+
}
|
|
35
|
+
.ContextMenu-item {
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
display: block;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
text-overflow: ellipsis;
|
|
40
|
+
white-space: nowrap;
|
|
41
|
+
}
|
|
42
|
+
.ContextMenu-divider {
|
|
43
|
+
height: 1px;
|
|
44
|
+
margin: 4px 0;
|
|
45
|
+
}
|