@abi-software/simulationvuer 2.0.8 → 2.0.9

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.
@@ -1,9 +1,9 @@
1
1
  <template>
2
2
  <div class="simulation-vuer" v-loading="showUserMessage" :element-loading-text="userMessage">
3
- <p v-if="!hasValidSimulationUiInfo && !showUserMessage" class="default error"><span class="error">Error:</span> an unknown or invalid model was provided.</p>
3
+ <p v-if="!hasValidSimulationUiInfo && !showUserMessage" class="default error"><span class="error">Error:</span> {{ errorMessage }}.</p>
4
4
  <div class="main" v-if="hasValidSimulationUiInfo">
5
5
  <div class="main-left">
6
- <p class="default name" v-if="!libopencorSet">{{name}}</p>
6
+ <p class="default name" v-if="!libopencorSet">{{ name }}</p>
7
7
  <el-divider v-if="!libopencorSet"></el-divider>
8
8
  <p class="default input-parameters">Input parameters</p>
9
9
  <div class="input scrollbar">
@@ -36,7 +36,7 @@
36
36
  <PlotVuer v-for="(_outputPlot, index) in simulationUiInfo.output.plots"
37
37
  :key="`output-${index}`"
38
38
  :metadata="plotMetadata(index)"
39
- :data-source="{data: simulationResults[index]}"
39
+ :data-source="{ data: simulationResults[index] }"
40
40
  :plotLayout="layout[index]"
41
41
  :plotType="'plotly-only'"
42
42
  :selectorUi="false"
@@ -105,7 +105,7 @@ export default {
105
105
  default: undefined,
106
106
  },
107
107
  },
