caixanegra 0.3.4 → 0.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37e8a2fbf540c8bfe1f1c85cf1dd82638bde895abe10bf24369860a6996c1c7b
4
- data.tar.gz: f65ae1c5c97c7cd8264c157d93748748ec7e8084e7baba2c3f3c7113adfd60d9
3
+ metadata.gz: 002efa6f5c1a220426d102e5f055b50eff9fb55e5742d6c2c33d6474f890fd8a
4
+ data.tar.gz: 03c265dbfa950af4d043bfc56711593b2cb1661f76fc2ca40327fbc7a7f5e309
5
5
  SHA512:
6
- metadata.gz: 7eb792dd9e30f18921dd6ff8c4bab52a9c564f4f93204aeb8925bf5e0255093e64e00dfb9b16b9ac0f19991fccf9868157e17c788cafa66932ef5112f96b187d
7
- data.tar.gz: '09b9f690181fd28e25f5a8cd25ee1e3588af0c9efdadcd3597676fb80d9b93cbec43563d73a4086314f73fab34b11c27653cab163f10521121dd04c85a53a756'
6
+ metadata.gz: da48c854b8646515af968b7c685cece267a5bed236f1f58d5331de39a2d75bded3f0896b84d09de7cbe70c0fad7f388ef5891684bc04f45995e0b1afdc9d1784
7
+ data.tar.gz: 28b98d23640368db0f32475c4cd6209fb39648e723e63d70ae2fd814e88c05ca6c9033dccc1accfa3c2c331413569fb54e1b52f115cc26841703019dec4fa0a2
data/README.md CHANGED
@@ -46,7 +46,7 @@ This will give you the UID that **caixanegra** designer will understand, using t
46
46
 
47
47
  ```ruby
48
48
  my_flow = somewhere.get_flow # get from your own persistence or transport solution
49
- uid = Caixanegra::Manager.handler(my_flow || {})
49
+ uid = Caixanegra::Manager.handler(flow_definition: my_flow || {})
50
50
  ```
51
51
 
52
52
  You can then safely navigate to the designer.
