@opencor/opencor 0.20250813.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.
- package/LICENSE +201 -0
- package/README.md +61 -0
- package/dist/index-BxzyBYAw.js +98574 -0
- package/dist/index.d.ts +8 -0
- package/dist/opencor.css +1 -0
- package/dist/opencor.es.js +5 -0
- package/dist/opencor.umd.js +6275 -0
- package/dist/quill-BGLY3Ud3.js +7521 -0
- package/package.json +90 -0
- package/src/App.vue +27 -0
- package/src/assets/app.css +14 -0
- package/src/assets/base.css +28 -0
- package/src/assets/logo.svg +17 -0
- package/src/common/common.ts +86 -0
- package/src/common/constants.ts +12 -0
- package/src/common/electron.ts +23 -0
- package/src/common/electronApi.ts +63 -0
- package/src/common/locCommon.ts +170 -0
- package/src/common/settings.ts +95 -0
- package/src/common/vueCommon.ts +50 -0
- package/src/components/BackgroundComponent.vue +26 -0
- package/src/components/ContentsComponent.vue +262 -0
- package/src/components/DragNDropComponent.vue +29 -0
- package/src/components/LoadOpencorComponent.vue +33 -0
- package/src/components/MainMenu.vue +220 -0
- package/src/components/OpenCOR.vue +546 -0
- package/src/components/SpinningWheelComponent.vue +15 -0
- package/src/components/dialogs/AboutDialog.vue +51 -0
- package/src/components/dialogs/BaseDialog.vue +11 -0
- package/src/components/dialogs/OpenRemoteDialog.vue +37 -0
- package/src/components/dialogs/ResetAllDialog.vue +13 -0
- package/src/components/dialogs/SettingsDialog.vue +42 -0
- package/src/components/dialogs/UpdateAvailableDialog.vue +16 -0
- package/src/components/dialogs/UpdateDownloadProgressDialog.vue +17 -0
- package/src/components/dialogs/UpdateErrorDialog.vue +18 -0
- package/src/components/dialogs/UpdateNotAvailableDialog.vue +12 -0
- package/src/components/propertyEditors/GraphsPropertyEditor.vue +3 -0
- package/src/components/propertyEditors/ParametersPropertyEditor.vue +3 -0
- package/src/components/propertyEditors/PropertyEditor.vue +61 -0
- package/src/components/propertyEditors/SimulationPropertyEditor.vue +45 -0
- package/src/components/propertyEditors/SolversPropertyEditor.vue +3 -0
- package/src/components/views/IssuesView.vue +37 -0
- package/src/components/views/SimulationExperimentUiView.vue +152 -0
- package/src/components/views/SimulationExperimentView.vue +214 -0
- package/src/components/widgets/GraphPanelWidget.vue +137 -0
- package/src/components/widgets/InputWidget.vue +128 -0
- package/src/libopencor/locApi.ts +167 -0
- package/src/libopencor/locFileApi.ts +263 -0
- package/src/libopencor/locLoggerApi.ts +36 -0
- package/src/libopencor/locSedApi.ts +486 -0
- package/src/libopencor/locUiJsonApi.ts +485 -0
- package/src/libopencor/locVersionApi.ts +17 -0
- package/src/libopencor/src/common.cpp +67 -0
- package/src/libopencor/src/common.h +27 -0
- package/src/libopencor/src/file.cpp +72 -0
- package/src/libopencor/src/file.h +15 -0
- package/src/libopencor/src/main.cpp +78 -0
- package/src/libopencor/src/sed.cpp +348 -0
- package/src/libopencor/src/sed.h +53 -0
- package/src/libopencor/src/version.cpp +8 -0
- package/src/libopencor/src/version.h +5 -0
- package/src/main.ts +5 -0
- package/src/types/types.d.ts +9 -0
|
@@ -0,0 +1,546 @@
|
|
|
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">
|
|
5
|
+
<div v-show="!electronApi && omex === undefined">
|
|
6
|
+
<MainMenu
|
|
7
|
+
:hasFiles="hasFiles"
|
|
8
|
+
@about="onAbout"
|
|
9
|
+
@open="($refs.files as HTMLInputElement).click()"
|
|
10
|
+
@openRemote="openRemoteVisible = true"
|
|
11
|
+
@openSampleLorenz="onOpenSampleLorenz"
|
|
12
|
+
@openSampleInteractiveLorenz="onOpenSampleInteractiveLorenz"
|
|
13
|
+
@close="onClose"
|
|
14
|
+
@closeAll="onCloseAll"
|
|
15
|
+
@settings="onSettings"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="h-full" @dragenter="onDragEnter" @dragover.prevent @drop.prevent="onDrop" @dragleave="onDragLeave">
|
|
19
|
+
<LoadOpencorComponent v-show="loadingLipencorWebAssemblyModuleVisible" />
|
|
20
|
+
<ContentsComponent ref="contents" :simulationOnly="omex !== undefined" />
|
|
21
|
+
<DragNDropComponent v-show="dropAreaCounter > 0" />
|
|
22
|
+
<BlockUI :blocked="!uiEnabled" :fullScreen="true"></BlockUI>
|
|
23
|
+
<SpinningWheelComponent v-show="spinningWheelVisible" />
|
|
24
|
+
</div>
|
|
25
|
+
</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 />
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<script setup lang="ts">
|
|
50
|
+
import primeVueAuraTheme from '@primeuix/themes/aura'
|
|
51
|
+
import * as vueusecore from '@vueuse/core'
|
|
52
|
+
|
|
53
|
+
import 'primeicons/primeicons.css'
|
|
54
|
+
import primeVueConfig from 'primevue/config'
|
|
55
|
+
import primeVueConfirmationService from 'primevue/confirmationservice'
|
|
56
|
+
import primeVueToastService from 'primevue/toastservice'
|
|
57
|
+
import { useToast } from 'primevue/usetoast'
|
|
58
|
+
import * as vue from 'vue'
|
|
59
|
+
|
|
60
|
+
import '../assets/app.css'
|
|
61
|
+
import * as common from '../common/common'
|
|
62
|
+
import { SHORT_DELAY, TOAST_LIFE } from '../common/constants'
|
|
63
|
+
import { electronApi } from '../common/electronApi'
|
|
64
|
+
import * as locCommon from '../common/locCommon'
|
|
65
|
+
import * as vueCommon from '../common/vueCommon'
|
|
66
|
+
import IContentsComponent from '../components/ContentsComponent.vue'
|
|
67
|
+
import * as locApi from '../libopencor/locApi'
|
|
68
|
+
|
|
69
|
+
const props = defineProps<{
|
|
70
|
+
omex?: string
|
|
71
|
+
}>()
|
|
72
|
+
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
74
|
+
const contents = vue.ref<InstanceType<typeof IContentsComponent> | null>(null)
|
|
75
|
+
const issues = vue.ref<locApi.IIssue[]>([])
|
|
76
|
+
|
|
77
|
+
// Get the current Vue app instance to use some PrimeVue components.
|
|
78
|
+
|
|
79
|
+
const getCurrentInstance = vue.getCurrentInstance()
|
|
80
|
+
|
|
81
|
+
if (getCurrentInstance !== null) {
|
|
82
|
+
const app = getCurrentInstance.appContext.app
|
|
83
|
+
|
|
84
|
+
app.use(primeVueConfig as unknown as vue.Plugin, {
|
|
85
|
+
theme: {
|
|
86
|
+
preset: primeVueAuraTheme
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
app.use(primeVueConfirmationService as unknown as vue.Plugin)
|
|
90
|
+
app.use(primeVueToastService as unknown as vue.Plugin)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const toast = useToast()
|
|
94
|
+
|
|
95
|
+
// Asynchronous initialise our libOpenCOR API.
|
|
96
|
+
|
|
97
|
+
const locApiInitialised = vue.ref(false)
|
|
98
|
+
|
|
99
|
+
void locApi.initialiseLocApi().then(() => {
|
|
100
|
+
locApiInitialised.value = true
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Handle an action.
|
|
104
|
+
|
|
105
|
+
electronApi?.onAction((action: string) => {
|
|
106
|
+
handleAction(action)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
function handleAction(action: string): void {
|
|
110
|
+
function isAction(actionName: string, expectedActionName: string): boolean {
|
|
111
|
+
return actionName.localeCompare(expectedActionName, undefined, { sensitivity: 'base' }) === 0
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const index = action.indexOf('/')
|
|
115
|
+
const actionName = index !== -1 ? action.substring(0, index) : action
|
|
116
|
+
const actionArguments = index !== -1 ? action.substring(index + 1) : ''
|
|
117
|
+
|
|
118
|
+
if (isAction(actionName, 'openAboutDialog')) {
|
|
119
|
+
onAbout()
|
|
120
|
+
} else if (isAction(actionName, 'openSettingsDialog')) {
|
|
121
|
+
onSettings()
|
|
122
|
+
} else {
|
|
123
|
+
const filePaths = actionArguments.split('%7C')
|
|
124
|
+
|
|
125
|
+
if (
|
|
126
|
+
(isAction(actionName, 'openFile') && filePaths.length === 1) ||
|
|
127
|
+
(isAction(actionName, 'openFiles') && filePaths.length > 1)
|
|
128
|
+
) {
|
|
129
|
+
for (const filePath of filePaths) {
|
|
130
|
+
openFile(filePath)
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
toast.add({
|
|
134
|
+
severity: 'error',
|
|
135
|
+
summary: 'Handling an action',
|
|
136
|
+
detail: `${action}\n\nThe action could not be handled.`,
|
|
137
|
+
life: TOAST_LIFE
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Enable/disable the UI.
|
|
144
|
+
|
|
145
|
+
const uiEnabled = vue.ref<boolean>(true)
|
|
146
|
+
|
|
147
|
+
electronApi?.onEnableDisableUi((enable: boolean) => {
|
|
148
|
+
enableDisableUi(enable)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
function enableDisableUi(enable: boolean): void {
|
|
152
|
+
uiEnabled.value = enable
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Enable/disable some menu items.
|
|
156
|
+
|
|
157
|
+
const hasFiles = vue.computed(() => {
|
|
158
|
+
return contents.value?.hasFiles() ?? false
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
vue.watch(hasFiles, (hasFiles) => {
|
|
162
|
+
electronApi?.enableDisableFileCloseAndCloseAllMenuItems(hasFiles)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Loading libOpenCOR's WebAssembly module.
|
|
166
|
+
// Note: this is only done if window.locApi is not defined, which means that we are running OpenCOR's Web app.
|
|
167
|
+
|
|
168
|
+
const loadingLipencorWebAssemblyModuleVisible = vue.ref<boolean>(false)
|
|
169
|
+
|
|
170
|
+
// @ts-expect-error (window.locApi may or may not be defined which is why we test it)
|
|
171
|
+
if (window.locApi === undefined) {
|
|
172
|
+
enableDisableUi(false)
|
|
173
|
+
|
|
174
|
+
loadingLipencorWebAssemblyModuleVisible.value = true
|
|
175
|
+
|
|
176
|
+
vue.watch(locApiInitialised, (initialised) => {
|
|
177
|
+
if (initialised) {
|
|
178
|
+
loadingLipencorWebAssemblyModuleVisible.value = false
|
|
179
|
+
|
|
180
|
+
enableDisableUi(true)
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Spinning wheel.
|
|
186
|
+
|
|
187
|
+
const spinningWheelVisible = vue.ref<boolean>(false)
|
|
188
|
+
|
|
189
|
+
function showSpinningWheel(): void {
|
|
190
|
+
enableDisableUi(false)
|
|
191
|
+
|
|
192
|
+
spinningWheelVisible.value = true
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function hideSpinningWheel(): void {
|
|
196
|
+
enableDisableUi(true)
|
|
197
|
+
|
|
198
|
+
spinningWheelVisible.value = false
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Auto update.
|
|
202
|
+
|
|
203
|
+
electronApi?.onCheckForUpdates(() => {
|
|
204
|
+
electronApi?.checkForUpdates(false)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
const updateErrorVisible = vue.ref<boolean>(false)
|
|
208
|
+
const updateErrorTitle = vue.ref<string>('')
|
|
209
|
+
const updateErrorIssue = vue.ref<string>('')
|
|
210
|
+
|
|
211
|
+
function onUpdateErrorDialogClose(): void {
|
|
212
|
+
updateErrorVisible.value = false
|
|
213
|
+
updateDownloadProgressVisible.value = false
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const updateAvailableVisible = vue.ref<boolean>(false)
|
|
217
|
+
const updateDownloadProgressVisible = vue.ref<boolean>(false)
|
|
218
|
+
const updateVersion = vue.ref<string>('')
|
|
219
|
+
const updateDownloadPercent = vue.ref<number>(0)
|
|
220
|
+
|
|
221
|
+
electronApi?.onUpdateAvailable((version: string) => {
|
|
222
|
+
updateAvailableVisible.value = true
|
|
223
|
+
updateVersion.value = version
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
function onDownloadAndInstall(): void {
|
|
227
|
+
updateDownloadPercent.value = 0 // Just to be on the safe side.
|
|
228
|
+
updateDownloadProgressVisible.value = true
|
|
229
|
+
updateAvailableVisible.value = false
|
|
230
|
+
|
|
231
|
+
electronApi?.downloadAndInstallUpdate()
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
electronApi?.onUpdateDownloadError((issue: string) => {
|
|
235
|
+
updateErrorTitle.value = 'Downloading Update...'
|
|
236
|
+
updateErrorIssue.value = `An error occurred while downloading the update (${issue}).`
|
|
237
|
+
updateErrorVisible.value = true
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
electronApi?.onUpdateDownloadProgress((percent: number) => {
|
|
241
|
+
updateDownloadPercent.value = percent
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
electronApi?.onUpdateDownloaded(() => {
|
|
245
|
+
updateDownloadPercent.value = 100 // Just to be on the safe side.
|
|
246
|
+
|
|
247
|
+
electronApi?.installUpdateAndRestart()
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
const updateNotAvailableVisible = vue.ref<boolean>(false)
|
|
251
|
+
|
|
252
|
+
electronApi?.onUpdateNotAvailable(() => {
|
|
253
|
+
updateNotAvailableVisible.value = true
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
electronApi?.onUpdateCheckError((issue: string) => {
|
|
257
|
+
updateErrorTitle.value = 'Checking For Updates...'
|
|
258
|
+
updateErrorIssue.value = `An error occurred while checking for updates (${issue}).`
|
|
259
|
+
updateErrorVisible.value = true
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
// About dialog.
|
|
263
|
+
|
|
264
|
+
const aboutVisible = vue.ref<boolean>(false)
|
|
265
|
+
|
|
266
|
+
electronApi?.onAbout(() => {
|
|
267
|
+
onAbout()
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
function onAbout(): void {
|
|
271
|
+
aboutVisible.value = true
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Settings dialog.
|
|
275
|
+
|
|
276
|
+
const settingsVisible = vue.ref<boolean>(false)
|
|
277
|
+
|
|
278
|
+
electronApi?.onSettings(() => {
|
|
279
|
+
onSettings()
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
function onSettings(): void {
|
|
283
|
+
settingsVisible.value = true
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Open a file.
|
|
287
|
+
|
|
288
|
+
function openFile(fileOrFilePath: string | File): void {
|
|
289
|
+
// Check whether the file is already open and if so then select it.
|
|
290
|
+
|
|
291
|
+
const filePath = locCommon.filePath(fileOrFilePath)
|
|
292
|
+
|
|
293
|
+
if (contents.value?.hasFile(filePath) ?? false) {
|
|
294
|
+
contents.value?.selectFile(filePath)
|
|
295
|
+
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Retrieve a locApi.File object for the given file or file path and add it to the contents.
|
|
300
|
+
|
|
301
|
+
if (locCommon.isRemoteFilePath(filePath)) {
|
|
302
|
+
showSpinningWheel()
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
locCommon
|
|
306
|
+
.file(fileOrFilePath)
|
|
307
|
+
.then((file) => {
|
|
308
|
+
const fileType = file.type()
|
|
309
|
+
|
|
310
|
+
if (fileType === locApi.EFileType.UNKNOWN_FILE || fileType === locApi.EFileType.IRRETRIEVABLE_FILE) {
|
|
311
|
+
if (props.omex !== undefined) {
|
|
312
|
+
void vue.nextTick().then(() => {
|
|
313
|
+
issues.value.push({
|
|
314
|
+
type: locApi.EIssueType.ERROR,
|
|
315
|
+
description:
|
|
316
|
+
fileType === locApi.EFileType.UNKNOWN_FILE
|
|
317
|
+
? 'Only CellML files, SED-ML files, and COMBINE archives are supported.'
|
|
318
|
+
: 'The file could not be retrieved.'
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
} else {
|
|
322
|
+
toast.add({
|
|
323
|
+
severity: 'error',
|
|
324
|
+
summary: 'Opening a file',
|
|
325
|
+
detail:
|
|
326
|
+
filePath +
|
|
327
|
+
'\n\n' +
|
|
328
|
+
(fileType === locApi.EFileType.UNKNOWN_FILE
|
|
329
|
+
? 'Only CellML files, SED-ML files, and COMBINE archives are supported.'
|
|
330
|
+
: 'The file could not be retrieved.'),
|
|
331
|
+
life: TOAST_LIFE
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
electronApi?.fileIssue(filePath)
|
|
336
|
+
} else {
|
|
337
|
+
contents.value?.openFile(file)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (locCommon.isRemoteFilePath(filePath)) {
|
|
341
|
+
hideSpinningWheel()
|
|
342
|
+
}
|
|
343
|
+
})
|
|
344
|
+
.catch((error: unknown) => {
|
|
345
|
+
if (locCommon.isRemoteFilePath(filePath)) {
|
|
346
|
+
hideSpinningWheel()
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (props.omex !== undefined) {
|
|
350
|
+
void vue.nextTick().then(() => {
|
|
351
|
+
issues.value.push({
|
|
352
|
+
type: locApi.EIssueType.ERROR,
|
|
353
|
+
description: common.formatIssue(error instanceof Error ? error.message : String(error))
|
|
354
|
+
})
|
|
355
|
+
})
|
|
356
|
+
} else {
|
|
357
|
+
toast.add({
|
|
358
|
+
severity: 'error',
|
|
359
|
+
summary: 'Opening a file',
|
|
360
|
+
detail: `${filePath}\n\n${common.formatIssue(error instanceof Error ? error.message : String(error))}`,
|
|
361
|
+
life: TOAST_LIFE
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
electronApi?.fileIssue(filePath)
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Open file(s) dialog.
|
|
370
|
+
|
|
371
|
+
function onChange(event: Event): void {
|
|
372
|
+
const files = (event.target as HTMLInputElement).files
|
|
373
|
+
|
|
374
|
+
if (files !== null) {
|
|
375
|
+
for (const file of Array.from(files)) {
|
|
376
|
+
openFile(file)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Drag and drop.
|
|
382
|
+
|
|
383
|
+
const dropAreaCounter = vue.ref<number>(0)
|
|
384
|
+
|
|
385
|
+
function onDragEnter(): void {
|
|
386
|
+
if (props.omex !== undefined) {
|
|
387
|
+
return
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
dropAreaCounter.value += 1
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function onDrop(event: DragEvent): void {
|
|
394
|
+
if (props.omex !== undefined) {
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
dropAreaCounter.value = 0
|
|
399
|
+
|
|
400
|
+
const files = event.dataTransfer?.files
|
|
401
|
+
|
|
402
|
+
if (files !== undefined) {
|
|
403
|
+
for (const file of Array.from(files)) {
|
|
404
|
+
openFile(file)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function onDragLeave(): void {
|
|
410
|
+
if (props.omex !== undefined) {
|
|
411
|
+
return
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
dropAreaCounter.value -= 1
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Open.
|
|
418
|
+
|
|
419
|
+
electronApi?.onOpen((filePath: string) => {
|
|
420
|
+
openFile(filePath)
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
// Open remote.
|
|
424
|
+
|
|
425
|
+
const openRemoteVisible = vue.ref<boolean>(false)
|
|
426
|
+
|
|
427
|
+
electronApi?.onOpenRemote(() => {
|
|
428
|
+
openRemoteVisible.value = true
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
function onOpenRemote(url: string): void {
|
|
432
|
+
// Note: no matter whether this is OpenCOR or OpenCOR's Web app, we always retrieve the file contents of a remote
|
|
433
|
+
// file. We could, in OpenCOR, rely on libOpenCOR to retrieve it for us, but this would block the UI. To
|
|
434
|
+
// retrieve the file here means that it is done asynchronously, which in turn means that the UI is not blocked
|
|
435
|
+
// and that we can show a spinning wheel to indicate that something is happening.
|
|
436
|
+
|
|
437
|
+
openFile(url)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Open sample Lorenz.
|
|
441
|
+
|
|
442
|
+
electronApi?.onOpenSampleLorenz(() => {
|
|
443
|
+
onOpenSampleLorenz()
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
function onOpenSampleLorenz(): void {
|
|
447
|
+
openFile('https://github.com/opencor/webapp/raw/refs/heads/main/tests/models/lorenz.omex')
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Open sample interactive Lorenz.
|
|
451
|
+
|
|
452
|
+
electronApi?.onOpenSampleInteractiveLorenz(() => {
|
|
453
|
+
onOpenSampleInteractiveLorenz()
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
function onOpenSampleInteractiveLorenz(): void {
|
|
457
|
+
openFile('https://github.com/opencor/webapp/raw/refs/heads/main/tests/models/ui/lorenz.omex')
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Close.
|
|
461
|
+
|
|
462
|
+
electronApi?.onClose(() => {
|
|
463
|
+
onClose()
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
function onClose(): void {
|
|
467
|
+
contents.value?.closeCurrentFile()
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Close all.
|
|
471
|
+
|
|
472
|
+
electronApi?.onCloseAll(() => {
|
|
473
|
+
onCloseAll()
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
function onCloseAll(): void {
|
|
477
|
+
contents.value?.closeAllFiles()
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Reset all.
|
|
481
|
+
|
|
482
|
+
const resetAllVisible = vue.ref<boolean>(false)
|
|
483
|
+
|
|
484
|
+
electronApi?.onResetAll(() => {
|
|
485
|
+
resetAllVisible.value = true
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
function onResetAll(): void {
|
|
489
|
+
electronApi?.resetAll()
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Select.
|
|
493
|
+
|
|
494
|
+
electronApi?.onSelect((filePath: string) => {
|
|
495
|
+
contents.value?.selectFile(filePath)
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
// If a COMBINE archive is provided then open it (and then the Simulation Experiment view will be shown in isolation) or
|
|
499
|
+
// carry as normal (i.e. the whole OpenCOR UI will be shown).
|
|
500
|
+
|
|
501
|
+
if (props.omex !== undefined) {
|
|
502
|
+
vue.watch(locApiInitialised, (initialised) => {
|
|
503
|
+
if (initialised) {
|
|
504
|
+
if (props.omex !== undefined) {
|
|
505
|
+
openFile(props.omex)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
})
|
|
509
|
+
} else {
|
|
510
|
+
// Track the height of our main menu.
|
|
511
|
+
|
|
512
|
+
vueCommon.trackElementHeight('mainMenu')
|
|
513
|
+
|
|
514
|
+
// Things that need to be done when the component is mounted.
|
|
515
|
+
|
|
516
|
+
vue.onMounted(() => {
|
|
517
|
+
// Do what follows with a bit of a delay to give our background (with the OpenCOR logo) time to be renderered.
|
|
518
|
+
|
|
519
|
+
setTimeout(() => {
|
|
520
|
+
if (electronApi !== undefined) {
|
|
521
|
+
// Check for updates.
|
|
522
|
+
// Note: the main process will actually check for updates if requested and if OpenCOR is packaged.
|
|
523
|
+
|
|
524
|
+
electronApi.checkForUpdates(true)
|
|
525
|
+
} else {
|
|
526
|
+
// Handle the action passed to our Web app, if any.
|
|
527
|
+
// Note: to use vue.nextTick() doesn't do the trick, so we have no choice but to use setTimeout().
|
|
528
|
+
|
|
529
|
+
const action = vueusecore.useStorage('action', '')
|
|
530
|
+
|
|
531
|
+
if (window.location.search !== '') {
|
|
532
|
+
action.value = window.location.search.substring(1)
|
|
533
|
+
|
|
534
|
+
window.location.search = ''
|
|
535
|
+
} else if (action.value !== '') {
|
|
536
|
+
setTimeout(() => {
|
|
537
|
+
handleAction(action.value)
|
|
538
|
+
|
|
539
|
+
action.value = ''
|
|
540
|
+
}, SHORT_DELAY)
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}, SHORT_DELAY)
|
|
544
|
+
})
|
|
545
|
+
}
|
|
546
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ProgressSpinner class="spinning-wheel" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<style scoped>
|
|
6
|
+
.spinning-wheel {
|
|
7
|
+
width: 50% !important;
|
|
8
|
+
height: 50% !important;
|
|
9
|
+
position: fixed !important;
|
|
10
|
+
top: 50%;
|
|
11
|
+
left: 50%;
|
|
12
|
+
transform: translate(-50%, -50%);
|
|
13
|
+
z-index: 99999;
|
|
14
|
+
}
|
|
15
|
+
</style>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseDialog header=" " style="width: 39rem">
|
|
3
|
+
<div class="space-y-7">
|
|
4
|
+
<div class="text-center">
|
|
5
|
+
<div class="text-3xl font-bold">OpenCOR {{ version }}</div>
|
|
6
|
+
<div v-if="electronApi !== undefined" class="text-xl italic font-bold">{{ electronApi.operatingSystem() }}</div>
|
|
7
|
+
<div class="text-sm italic">Copyright {{ constants.COPYRIGHT }}</div>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="space-y-2">
|
|
10
|
+
<div>
|
|
11
|
+
<a href="https://opencor.ws/" target="_blank" rel="noopener">OpenCOR</a> is a frontend to
|
|
12
|
+
<a href="https://opencor.ws/libopencor/" target="_blank" rel="noopener">libOpenCOR</a> {{ locApi.version() }},
|
|
13
|
+
a library that can be used to organise, edit, simulate, and analyse
|
|
14
|
+
<a href="https://cellml.org/" target="_blank" rel="noopener">CellML</a> files.
|
|
15
|
+
</div>
|
|
16
|
+
<div class="space-y-0">
|
|
17
|
+
<div>There are two versions of OpenCOR:</div>
|
|
18
|
+
|
|
19
|
+
<ol class="list-decimal list-inside ml-2">
|
|
20
|
+
<li>
|
|
21
|
+
<span class="font-bold">OpenCOR:</span> a desktop application that can be run on
|
|
22
|
+
<a href="https://en.wikipedia.org/wiki/List_of_Intel_processors">Intel</a>-based and
|
|
23
|
+
<a href="https://en.wikipedia.org/wiki/ARM_architecture_family">ARM</a>-based
|
|
24
|
+
<a href="https://en.wikipedia.org/wiki/Microsoft_Windows" target="_blank" rel="noopener">Windows</a>,
|
|
25
|
+
<a href="https://en.wikipedia.org/wiki/Linux" target="_blank" rel="noopener">Linux</a>, and
|
|
26
|
+
<a href="https://en.wikipedia.org/wiki/MacOS" target="_blank" rel="noopener">macOS</a> machines; and
|
|
27
|
+
</li>
|
|
28
|
+
<li>
|
|
29
|
+
<span class="font-bold">OpenCOR's Web app:</span> a
|
|
30
|
+
<a href="https://en.wikipedia.org/wiki/Web_application" target="_blank" rel="noopener">Web app</a> that
|
|
31
|
+
can be run on a Web browser.
|
|
32
|
+
</li>
|
|
33
|
+
</ol>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<template #footer>
|
|
38
|
+
<Button label="OK" autofocus @click="$emit('close')" />
|
|
39
|
+
</template>
|
|
40
|
+
</BaseDialog>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<script setup lang="ts">
|
|
44
|
+
import * as constants from '../../common/constants'
|
|
45
|
+
import { electronApi } from '../../common/electronApi'
|
|
46
|
+
import * as locApi from '../../libopencor/locApi'
|
|
47
|
+
|
|
48
|
+
defineEmits(['close'])
|
|
49
|
+
|
|
50
|
+
import { version } from '../../../package.json'
|
|
51
|
+
</script>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Dialog :modal="true" @show="enableDisableMainMenu(false)" @hide="enableDisableMainMenu(true)">
|
|
3
|
+
<template v-for="(_event, slot) of $slots" #[slot]="scope">
|
|
4
|
+
<slot :name="slot" v-bind="scope" />
|
|
5
|
+
</template>
|
|
6
|
+
</Dialog>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import { enableDisableMainMenu } from '../../common/common'
|
|
11
|
+
</script>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseDialog header="Open Remote..." style="width: 39rem">
|
|
3
|
+
<div class="items-center mt-2 mb-4">
|
|
4
|
+
<FloatLabel variant="on">
|
|
5
|
+
<InputText autofocus fluid v-model="url" @keyup.enter="emitOpenRemote()" />
|
|
6
|
+
<label>URL</label>
|
|
7
|
+
</FloatLabel>
|
|
8
|
+
</div>
|
|
9
|
+
<template #footer>
|
|
10
|
+
<Button label="Open" :disabled="url === ''" @click="emitOpenRemote()" />
|
|
11
|
+
<Button label="Cancel" severity="secondary" @click="emitClose()" />
|
|
12
|
+
</template>
|
|
13
|
+
</BaseDialog>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import * as vue from 'vue'
|
|
18
|
+
|
|
19
|
+
const emit = defineEmits(['openRemote', 'close'])
|
|
20
|
+
const url = vue.ref<string>('')
|
|
21
|
+
|
|
22
|
+
function emitOpenRemote(): void {
|
|
23
|
+
if (url.value === '') {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
emit('openRemote', url.value)
|
|
28
|
+
|
|
29
|
+
emitClose()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function emitClose(): void {
|
|
33
|
+
url.value = ''
|
|
34
|
+
|
|
35
|
+
emit('close')
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseDialog header="Reset All...">
|
|
3
|
+
<div class="mt-2 mb-4">You are about to reset all of your settings. Do you want to proceed?</div>
|
|
4
|
+
<template #footer>
|
|
5
|
+
<Button autofocus label="Yes" severity="danger" @click="$emit('resetAll')" />
|
|
6
|
+
<Button label="No" severity="secondary" @click="$emit('close')" />
|
|
7
|
+
</template>
|
|
8
|
+
</BaseDialog>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
defineEmits(['resetAll', 'close'])
|
|
13
|
+
</script>
|