108
- data: function() {
108
+ data: function () {
109
109
  // Retrieve some information about the dataset.
110
110
 
111
111
  if (this.id > 0) {
@@ -118,7 +118,7 @@ export default {
118
118
  const datasetInfo = JSON.parse(xmlhttp.responseText);
119
119
 
120
120
  this.name = datasetInfo.name;
121
- this.uuid = (datasetInfo.study !== undefined)?datasetInfo.study.uuid:undefined;
121
+ this.uuid = (datasetInfo.study !== undefined) ? datasetInfo.study.uuid : undefined;
122
122
  }
123
123
  }
124
124
  };
@@ -221,10 +221,16 @@ export default {
221
221
 
222
222
  const res = {};
223
223
  const instanceTask = this.instance.tasks().get(0);
224
+ let foundAllOutputs = true;
225
+ let foundOutput;
224
226
 
225
227
  for (const output of this.outputData()) {
228
+ foundOutput = false;
229
+
226
230
  if (output === instanceTask.voiName()) {
227
231
  res[output] = instanceTask.voiAsArray();
232
+
233
+ foundOutput = true;
228
234
  }
229
235
 
230
236
  if (res[output] === undefined) {
@@ -232,23 +238,38 @@ export default {
232
238
  if (output === instanceTask.stateName(i)) {
233
239
  res[output] = instanceTask.stateAsArray(i);
234
240
 
241
+ foundOutput = true;
242
+
235
243
  break;
236
244
  }
237
245
  }
238
246
  }
239
247
 
240
248
  if (res[output] === undefined) {
241
- for (let i = 0; instanceTask.variableCount(); ++i) {
249
+ for (let i = 0; i < instanceTask.variableCount(); ++i) {
242
250
  if (output === instanceTask.variableName(i)) {
243
251
  res[output] = instanceTask.variableAsArray(i);
244
252
 
253
+ foundOutput = true;
254
+
245
255
  break;
246
256
  }
247
257
  }
248
258
  }
259
+
260
+ if (!foundOutput) {
261
+ console.warn("SIMULATION: output '" + output + "' could not be found.");
262
+
263
+ foundAllOutputs = false;
264
+ }
249
265
  }
250
266
 
251
- this.processSimulationResults(res);
267
+ if (foundAllOutputs) {
268
+ this.processSimulationResults(res);
269
+ } else {
270
+ this.hasValidSimulationUiInfo = false;
271
+ this.errorMessage = "some outputs could not be found";
272
+ }
252
273
 
253
274
  this.showUserMessage = false;
254
275
  },
@@ -267,6 +288,8 @@ export default {
267
288
  this.hasValidSimulationUiInfo = validJson(this.simulationUiInfo, this.libopencor === undefined);
268
289
 
269
290
  if (!this.hasValidSimulationUiInfo) {
291
+ this.errorMessage = "the simulation.json file is malformed";
292
+
270
293
  return;
271
294
  }
272
295
 
@@ -281,7 +304,8 @@ export default {
281
304
  });
282
305
 
283
306
  if (this.solver === undefined) {
284
- console.warn("SIMULATION: no solver name and/or solver version specified.");
307
+ this.hasValidSimulationUiInfo = false;
308
+ this.errorMessage = "no solver name and/or solver version specified";
285
309
 
286
310
  return;
287
311
  }
@@ -289,18 +313,6 @@ export default {
289
313
  this.opencorBasedSimulation = this.solver.name === OPENCOR_SOLVER_NAME;
290
314
  }
291
315
 
292
- // Run the model if we are dealing with a PMR-based COMBINE archive or a
293
- // COMBINE archive that was passed directly.
294
-
295
- if (this.libopencor !== undefined) {
296
- this.userMessage = "Running the model...";
297
- this.showUserMessage = true;
298
-
299
- this.$nextTick(() => {
300
- this.runSimulation();
301
- });
302
- }
303
-
304
316
  // Initialise our UI.
305
317
 
306
318
  this.simulationUiInfo.output.data.forEach((data) => {
@@ -357,6 +369,40 @@ export default {
357
369
  finaliseUi(this);
358
370
  });
359
371
  },
372
+ /**
373
+ * @public
374
+ * Extract the simulation UI JSON file from the given file contents and build the simulation UI.
375
+ * @arg `libopencor`
376
+ * @arg `fileContents`
377
+ */
378
+ extractAndBuildSimulationUi(libopencor, fileContents) {
379
+ this.libopencor = markRaw(libopencor);
380
+ this.libopencorSet = true;
381
+ this.fileManager = markRaw(this.libopencor.FileManager.instance());
382
+
383
+ const file = this.manageFile(PMR_URL + this.id, fileContents);
384
+
385
+ if (file.type().value !== libopencor.File.Type.COMBINE_ARCHIVE.value) {
386
+ this.showUserMessage = false;
387
+ } else {
388
+ const decoder = new TextDecoder();
389
+ const simulationJson = file.childFile("simulation.json");
390
+
391
+ if (simulationJson === null) {
392
+ this.errorMessage = "no simulation JSON file could be found";
393
+
394
+ this.showUserMessage = false;
395
+ } else {
396
+ const simulationUiInfo = JSON.parse(decoder.decode(simulationJson.contents()));
397
+
398
+ this.showUserMessage = false;
399
+
400
+ this.$nextTick(() => {
401
+ this.buildSimulationUi(simulationUiInfo);
402
+ });
403
+ }
404
+ }
405
+ },
360
406
  /**
361
407
  * @public
362
408
  * Run the simulation-based dataset directly on oSPARC. Not all simulation-based datasets can be run directly on
@@ -401,9 +447,9 @@ export default {
401
447
  * @public
402
448
  * Data needed to specify the model output.
403
449
  */
404
- outputData() {
450
+ outputData() {
405
451
  if (this.output === undefined) {
406
- if (this.simulationUiInfo.output.data !== undefined) {
452
+ if (this.simulationUiInfo.output.data !== undefined) {
407
453
  this.output = [];
408
454
 
409
455
  this.simulationUiInfo.output.data.forEach((output) => {
@@ -429,8 +475,8 @@ export default {
429
475
  json_config: {},
430
476
  };
431
477
 
432
- if ( (this.simulationUiInfo.simulation.opencor.endingPoint !== undefined)
433
- && (this.simulationUiInfo.simulation.opencor.pointInterval !== undefined)) {
478
+ if ((this.simulationUiInfo.simulation.opencor.endingPoint !== undefined)
479
+ && (this.simulationUiInfo.simulation.opencor.pointInterval !== undefined)) {
434
480
  request.opencor.json_config.simulation = {
435
481
  "Ending point": this.simulationUiInfo.simulation.opencor.endingPoint,
436
482
  "Point interval": this.simulationUiInfo.simulation.opencor.pointInterval,
@@ -462,7 +508,7 @@ export default {
462
508
  // Convert, if needed, the results to a JSON format that is compatible
463
509
  // with our OpenCOR results.
464
510
 
465
- if (typeof(results) === "string") {
511
+ if (typeof (results) === "string") {
466
512
  const SPACES = /[ \t]+/g;
467
513
  const lines = results.trim().split("\n");
468
514
  const iMax = lines[0].trim().split(SPACES).length;
@@ -551,7 +597,7 @@ export default {
551
597
 
552
598
  let that = this;
553
599
 
554
- setTimeout(function() {
600
+ setTimeout(function () {
555
601
  that.checkSimulation(data);
556
602
  }, 1000);
557
603
  }
@@ -596,7 +642,7 @@ export default {
596
642
  this.checkSimulation(response.data);
597
643
  } else {
598
644
  this.showUserMessage = false;
599
- this.errorMessage = response.description + "ASDASDASD";
645
+ this.errorMessage = response.description;
600
646
  }
601
647
  } else {
602
648
  this.showHttpIssue(xmlhttp);
@@ -607,7 +653,7 @@ export default {
607
653
  });
608
654
  },
609
655
  },
610
- created: function() {
656
+ created: function () {
611
657
  // Try to retrieve the UI information.
612
658
 
613
659
  if (this.id > 0) {
@@ -628,6 +674,8 @@ export default {
628
674
  this.$nextTick(() => {
629
675
  this.buildSimulationUi(JSON.parse(xmlhttp.responseText));
630
676
  });
677
+ } else {
678
+ this.errorMessage = "the simulation dataset could not be retrieved";
631
679
  }
632
680
  }
633
681
  };
@@ -649,28 +697,15 @@ export default {
649
697
  if (xmlhttp.readyState === 4) {
650
698
  if (xmlhttp.status === 200) {
651
699
  libOpenCOR().then((libopencor) => {
652
- this.libopencor = markRaw(libopencor);
653
- this.libopencorSet = true;
654
- this.fileManager = markRaw(this.libopencor.FileManager.instance());
655
-
656
- const fileContents = Uint8Array.from(atob(xmlhttp.response), (c) => c.charCodeAt(0));
657
- const file = this.manageFile(PMR_URL + this.id, fileContents);
658
-
659
- const decoder = new TextDecoder();
660
- const simulationUiInfo = JSON.parse(decoder.decode(file.childFile("simulation.json").contents()));
661
-
662
- this.showUserMessage = false;
663
-
664
- this.$nextTick(() => {
665
- this.buildSimulationUi(simulationUiInfo);
666
- });
700
+ this.extractAndBuildSimulationUi(libopencor, Uint8Array.from(atob(xmlhttp.response), (c) => c.charCodeAt(0)));
667
701
  });
668
702
  } else {
703
+ this.errorMessage = "the COMBINE archive chould not be retrieved";
669
704
  this.showUserMessage = false;
670
705
  }
671
706
  }
672
707
  };
673
- xmlhttp.send(JSON.stringify({path: this.id}));
708
+ xmlhttp.send(JSON.stringify({ path: this.id }));
674
709
  });
675
710
  } else if (this.combineArchive !== undefined) {
676
711
  // Extract the simulation UI JSON file from the COMBINE archive and build
@@ -681,30 +716,14 @@ export default {
681
716
 
682
717
  this.$nextTick(() => {
683
718
  libOpenCOR().then((libopencor) => {
684
- this.libopencor = markRaw(libopencor);
685
- this.libopencorSet = true;
686
- this.fileManager = markRaw(this.libopencor.FileManager.instance());
687
-
688
- const fileContents = this.combineArchive;
689
- const file = this.manageFile(PMR_URL + this.id, fileContents);
690
-
691
- if (file.type().value !== libopencor.File.Type.COMBINE_ARCHIVE.value) {
692
- this.showUserMessage = false;
693
- } else {
694
- const decoder = new TextDecoder();
695
- const simulationUiInfo = JSON.parse(decoder.decode(file.childFile("simulation.json").contents()));
696
-
697
- this.showUserMessage = false;
698
-
699
- this.$nextTick(() => {
700
- this.buildSimulationUi(simulationUiInfo);
701
- });
702
- }
719
+ this.extractAndBuildSimulationUi(libopencor, this.combineArchive);
703
720
  });
704
721
  });
722
+ } else {
723
+ this.errorMessage = "an non-simulation dataset was provided";
705
724
  }
706
725
  },
707
- mounted: function() {
726
+ mounted: function () {
708
727
  // Finalise our UI.
709
728
  // Note: we try both here and in the created() function since we have no
710
729
  // idea how long it's going to take to retrieve the simulation UI
@@ -719,7 +738,6 @@ export default {
719
738
 
720
739
  <!-- Add "scoped" attribute to limit CSS to this component only -->
721
740
  <style scoped lang="scss">
722
-
723
741
  .simulation-vuer {
724
742
  --el-color-primary: #8300BF;
725
743
  --el-color-primary-light-7: #DAB3EC;
@@ -727,80 +745,101 @@ export default {
727
745
  --el-color-primary-light-9: #F3E6F9;
728
746
  }
729
747
 
730
- :deep( .el-button:hover) {
748
+ :deep(.el-button:hover) {
731
749
  box-shadow: -3px 2px 4px #00000040;
732
750
  }
733
- :deep( .el-divider) {
751
+
752
+ :deep(.el-divider) {
734
753
  margin: -8px 0 8px 0 !important;
735
754
  width: 210px;
736
755
  }
737
- :deep( .el-loading-spinner) {
756
+
757
+ :deep(.el-loading-spinner) {
738
758
  .path {
739
759
  stroke: #8300BF;
740
760
  }
741
- i, .el-loading-text {
761
+
762
+ i,
763
+ .el-loading-text {
742
764
  color: #8300BF;
743
765
  }
744
766
  }
767
+
745
768
  div.input {
746
769
  border: 1px solid #dcdfe6;
747
770
  padding: 4px;
748
771
  height: 300px;
749
772
  }
773
+
750
774
  div.main {
751
775
  display: grid;
752
776
  --mainLeftWidth: 243px;
753
777
  grid-template-columns: var(--mainLeftWidth) calc(100% - var(--mainLeftWidth));
754
778
  height: 100%;
755
779
  }
780
+
756
781
  div.main-left {
757
782
  border-right: 1px solid #dcdfe6;
758
783
  padding: 12px 20px 12px 12px;
759
784
  height: 100%;
760
785
  overflow: auto;
761
786
  }
787
+
762
788
  div.main-right.x1 {
763
789
  height: 100%;
764
790
  }
791
+
765
792
  div.main-right.x2 {
766
793
  height: 50%;
767
794
  }
795
+
768
796
  div.main-right.x3 {
769
797
  height: 33.333%;
770
798
  }
799
+
771
800
  div.main-right.x4 {
772
801
  height: 25%;
773
802
  }
803
+
774
804
  div.main-right.x5 {
775
805
  height: 20%;
776
806
  }
807
+
777
808
  div.main-right.x6 {
778
809
  height: 16.667%;
779
810
  }
811
+
780
812
  div.main-right.x7 {
781
813
  height: 14.286%;
782
814
  }
815
+
783
816
  div.main-right.x8 {
784
817
  height: 12.5%;
785
818
  }
819
+
786
820
  div.main-right.x9 {
787
821
  height: 11.111%;
788
822
  }
789
- :deep( div.main-right div.controls) {
823
+
824
+ :deep(div.main-right div.controls) {
790
825
  height: 0;
791
826
  }
827
+
792
828
  div.primary-button,
793
829
  div.secondary-button {
794
830
  display: flex;
795
831
  justify-content: flex-end;
796
832
  width: 210px;
797
833
  }
834
+
798
835
  div.primary-button {
799
836
  margin-top: 14px;
800
837
  }
838
+
801
839
  div.secondary-button {
802
840
  margin-top: 8px;
803
841
  }
842
+
804
843
  div.primary-button .el-button,
805
844
  div.secondary-button .el-button,
806
845
  div.primary-button .el-button:hover,
@@ -808,61 +847,75 @@ div.secondary-button .el-button:hover {
808
847
  width: 121px;
809
848
  border-color: #8300bf;
810
849
  }
850
+
811
851
  div.primary-button .el-button,
812
852
  div.primary-button .el-button:hover {
813
853
  background-color: #8300bf;
814
854
  }
855
+
815
856
  div.secondary-button .el-button,
816
857
  div.secondary-button .el-button:hover {
817
858
  background-color: #f9f2fc;
818
859
  color: #8300bf;
819
860
  }
861
+
820
862
  div.scrollbar {
821
863
  overflow-y: scroll;
822
864
  scrollbar-width: thin;
823
865
  }
866
+
824
867
  div.scrollbar::-webkit-scrollbar {
825
868
  width: 8px;
826
869
  right: -8px;
827
870
  background-color: #f5f5f5;
828
871
  }
872
+
829
873
  div.scrollbar::-webkit-scrollbar-thumb {
830
874
  border-radius: 4px;
831
875
  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.06);
832
876
  background-color: #979797;
833
877
  }
878
+
834
879
  div.scrollbar::-webkit-scrollbar-track {
835
880
  border-radius: 10px;
836
881
  background-color: #f5f5f5;
837
882
  }
883
+
838
884
  div.simulation-vuer {
839
885
  height: 100%;
840
886
  }
887
+
841
888
  p.default {
842
889
  font-family: Asap, sans-serif;
843
890
  letter-spacing: 0;
844
891
  margin: 16px 0;
845
892
  text-align: start;
846
893
  }
894
+
847
895
  p.error {
848
896
  margin-left: 16px;
849
897
  }
898
+
850
899
  p.input-parameters {
851
900
  margin-bottom: 8px;
852
901
  }
902
+
853
903
  p.name,
854
904
  p.input-parameters {
855
905
  margin-top: 0;
856
- font-weight: 500 /* Medium */;
906
+ font-weight: bold;
857
907
  }
908
+
858
909
  p.name {
859
910
  line-height: 20px;
860
911
  }
912
+
861
913
  p.note {
862
914
  font-size: 12px;
863
915
  line-height: 16px;
864
916
  }
917
+
865
918
  span.error {
866
- font-weight: 500 /* Medium */;
919
+ font-weight: bold;
867
920
  }
868
921
  </style>
@@ -45,16 +45,16 @@ export default {
45
45
  type: Number,
46
46
  },
47
47
  },
48
- data: function() {
48
+ data: function () {
49
49
  return {
50
50
  isDiscrete: this.possibleValues !== undefined,
51
- labelClasses: "default " + ((this.possibleValues !== undefined)?"discrete":"scalar"),
51
+ labelClasses: "default " + ((this.possibleValues !== undefined) ? "discrete" : "scalar"),
52
52
  visible: true,
53
53
  vModel: this.defaultValue,
54
54
  };
55
55
  },
56
56
  methods: {
57
- updateUi: function() {
57
+ updateUi: function () {
58
58
  updateUi(this.$parent);
59
59
  },
60
60
  },
@@ -63,63 +63,76 @@ export default {
63
63
 
64
64
  <!-- Add "scoped" attribute to limit CSS to this component only -->
65
65
  <style scoped lang="scss">
66
-
67
- :deep( .el-input-number.scalar ){
66
+ :deep(.el-input-number.scalar) {
68
67
  margin-top: -8px;
69
68
  width: 45px;
70
69
  }
71
- :deep( .el-input-number.scalar .el-input ){
70
+
71
+ :deep(.el-input-number.scalar .el-input) {
72
72
  width: 60px;
73
73
  }
74
- :deep( .el-input-number.scalar .el-input__inner:focus ){
74
+
75
+ :deep(.el-input-number.scalar .el-input__inner:focus) {
75
76
  border-color: #8300bf;
76
77
  }
77
- :deep( .el-select.discrete ){
78
+
79
+ :deep(.el-select.discrete) {
78
80
  margin-bottom: 16px;
79
81
  }
80
- :deep( .el-slider ){
82
+
83
+ :deep(.el-slider) {
81
84
  width: 108px;
82
85
  margin-top: -12px;
83
86
  margin-left: 8px;
84
87
  }
85
- :deep( .el-slider__bar ){
88
+
89
+ :deep(.el-slider__bar) {
86
90
  background-color: #8300bf;
87
91
  }
88
- :deep( .el-slider__button ){
92
+
93
+ :deep(.el-slider__button) {
89
94
  border-color: #8300bf;
90
95
  }
96
+
91
97
  .discrete {
92
98
  margin-left: 8px;
93
99
  width: 160px;
94
100
  }
101
+
95
102
  .discrete {
96
- :deep( .el-input__inner ){
103
+ :deep(.el-input__inner) {
97
104
  font-family: Asap, sans-serif;
98
105
  font-size: 16px;
99
106
  }
100
107
  }
101
- .discrete :deep( .el-input__inner:focus),
102
- .discrete :deep( .el-input.is-focus .el-input__inner) {
108
+
109
+ .discrete :deep(.el-input__inner:focus),
110
+ .discrete :deep(.el-input.is-focus .el-input__inner) {
103
111
  border-color: #8300bf;
104
112
  }
113
+
105
114
  .discrete-popper .el-select-dropdown__item {
106
115
  font-family: Asap, sans-serif;
107
116
  }
117
+
108
118
  .discrete-popper .el-select-dropdown__item.is-selected {
109
119
  font-weight: normal;
110
120
  color: #8300bf;
111
121
  }
112
- .scalar :deep( .el-input__inner ){
122
+
123
+ .scalar :deep(.el-input__inner) {
113
124
  text-align: center;
114
125
  }
115
126
 
116
- .scalar :deep(.el-input__wrapper){
127
+ .scalar :deep(.el-input__wrapper) {
117
128
  padding-left: 4px;
118
129
  padding-right: 4px;
119
130
  }
131
+
120
132
  div.simulation-vuer {
121
133
  height: 100%;
122
134
  }
135
+
123
136
  div.sliders-and-fields {
124
137
  display: grid;
125
138
  grid-template-columns: 132px auto;
@@ -127,16 +140,19 @@ div.sliders-and-fields {
127
140
  height: 26px;
128
141
  margin-bottom: 4px;
129
142
  }
143
+
130
144
  p.default {
131
145
  font-family: Asap, sans-serif;
132
146
  letter-spacing: 0;
133
147
  margin: 16px 0;
134
148
  text-align: start;
135
149
  }
150
+
136
151
  p.discrete {
137
152
  margin-top: 0;
138
153
  margin-bottom: 4px;
139
154
  }
155
+
140
156
  p.scalar {
141
157
  grid-column-start: 1;
142
158
  grid-column-end: 3;
@@ -30,7 +30,7 @@ export function updateUi(parent) {
30
30
  });
31
31
 
32
32
  if (parent.libopencor !== undefined) {
33
- parent.userMessage = "Rerunning the model...";
33
+ parent.userMessage = "Running the model...";
34
34
  parent.showUserMessage = true;
35
35
 
36
36
  parent.$nextTick(() => {