caixanegra 0.3.4 → 0.4.1

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: 182cc119673359db756adcca903e137025ed5283bdae367d7d6c662fc5fdc061
4
+ data.tar.gz: f075d702d2f361678a7775d286cb7f3b9ec1cb556778b22c4180588efe32d830
5
5
  SHA512:
6
- metadata.gz: 7eb792dd9e30f18921dd6ff8c4bab52a9c564f4f93204aeb8925bf5e0255093e64e00dfb9b16b9ac0f19991fccf9868157e17c788cafa66932ef5112f96b187d
7
- data.tar.gz: '09b9f690181fd28e25f5a8cd25ee1e3588af0c9efdadcd3597676fb80d9b93cbec43563d73a4086314f73fab34b11c27653cab163f10521121dd04c85a53a756'
6
+ metadata.gz: 29c0edd8fab4949cab99f6d513b987cda6c87b2fb29fbeb33e447616ff580c0a9fbd3413a2fe4281828f28a8ac1123942fcecbdbeedf7511c64cb000384bd797
7
+ data.tar.gz: 23e9e398d71b3900759d8a944f20ddf1920ac1f38bb5961d3e36a34d112e36a488301d3c4709de8c9cfc48a8a9f995869ebfea90ed20acf72915b6c61ac48345
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.
@@ -66,4 +66,20 @@ window.Caixanegra.API = class {
66
66
  request.send(JSON.stringify(initialCarryOver));
67
67
  });
68
68
  }
69
+
70
+ evaluateRegex(expression, sample) {
71
+ return new Promise((resolve, reject) => {
72
+ const request = new XMLHttpRequest();
73
+
74
+ request.addEventListener("load", (event) => {
75
+ resolve(JSON.parse(event.target.response));
76
+ });
77
+ request.addEventListener("error", () => {
78
+ reject();
79
+ });
80
+
81
+ request.open("GET", `${this.mountedPath()}/api/designer/inputs/evaluate_regex?expression=${encodeURIComponent(expression)}&sample=${encodeURIComponent(sample)}`);
82
+ request.send();
83
+ });
84
+ }
69
85
  }
