@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.
Files changed (63) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +61 -0
  3. package/dist/index-BxzyBYAw.js +98574 -0
  4. package/dist/index.d.ts +8 -0
  5. package/dist/opencor.css +1 -0
  6. package/dist/opencor.es.js +5 -0
  7. package/dist/opencor.umd.js +6275 -0
  8. package/dist/quill-BGLY3Ud3.js +7521 -0
  9. package/package.json +90 -0
  10. package/src/App.vue +27 -0
  11. package/src/assets/app.css +14 -0
  12. package/src/assets/base.css +28 -0
  13. package/src/assets/logo.svg +17 -0
  14. package/src/common/common.ts +86 -0
  15. package/src/common/constants.ts +12 -0
  16. package/src/common/electron.ts +23 -0
  17. package/src/common/electronApi.ts +63 -0
  18. package/src/common/locCommon.ts +170 -0
  19. package/src/common/settings.ts +95 -0
  20. package/src/common/vueCommon.ts +50 -0
  21. package/src/components/BackgroundComponent.vue +26 -0
  22. package/src/components/ContentsComponent.vue +262 -0
  23. package/src/components/DragNDropComponent.vue +29 -0
  24. package/src/components/LoadOpencorComponent.vue +33 -0
  25. package/src/components/MainMenu.vue +220 -0
  26. package/src/components/OpenCOR.vue +546 -0
  27. package/src/components/SpinningWheelComponent.vue +15 -0
  28. package/src/components/dialogs/AboutDialog.vue +51 -0
  29. package/src/components/dialogs/BaseDialog.vue +11 -0
  30. package/src/components/dialogs/OpenRemoteDialog.vue +37 -0
  31. package/src/components/dialogs/ResetAllDialog.vue +13 -0
  32. package/src/components/dialogs/SettingsDialog.vue +42 -0
  33. package/src/components/dialogs/UpdateAvailableDialog.vue +16 -0
  34. package/src/components/dialogs/UpdateDownloadProgressDialog.vue +17 -0
  35. package/src/components/dialogs/UpdateErrorDialog.vue +18 -0
  36. package/src/components/dialogs/UpdateNotAvailableDialog.vue +12 -0
  37. package/src/components/propertyEditors/GraphsPropertyEditor.vue +3 -0
  38. package/src/components/propertyEditors/ParametersPropertyEditor.vue +3 -0
  39. package/src/components/propertyEditors/PropertyEditor.vue +61 -0
  40. package/src/components/propertyEditors/SimulationPropertyEditor.vue +45 -0
  41. package/src/components/propertyEditors/SolversPropertyEditor.vue +3 -0
  42. package/src/components/views/IssuesView.vue +37 -0
  43. package/src/components/views/SimulationExperimentUiView.vue +152 -0
  44. package/src/components/views/SimulationExperimentView.vue +214 -0
  45. package/src/components/widgets/GraphPanelWidget.vue +137 -0
  46. package/src/components/widgets/InputWidget.vue +128 -0
  47. package/src/libopencor/locApi.ts +167 -0
  48. package/src/libopencor/locFileApi.ts +263 -0
  49. package/src/libopencor/locLoggerApi.ts +36 -0
  50. package/src/libopencor/locSedApi.ts +486 -0
  51. package/src/libopencor/locUiJsonApi.ts +485 -0
  52. package/src/libopencor/locVersionApi.ts +17 -0
  53. package/src/libopencor/src/common.cpp +67 -0
  54. package/src/libopencor/src/common.h +27 -0
  55. package/src/libopencor/src/file.cpp +72 -0
  56. package/src/libopencor/src/file.h +15 -0
  57. package/src/libopencor/src/main.cpp +78 -0
  58. package/src/libopencor/src/sed.cpp +348 -0
  59. package/src/libopencor/src/sed.h +53 -0
  60. package/src/libopencor/src/version.cpp +8 -0
  61. package/src/libopencor/src/version.h +5 -0
  62. package/src/main.ts +5 -0
  63. 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>