@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.
Files changed (43) hide show
  1. package/js/src/GraphEditor.js +159 -0
  2. package/js/src/c/GraphController.js +646 -0
  3. package/js/src/libs/CodeMirror.js +8 -0
  4. package/js/src/libs/fontawesome.js +1 -0
  5. package/js/src/libs/jsoneditor.css +2 -0
  6. package/js/src/libs/jsoneditor.js +4 -0
  7. package/js/src/m/DualboxUtils.js +35 -0
  8. package/js/src/m/GraphModel.js +2167 -0
  9. package/js/src/m/History.js +94 -0
  10. package/js/src/m/Merger.js +357 -0
  11. package/js/src/v/AppManager.js +61 -0
  12. package/js/src/v/CanvasSizeHandler.js +136 -0
  13. package/js/src/v/ContextMenu.css +45 -0
  14. package/js/src/v/ContextMenu.js +239 -0
  15. package/js/src/v/GraphView.js +928 -0
  16. package/js/src/v/PlumbStyle.js +254 -0
  17. package/js/src/v/Selector.js +239 -0
  18. package/js/src/v/TemplateManager.js +79 -0
  19. package/js/src/v/Translater.js +174 -0
  20. package/js/src/v/Utils.js +7 -0
  21. package/js/src/v/Zoomer.js +201 -0
  22. package/js/src/v/templates/addNode.css +45 -0
  23. package/js/src/v/templates/addNode.html +62 -0
  24. package/js/src/v/templates/addNode.js +34 -0
  25. package/js/src/v/templates/debugNodeInfos.css +5 -0
  26. package/js/src/v/templates/debugNodeInfos.html +336 -0
  27. package/js/src/v/templates/debugNodeInfos.js +31 -0
  28. package/js/src/v/templates/editMainSettings.css +67 -0
  29. package/js/src/v/templates/editMainSettings.html +265 -0
  30. package/js/src/v/templates/editMainSettings.js +240 -0
  31. package/js/src/v/templates/editNodeSettings.css +86 -0
  32. package/js/src/v/templates/editNodeSettings.html +539 -0
  33. package/js/src/v/templates/editNodeSettings.js +356 -0
  34. package/js/src/v/templates/graphNode.css +333 -0
  35. package/js/src/v/templates/graphNode.html +227 -0
  36. package/js/src/v/templates/graphNode.js +412 -0
  37. package/js/src/v/templates/main.css +353 -0
  38. package/js/src/v/templates/main.html +149 -0
  39. package/js/src/v/templates/main.js +511 -0
  40. package/js/src/v/templates/searchResults.css +50 -0
  41. package/js/src/v/templates/searchResults.html +46 -0
  42. package/js/src/v/templates/searchResults.js +176 -0
  43. 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
+ }