@opencor/opencor 0.20250818.0 → 0.20250822.0

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,4 +1,4 @@
1
- import { c as ce, g as gl } from "./index-DBCnkxl5.js";
1
+ import { c as ce, g as gl } from "./index-Bo9Lkhfn.js";
2
2
  var pl = typeof global == "object" && global && global.Object === Object && global, yo = typeof self == "object" && self && self.Object === Object && self, Zt = pl || yo || Function("return this")(), de = Zt.Symbol, ml = Object.prototype, vo = ml.hasOwnProperty, Eo = ml.toString, yr = de ? de.toStringTag : void 0;
3
3
  function Ao(n) {
4
4
  var t = vo.call(n, yr), e = n[yr];
package/package.json CHANGED
@@ -42,20 +42,20 @@
42
42
  },
43
43
  "./style.css": "./dist/opencor.css"
44
44
  },
45
- "version": "0.20250818.0",
45
+ "version": "0.20250822.0",
46
46
  "peerDependencies": {
47
47
  "vue": "^3.4.21"
48
48
  },
49
49
  "dependencies": {
50
50
  "@primeuix/themes": "^1.2.3",
51
51
  "@primevue/auto-import-resolver": "4.2.5",
52
- "@vueuse/core": "12.8.2",
53
52
  "@tailwindcss/postcss": "^4.1.12",
54
53
  "@tailwindcss/vite": "^4.1.12",
54
+ "@vueuse/core": "12.8.2",
55
55
  "js-cookie": "^3.0.5",
56
56
  "jsonschema": "^1.5.0",
57
57
  "mathjs": "^14.6.0",
58
- "plotly.js-gl2d-dist-min": "2.35.3",
58
+ "plotly.js-gl2d-dist-min": "3.1.0",
59
59
  "primeicons": "^7.0.0",
60
60
  "primevue": "4.2.5",
61
61
  "quill": "^2.0.3",
@@ -65,17 +65,17 @@
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/js-cookie": "^3.0.6",
68
- "@types/plotly.js": "2.35.3",
69
68
  "@types/node": "^24.3.0",
69
+ "@types/plotly.js": "3.0.3",
70
70
  "@vitejs/plugin-vue": "^6.0.1",
71
71
  "@vue/eslint-config-prettier": "^10.2.0",
72
72
  "@vue/eslint-config-typescript": "^14.6.0",
73
- "@vue/tsconfig": "^0.8.0",
73
+ "@vue/tsconfig": "^0.8.1",
74
74
  "autoprefixer": "^10.4.21",
75
75
  "eslint": "^9.33.0",
76
76
  "prettier": "^3.6.2",
77
77
  "unplugin-vue-components": "^29.0.0",
78
- "vite": "^7.1.2"
78
+ "vite": "^7.1.3"
79
79
  },