@@ -303,6 +303,15 @@ 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));
310
+ document.querySelector("#regexInput").addEventListener("keyup", this.#updateRegexInput.bind(this));
311
+ document.querySelector("#regexSample").addEventListener("keyup", this.#startRegexEvaluation.bind(this));
312
+ document.querySelector("#addRegexVariables").addEventListener("click", this.#addRegexVariablesRow.bind(this))
313
+ document.querySelector("#regexEditorToggleVars").addEventListener("click", this.#toggleRegexVariables.bind(this));
314
+ document.querySelector("#dismissRegexEditor").addEventListener("click", this.#dismissRegexEditor.bind(this));
306
315
  this.toggleBlocker(true, "loading your flow");
307
316
  window.addEventListener("resize", this.#windowResized.bind(drawingSurface));
308
317
  drawingSurface.addEventListener("update_start", this.#engineUpdate.bind(this));
@@ -315,7 +324,6 @@ window.Caixanegra.Designer = {
315
324
 
316
325
  this.gre.disable();
317
326
  this.getUnits();
318
- this.loadFlow();
319
327
  this.gre.enable();
320
328
  }
321
329
 
@@ -385,6 +393,8 @@ window.Caixanegra.Designer = {
385
393
  }
386
394
 
387
395
  container.append(wrapper);
396
+
397
+ return wrapper;
388
398
  }
389
399
 
390
400
  deleteUnit() {
@@ -394,6 +404,12 @@ window.Caixanegra.Designer = {
394
404
  this.toggleDeleteConfirmation();
395
405
  }
396
406
 
407
+ duplicateUnit() {
408
+ this.unitDetailPane.classList.remove("-open");
409
+ this.unitDebugHitsPane.classList.remove("-open");
410
+ this.cloneUnit(this.selectedUnit.oid);
411
+ }
412
+
397
413
  flushToConsole(entries) {
398
414
  const container = document.querySelector("#executionConsole");
399
415
  container.innerHTML = "";
@@ -468,7 +484,8 @@ window.Caixanegra.Designer = {
468
484
  flowSnapshot() {
469
485
  const snapshot = {
470
486
  entrypoint: this.#units.find((unit) => {return unit.type === "starter"})?.oid,
471
- units: []
487
+ units: [],
488
+ initialCarryover: this.digInitialCarryOver(document.querySelector("#carryOverObject"))
472
489
  };
473
490
 
474
491
  this.#units.forEach((unit) => {
@@ -587,33 +604,85 @@ window.Caixanegra.Designer = {
587
604
  });
588
605
 
589
606
  this.#loadedComponents.catalog = true;
590
- this.#reveal();
607
+ this.loadFlow();
591
608
  });
592
609
  }
593
610
 
594
611
  loadFlow() {
595
612
  this.api.getFlow(this.flowId).then((response) => {
596
613
  const flowData = response;
597
- this.gre.clear();
598
- this.#units = [];
599
614
 
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
- });
615
+ this.#loadFlowFromJSON(flowData);
610
616
 
611
617
  this.#loadedComponents.flow = true;
612
- this.#reveal();
618
+ this.reveal();
619
+ });
620
+ }
621
+
622
+ #loadFlowFromJSON(flowData) {
623
+ this.gre.clear();
624
+ this.#units = [];
625
+
626
+ (flowData?.units || []).forEach((unit) => {
627
+ const catalogUnit = this.#catalog.find((cUnit) => cUnit.class === unit.class);
628
+ if (catalogUnit) {
629
+ unit.color = catalogUnit.color;
630
+ }
631
+ this.createUnit(unit);
632
+ });
633
+
634
+ this.#units.forEach((unit) => {
635
+ (unit?.exits || []).forEach((exit) => {
636
+ const object = this.#units.find((targetUnit) => targetUnit.oid === exit.target);
637
+ if (object) { exit.target = object; }
638
+ });
613
639
  });
640
+
641
+ this.buildInitialCarryover(flowData.initialCarryover);
614
642
  }
615
643
 
616
- #reveal() {
644
+ digBuildInitialCarryover(container, key, objectPointer) {
645
+ const lastChild = container.childNodes[container.childNodes.length -1];
646
+
647
+ if (key !== null) {
648
+ container.childNodes[0].querySelector("input").value = key;
649
+ }
650
+
651
+ if (Array.isArray(objectPointer)) {
652
+ this.coeAssignType(lastChild, "array");
653
+
654
+ for (let idx = 0; idx < objectPointer.length; idx++) {
655
+ this.digBuildInitialCarryover(
656
+ this.coeAddRow(container.querySelector(".rows")),
657
+ null,
658
+ objectPointer[idx]
659
+ );
660
+ }
661
+ } else if (typeof objectPointer === "object") {
662
+ this.coeAssignType(lastChild, "object");
663
+
664
+ for (const key in objectPointer) {
665
+ this.digBuildInitialCarryover(this.coeAddRow(container.querySelector(".rows")), key, objectPointer[key]);
666
+ }
667
+ } else if (typeof objectPointer === "number") {
668
+ this.coeAssignType(lastChild, "number");
669
+ lastChild.querySelector("input").value = objectPointer;
670
+ } else {
671
+ this.coeAssignType(lastChild, "string");
672
+ lastChild.querySelector("input").value = objectPointer;
673
+ }
674
+ }
675
+
676
+ buildInitialCarryover(initialCarryover) {
677
+ this.clearCarryOverObject();
678
+ const base = document.querySelector("#carryOverObject > .rows");
679
+
680
+ for (const key in initialCarryover) {
681
+ this.digBuildInitialCarryover(this.coeAddRow(base), key, initialCarryover[key]);
682
+ }
683
+ }
684
+
685
+ reveal() {
617
686
  if (this.#loadedComponents.flow && this.#loadedComponents.catalog) {
618
687
  this.toggleBlocker(false);
619
688
  }
@@ -713,6 +782,21 @@ window.Caixanegra.Designer = {
713
782
  });
714
783
  }
715
784
 