@@ -303,6 +303,10 @@ window.Caixanegra.Designer = {
303
303
  document.querySelector("#console").addEventListener("click", this.toggleExecutionConsole.bind(this));
304
304
  document.querySelector("#clearCarryOverObject").addEventListener("click", this.clearCarryOverObject.bind(this));
305
305
  document.querySelector("#reset").addEventListener("click", this.resetExecution.bind(this));
306
+ document.querySelector("#duplicate").addEventListener("click", this.duplicateUnit.bind(this));
307
+ document.querySelector("#importFlow").addEventListener("click", this.#importFlow.bind(this));
308
+ document.querySelector("#exportFlow").addEventListener("click", this.#exportFlow.bind(this));
309
+ document.querySelector("#importFlowFile").addEventListener("change", this.#importFlowFileChanged.bind(this));
306
310
  this.toggleBlocker(true, "loading your flow");
307
311
  window.addEventListener("resize", this.#windowResized.bind(drawingSurface));
308
312
  drawingSurface.addEventListener("update_start", this.#engineUpdate.bind(this));
@@ -385,6 +389,8 @@ window.Caixanegra.Designer = {
385
389
  }
386
390
 
387
391
  container.append(wrapper);
392
+
393
+ return wrapper;
388
394
  }
389
395
 
390
396
  deleteUnit() {
@@ -394,6 +400,12 @@ window.Caixanegra.Designer = {
394
400
  this.toggleDeleteConfirmation();
395
401
  }
396
402
 
403
+ duplicateUnit() {
404
+ this.unitDetailPane.classList.remove("-open");
405
+ this.unitDebugHitsPane.classList.remove("-open");
406
+ this.cloneUnit(this.selectedUnit.oid);
407
+ }
408
+
397
409
  flushToConsole(entries) {
398
410
  const container = document.querySelector("#executionConsole");
399
411
  container.innerHTML = "";
@@ -468,7 +480,8 @@ window.Caixanegra.Designer = {
468
480
  flowSnapshot() {
469
481
  const snapshot = {
470
482
  entrypoint: this.#units.find((unit) => {return unit.type === "starter"})?.oid,
471
- units: []
483
+ units: [],
484
+ initialCarryover: this.digInitialCarryOver(document.querySelector("#carryOverObject"))
472
485
  };
473
486
 
474
487
  this.#units.forEach((unit) => {
@@ -587,33 +600,81 @@ window.Caixanegra.Designer = {
587
600
  });
588
601
 
589
602
  this.#loadedComponents.catalog = true;
590
- this.#reveal();
603
+ this.reveal();
591
604
  });
592
605
  }
593
606
 
594
607
  loadFlow() {
595
608
  this.api.getFlow(this.flowId).then((response) => {
596
609
  const flowData = response;
597
- this.gre.clear();
598
- this.#units = [];
599
610
 
600
- (flowData?.units || []).forEach((unit) => {
601
- this.createUnit(unit);
602
- });
603
-
604
- this.#units.forEach((unit) => {
605
- (unit?.exits || []).forEach((exit) => {
606
- const object = this.#units.find((targetUnit) => targetUnit.oid === exit.target);
607
- if (object) { exit.target = object; }
608
- });
609
- });
611
+ this.#loadFlowFromJSON(flowData);
610
612
 
611
613
  this.#loadedComponents.flow = true;
612
- this.#reveal();
614
+ this.reveal();
615
+ });
616
+ }
617
+
618
+ #loadFlowFromJSON(flowData) {
619
+ this.gre.clear();
620
+ this.#units = [];
621
+
622
+ (flowData?.units || []).forEach((unit) => {
623
+ this.createUnit(unit);
624
+ });
625
+
626
+ this.#units.forEach((unit) => {
627
+ (unit?.exits || []).forEach((exit) => {
628
+ const object = this.#units.find((targetUnit) => targetUnit.oid === exit.target);
629
+ if (object) { exit.target = object; }
630
+ });
613
631
  });
632
+
633
+ this.buildInitialCarryover(flowData.initialCarryover);
634
+ }
635
+
636
+ digBuildInitialCarryover(container, key, objectPointer) {
637
+ const lastChild = container.childNodes[container.childNodes.length -1];
638
+
639
+ if (key !== null) {
640
+ container.childNodes[0].querySelector("input").value = key;
641
+ }
642
+
643
+ if (Array.isArray(objectPointer)) {
644
+ this.coeAssignType(lastChild, "array");
645
+
646
+ for (let idx = 0; idx < objectPointer.length; idx++) {
647
+ this.digBuildInitialCarryover(
648
+ this.coeAddRow(container.querySelector(".rows")),
649
+ null,
650
+ objectPointer[idx]
651
+ );
652
+ }
653
+ } else if (typeof objectPointer === "object") {
654
+ this.coeAssignType(lastChild, "object");
655
+
656
+ for (const key in objectPointer) {
657
+ this.digBuildInitialCarryover(this.coeAddRow(container.querySelector(".rows")), key, objectPointer[key]);
658
+ }
659
+ } else if (typeof objectPointer === "number") {
660
+ this.coeAssignType(lastChild, "number");
661
+ lastChild.querySelector("input").value = objectPointer;
662
+ } else {
663
+ this.coeAssignType(lastChild, "string");
664
+ lastChild.querySelector("input").value = objectPointer;
665
+ }
666
+ }
667
+
668
+ buildInitialCarryover(initialCarryover) {
669
+ this.clearCarryOverObject();
670
+ const base = document.querySelector("#carryOverObject > .rows");
671
+
672
+ for (const key in initialCarryover) {
673
+ this.digBuildInitialCarryover(this.coeAddRow(base), key, initialCarryover[key]);
674
+ }
614
675
  }
615
676
 
616
- #reveal() {
677
+ reveal() {
617
678
  if (this.#loadedComponents.flow && this.#loadedComponents.catalog) {
618
679
  this.toggleBlocker(false);
619
680
  }
@@ -713,6 +774,21 @@ window.Caixanegra.Designer = {
713
774
  });
714
775
  }
715
776
 