80
80
  "scripts": {
81
81
  "build": "vite build",
package/src/App.vue CHANGED
@@ -1,27 +1,9 @@
1
1
  <template>
2
2
  <div id="app">
3
3
  <OpenCOR />
4
- <!-- <OpenCOR omex="https://raw.githubusercontent.com/opencor/webapp/refs/heads/main/tests/models/lorenz.omex"/> -->
5
- <!-- <OpenCOR omex="https://raw.githubusercontent.com/opencor/webapp/refs/heads/main/tests/models/ui/lorenz.omex"/> -->
6
- <!-- <OpenCOR omex="https://models.physiomeproject.org/workspace/b7c/rawfile/e0ae8d2d56aaaa091e23e1ee7e84cacbda1dfb6b/lorenz.omex"/> -->
4
+ <!-- <OpenCOR omex="https://raw.githubusercontent.com/opencor/webapp/refs/heads/main/tests/models/lorenz.cellml" /> -->
5
+ <!-- <OpenCOR omex="https://raw.githubusercontent.com/opencor/webapp/refs/heads/main/tests/models/lorenz.omex" /> -->
6
+ <!-- <OpenCOR omex="https://raw.githubusercontent.com/opencor/webapp/refs/heads/main/tests/models/ui/lorenz.omex" /> -->
7
+ <!-- <OpenCOR omex="https://models.physiomeproject.org/workspace/b7c/rawfile/e0ae8d2d56aaaa091e23e1ee7e84cacbda1dfb6b/lorenz.omex" /> -->
7
8
  </div>
8
9
  </template>
9
-
10
- <script setup lang="ts">
11
- import OpenCOR from './components/OpenCOR.vue'
12
-
13
- function updateViewportHeight() {
14
- document.documentElement.style.setProperty('--vh', `${String(window.innerHeight * 0.01)}px`)
15
- }
16
-
17
- window.addEventListener('resize', updateViewportHeight)
18
-
19
- updateViewportHeight()
20
- </script>
21
-
22
- <style scoped>
23
- #app {
24
- width: 100%;
25
- height: calc(100 * var(--vh, 1vh));
26
- }
27
- </style>
@@ -24,5 +24,8 @@ body {
24
24
  -webkit-font-smoothing: antialiased;
25
25
  -moz-osx-font-smoothing: grayscale;
26
26
  user-select: none;
27
- overflow: hidden;
27
+ }
28
+
29
+ .p-component {
30
+ transition: none !important;
28
31
  }
@@ -51,12 +51,7 @@ export function trackElementHeight(id: string): void {
51
51
  }
52
52
 
53
53
  const cssVariableName =