785
+ cloneUnit(oid) {
786
+ const unit = this.#units.find((unit) => unit.oid === oid);
787
+ const dup = this.#dupObject(unit);
788
+
789
+ dup.oid = Sabertooth.Utils.generateOId();
790
+ dup.position = { x: unit.position.x + 25, y: unit.position.y + 25 };
791
+ dup.title = `${dup.title} (copy)`;
792
+
793
+ dup.exits.forEach(exit => {
794
+ delete exit.target;
795
+ });
796
+
797
+ this.createUnit(dup);
798
+ }
799
+
716
800
  showError(message) {
717
801
  this.actionMessage(true, message, "error");
718
802
  setTimeout(() => {this.actionMessage(false)}, 5000);
@@ -762,6 +846,73 @@ window.Caixanegra.Designer = {
762
846
  this.unitDebugHitsPane.classList.remove("-open");
763
847
  }
764
848
 
849
+ #dupObject(obj) {
850
+ if (typeof obj !== 'object' || obj === null) {
851
+ return obj;
852
+ }
853
+
854
+ const clonedObject = Array.isArray(obj) ? [] : {};
855
+
856
+ for (const key in obj) {
857
+ if (obj.hasOwnProperty(key)) {
858
+ clonedObject[key] = this.#dupObject(obj[key]);
859
+ }
860
+ }
861
+
862
+ return clonedObject;
863
+ }
864
+
865
+ #importFlow() {
866
+ document.querySelector("#importFlowFile").click();
867
+ }
868
+
869
+ #importFlowFileChanged() {
870
+ const fileInput = document.querySelector("#importFlowFile");
871
+
872
+ if (fileInput.files.length === 0) {
873
+ return;
874
+ }
875
+
876
+ const selectedFile = fileInput.files[0];
877
+ const reader = new FileReader();
878
+
879
+ reader.onload = (event) => {
880
+ this.toggleBlocker(true, "importing your flow");
881
+ this.gre.disable();
882
+ const flowBackup = this.flowSnapshot();
883
+
884
+ try {
885
+ this.#loadFlowFromJSON(JSON.parse(event.target.result));
886
+
887
+ this.actionMessage(true, "Flow imported", "ok");
888
+ setTimeout(() => {this.actionMessage(false)}, 4000);
889
+ } catch (error) {
890
+ this.#loadFlowFromJSON(flowBackup);
891
+
892
+ this.actionMessage(true, "Failed to import flow. Rolled back", "error");
893
+ setTimeout(() => {this.actionMessage(false)}, 4000);
894
+ }
895
+
896
+ this.gre.enable();
897
+ this.reveal();
898
+ };
899
+
900
+ reader.readAsText(selectedFile);
901
+ }
902
+
903
+ #exportFlow() {
904
+ const blob = new Blob([JSON.stringify(this.flowSnapshot())], { type: "application/json" });
905
+ const url = URL.createObjectURL(blob);
906
+ const link = document.createElement('a');
907
+ link.href = url;
908
+ link.download = "flow_definition.cxn";
909
+ link.click();
910
+ URL.revokeObjectURL(url);
911
+
912
+ this.actionMessage(true, "Flow exported", "ok");
913
+ setTimeout(() => {this.actionMessage(false)}, 4000);
914
+ }
915
+
765
916
  #executeFlow() {
766
917
  this.toggleBlocker(true, "saving your flow");
767
918
 
