@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,511 @@
1
+ const swal = require('sweetalert2');
2
+ var CodeMirror = require('../../libs/CodeMirror.js');
3
+
4
+ var TemplateBinds = function(view, div) {
5
+ // instanciate main settings first
6
+ view.setMainMenu();
7
+
8
+ var getOptions = () => {
9
+ return {
10
+ profiler : div.find('.run-profiler').is(':checked'),
11
+ logLevel : div.find('.run-loglevel').val(),
12
+ options : {
13
+ noVersionCheck: div.find('.run-noversioncheck').is(':checked'),
14
+ debug: {
15
+ removeTryCatch: div.find('.run-removetrycatch').is(':checked'),
16
+ makeSynchrone: div.find('.run-makesynchrone').is(':checked'),
17
+ record: div.find('.run-record').is(':checked'),
18
+ }
19
+ }
20
+ }
21
+ }
22
+
23
+ // Resize horizontally
24
+ div.find('.dragbar').mousedown(function(e){
25
+ e.preventDefault();
26
+ $(document).mouseup(function(e){
27
+ $(document).unbind('mousemove');
28
+ });
29
+ $(document).mousemove(function(e){
30
+ $('.code-panel').css('width', e.pageX + "px");
31
+ $('.application-container').css('width', ($(".main").width() - e.pageX - 5) + "px");
32
+ })
33
+ });
34
+
35
+ // bind the app load
36
+ div.find('.load-app').click(function(e) {
37
+ // create a fake input and click it to select a file
38
+ var input = $('<input/>', { "type": "file", "class": "upload", "accept" : ".json" });
39
+ input.change( function(e) {
40
+ // if we're not here, go to 1st tab
41
+ $("a[data-toggle='tab'][href='#1']").click();
42
+
43
+ // parse the file JSON and load it
44
+ var files = e.target.files; // FileList object
45
+ var r = new FileReader();
46
+ r.onload = function(e) {
47
+ var contents = e.target.result;
48
+ var json = JSON.parse(contents);
49
+ view.e.setApp(json);
50
+ };
51
+
52
+ r.readAsText(files[0]);
53
+ });
54
+ input.click();
55
+ });
56
+
57
+ // bind the app load
58
+ div.find('.save-app').click(function(e) {
59
+ var app = view.m.getCleanJson();
60
+ var text = JSON.stringify(app, null, 2);
61
+ var blob = new Blob([text], { "type" : "application/octet-stream" });
62
+
63
+ var a = document.createElement('a');
64
+ a.href = window.URL.createObjectURL(blob);
65
+ a.download = "app.json";
66
+
67
+ // simulate a click on the link
68
+ if (document.createEvent) {
69
+ var event = document.createEvent("MouseEvents");
70
+ event.initEvent("click", true, true);
71
+ a.dispatchEvent(event);
72
+ } else {
73
+ a.click();
74
+ }
75
+ });
76
+
77
+ div.find('.add-metabox').click(function(e) {
78
+ e.preventDefault();
79
+ e.stopPropagation();
80
+
81
+ swal({
82
+ input: 'text',
83
+ title: 'Choose a name for the metabox',
84
+ }).then(function(result) {
85
+ if( result.value ) {
86
+ view.e.c.addNewMetabox(result.value);
87
+ }
88
+ });
89
+ });
90
+
91
+ div.find('.add-input').click(function(e) {
92
+ view.c.createInput();
93
+ });
94
+
95
+ div.find('.add-output').click(function(e) {
96
+ view.c.createOutput();
97
+ });
98
+
99
+ div.find('.import-metabox').click(async function(e) {
100
+ /*
101
+ swal({
102
+ title: "Pick your metabox file",
103
+ html: "<input type='file' id='fileToUploadAlert' accept='application/json' />",
104
+ showCancelButton: true,
105
+ confirmButtonColor: "#07A803",
106
+ confirmButtonText: "Upload",
107
+ closeOnConfirm: false,
108
+ showLoaderOnConfirm: true
109
+ }, function () {
110
+ var files = $('input#fileToUploadAlert').prop('files');
111
+ if (files.length === 0) {
112
+ swal.showInputError("You need to upload a file!");
113
+ return false
114
+ }
115
+
116
+ console.log(files[0]);
117
+ });
118
+ */
119
+
120
+ const { value: file } = await swal({
121
+ title: 'Select your metabox file',
122
+ input: 'file',
123
+ inputAttributes: {
124
+ 'accept': 'application/json',
125
+ 'aria-label': 'Select your metabox file'
126
+ }
127
+ })
128
+
129
+ if (file) {
130
+ const reader = new FileReader()
131
+ reader.onload = (e) => {
132
+ var json = JSON.parse(e.target.result);
133
+
134
+ view.e.loadPackages(json).then(async () => {
135
+ const { value: name } = await swal({
136
+ title: 'Choose a name for your metabox',
137
+ input: 'text',
138
+ showCancelButton: true,
139
+ inputValidator: (value) => {
140
+ return !value && 'You need to write something!'
141
+ }
142
+ });
143
+
144
+ view.c.addNewMetabox(name, json);
145
+ });
146
+ }
147
+ reader.readAsText(file)
148
+ }
149
+ });
150
+
151
+ div.find('.btn-goto-topleft').click(function(e) {
152
+ e.preventDefault();
153
+ e.stopPropagation();
154
+ view.translater.gotoTopLeft();
155
+ });
156
+ div.find('.btn-goto-topright').click(function(e) {
157
+ e.preventDefault();
158
+ e.stopPropagation();
159
+ view.translater.gotoTopRight();
160
+ });
161
+ div.find('.btn-goto-bottomleft').click(function(e) {
162
+ e.preventDefault();
163
+ e.stopPropagation();
164
+ view.translater.gotoBottomLeft();
165
+ });
166
+ div.find('.btn-goto-bottomright').click(function(e) {
167
+ e.preventDefault();
168
+ e.stopPropagation();
169
+ view.translater.gotoBottomRight();
170
+ });
171
+
172
+ div.find("a[data-toggle='tab']").on("shown.bs.tab", function(e) {
173
+ var target = $(e.target).attr("href") // activated tab
174
+ if( target == "#1" ) {
175
+ view.killApp();
176
+ }
177
+ else if( target == "#2" ) {
178
+ view.htmlCode.refresh();
179
+ view.cssCode.refresh();
180
+ view.runApp( getOptions() );
181
+ }
182
+ });
183
+
184
+ // instanciate codemirror for html and css
185
+ if( !view.htmlCode ) {
186
+ view.htmlCode = CodeMirror.fromTextArea( div.find(".code-html")[0], {
187
+ lineNumbers: true,
188
+ mode : "htmlmixed",
189
+ lineWrapping: true
190
+ });
191
+ }
192
+ if( !view.cssCode ) {
193
+ view.cssCode = CodeMirror.fromTextArea( div.find(".code-css")[0], {
194
+ lineNumbers: true,
195
+ mode : "css",
196
+ lineWrapping: true
197
+ });
198
+ }
199
+
200
+ // bind the interface buttons
201
+ // Load the interface HTML when selected
202
+ div.find(".app-interface-select").change( function(e) {
203
+ var uiName = $(this).val();
204
+ if( uiName !== "" && uiName !== "Load UI..." ) {
205
+ var interface = view.m.data.root.interface;
206
+ var htmlString = htmltool.json2html(interface[uiName]);
207
+ var prettyString = htmltool.htmlPrettyPrint(htmlString);
208
+ view.htmlCode.setValue(prettyString);
209
+ }
210
+ else {
211
+ view.htmlCode.setValue("");
212
+ }
213
+ });
214
+
215
+ div.find(".btn-save-interface-element").click( function(e) {
216
+ var currentInterface = div.find('.app-interface-select').val();
217
+ if( currentInterface !== "" ) {
218
+ var currentHTML = view.htmlCode.getValue();
219
+
220
+ // save html code into app
221
+ view.m.data.root.interface[currentInterface] = htmltool.html2json(currentHTML);
222
+ }
223
+
224
+ // save css code into app
225
+ view.m.data.root.css = view.cssCode.getValue();
226
+ });
227
+
228
+ // Buttons for adding and remove interface
229
+ div.find('.btn-add-interface').click(function(e) {
230
+ swal.mixin({
231
+ confirmButtonText: 'Next &rarr;',
232
+ showCancelButton: true,
233
+ progressSteps: ['1', '2', '3']
234
+ }).queue([
235
+ {
236
+ input: 'text',
237
+ title: 'Choose a name',
238
+ text: 'Enter a name for the new interface'
239
+ },
240
+ {
241
+ input: 'select',
242
+ title: 'Choose the type',
243
+ text : 'Is it a viewer panel or a control panel?',
244
+ inputOptions: {
245
+ 'control': 'A control panel',
246
+ 'viewer': 'A viewer',
247
+ },
248
+ },
249
+ {
250
+ input: 'select',
251
+ title: 'Choose the position',
252
+ text : 'Where do you want to position your panel?',
253
+ inputOptions: {
254
+ 'top-left': 'At the top-left',
255
+ 'top-center': 'At the top-center',
256
+ 'top-right': 'At the top-right',
257
+ 'center-left': 'At the center-left',
258
+ 'center': 'At the center',
259
+ 'center-right': 'At the center-right',
260
+ 'bottom-left': 'At the bottom-left',
261
+ 'bottom-center': 'At the bottom-center',
262
+ 'bottom-right': 'At the bottom-right',
263
+ 'whole-screen': 'I want my panel in full-screen',
264
+ },
265
+ }
266
+ ]).then((result) => {
267
+ if (result.value) {
268
+ var name = result.value[0];
269
+ var type = result.value[1];
270
+ var position = result.value[2];
271
+
272
+ if (name === "") {
273
+ swal.showInputError("the name is empty!");
274
+ return false
275
+ }
276
+
277
+ var appInterface = view.m.data.root.interface;
278
+ if( appInterface[name] ) {
279
+ swal.showInputError("Interface " + name + " already exists!");
280
+ return false
281
+ }
282
+ else {
283
+ var style={};
284
+ switch(position) {
285
+ case 'top-left': style={ 'position': 'absolute', 'top': 0, 'left': 0, 'margin': '15px' }; break;
286
+ case 'top-center': style={ 'position': 'absolute', 'top': 0, 'margin': '15px auto' }; break;
287
+ case 'top-right': style={ 'position': 'absolute', 'top': 0, 'right': 0, 'margin': '15px' }; break;
288
+ case 'center-left': style={ 'position': 'absolute', 'top': 0, 'bottom': 0, 'left': 0, 'margin': 'auto 15px' }; break;
289
+ case 'center': style={ 'position': 'absolute', 'top': 0, 'bottom': 0, 'left': 0, 'right': 0, 'margin': 'auto' }; break;
290
+ case 'center-right': style={ 'position': 'absolute', 'top': 0, 'bottom': 0, 'right': 0, 'margin': 'auto 15px' }; break;
291
+ case 'bottom-left': style={ 'position': 'absolute', 'bottom': 0, 'left': 0, 'margin': '15px' }; break;
292
+ case 'bottom-center': style={ 'position': 'absolute', 'bottom': 0, 'left': 0, 'right': 0, 'margin': '15px auto' }; break;
293
+ case 'bottom-right': style={ 'position': 'absolute', 'bottom': 0, 'right': 0, 'margin': '15px' }; break;
294
+ case 'whole-screen': style={ 'position': 'absolute', 'top': 0, 'left': 0 }; break;
295
+ }
296
+
297
+ // some prefilled data
298
+ style['width'] = "300px";
299
+ style['padding'] = "15px";
300
+ style['border'] = "1px solid #ccc";
301
+ style['border-radius'] = "4px";
302
+ style['background'] = "white";
303
+ if( position.startsWith("center") ) {
304
+ style['height'] = "60px"; // must define height
305
+ }
306
+ else {
307
+ style['height'] = "auto";
308
+ }
309
+
310
+ // add a basic control
311
+ appInterface[name] = {
312
+ type: "Element",
313
+ tagName: "div",
314
+ attributes: {
315
+ className: [
316
+ "dualbox",
317
+ "dualbox-container",
318
+ "dualbox-container-" + name,
319
+ type=="viewer" ? "dualbox-viewer" : "dualbox-controls"
320
+ ],
321
+ style: style,
322
+ },
323
+ children: []
324
+ }
325
+
326
+ // add the value to our select, and load it
327
+ div.find(".app-interface-select").append(
328
+ $("<option/>", { "value" : name }).append(name)
329
+ );
330
+ $(document).ready(function() {
331
+ div.find('.app-interface-select').val(name).change();
332
+ });
333
+
334
+ view.runApp( getOptions() );
335
+ }
336
+ }
337
+ })
338
+ });
339
+
340
+ div.find('.btn-remove-interface').click(function(e) {
341
+ var name = $('.app-interface-select').val();
342
+
343
+ swal({
344
+ title: "Confirm deleting " + name + " ?",
345
+ type: "warning",
346
+ showCancelButton: true,
347
+ confirmButtonColor: "#DD6B55",
348
+ confirmButtonText: "Yes, delete it!",
349
+ closeOnConfirm: true,
350
+ closeOnCancel: true
351
+ }).then((result) => {
352
+ if( result.value ) {
353
+ // set the interface back to first value
354
+ div.find(".app-interface-select option[value='" + name + "']").remove();
355
+ div.find(".app-interface-select").change();
356
+ delete view.m.data.root.interface[name];
357
+ view.htmlCode.setValue("");
358
+ view.runApp( getOptions() );
359
+ }
360
+ });
361
+ });
362
+
363
+
364
+ div.find('.btn-edit-panel-description').click(function(e) {
365
+ var name = $('.app-interface-select').val();
366
+
367
+ swal({
368
+ title: "Enter a description for this panel!",
369
+ input: "textarea",
370
+ inputValue: view.m.data.root.interface[name].description || "",
371
+ showCancelButton: true,
372
+ closeOnConfirm: false,
373
+ showLoaderOnConfirm: true,
374
+ animation: "slide-from-top",
375
+ inputPlaceholder: "Write something"
376
+ }).then( (result) => {
377
+ if (result.value === "") {
378
+ swal.showInputError("You need to write something!");
379
+ return false
380
+ }
381
+ else {
382
+ view.m.data.root.interface[name].description = result.value;
383
+ }
384
+ });
385
+ });
386
+
387
+ div.bind('expandSettings', function(e) {
388
+ // expand
389
+ div.find('.dualbox-graph-left-section').css('margin-left', '0');
390
+ div.find('.dualbox-graph-main').addClass('left-panel-expanded');
391
+ div.find('.btn-toggle-left-window').data('expanded', true).find('i')
392
+ .removeClass('fa-angle-double-right')
393
+ .addClass('fa-angle-double-left')
394
+ .attr('title', 'shrink window');
395
+ });
396
+
397
+ div.bind('shrinkSettings', function(e) {
398
+ // shrink
399
+ div.find('.dualbox-graph-left-section').css('margin-left', '-465px');
400
+ div.find('.dualbox-graph-main').removeClass('left-panel-expanded');
401
+ div.find('.btn-toggle-left-window').data('expanded', false).find('i')
402
+ .removeClass('fa-angle-double-left')
403
+ .addClass('fa-angle-double-right')
404
+ .attr('title', 'expand window');
405
+ });
406
+
407
+ div.bind('expandDebug', function(e) {
408
+ // expand
409
+ div.find('.dualbox-graph-right-section').css('margin-right', '0');
410
+ div.find('.dualbox-graph-main').addClass('right-panel-expanded');
411
+ div.find('.btn-toggle-right-window').data('expanded', true).find('i')
412
+ .removeClass('fa-angle-double-left')
413
+ .addClass('fa-angle-double-right')
414
+ .attr('title', 'shrink window');
415
+ });
416
+
417
+ div.bind('shrinkDebug', function(e) {
418
+ // shrink
419
+ div.find('.dualbox-graph-right-section').css('margin-right', '-465px');
420
+ div.find('.dualbox-graph-main').removeClass('right-panel-expanded');
421
+ div.find('.btn-toggle-right-window').data('expanded', false).find('i')
422
+ .removeClass('fa-angle-double-right')
423
+ .addClass('fa-angle-double-left')
424
+ .attr('title', 'expand window');
425
+ });
426
+
427
+ div.find('.btn-toggle-left-window').click(function(e) {
428
+ var expanded = $(this).data('expanded');
429
+ if( expanded ) {
430
+ div.trigger('shrinkSettings');
431
+ }
432
+ else {
433
+ div.trigger('expandSettings');
434
+ }
435
+ });
436
+
437
+ div.find('.btn-toggle-right-window').click(function(e) {
438
+ var expanded = $(this).data('expanded');
439
+ if( expanded ) {
440
+ div.trigger('shrinkDebug');
441
+ }
442
+ else {
443
+ div.trigger('expandDebug');
444
+ }
445
+ });
446
+
447
+ div.find('.show-events').change(function(e) {
448
+ view.setEventsVisibility( $(this).is(':checked') );
449
+ });
450
+
451
+ div.find('.btn-run').click(function(e) {
452
+ view.runApp( getOptions() );
453
+ });
454
+
455
+ div.find('.btn-snapshot').click(function(e) {
456
+ view.takeAndLoadSnapshot();
457
+ });
458
+
459
+ div.find('.btn-undo').click(function(e) {
460
+ view.c.undo();
461
+ });
462
+
463
+ div.find('.btn-redo').click(function(e) {
464
+ view.c.redo();
465
+ });
466
+
467
+ div.find('.dualbox-remove-selection').click(function(e) {
468
+ view.c.deleteSelection();
469
+ });
470
+
471
+ div.find('.dualbox-merge-selection').click(function(e) {
472
+ view.c.mergeSelection();
473
+ });
474
+
475
+ div.find('.dualbox-graph-main').click(function(e) {
476
+ view.setMainMenu();
477
+ });
478
+
479
+ // autofocus editor when the div is clicked (allow to use keydown)
480
+ $(document).keydown(function(e) {
481
+ // Ensure event is not null
482
+ e = e || window.event;
483
+
484
+ if ((e.which == 90 || e.keyCode == 90) && e.ctrlKey) { // ctrl + z
485
+ if( div.find('.dualbox-graph-container').is(':hover') ) {
486
+ view.c.undo();
487
+ }
488
+ }
489
+ else if((e.which == 89 || e.keyCode == 89) && e.ctrlKey) { // ctrl + y
490
+ if( div.find('.dualbox-graph-container').is(':hover') ) {
491
+ view.c.redo();
492
+ }
493
+ }
494
+ else if((e.which == 46 || e.keyCode == 46)) { // suppr: delete selection
495
+ // idk why this doesnt work when binded directly to .dualbox-graph-container
496
+ if( div.find('.dualbox-graph-container').is(':hover') ) {
497
+ view.c.deleteSelection();
498
+ }
499
+ }
500
+ });
501
+
502
+ /*
503
+ // capture right click in order to prevent unintended behaviors
504
+ div.contextmenu(function(e) {
505
+ e.preventDefault();
506
+ e.stopPropagation();
507
+ });
508
+ */
509
+ }
510
+
511
+ module.exports = TemplateBinds;
@@ -0,0 +1,50 @@
1
+ .node-result {
2
+ border-bottom: 1px solid rgba(0,0,0,0.1);
3
+ padding-top: 10px;
4
+ padding-left: 5px;
5
+ padding-right: 5px;
6
+ padding-bottom: 5px;
7
+ cursor: pointer;
8
+ }
9
+
10
+ .node-result:hover {
11
+ background-color: #eee;
12
+ }
13
+
14
+ .node-result.selected {
15
+ background-color: #ddd;
16
+ }
17
+
18
+ .node-result.selected {
19
+ background-color: #eee;
20
+ }
21
+
22
+ .node-name {
23
+ display: block;
24
+ width: 100%;
25
+ margin: 0;
26
+ }
27
+
28
+ .node-desc {
29
+ overflow: hidden;
30
+ white-space: nowrap;
31
+ text-overflow: ellipsis;
32
+ display: inline-block;
33
+ max-width: 100%;
34
+ }
35
+
36
+ .search-nodes-results {
37
+ max-height: 100%;
38
+ overflow-y: scroll;
39
+ overflow-x: hidden;
40
+ padding-right: 15px;
41
+ }
42
+
43
+ .badge-node {
44
+ width: 70px;
45
+ text-align: center;
46
+ }
47
+
48
+ .noselect {
49
+ pointer-events: none;
50
+ }
@@ -0,0 +1,46 @@
1
+ @!(attrs)
2
+ @if (attrs.err) {
3
+ <div class="text-danger">@(attrs.err)</p>
4
+ }
5
+ else {
6
+ <div>
7
+ @for(var i=0; i<attrs.results.length; i++) {
8
+ @{m = attrs.results[i]}
9
+
10
+ @if( isNotLibrary(m.name) ) {
11
+ <div class="row node-result" data-package="@m.name" data-short-name="@displayName(m.name)">
12
+ <div class="col noselect">
13
+ <p class="node-name" style="margin: 0;">
14
+ <span>@displayType(m.name)</span>
15
+ <b>@displayName(m.name)</b>
16
+ </p>
17
+ <small class="node-desc"><i>@m.description</i></small>
18
+ </div>
19
+ </div>
20
+ }
21
+ }
22
+ </div>
23
+ }
24
+
25
+ @function displayName( str ) {
26
+ @{str = str.replace('@dualbox/', '')}
27
+ @{str = str.replace('dualbox-core-', '')}
28
+ @{str = str.replace('dualbox-lib-', '')}
29
+ @{str = str.replace('dualbox-ui-', '')}
30
+ @{str = str.replace('dualbox-module-', '')}
31
+ @str
32
+ }
33
+
34
+ @function displayType( name ) {
35
+ @{str = name.replace('@dualbox/', '')}
36
+ @if( str.indexOf("dualbox-core") !== -1 || str.indexOf("dualbox-module") !== -1 ) {
37
+ <span class="badge badge-secondary">compute</span>
38
+ }
39
+ else if( str.indexOf("dualbox-ui") !== -1 ) {
40
+ <span class="badge badge-success">ui</span>
41
+ }
42
+ }
43
+
44
+ @function isNotLibrary( name ) {
45
+ @{ return name.indexOf('dualbox-lib-') === -1; }
46
+ }