777
+ cloneUnit(oid) {
778
+ const unit = this.#units.find((unit) => unit.oid === oid);
779
+ const dup = this.#dupObject(unit);
780
+
781
+ dup.oid = Sabertooth.Utils.generateOId();
782
+ dup.position = { x: unit.position.x + 25, y: unit.position.y + 25 };
783
+ dup.title = `${dup.title} (copy)`;
784
+
785
+ dup.exits.forEach(exit => {
786
+ delete exit.target;
787
+ });
788
+
789
+ this.createUnit(dup);
790
+ }
791
+
716
792
  showError(message) {
717
793
  this.actionMessage(true, message, "error");
718
794
  setTimeout(() => {this.actionMessage(false)}, 5000);
@@ -762,6 +838,73 @@ window.Caixanegra.Designer = {
762
838
  this.unitDebugHitsPane.classList.remove("-open");
763
839
  }
764
840
 
841
+ #dupObject(obj) {
842
+ if (typeof obj !== 'object' || obj === null) {
843
+ return obj;
844
+ }
845
+
846
+ const clonedObject = Array.isArray(obj) ? [] : {};
847
+
848
+ for (const key in obj) {
849
+ if (obj.hasOwnProperty(key)) {
850
+ clonedObject[key] = this.#dupObject(obj[key]);
851
+ }
852
+ }
853
+
854
+ return clonedObject;
855
+ }
856
+
857
+ #importFlow() {
858
+ document.querySelector("#importFlowFile").click();
859
+ }
860
+
861
+ #importFlowFileChanged() {
862
+ const fileInput = document.querySelector("#importFlowFile");
863
+
864
+ if (fileInput.files.length === 0) {
865
+ return;
866
+ }
867
+
868
+ const selectedFile = fileInput.files[0];
869
+ const reader = new FileReader();
870
+
871
+ reader.onload = (event) => {
872
+ this.toggleBlocker(true, "importing your flow");
873
+ this.gre.disable();
874
+ const flowBackup = this.flowSnapshot();
875
+
876
+ try {
877
+ this.#loadFlowFromJSON(JSON.parse(event.target.result));
878
+
879
+ this.actionMessage(true, "Flow imported", "ok");
880
+ setTimeout(() => {this.actionMessage(false)}, 4000);
881
+ } catch (error) {
882
+ this.#loadFlowFromJSON(flowBackup);
883
+
884
+ this.actionMessage(true, "Failed to import flow. Rolled back", "error");
885
+ setTimeout(() => {this.actionMessage(false)}, 4000);
886
+ }
887
+
888
+ this.gre.enable();
889
+ this.reveal();
890
+ };
891
+
892
+ reader.readAsText(selectedFile);
893
+ }
894
+
895
+ #exportFlow() {
896
+ const blob = new Blob([JSON.stringify(this.flowSnapshot())], { type: "application/json" });
897
+ const url = URL.createObjectURL(blob);
898
+ const link = document.createElement('a');
899
+ link.href = url;
900
+ link.download = "flow_definition.cxn";
901
+ link.click();
902
+ URL.revokeObjectURL(url);
903
+
904
+ this.actionMessage(true, "Flow exported", "ok");
905
+ setTimeout(() => {this.actionMessage(false)}, 4000);
906
+ }
907
+
765
908
  #executeFlow() {
766
909
  this.toggleBlocker(true, "saving your flow");
767
910
 
@@ -3,6 +3,10 @@ $accent-color: rgb(105, 105, 105);
3
3
  $accent-highlight-color: lighten($accent-color, 20%);
4
4
  $accent-contrast-color: darken($accent-color, 20%);
5
5
 
6
+ #importFlowFile {
7
+ display: none;
8
+ }
9
+
6
10
  #execution {
7
11
  position: fixed;
8
12
  display: flex;
@@ -37,6 +41,33 @@ $accent-contrast-color: darken($accent-color, 20%);
37
41
  }
38
42
  }
39
43
 
44
+ #io {
45
+ position: fixed;
46
+ display: flex;
47
+ flex-direction: row;
48
+ bottom: 8px;
49
+ right: 8px;
50
+
51
+ div {
52
+ cursor: pointer;
53
+ transition: all 250ms ease-in-out;
54
+ height: 18px;
55
+ width: 18px;
56
+ margin-left: 8px;
57
+
58
+ &:hover {
59
+ transform: scale(1.1);
60
+ }
61
+
62
+ svg {
63
+ path, polygon, line, polyline {
64
+ fill: white;
65
+ stroke: white;
66
+ }
67
+ }
68
+ }
69
+ }
70
+
40
71
  #addUnit {
41
72
  position: fixed;
42
73
  border-radius: 50%;
