caixanegra 0.3.3 → 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: '085b4f44d1382bda853205ecb18725fd905576b427eb395aa76a8d1dbab1b564'
4
- data.tar.gz: 0b986cb888ef179de7052e789055232e77777082a07f9ddc5aabe5424fa93295
3
+ metadata.gz: 002efa6f5c1a220426d102e5f055b50eff9fb55e5742d6c2c33d6474f890fd8a
4
+ data.tar.gz: 03c265dbfa950af4d043bfc56711593b2cb1661f76fc2ca40327fbc7a7f5e309
5
5
  SHA512:
6
- metadata.gz: 39a3f318ef80818f3b1a5d447884cfa4e8ee650f21eb25dd9c7cd06d7355395b6a5c6858afa96dee8f482574fd07acc6cf102040f98b9840278145fd80f1c6aa
7
- data.tar.gz: 316312a2e90e971956ff6968eb5fa3d376ba9a331037d260d53fec5679a414700cb357c95bcefd994ac2a41cb81bff7883f6c048ec828246688c06ce23ba1a80
6
+ metadata.gz: da48c854b8646515af968b7c685cece267a5bed236f1f58d5331de39a2d75bded3f0896b84d09de7cbe70c0fad7f388ef5891684bc04f45995e0b1afdc9d1784
7
+ data.tar.gz: 28b98d23640368db0f32475c4cd6209fb39648e723e63d70ae2fd814e88c05ca6c9033dccc1accfa3c2c331413569fb54e1b52f115cc26841703019dec4fa0a2
data/README.md CHANGED
@@ -32,18 +32,21 @@ Caixanegra.setup do |config|
32
32
  config.units = [
33
33
  Caixanegra::Units::AwesomeUnit,
34
34
  Some::Other::Namespace::AnotherAwesomeUnit,
35
- Caixanegra::Units::SuperUnit,
35
+ [Caixanegra::Units::NamespaceOne::SuperUnit, :ns1_super_unit],
36
+ [Caixanegra::Units::NamespaceTwo::SuperUnit, :ns2_super_unit],
36
37
  ]
37
38
  config.transient_store = GreatTransientStore.new
38
39
  end
39
40
  ```
41
+ While configuring units from your codebase, if provided in the array form, **caixanegra** will use the second array item as the class name. Otherwise it will infer from class.
42
+ **NOTE:** You can't have two classes with the same name as **caixanegra** will use the last one
40
43
 
41
44
  With the designer configured, you can use `Caixanegra::Manager` to handle the previously stored definition (or an empty one).
42
45
  This will give you the UID that **caixanegra** designer will understand, using transient store.
43
46
 
44
47
  ```ruby
45
48
  my_flow = somewhere.get_flow # get from your own persistence or transport solution
46
- uid = Caixanegra::Manager.handler(my_flow || {})
49
+ uid = Caixanegra::Manager.handler(flow_definition: my_flow || {})
47
50
  ```
48
51
 
49
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
  }
@@ -652,7 +713,10 @@ window.Caixanegra.Designer = {
652
713
  createUnit(params) {
653
714
  this.#sequence++;
654
715
  const newUnit = new Caixanegra.Designer.Unit({
655
- position: new Sabertooth.Vector2(20, 20),
716
+ position: new Sabertooth.Vector2(
717
+ -this.gre.worldCenter.x + 20,
718
+ -this.gre.worldCenter.y + 20
719
+ ),
656
720
  type: params.type,
657
721
  title: params.title,
658
722
  class: params.class,
@@ -710,6 +774,21 @@ window.Caixanegra.Designer = {
710
774
  });
711
775
  }
712
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
+
713
792
  showError(message) {
714
793
  this.actionMessage(true, message, "error");
715
794
  setTimeout(() => {this.actionMessage(false)}, 5000);
@@ -759,6 +838,73 @@ window.Caixanegra.Designer = {
759
838
  this.unitDebugHitsPane.classList.remove("-open");
760
839
  }
761
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
+
762
908
  #executeFlow() {
763
909
  this.toggleBlocker(true, "saving your flow");
764
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
 
@@ -6,8 +6,13 @@ module Caixanegra
6
6
 
7
7
  scopes = (scope || "").split(",").map(&:to_sym)
8
8
  Caixanegra.units.each do |unit|
9
- if unit.scope.nil? || unit.scope.any? { |checking_scope| scopes.include?(checking_scope) }
10
- units[unit.name.demodulize.underscore.to_sym] = unit
9
+ unit_scope = unit.is_a?(Array) ? unit[0].scope : unit.scope
10
+ if unit_scope.nil? || unit_scope.any? { |checking_scope| scopes.include?(checking_scope) }
11
+ if unit.is_a? Array
12
+ units[unit[1]] = unit[0]
13
+ else
14
+ units[unit.name.demodulize.underscore.to_sym] = unit
15
+ end
11
16
  end
12
17
  end
13
18
 
@@ -18,11 +23,15 @@ module Caixanegra
18
23
  units = {}
19
24
 
20
25
  Caixanegra.units.each do |unit|
21
- units[unit.name.demodulize.underscore.to_sym] = unit
26
+ if unit.is_a? Array
27
+ units[unit[1]] = unit[0]
28
+ else
29
+ units[unit.name.demodulize.underscore.to_sym] = unit
30
+ end
22
31
  end
23
32
 
24
33
  units
25
34
  end
26
35
  end
27
36
  end
28
- end
37
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Caixanegra
4
- VERSION = '0.3.3'
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.3
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-04 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