@@ -1135,9 +1286,10 @@ window.Caixanegra.Designer = {
1135
1286
  case "regex":
1136
1287
  {
1137
1288
  valueInput = document.createElement("input");
1138
- if(matrix.type === "regex") { valueInput.classList.add("-monospace")}
1289
+ if(matrix.type === "regex") { valueInput.classList.add("-regex")}
1139
1290
  valueInput.value = unit.mappings[input]?.value || matrix.default || "";
1140
1291
  valueInput.addEventListener("change", this.#unitInputValueChanged.bind(this));
1292
+ valueInput.addEventListener("click", this.#regexEditor.bind(this));
1141
1293
  }
1142
1294
  break;
1143
1295
  case "dataset":
@@ -1237,6 +1389,125 @@ window.Caixanegra.Designer = {
1237
1389
  this.selectedUnit.mappings[input].value = ev.target.value;
1238
1390
  }
1239
1391
 
1392
+ #toggleRegexVariables() {
1393
+ const target = document.querySelector("#blocker .regexEditor > div:last-child");
1394
+ if(target.classList.contains("-visible")) {
1395
+ target.classList.remove("-visible");
1396
+ } else {
1397
+ target.classList.add("-visible");
1398
+ }
1399
+ }
1400
+
1401
+ #updateRegexInput() {
1402
+ this.blocker.editorListener.value = this.blocker.querySelector("#regexInput").value;
1403
+
1404
+ this.#startRegexEvaluation();
1405
+ }
1406
+
1407
+ #startRegexEvaluation() {
1408
+ window.clearTimeout(this.blocker.evalTimeout);
1409
+ this.blocker.querySelector("#regexResults").innerHTML = "wating input end...";
1410
+ this.blocker.evalTimeout = window.setTimeout(this.#evaluateRegex.bind(this), 1500);
1411
+ }
1412
+
1413
+ #evaluateRegex() {
1414
+ this.blocker.querySelector("#regexResults").innerHTML = "evaluating...";
1415
+ let expression = this.blocker.querySelector("#regexInput").value;
1416
+ const sample = this.blocker.querySelector("#regexSample").value;
1417
+ document.querySelectorAll("#regexVariablesList .regex-variable-entry").forEach((pair) => {
1418
+ const placeholder = pair.querySelector("input.placeholder").value;
1419
+ const value = pair.querySelector("input.value").value;
1420
+ expression = expression.replace(new RegExp(placeholder, "g"), value);
1421
+ });
1422
+
1423
+ this.api.evaluateRegex(expression, sample).then((response) => {
1424
+ const tableEntries = Object.keys(response).length - 2 > 0;
1425
+
1426
+ this.blocker.querySelector("#regexResults").innerHTML = "";
1427
+ const expression = document.createElement("div");
1428
+ const full_match = document.createElement("div");
1429
+ expression.innerHTML = `<strong>Expression: </strong><code>${response["_expression"] || "unable to get expression"}</code>`;
1430
+ full_match.innerHTML = `<strong>Full Match: </strong><code>${response["_full"] || "unable to get full match"}</code>`;
1431
+
1432
+ this.blocker.querySelector("#regexResults").append(expression, full_match);
1433
+
1434
+ if (tableEntries) {
1435
+ const table = document.createElement("table");
1436
+ let tr = document.createElement("tr");
1437
+ let th = document.createElement("th");
1438
+ th.innerHTML = "capture";
1439
+ tr.append(th);
1440
+ th = document.createElement("th");
1441
+ th.innerHTML = "match";
1442
+ tr.append(th);
1443
+ table.append(tr);
1444
+
1445
+ for (const key of Object.keys(response)) {
1446
+ if (key.startsWith("_")) { continue; }
1447
+
1448
+ tr = document.createElement("tr");
1449
+ let td = document.createElement("td");
1450
+ td.innerHTML = key;
1451
+ tr.append(td);
1452
+ td = document.createElement("td");
1453
+ td.innerHTML = response[key];
1454
+ tr.append(td);
1455
+
1456
+ table.append(tr);
1457
+ }
1458
+
1459
+ this.blocker.querySelector("#regexResults").append(table);
1460
+ }
1461
+ }).catch((error) => {
1462
+ this.blocker.querySelector("#regexResults").innerHTML = "unable to evaluate";
1463
+ });
1464
+ }
1465
+
1466
+ #regexEditor(ev) {
1467
+ this.blocker.classList.add("-in-regex-editor");
1468
+ this.blocker.classList.remove("-released");
1469
+ this.blocker.editorListener = ev.target;
1470
+ this.blocker.querySelector("#regexInput").value = ev.target.value;
1471
+
1472
+ this.#startRegexEvaluation();
1473
+ }
1474
+
1475
+ #dismissRegexEditor() {
1476
+ this.blocker.classList.remove("-in-regex-editor");
1477
+ this.blocker.classList.add("-released");
1478
+ }
1479
+
1480
+ #removeRegexVariable() {
1481
+ this.parentElement.remove();
1482
+ }
1483
+
1484
+ #addRegexVariablesRow() {
1485
+ const list = document.querySelector("#regexVariablesList");
1486
+
1487
+ const wrapper = document.createElement("div");
1488
+ const name = document.createElement("input");
1489
+ const value = document.createElement("input");
1490
+ const remove = document.createElement("button");
1491
+
1492
+ name.placeholder = "placeholder";
1493
+ name.classList.add("placeholder");
1494
+ value.classList.add("value");
1495
+ name.autocomplete=false;
1496
+ value.autocomplete=false;
1497
+ value.placeholder = "value";
1498
+ remove.innerHTML = "-";
1499
+
1500
+ remove.addEventListener("click", this.#removeRegexVariable.bind(remove));
1501
+
1502
+ name.addEventListener("keyup", this.#startRegexEvaluation.bind(this));
1503
+ value.addEventListener("keyup", this.#startRegexEvaluation.bind(this));
1504
+
1505
+ wrapper.classList.add("regex-variable-entry");
1506
+
1507
+ wrapper.append(name, value, remove);
1508
+ list.append(wrapper);
1509
+ }
1510
+
1240
1511
  #addUnitExitMapping(ev) {
1241
1512
  const unit = this.selectedUnit;
1242
1513
  const mapping = { use: "", as: "" };
@@ -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%;
@@ -507,6 +538,7 @@ $accent-contrast-color: darken($accent-color, 20%);
507
538
  }
508
539
 
509
540
  input {
541
+ position: relative;
510
542
  width: 100%;
511
543
  box-sizing: border-box;
512
544
  background-color: $accent-highlight-color;
@@ -514,7 +546,7 @@ $accent-contrast-color: darken($accent-color, 20%);
514
546
  border: none;
515
547
  outline: none;
516
548
 
517
- &.-monospace {
549
+ &.-regex {
518
550
  font-family: monospace;
519
551
  }
520
552
  }
@@ -649,10 +681,25 @@ $accent-contrast-color: darken($accent-color, 20%);
649
681
  flex-direction: row;
650
682
  justify-content: space-between;
651
683
 
652
- .name {
653
- font-size: 1em;
654
- font-weight: bold;
684
+ .name-holder {
655
685
  width: 100%;
686
+ display: flex;
687
+ flex-direction: row;
688
+
689
+ .name {
690
+ font-size: 1em;
691
+ font-weight: bold;
692
+ }
693
+
694
+ #duplicate {
695
+ margin-left: 5px;
696
+ width: 10px;
697
+ cursor: pointer;
698
+
699
+ svg path {
700
+ fill: $accent-contrast-color;
701
+ }
702
+ }
656
703
  }
657
704
 
658
705
  .delete {
@@ -744,7 +791,7 @@ $accent-contrast-color: darken($accent-color, 20%);
744
791
  left:0;
745
792
  right:0;
746
793
  bottom:0;
747
- background-color: rgb(0,0,0);
794
+ background-color: rgba(0,0,0, .5);
748
795
  transition: opacity 250ms ease-in-out;
749
796
  opacity: 1;
750
797
 
@@ -771,6 +818,155 @@ $accent-contrast-color: darken($accent-color, 20%);
771
818
  }
772
819
  }
773
820
 
821
+ .regexEditor {
822
+ position: absolute;
823
+ top: 50%;
824
+ left: 50%;
825
+ transform: translate(-50%, -50%);
826
+ display: none;
827
+ font-family: Verdana, Geneva, Tahoma, sans-serif;
828
+ min-width: 70%;
829
+ flex-direction: row;
830
+ align-items: center;
831
+ justify-content: center;
832
+
833
+ & > div {
834
+ background-color: $accent-color;
835
+ box-shadow: $accent-shadow;
836
+ border-radius: 5px;
837
+ display: flex;
838
+ flex-direction: column;
839
+ padding: 12px;
840
+ position: relative;
841
+ font-size: 16px;
842
+
843
+ &:first-child {
844
+ margin-right: 15px;
845
+ width: 70%;
846
+ z-index: 10;
847
+ font-size: 0.8em;
848
+ }
849
+
850
+ &:last-child {
851
+ font-size: 0.8em;
852
+ transform: translateX(-200%);
853
+ width: 0;
854
+ box-shadow: 0 0 0 rgba(0,0,0,0);
855
+ z-index: 9;
856
+ overflow: hidden;
857
+ transition: all 250ms ease-in-out;
858
+
859
+ &.-visible {
860
+ transform: translateX(0%);
861
+ width: 30%;
862
+ }
863
+ }
864
+ }
865
+
866
+ label {
867
+ margin-bottom: 4px;
868
+ }
869
+
870
+ input {
871
+ margin-bottom: 6px;
872
+ box-sizing: border-box;
873
+ background-color: #9c9c9c;
874
+ padding: 6px 4px;
875
+ border: none;
876
+ outline: none;
877
+ }
878
+
879
+ #regexInput {
880
+ font-family: monospace;
881
+ font-size: 1.1em;
882
+ }
883
+
884
+ #regexSample {
885
+ font-size: 0.8em;
886
+ }
887
+
888
+ #regexResults {
889
+ padding: 8px;
890
+ background-color:rgb(200, 200, 200);
891
+ color: black;
892
+ max-height: 50vh;
893
+ overflow: auto;
894
+ display: block;
895
+ margin-bottom: 30px;
896
+ font-size: 0.7em;
897
+
898
+ table {
899
+ margin-top: 15px;
900
+ width: 100%;
901
+ border-collapse: collapse;
902
+
903
+ th, td {
904
+ text-align: left;
905
+ border: 1px solid rgba(0,0,0,.3);
906
+ padding: 4px;
907
+ }
908
+
909
+ tr:nth-child(even) {
910
+ background-color: rgba(0,0,0,.15);
911
+ }
912
+ }
913
+ }
914
+
915
+ #regexVariables {
916
+ #regexVariablesList {
917
+ margin-bottom: 15px;
918
+ }
919
+
920
+ .regex-variable-entry {
921
+ display: flex;
922
+ flex-direction: row;
923
+ margin: 2px 0;
924
+ align-items: center;
925
+
926
+ input {
927
+ width: 100%;
928
+ margin-bottom: 0;
929
+
930
+ &:first-child {
931
+ margin-right: 5px;
932
+ }
933
+ }
934
+
935
+ button {
936
+ margin-left: 5px;
937
+ }
938
+ }
939
+ }
940
+
941
+ button {
942
+ background-color: $accent-highlight-color;
943
+ border: 1px solid $accent-color;
944
+ color: black;
945
+ padding: 3px 6px;
946
+ cursor: pointer;
947
+ transition: all 250ms ease-in-out;
948
+
949
+ &:hover {
950
+ background-color: $accent-contrast-color;
951
+ color: $accent-highlight-color;
952
+ }
953
+ }
954
+
955
+ #regexEditorToggleVars {
956
+ float: right;
957
+ }
958
+ }
959
+
960
+ &.-in-regex-editor {
961
+ .information {
962
+ display: none;
963
+ }
964
+
965
+ .regexEditor {
966
+ display: flex;
967
+ }
968
+ }
969
+
774
970
  &.-released {
775
971
  pointer-events: none;
776
972
  opacity: 0;
@@ -0,0 +1,32 @@
1
+ module Caixanegra
2
+ module API
3
+ module Designer
4
+ class InputsController < ::Caixanegra::APIController
5
+ def evaluate_regex
6
+ full_match = sample.match(Regexp.new(expression, Regexp::IGNORECASE))
7
+ direct = { "_full": full_match.to_s, "_expression": expression }
8
+ captures = {}
9
+
10
+ (full_match&.captures || []).each_with_index do |capture, index|
11
+ captures["c#{index}"] = capture
12
+ end
13
+ named_captures = full_match&.named_captures || {}
14
+
15
+ render json: direct.merge(captures).merge(named_captures).symbolize_keys
16
+ rescue RegexpError
17
+ head 422
18
+ end
19
+
20
+ private
21
+
22
+ def expression
23
+ @expression ||= params[:expression]
24
+ end
25
+
26
+ def sample
27
+ @sample ||= params[:sample]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -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">
@@ -84,6 +108,27 @@
84
108
  <div>caixa<span>negra</span></div>
85
109
  <div class="message">loading your flow</div>
86
110
  </div>
111
+ <div class="regexEditor">
112
+ <div>
113
+ <label for="regexInput">Regular Expression</label>
114
+ <input id="regexInput" type="text" />
115
+ <label for="regexSample" placeholder="type sample text here">Sample</label>
116
+ <input id="regexSample" type="text" />
117
+ <label for="regexResults">Result</label>
118
+ <div id="regexResults"></div>
119
+ <div>
120
+ <button id="dismissRegexEditor">dismiss</button>
121
+ <button id="regexEditorToggleVars">vars</button>
122
+ </div>
123
+ </div>
124
+ <div>
125
+ <label for="regexVariables">Variables</label>
126
+ <div id="regexVariables">
127
+ <div id="regexVariablesList"></div>
128
+ <button id="addRegexVariables">add</button>
129
+ </div>
130
+ </div>
131
+ </div>
87
132
  </div>
88
133
  <div id="action_messenger">
89
134
  <div class="icon icon-error">
@@ -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 %>
data/config/routes.rb CHANGED
@@ -9,6 +9,9 @@ Caixanegra::Engine.routes.draw do
9
9
  patch :debug_run
10
10
  end
11
11
  end
12
+ namespace :inputs do
13
+ get :evaluate_regex
14
+ end
12
15
  end
13
16
  end
14
17
  end
@@ -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.1'
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.1
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-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -57,6 +57,7 @@ files:
57
57
  - app/assets/stylesheets/caixanegra/application.css
58
58
  - app/assets/stylesheets/caixanegra/designer.scss
59
59
  - app/controllers/caixanegra/api/designer/flows_controller.rb
60
+ - app/controllers/caixanegra/api/designer/inputs_controller.rb
60
61
  - app/controllers/caixanegra/api/designer/units_controller.rb
61
62
  - app/controllers/caixanegra/api_controller.rb
62
63
  - app/controllers/caixanegra/application_controller.rb