@@ -649,10 +680,25 @@ $accent-contrast-color: darken($accent-color, 20%);
649
680
  flex-direction: row;
650
681
  justify-content: space-between;
651
682
 
652
- .name {
653
- font-size: 1em;
654
- font-weight: bold;
683
+ .name-holder {
655
684
  width: 100%;
685
+ display: flex;
686
+ flex-direction: row;
687
+
688
+ .name {
689
+ font-size: 1em;
690
+ font-weight: bold;
691
+ }
692
+
693
+ #duplicate {
694
+ margin-left: 5px;
695
+ width: 10px;
696
+ cursor: pointer;
697
+
698
+ svg path {
699
+ fill: $accent-contrast-color;
700
+ }
701
+ }
656
702
  }
657
703
 
658
704
  .delete {
@@ -25,11 +25,28 @@
25
25
  </svg>
26
26
  </div>
27
27
  <div id="console">
28
- <svg version="1.1" id="XMLID_75_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve">
28
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve">
29
29
  <path d="M24,24H0V0h24V24z M2,22h20V2H6v2h16v2H2V22z M2,4h2V2H2V4z M16,16h-6v-2h6V16z M5.7,15.7l-1.4-1.4L6.6,12L4.3,9.7 l1.4-1.4L9.4,12L5.7,15.7z"/>
30
30
  </svg>
31
31
  </div>
32
32
  </div>
33
+ <div id="io">
34
+ <input type="file" id="importFlowFile" accept=".cxn">
35
+ <div id="importFlow">
36
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve">
37
+ <polyline id="primary" points="13 9 13 13 9 13" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></polyline>
38
+ <line id="primary-2" data-name="primary" x1="3" y1="3" x2="13" y2="13" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
39
+ <path id="primary-3" data-name="primary" d="M13.89,5H20a1,1,0,0,1,1,1V20a1,1,0,0,1-1,1H6a1,1,0,0,1-1-1V13.89" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
40
+ </svg>
41
+ </div>
42
+ <div id="exportFlow">
43
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve">
44
+ <polyline id="primary" points="17 3 21 3 21 7" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></polyline>
45
+ <line id="primary-2" data-name="primary" x1="11" y1="13" x2="21" y2="3" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
46
+ <path id="primary-3" data-name="primary" d="M19,13.89V20a1,1,0,0,1-1,1H4a1,1,0,0,1-1-1V6A1,1,0,0,1,4,5h6.11" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
47
+ </svg>
48
+ </div>
49
+ </div>
33
50
  <div id="addUnit"><span>+</span></div>
34
51
  <div id="save"><span>save</span></div>
35
52
  <div id="unitMenu"></div>
@@ -51,7 +68,14 @@
51
68
  <div class="color-code"></div>
52
69
  <div class="content">
53
70
  <div id="unitDetailClass" class="unit-detail-headers">
54
- <div class="name"></div>
71
+ <div class="name-holder">
72
+ <div class="name"></div>
73
+ <span id="duplicate">
74
+ <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
75
+ <path d="M6 6V2c0-1.1.9-2 2-2h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-4v4a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V8c0-1.1.9-2 2-2h4zm2 0h4a2 2 0 0 1 2 2v4h4V2H8v4zM2 8v10h10V8H2zm4 4v-2h2v2h2v2H8v2H6v-2H4v-2h2z"/>
76
+ </svg>
77
+ </span>
78
+ </div>
55
79
  <div class="delete">
56
80
  <div id="toggler">
57
81
  <svg viewBox="0 0 56 56" xmlns="http://www.w3.org/2000/svg">
@@ -1,7 +1,7 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Caixanegra</title>
4
+ <title>caixanegra flow designer</title>
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <%= csrf_meta_tags %>
7
7
  <%= csp_meta_tag %>
@@ -3,7 +3,7 @@
3
3
  module Caixanegra
4
4
  class Manager
5
5
  class << self
6
- def handler(flow_definition = {})
6
+ def handler(flow_definition: {})
7
7
  transient_store = Caixanegra.transient_store
8
8
  uid = transient_store.new_uid
9
9
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Caixanegra
4
- VERSION = '0.3.4'
4
+ VERSION = '0.4.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: caixanegra
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - sergiorribeiro
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-20 00:00:00.000000000 Z
11
+ date: 2023-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails