@abi-software/simulationvuer 2.0.8 → 2.0.10

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