caixanegra 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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