caixanegra 0.3.4 → 0.4.1

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: 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