54
- '--' +
55
- id
56
- .split('_')[0]
57
- .replace(/([a-z])([A-Z])/g, '$1-$2')
58
- .toLowerCase() +
59
- '-height'
54
+ '--' + (id.split('_')[0] ?? '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() + '-height'
60
55
  const oldElementHeight = window.getComputedStyle(document.documentElement).getPropertyValue(cssVariableName)
61
56
 
62
57
  if (oldElementHeight === '' || (elementHeight !== '0px' && oldElementHeight !== elementHeight)) {
@@ -1,11 +1,9 @@
1
1
  <template>
2
- <div class="blocking-message p-overlay-mask">
3
- <Message class="message" severity="secondary">
4
- <i class="message-icon pi pi-spin pi-cog" />
5
- <br />
6
- <span class="message-text">{{ message }}</span>
7
- </Message>
8
- </div>
2
+ <Message class="message" severity="secondary">
3
+ <i class="message-icon pi pi-spin pi-cog" />
4
+ <br />
5
+ <span class="message-text">{{ message }}</span>
6
+ </Message>
9
7
  </template>
10
8
 
11
9
  <script setup lang="ts">
@@ -15,16 +13,13 @@ defineProps<{
15
13
  </script>
16
14
 
17
15
  <style scoped>
18
- .blocking-message {
19
- z-index: 99999;
20
- }
21
-
22
16
  .message {
23
- position: fixed;
17
+ position: absolute;
24
18
  top: 50%;
25
19
  left: 50%;
26
20
  transform: translate(-50%, -50%);
27
21
  padding: 1.5rem 1rem 0.75rem;
22
+ z-index: 99999;
28
23
  }
29
24
 
30
25
  .message-icon {
@@ -1,23 +1,26 @@
1
1
  <template>
2
2
  <div v-if="simulationOnly" class="h-full">
3
3
  <div v-for="(fileTab, index) in fileTabs" :key="`tabPanel_${fileTab.file.path()}`" :value="fileTab.file.path()">
4
- <IssuesView v-if="fileTab.file.issues().length !== 0" :issues="fileTab.file.issues()" />
4
+ <IssuesView
5
+ v-if="fileTab.file.issues().length !== 0"
6
+ :issues="fileTab.file.issues()"
7
+ :simulationOnly="simulationOnly"
8
+ />
5
9
  <SimulationExperimentView
6
10
  v-else-if="fileTab.uiJson === undefined"
7
- :file="fileTabs[index].file"
11
+ :file="fileTabs[index]?.file"
8
12
  :isActiveFile="fileTab.file.path() === activeFile"
9
13
  :simulationOnly="true"
10
14
  />
11
15
  <SimulationExperimentUiView
12
16
  v-else
13
- :file="fileTabs[index].file"
17
+ :file="fileTabs[index]?.file"
14
18
  :simulationOnly="true"
15
- :uiJson="fileTabs[index].uiJson"
19
+ :uiJson="fileTabs[index]?.uiJson"
16
20
  />
17
21
  </div>
18
22
  </div>
19
23
  <div v-else class="h-full">
20
- <BackgroundComponent v-show="fileTabs.length === 0" />
21
24
  <Tabs
22
25
  v-show="fileTabs.length !== 0"
23
26
  id="fileTabs"
@@ -54,10 +57,11 @@
54
57
  <IssuesView v-if="fileTab.file.issues().length !== 0" :issues="fileTab.file.issues()" />
55
58
  <SimulationExperimentView
56
59
  v-else-if="fileTab.uiJson === undefined"
57
- :file="fileTabs[index].file"
60
+ :uiEnabled="uiEnabled"
61
+ :file="fileTabs[index]?.file"
58
62
  :isActiveFile="fileTab.file.path() === activeFile"
59
63
  />
60
- <SimulationExperimentUiView v-else :file="fileTabs[index].file" :uiJson="fileTabs[index].uiJson" />
64
+ <SimulationExperimentUiView v-else :file="fileTabs[index]?.file" :uiJson="fileTabs[index]?.uiJson" />
61
65
  </TabPanel>
62
66
  </TabPanels>
63
67
  </Tabs>
@@ -79,7 +83,8 @@ export interface IFileTab {
79
83
  uiJson?: locApi.IUiJson
80
84
  }
81
85
 
82
- defineProps<{
86
+ const props = defineProps<{
87
+ uiEnabled: boolean
83
88
  simulationOnly?: boolean
84
89
  }>()
85
90
  defineExpose({ openFile, closeCurrentFile, closeAllFiles, hasFile, hasFiles, selectFile })
@@ -146,15 +151,21 @@ function selectFile(filePath: string): void {
146
151
  function selectNextFile(): void {
147
152
  const activeFileIndex = fileTabs.value.findIndex((fileTab) => fileTab.file.path() === activeFile.value)
148
153
  const nextFileIndex = (activeFileIndex + 1) % fileTabs.value.length
154
+ const nextFileTab = fileTabs.value[nextFileIndex]
149
155
 
150
- selectFile(fileTabs.value[nextFileIndex].file.path())
156
+ if (nextFileTab !== undefined) {
157
+ selectFile(nextFileTab.file.path())
158
+ }
151
159
  }
152
160
 
153
161
  function selectPreviousFile(): void {
154
162
  const activeFileIndex = fileTabs.value.findIndex((fileTab) => fileTab.file.path() === activeFile.value)
155
163
  const nextFileIndex = (activeFileIndex - 1 + fileTabs.value.length) % fileTabs.value.length
164
+ const nextFileTab = fileTabs.value[nextFileIndex]
156
165
 
157
- selectFile(fileTabs.value[nextFileIndex].file.path())
166
+ if (nextFileTab !== undefined) {
167
+ selectFile(nextFileTab.file.path())
168
+ }
158
169
  }
159
170
 
160
171
  function closeFile(filePath: string): void {
@@ -165,7 +176,11 @@ function closeFile(filePath: string): void {
165
176
  fileTabs.value.splice(activeFileIndex, 1)
166
177
 
167
178
  if (activeFile.value === filePath && fileTabs.value.length > 0) {
168
- selectFile(fileTabs.value[Math.min(activeFileIndex, fileTabs.value.length - 1)].file.path())
179
+ const nextFileTab = fileTabs.value[Math.min(activeFileIndex, fileTabs.value.length - 1)]
180
+
181
+ if (nextFileTab !== undefined) {
182
+ selectFile(nextFileTab.file.path())
183
+ }
169
184
  }
170
185
 
171
186
  electronApi?.fileClosed(filePath)
@@ -189,7 +204,7 @@ vueCommon.trackElementHeight('fileTablist')
189
204
 
190
205
  if (!common.isMobile()) {
191
206
  vueusecore.onKeyStroke((event: KeyboardEvent) => {
192
- if (fileTabs.value.length === 0) {
207
+ if (!props.uiEnabled || fileTabs.value.length === 0) {
193
208
  return
194
209
  }
195
210
 
@@ -1,11 +1,15 @@
1
1
  <template>
2
- <div class="drop-area p-overlay-mask">
3
- <div class="message">CellML files, SED-ML files, and COMBINE archives can be dropped here.</div>
2
+ <div class="drop-area">
3
+ <div class="message">
4
+ CellML files, SED-ML files, and COMBINE archives<br />
5
+ can be dropped here.
6
+ </div>
4
7
  </div>
5
8
  </template>
6
9
 
7
10
  <style scoped>
8
11
  .drop-area {
12
+ position: absolute;
9
13
  width: 100%;
10
14
  height: 100%;
11
15
  border: 0.375rem dashed;
@@ -15,15 +19,17 @@
15
19
 
16
20
  .message {
17
21
  font-size: 1.5rem;
22
+ line-height: 1.25;
18
23
  text-align: center;
19
24
  border-radius: 1rem;
20
25
  padding: 0.5rem 1rem;
21
26
  background-color: var(--p-primary-color);
22
27
  color: var(--p-primary-contrast-color);
23
28
  box-shadow: 0 0 0.75rem 0.375rem var(--p-content-border-color);
24
- position: fixed;
29
+ position: absolute;
25
30
  top: 50%;
26
31
  left: 50%;
27
32
  transform: translate(-50%, -50%);
33
+ white-space: nowrap;
28
34
  }
29
35
  </style>
@@ -31,6 +31,7 @@ import * as vue from 'vue'
31
31
  import * as common from '../common/common'
32
32
 
33
33
  const props = defineProps<{
34
+ uiEnabled: boolean
34
35
  hasFiles: boolean
35
36
  }>()
36
37
 
@@ -161,6 +162,10 @@ vue.onMounted(() => {
161
162
 
162
163
  if (!common.isMobile()) {
163
164
  vueusecore.onKeyStroke((event: KeyboardEvent) => {
165
+ if (!props.uiEnabled) {
166
+ return
167
+ }
168
+
164
169
  if (common.isCtrlOrCmd(event) && !event.shiftKey && event.code === 'KeyO') {
165
170
  event.preventDefault()
166
171
 
@@ -1,9 +1,23 @@
1
1
  <template>
2
- <div class="flex flex-col h-screen overflow-hidden">
3
- <IssuesView v-if="issues.length !== 0" :issues="issues" :simulationOnly="omex !== undefined" />
4
- <div v-else class="h-full">
2
+ <BlockUI id="blockUi" :blocked="!uiEnabled" class="h-full">
3
+ <Toast id="toast" :pt:root:style="{ position: 'absolute' }" />
4
+ <BackgroundComponent v-show="loadingOpencorMessageVisible || loadingModelMessageVisible || omex === undefined" />
5
+ <BlockingMessageComponent message="Loading OpenCOR..." v-show="loadingOpencorMessageVisible" />
6
+ <BlockingMessageComponent message="Loading model..." v-show="loadingModelMessageVisible" />
7
+ <IssuesView v-if="issues.length !== 0" class="h-full" :issues="issues" :simulationOnly="omex !== undefined" />
8
+ <div
9
+ v-else
10
+ @dragenter="onDragEnter"
11
+ class="h-full"
12
+ @dragover.prevent
13
+ @drop.prevent="onDrop"
14
+ @dragleave="onDragLeave"
15
+ >
16
+ <input ref="files" type="file" multiple style="display: none" @change="onChange" />
17
+ <DragNDropComponent v-show="dragAndDropCounter > 0" />
5
18
  <div v-show="!electronApi && omex === undefined">
6
19
  <MainMenu
20
+ :uiEnabled="compUiEnabled"
7
21
  :hasFiles="hasFiles"
8
22
  @about="onAbout"
9
23
  @open="($refs.files as HTMLInputElement).click()"
@@ -15,35 +29,31 @@
15
29
  @settings="onSettings"
16
30
  />
17
31
  </div>
18
- <div class="h-full" @dragenter="onDragEnter" @dragover.prevent @drop.prevent="onDrop" @dragleave="onDragLeave">
19
- <BlockingMessageComponent message="Loading OpenCOR..." v-show="loadingLipencorWebAssemblyModuleVisible" />
20
- <ContentsComponent ref="contents" :simulationOnly="omex !== undefined" />
21
- <DragNDropComponent v-show="dropAreaCounter > 0" />
22
- <BlockUI :blocked="!uiEnabled" :fullScreen="true"></BlockUI>
23
- <BlockingMessageComponent message="Loading model..." v-show="spinningWheelVisible" />
24
- </div>
32
+ <ContentsComponent ref="contents" :uiEnabled="compUiEnabled" :simulationOnly="omex !== undefined" />
33
+ <OpenRemoteDialog
34
+ v-model:visible="openRemoteVisible"
35
+ @openRemote="onOpenRemote"
36
+ @close="openRemoteVisible = false"
37
+ />
38
+ <SettingsDialog v-model:visible="settingsVisible" @close="settingsVisible = false" />
39
+ <ResetAllDialog v-model:visible="resetAllVisible" @resetAll="onResetAll" @close="resetAllVisible = false" />
40
+ <AboutDialog v-model:visible="aboutVisible" @close="aboutVisible = false" />
25
41
  </div>
26
- </div>
27
- <input ref="files" type="file" multiple style="display: none" @change="onChange" />
28
- <UpdateErrorDialog
29
- v-model:visible="updateErrorVisible"
30
- :title="updateErrorTitle"
31
- :issue="updateErrorIssue"
32
- @close="onUpdateErrorDialogClose"
33
- />
34
- <UpdateAvailableDialog
35
- v-model:visible="updateAvailableVisible"
36
- :version="updateVersion"
37
- @downloadAndInstall="onDownloadAndInstall"
38
- @close="updateAvailableVisible = false"
39
- />
40
- <UpdateDownloadProgressDialog v-model:visible="updateDownloadProgressVisible" :percent="updateDownloadPercent" />
41
- <UpdateNotAvailableDialog v-model:visible="updateNotAvailableVisible" @close="updateNotAvailableVisible = false" />
42
- <OpenRemoteDialog v-model:visible="openRemoteVisible" @openRemote="onOpenRemote" @close="openRemoteVisible = false" />
43
- <ResetAllDialog v-model:visible="resetAllVisible" @resetAll="onResetAll" @close="resetAllVisible = false" />
44
- <AboutDialog v-model:visible="aboutVisible" @close="aboutVisible = false" />
45
- <SettingsDialog v-model:visible="settingsVisible" @close="settingsVisible = false" />
46
- <Toast />
42
+ <UpdateErrorDialog
43
+ v-model:visible="updateErrorVisible"
44
+ :title="updateErrorTitle"
45
+ :issue="updateErrorIssue"
46
+ @close="onUpdateErrorDialogClose"
47
+ />
48
+ <UpdateAvailableDialog
49
+ v-model:visible="updateAvailableVisible"
50
+ :version="updateVersion"
51
+ @downloadAndInstall="onDownloadAndInstall"
52
+ @close="updateAvailableVisible = false"
53
+ />
54
+ <UpdateDownloadProgressDialog v-model:visible="updateDownloadProgressVisible" :percent="updateDownloadPercent" />
55
+ <UpdateNotAvailableDialog v-model:visible="updateNotAvailableVisible" @close="updateNotAvailableVisible = false" />
56
+ </BlockUI>
47
57
  </template>
48
58
 
49
59
  <script setup lang="ts">
@@ -74,6 +84,20 @@ const props = defineProps<IOpenCORProps>()
74
84
  const contents = vue.ref<InstanceType<typeof IContentsComponent> | null>(null)
75
85
  const issues = vue.ref<locApi.IIssue[]>([])
76
86
 
87
+ const compUiEnabled = vue.computed(() => {
88
+ return (
89
+ uiEnabled.value &&
90
+ !openRemoteVisible.value &&
91
+ !settingsVisible.value &&
92
+ !resetAllVisible.value &&
93
+ !aboutVisible.value &&
94
+ !updateErrorVisible.value &&
95
+ !updateAvailableVisible.value &&
96
+ !updateDownloadProgressVisible.value &&
97
+ !updateNotAvailableVisible.value
98
+ )
99
+ })
100
+
77
101
  // Get the current Vue app instance to use some PrimeVue components.
78
102
 
79
103
  const getCurrentInstance = vue.getCurrentInstance()
@@ -181,40 +205,40 @@ vue.watch(hasFiles, (hasFiles) => {
181
205
  electronApi?.enableDisableFileCloseAndCloseAllMenuItems(hasFiles)
182
206
  })
183
207
 
184
- // Loading libOpenCOR's WebAssembly module.
208
+ // Loading OpenCOR.
185
209
  // Note: this is only done if window.locApi is not defined, which means that we are running OpenCOR's Web app.
186
210
 
187
- const loadingLipencorWebAssemblyModuleVisible = vue.ref<boolean>(false)
211
+ const loadingOpencorMessageVisible = vue.ref<boolean>(false)
188
212
 
189
213
  // @ts-expect-error (window.locApi may or may not be defined which is why we test it)
190
214
  if (window.locApi === undefined) {
191
215
  enableDisableUi(false)
192
216
 
193
- loadingLipencorWebAssemblyModuleVisible.value = true
217
+ loadingOpencorMessageVisible.value = true
194
218
 
195
219
  vue.watch(locApiInitialised, (initialised) => {
196
220
  if (initialised) {
197
- loadingLipencorWebAssemblyModuleVisible.value = false
221
+ loadingOpencorMessageVisible.value = false
198
222
 
199
223
  enableDisableUi(true)
200
224
  }
201
225
  })
202
226
  }
203
227
 
204
- // Spinning wheel.
228
+ // Loading model.
205
229
 
206
- const spinningWheelVisible = vue.ref<boolean>(false)
230
+ const loadingModelMessageVisible = vue.ref<boolean>(false)
207
231
 
208
- function showSpinningWheel(): void {
232
+ function showLoadingModelMessage(): void {
209
233
  enableDisableUi(false)
210
234
 
211
- spinningWheelVisible.value = true
235
+ loadingModelMessageVisible.value = true
212
236
  }
213
237
 
214
- function hideSpinningWheel(): void {
238
+ function hideLoadingModelMessage(): void {
215
239
  enableDisableUi(true)
216
240
 
217
- spinningWheelVisible.value = false
241
+ loadingModelMessageVisible.value = false
218
242
  }
219
243
 
220
244
  // Auto update.
@@ -318,7 +342,7 @@ function openFile(fileOrFilePath: string | File): void {
318
342
  // Retrieve a locApi.File object for the given file or file path and add it to the contents.
319
343
 
320
344
  if (locCommon.isRemoteFilePath(filePath)) {
321
- showSpinningWheel()
345
+ showLoadingModelMessage()
322
346
  }
323
347
 
324
348
  locCommon
@@ -326,15 +350,19 @@ function openFile(fileOrFilePath: string | File): void {
326
350
  .then((file) => {
327
351
  const fileType = file.type()
328
352
 
329
- if (fileType === locApi.EFileType.UNKNOWN_FILE || fileType === locApi.EFileType.IRRETRIEVABLE_FILE) {
353
+ if (
354
+ fileType === locApi.EFileType.IRRETRIEVABLE_FILE ||
355
+ fileType === locApi.EFileType.UNKNOWN_FILE ||
356
+ (props.omex !== undefined && fileType !== locApi.EFileType.COMBINE_ARCHIVE)
357
+ ) {
330
358
  if (props.omex !== undefined) {
331
359
  void vue.nextTick().then(() => {
332
360
  issues.value.push({
333
361
  type: locApi.EIssueType.ERROR,
334
362
  description:
335
- fileType === locApi.EFileType.UNKNOWN_FILE
336
- ? 'Only CellML files, SED-ML files, and COMBINE archives are supported.'
337
- : 'The file could not be retrieved.'
363
+ fileType === locApi.EFileType.IRRETRIEVABLE_FILE
364
+ ? 'The file could not be retrieved.'
365
+ : 'Only COMBINE archives are supported.'
338
366
  })
339
367
  })
340
368
  } else {
@@ -344,9 +372,9 @@ function openFile(fileOrFilePath: string | File): void {
344
372
  detail:
345
373
  filePath +
346
374
  '\n\n' +
347
- (fileType === locApi.EFileType.UNKNOWN_FILE
348
- ? 'Only CellML files, SED-ML files, and COMBINE archives are supported.'
349
- : 'The file could not be retrieved.'),
375
+ (fileType === locApi.EFileType.IRRETRIEVABLE_FILE
376
+ ? 'The file could not be retrieved.'
377
+ : 'Only CellML files, SED-ML files, and COMBINE archives are supported.'),
350
378
  life: TOAST_LIFE
351
379
  })
352
380
  }
@@ -357,12 +385,12 @@ function openFile(fileOrFilePath: string | File): void {
357
385
  }
358
386
 
359
387
  if (locCommon.isRemoteFilePath(filePath)) {
360
- hideSpinningWheel()
388
+ hideLoadingModelMessage()
361
389
  }
362
390
  })
363
391
  .catch((error: unknown) => {
364
392
  if (locCommon.isRemoteFilePath(filePath)) {
365
- hideSpinningWheel()
393
+ hideLoadingModelMessage()
366
394
  }
367
395
 
368
396
  if (props.omex !== undefined) {
@@ -399,22 +427,22 @@ function onChange(event: Event): void {
399
427
 
400
428
  // Drag and drop.
401
429
 
402
- const dropAreaCounter = vue.ref<number>(0)
430
+ const dragAndDropCounter = vue.ref<number>(0)
403
431
 
404
432
  function onDragEnter(): void {
405
- if (props.omex !== undefined) {
433
+ if (!uiEnabled.value || props.omex !== undefined) {
406
434
  return
407
435
  }
408
436
 
409
- dropAreaCounter.value += 1
437
+ dragAndDropCounter.value += 1
410
438
  }
411
439
 
412
440
  function onDrop(event: DragEvent): void {
413
- if (props.omex !== undefined) {
441
+ if (dragAndDropCounter.value === 0) {
414
442
  return
415
443
  }
416
444
 
417
- dropAreaCounter.value = 0
445
+ dragAndDropCounter.value = 0
418
446
 
419
447
  const files = event.dataTransfer?.files
420
448
 
@@ -426,11 +454,11 @@ function onDrop(event: DragEvent): void {
426
454
  }
427
455
 
428
456
  function onDragLeave(): void {
429
- if (props.omex !== undefined) {
457
+ if (dragAndDropCounter.value === 0) {
430
458
  return
431
459
  }
432
460
 
433
- dropAreaCounter.value -= 1
461
+ dragAndDropCounter.value -= 1
434
462
  }
435
463
 
436
464
  // Open.
@@ -514,6 +542,10 @@ electronApi?.onSelect((filePath: string) => {
514
542
  contents.value?.selectFile(filePath)
515
543
  })
516
544
 
545
+ // Track the height of our block UI.
546
+
547
+ vueCommon.trackElementHeight('blockUi')
548
+
517
549
  // If a COMBINE archive is provided then open it (and then the Simulation Experiment view will be shown in isolation) or
518
550
  // carry as normal (i.e. the whole OpenCOR UI will be shown).
519
551
 
@@ -526,7 +558,7 @@ if (props.omex !== undefined) {
526
558
  }
527
559
  })
528
560
  } else {
529
- // Track the height of our main menu.
561
+ // (Also) track the height of our main menu.
530
562
 
531
563
  vueCommon.trackElementHeight('mainMenu')
532
564
 
@@ -536,6 +568,15 @@ if (props.omex !== undefined) {
536
568
  // Do what follows with a bit of a delay to give our background (with the OpenCOR logo) time to be renderered.
537
569
 
538
570
  setTimeout(() => {
571
+ // Ensure that our toasts are shown within our container.
572
+
573
+ const toastElement = document.getElementById('toast')
574
+ const blockUiElement = document.getElementById('blockUi')
575
+
576
+ if (toastElement !== null && blockUiElement !== null) {
577
+ blockUiElement.appendChild(toastElement)
578
+ }
579
+
539
580
  if (electronApi !== undefined) {
540
581
  // Check for updates.
541
582
  // Note: the main process will actually check for updates if requested and if OpenCOR is packaged.
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <Dialog :modal="true" @show="enableDisableMainMenu(false)" @hide="enableDisableMainMenu(true)">
2
+ <Dialog :modal="true" appendTo="self" :pt:mask:style="{ position: 'absolute' }" @show="onShow" @hide="onHide">
3
3
  <template v-for="(_event, slot) of $slots" #[slot]="scope">
4
4
  <slot :name="slot" v-bind="scope" />
5
5
  </template>
@@ -7,5 +7,52 @@
7
7
  </template>
8
8
 
9
9
  <script setup lang="ts">
10
+ import * as vue from 'vue'
11
+
10
12
  import { enableDisableMainMenu } from '../../common/common'
13
+
14
+ let dialogElement: HTMLElement | null = null
15
+ let containerElement: HTMLElement | null | undefined = null
16
+ let mutationObserver: MutationObserver | null = null
17
+
18
+ function checkDialogPosition() {
19
+ if (dialogElement instanceof HTMLElement && containerElement instanceof HTMLElement) {
20
+ const dialogRect = dialogElement.getBoundingClientRect()
21
+ const containerRect = containerElement.getBoundingClientRect()
22
+
23
+ dialogElement.style.top = `${String(Math.max(Math.min(dialogRect.top, containerRect.bottom - dialogRect.height), containerRect.top))}px`
24
+ dialogElement.style.left = `${String(Math.max(Math.min(dialogRect.left, containerRect.right - dialogRect.width), containerRect.left))}px`
25
+ }
26
+ }
27
+
28
+ function onShow() {
29
+ enableDisableMainMenu(false)
30
+
31
+ void vue.nextTick().then(() => {
32
+ dialogElement = document.querySelector('.p-dialog')
33
+ containerElement = dialogElement?.closest('[data-pc-section="mask"]')
34
+
35
+ if (dialogElement !== null && containerElement !== null) {
36
+ mutationObserver = new MutationObserver(() => {
37
+ checkDialogPosition()
38
+ })
39
+
40
+ mutationObserver.observe(dialogElement, { attributes: true, attributeFilter: ['style'] })
41
+
42
+ checkDialogPosition()
43
+ }
44
+ })
45
+ }
46
+
47
+ function onHide() {
48
+ enableDisableMainMenu(true)
49
+
50
+ void vue.nextTick().then(() => {
51
+ if (mutationObserver !== null) {
52
+ mutationObserver.disconnect()
53
+
54
+ mutationObserver = null
55
+ }
56
+ })
57
+ }
11
58
  </script>