@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,42 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseDialog header="Settings..." @hide="initialiseDialog">
|
|
3
|
+
<div class="flex gap-2 items-center">
|
|
4
|
+
<Checkbox inputId="checkForUpdatesAtStartup" :binary="true" v-model="checkForUpdatesAtStartup" />
|
|
5
|
+
<label for="checkForUpdatesAtStartup">Check for updates at startup</label>
|
|
6
|
+
</div>
|
|
7
|
+
<template #footer>
|
|
8
|
+
<Button autofocus label="OK" @click="onOk" />
|
|
9
|
+
<Button label="Cancel" severity="secondary" @click="$emit('close')" />
|
|
10
|
+
</template>
|
|
11
|
+
</BaseDialog>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import * as vue from 'vue'
|
|
16
|
+
|
|
17
|
+
import { settings } from '../../common/settings'
|
|
18
|
+
|
|
19
|
+
const emit = defineEmits(['close'])
|
|
20
|
+
|
|
21
|
+
const checkForUpdatesAtStartup = vue.ref(settings.general.checkForUpdatesAtStartup)
|
|
22
|
+
|
|
23
|
+
function initialiseDialog() {
|
|
24
|
+
// Note: we can come here as a result of hiding the dialog and this in case the dialog gets opened multiple times. We
|
|
25
|
+
// could do this when showing the dialog, but it might result in the UI flashing (e.g., a checkbox was checked
|
|
26
|
+
// and then it gets unchecked), hence we do it when hiding the dialog.
|
|
27
|
+
|
|
28
|
+
checkForUpdatesAtStartup.value = settings.general.checkForUpdatesAtStartup
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
settings.onInitialised(() => {
|
|
32
|
+
initialiseDialog()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
function onOk() {
|
|
36
|
+
settings.general.checkForUpdatesAtStartup = checkForUpdatesAtStartup.value
|
|
37
|
+
|
|
38
|
+
settings.save()
|
|
39
|
+
|
|
40
|
+
emit('close')
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseDialog header="Check For Updates...">
|
|
3
|
+
<div class="mt-2 mb-4">Version {{ version }} is available. Do you want to download it and install it?</div>
|
|
4
|
+
<template #footer>
|
|
5
|
+
<Button autofocus label="Yes" @click="$emit('downloadAndInstall')" />
|
|
6
|
+
<Button label="No" severity="secondary" @click="$emit('close')" />
|
|
7
|
+
</template>
|
|
8
|
+
</BaseDialog>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
defineEmits(['downloadAndInstall', 'close'])
|
|
13
|
+
defineProps<{
|
|
14
|
+
version: string
|
|
15
|
+
}>()
|
|
16
|
+
</script>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseDialog header="Downloading Update..." :closable="false" :closeOnEscape="false" style="width: 39rem">
|
|
3
|
+
<ProgressBar class="no-animation" :showValue="false" :value="percent" />
|
|
4
|
+
</BaseDialog>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
defineProps<{
|
|
9
|
+
percent: number
|
|
10
|
+
}>()
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<style scoped>
|
|
14
|
+
.no-animation :deep(.p-progressbar-value) {
|
|
15
|
+
transition: none !important;
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseDialog :header="title">
|
|
3
|
+
<div class="mt-2 mb-4">
|
|
4
|
+
{{ issue }}
|
|
5
|
+
</div>
|
|
6
|
+
<template #footer>
|
|
7
|
+
<Button autofocus label="OK" @click="$emit('close')" />
|
|
8
|
+
</template>
|
|
9
|
+
</BaseDialog>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
defineEmits(['close'])
|
|
14
|
+
defineProps<{
|
|
15
|
+
title: string
|
|
16
|
+
issue: string
|
|
17
|
+
}>()
|
|
18
|
+
</script>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BaseDialog header="Check For Updates...">
|
|
3
|
+
<div class="mt-2 mb-4">No updates are available at this time.</div>
|
|
4
|
+
<template #footer>
|
|
5
|
+
<Button autofocus label="OK" @click="$emit('close')" />
|
|
6
|
+
</template>
|
|
7
|
+
</BaseDialog>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
defineEmits(['close'])
|
|
12
|
+
</script>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Fieldset :legend="name">
|
|
3
|
+
<DataTable
|
|
4
|
+
editMode="cell"
|
|
5
|
+
resizableColumns
|
|
6
|
+
showGridlines
|
|
7
|
+
size="small"
|
|
8
|
+
:value="properties"
|
|
9
|
+
@cell-edit-complete="onCellEditComplete"
|
|
10
|
+
>
|
|
11
|
+
<Column field="property" header="Property" :style="columnWidth" />
|
|
12
|
+
<Column field="value" header="Value" :style="columnWidth">
|
|
13
|
+
<template #body="{ data, field }">
|
|
14
|
+
{{ data[field as string] }}
|
|
15
|
+
</template>
|
|
16
|
+
<template #editor="{ data, field }">
|
|
17
|
+
<InputNumber fluid :maxFractionDigits="15" v-model="data[field]" size="small" />
|
|
18
|
+
</template>
|
|
19
|
+
</Column>
|
|
20
|
+
<Column v-if="hasUnits" field="unit" header="Unit" :style="columnWidth" />
|
|
21
|
+
</DataTable>
|
|
22
|
+
</Fieldset>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup lang="ts">
|
|
26
|
+
import { type DataTableCellEditCompleteEvent } from 'primevue/datatable'
|
|
27
|
+
|
|
28
|
+
interface IProps {
|
|
29
|
+
name: string
|
|
30
|
+
hasUnits?: boolean
|
|
31
|
+
properties: {
|
|
32
|
+
property: string
|
|
33
|
+
value: number
|
|
34
|
+
unit?: string
|
|
35
|
+
}[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { hasUnits = true, name, properties } = defineProps<IProps>()
|
|
39
|
+
const columnWidth = `width: calc(100% / ${hasUnits ? '3' : '2'})`
|
|
40
|
+
const emit = defineEmits(['propertyUpdated'])
|
|
41
|
+
|
|
42
|
+
function onCellEditComplete(event: DataTableCellEditCompleteEvent): void {
|
|
43
|
+
const { data, newValue, field } = event
|
|
44
|
+
|
|
45
|
+
data[field] = newValue
|
|
46
|
+
|
|
47
|
+
emit('propertyUpdated', event.index, event.newValue)
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<style scoped>
|
|
52
|
+
:deep(.p-datatable-header-cell) {
|
|
53
|
+
transition: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
:deep(.p-inputnumber-input) {
|
|
57
|
+
padding: 0 2px !important;
|
|
58
|
+
margin: 0 !important;
|
|
59
|
+
line-height: 1.2px !important;
|
|
60
|
+
}
|
|
61
|
+
</style>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PropertyEditor name="Simulation" :properties="properties" @propertyUpdated="onPropertyUpdated" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import * as vue from 'vue'
|
|
7
|
+
|
|
8
|
+
import * as locApi from '../../libopencor/locApi'
|
|
9
|
+
|
|
10
|
+
const props = defineProps<{
|
|
11
|
+
file: locApi.File
|
|
12
|
+
}>()
|
|
13
|
+
|
|
14
|
+
const uniformTimeCourse = props.file.document().simulation(0) as locApi.SedSimulationUniformTimeCourse
|
|
15
|
+
const voiUnit = props.file.instance().task(0).voiUnit()
|
|
16
|
+
|
|
17
|
+
const properties = vue.ref([
|
|
18
|
+
{
|
|
19
|
+
property: 'Starting point',
|
|
20
|
+
value: uniformTimeCourse.outputStartTime(),
|
|
21
|
+
unit: voiUnit
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
property: 'Ending point',
|
|
25
|
+
value: uniformTimeCourse.outputEndTime(),
|
|
26
|
+
unit: voiUnit
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
property: 'Point interval',
|
|
30
|
+
value:
|
|
31
|
+
(uniformTimeCourse.outputEndTime() - uniformTimeCourse.outputStartTime()) / uniformTimeCourse.numberOfSteps(),
|
|
32
|
+
unit: voiUnit
|
|
33
|
+
}
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
function onPropertyUpdated(index: number, value: number): void {
|
|
37
|
+
if (index === 0) {
|
|
38
|
+
uniformTimeCourse.setOutputStartTime(value)
|
|
39
|
+
} else if (index === 1) {
|
|
40
|
+
uniformTimeCourse.setOutputEndTime(value)
|
|
41
|
+
} else if (index === 2) {
|
|
42
|
+
uniformTimeCourse.setNumberOfSteps((properties.value[1].value - properties.value[0].value) / value)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Fieldset class="ml-4! mr-4! mb-4!" legend="Issues">
|
|
3
|
+
<ScrollPanel :class="simulationOnly ? 'issues-scroll-panel-only' : 'issues-scroll-panel'">
|
|
4
|
+
<div v-for="(issue, index) in issues" :key="`issue_${index}`" :class="`issue ${index > 0 ? 'mt-4!' : ''}`">
|
|
5
|
+
<Message v-if="issue.type === locApi.EIssueType.ERROR" severity="error" icon="pi pi-times-circle">
|
|
6
|
+
{{ issue.description }}
|
|
7
|
+
</Message>
|
|
8
|
+
<Message v-else severity="warn" icon="pi pi-exclamation-triangle">
|
|
9
|
+
{{ issue.description }}
|
|
10
|
+
</Message>
|
|
11
|
+
</div>
|
|
12
|
+
</ScrollPanel>
|
|
13
|
+
</Fieldset>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import * as locApi from '../../libopencor/locApi'
|
|
18
|
+
|
|
19
|
+
defineProps<{
|
|
20
|
+
issues: locApi.IIssue[]
|
|
21
|
+
simulationOnly?: boolean
|
|
22
|
+
}>()
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<style scoped>
|
|
26
|
+
.issue {
|
|
27
|
+
user-select: text;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.issues-scroll-panel-only {
|
|
31
|
+
height: calc(100vh - 4.75rem);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.issues-scroll-panel {
|
|
35
|
+
height: calc(100vh - var(--main-menu-height) - var(--file-tablist-height) - 4.75rem);
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="`flex flex-row h-full ${simulationOnly ? 'simulation-experiment-only' : 'simulation-experiment'}`">
|
|
3
|
+
<IssuesView v-if="issues.length !== 0" class="grow" :issues="issues" :simulationOnly="simulationOnly" />
|
|
4
|
+
<div v-else class="flex flex-row grow">
|
|
5
|
+
<div class="ml-4 mr-4 mb-4">
|
|
6
|
+
<Fieldset legend="Input parameters">
|
|
7
|
+
<InputWidget
|
|
8
|
+
v-for="(input, index) in (uiJson as any).input"
|
|
9
|
+
v-model="inputValues[index]"
|
|
10
|
+
v-show="showInput[index]"
|
|
11
|
+
:key="`input_${index}`"
|
|
12
|
+
:name="input.name"
|
|
13
|
+
:maximumValue="input.maximumValue"
|
|
14
|
+
:minimumValue="input.minimumValue"
|
|
15
|
+
:possibleValues="input.possibleValues"
|
|
16
|
+
:stepValue="input.stepValue"
|
|
17
|
+
:class="index !== 0 ? 'mt-6' : ''"
|
|
18
|
+
@change="updateUiAndSimulation"
|
|
19
|
+
/>
|
|
20
|
+
</Fieldset>
|
|
21
|
+
</div>
|
|
22
|
+
<div :id="plotsDivId" class="grow">
|
|
23
|
+
<GraphPanelWidget
|
|
24
|
+
v-for="(_plot, index) in (uiJson as any).output.plots"
|
|
25
|
+
:key="`plot_${index}`"
|
|
26
|
+
class="graph-panel-widget"
|
|
27
|
+
:plots="plots.length !== 0 ? plots[index] : []"
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup lang="ts">
|
|
35
|
+
import * as mathjs from 'mathjs'
|
|
36
|
+
import * as vue from 'vue'
|
|
37
|
+
|
|
38
|
+
import * as locCommon from '../../common/locCommon'
|
|
39
|
+
import * as locApi from '../../libopencor/locApi'
|
|
40
|
+
|
|
41
|
+
import { type IGraphPanelPlot } from '../widgets/GraphPanelWidget.vue'
|
|
42
|
+
|
|
43
|
+
const props = defineProps<{
|
|
44
|
+
file: locApi.File
|
|
45
|
+
simulationOnly?: boolean
|
|
46
|
+
uiJson: locApi.IUiJson
|
|
47
|
+
}>()
|
|
48
|
+
|
|
49
|
+
const math = mathjs.create(mathjs.all, {})
|
|
50
|
+
const model = props.file.document().model(0)
|
|
51
|
+
const instance = props.file.instance()
|
|
52
|
+
const instanceTask = instance.task(0)
|
|
53
|
+
const plotsDivId = `plotsDiv_${props.file.path()}`
|
|
54
|
+
const plots = vue.ref<IGraphPanelPlot[][]>([])
|
|
55
|
+
const issues = vue.ref(locApi.uiJsonIssues(props.uiJson))
|
|
56
|
+
const inputValues = vue.ref<number[]>([])
|
|
57
|
+
const showInput = vue.ref<boolean[]>([])
|
|
58
|
+
const idToInfo: Record<string, locCommon.ISimulationDataInfo> = {}
|
|
59
|
+
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
+
function evaluateValue(value: string): any {
|
|
62
|
+
let index = -1
|
|
63
|
+
const parser = math.parser()
|
|
64
|
+
|
|
65
|
+
props.uiJson.input.forEach((input: locApi.IUiJsonInput) => {
|
|
66
|
+
if (input.id !== undefined && input.id !== '') {
|
|
67
|
+
parser.set(input.id, inputValues.value[++index])
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return parser.evaluate(value)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
props.uiJson.input.forEach((input: locApi.IUiJsonInput) => {
|
|
75
|
+
inputValues.value.push(input.defaultValue)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
props.uiJson.input.forEach((input: locApi.IUiJsonInput) => {
|
|
79
|
+
showInput.value.push(evaluateValue(input.visible ?? 'true'))
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
props.uiJson.output.data.forEach((data: locApi.IUiJsonOutputData) => {
|
|
83
|
+
idToInfo[data.id] = locCommon.simulationDataInfo(instanceTask, data.name)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
vue.onMounted(() => {
|
|
87
|
+
updateUiAndSimulation()
|
|
88
|
+
|
|
89
|
+
// Determine the number of graph panel widgets (needed to set their height).
|
|
90
|
+
|
|
91
|
+
const plotsDiv = document.getElementById(plotsDivId)
|
|
92
|
+
|
|
93
|
+
plotsDiv?.style.setProperty('--graph-panel-widget-count', String(plotsDiv.children.length))
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
function updateUiAndSimulation() {
|
|
97
|
+
// Make sure that there are no issues.
|
|
98
|
+
|
|
99
|
+
if (issues.value.length > 0) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Show/hide the input widgets.
|
|
104
|
+
|
|
105
|
+
props.uiJson.input.forEach((input: locApi.IUiJsonInput, index: number) => {
|
|
106
|
+
showInput.value[index] = evaluateValue(input.visible ?? 'true')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Update the SED-ML document.
|
|
110
|
+
|
|
111
|
+
model.removeAllChanges()
|
|
112
|
+
|
|
113
|
+
props.uiJson.parameters.forEach((parameter: locApi.IUiJsonParameter) => {
|
|
114
|
+
const componentVariableNames = parameter.name.split('/')
|
|
115
|
+
|
|
116
|
+
model.addChange(componentVariableNames[0], componentVariableNames[1], String(evaluateValue(parameter.value)))
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Run the instance and update the plots.
|
|
120
|
+
|
|
121
|
+
instance.run()
|
|
122
|
+
|
|
123
|
+
const parser = math.parser()
|
|
124
|
+
|
|
125
|
+
props.uiJson.output.data.forEach((data: locApi.IUiJsonOutputData) => {
|
|
126
|
+
parser.set(data.id, locCommon.simulationData(instanceTask, idToInfo[data.id]))
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
plots.value = props.uiJson.output.plots.map((plot: locApi.IUiJsonOutputPlot) => {
|
|
130
|
+
return [
|
|
131
|
+
{
|
|
132
|
+
x: { data: parser.evaluate(plot.xValue) },
|
|
133
|
+
y: { data: parser.evaluate(plot.yValue) }
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<style scoped>
|
|
141
|
+
.graph-panel-widget {
|
|
142
|
+
height: calc(100% / var(--graph-panel-widget-count));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.simulation-experiment-only {
|
|
146
|
+
height: 100vh;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.simulation-experiment {
|
|
150
|
+
height: calc(100vh - var(--main-menu-height) - var(--file-tablist-height));
|
|
151
|
+
}
|
|
152
|
+
</style>
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="`h-full ${simulationOnly ? 'simulation-experiment-only' : 'simulation-experiment'}`">
|
|
3
|
+
<Toolbar :id="toolbarId" class="p-1!">
|
|
4
|
+
<template #start>
|
|
5
|
+
<Button class="p-1!" icon="pi pi-play-circle" severity="secondary" text @click="onRun()" />
|
|
6
|
+
<Button class="p-1!" disabled icon="pi pi-stop-circle" severity="secondary" text />
|
|
7
|
+
</template>
|
|
8
|
+
</Toolbar>
|
|
9
|
+
<Splitter class="border-none! h-full m-0" layout="vertical">
|
|
10
|
+
<SplitterPanel :size="simulationOnly ? 100 : 89">
|
|
11
|
+
<Splitter>
|
|
12
|
+
<SplitterPanel class="ml-4 mr-4 mb-4 min-w-fit" :size="25">
|
|
13
|
+
<SimulationPropertyEditor :file="file" />
|
|
14
|
+
<!--
|
|
15
|
+
<SolversPropertyEditor />
|
|
16
|
+
<GraphsPropertyEditor />
|
|
17
|
+
<ParametersPropertyEditor />
|
|
18
|
+
-->
|
|
19
|
+
<Fieldset legend="X-axis">
|
|
20
|
+
<Select
|
|
21
|
+
v-model="xParameter"
|
|
22
|
+
filter
|
|
23
|
+
filterMode="lenient"
|
|
24
|
+
:options="parameters"
|
|
25
|
+
size="small"
|
|
26
|
+
class="w-full"
|
|
27
|
+
@change="updatePlot()"
|
|
28
|
+
/>
|
|
29
|
+
</Fieldset>
|
|
30
|
+
<Fieldset legend="Y-axis">
|
|
31
|
+
<Select
|
|
32
|
+
v-model="yParameter"
|
|
33
|
+
filter
|
|
34
|
+
filterMode="lenient"
|
|
35
|
+
:options="parameters"
|
|
36
|
+
size="small"
|
|
37
|
+
class="w-full"
|
|
38
|
+
@change="updatePlot()"
|
|
39
|
+
/>
|
|
40
|
+
</Fieldset>
|
|
41
|
+
</SplitterPanel>
|
|
42
|
+
<SplitterPanel :size="75">
|
|
43
|
+
<GraphPanelWidget :plots="plots" />
|
|
44
|
+
</SplitterPanel>
|
|
45
|
+
</Splitter>
|
|
46
|
+
</SplitterPanel>
|
|
47
|
+
<SplitterPanel v-if="!simulationOnly" :size="11">
|
|
48
|
+
<Editor :id="editorId" class="border-none h-full" :readonly="true" v-model="consoleContents" />
|
|
49
|
+
</SplitterPanel>
|
|
50
|
+
</Splitter>
|
|
51
|
+
</div>
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<script setup lang="ts">
|
|
55
|
+
import * as vueusecore from '@vueuse/core'
|
|
56
|
+
|
|
57
|
+
import * as vue from 'vue'
|
|
58
|
+
|
|
59
|
+
import * as common from '../../common/common'
|
|
60
|
+
import * as locCommon from '../../common/locCommon'
|
|
61
|
+
import * as vueCommon from '../../common/vueCommon'
|
|
62
|
+
import * as locApi from '../../libopencor/locApi'
|
|
63
|
+
|
|
64
|
+
import { type IGraphPanelPlot } from '../widgets/GraphPanelWidget.vue'
|
|
65
|
+
|
|
66
|
+
const props = defineProps<{
|
|
67
|
+
file: locApi.File
|
|
68
|
+
isActiveFile: boolean
|
|
69
|
+
simulationOnly?: boolean
|
|
70
|
+
}>()
|
|
71
|
+
|
|
72
|
+
const toolbarId = `simulationExperimentToolbar_${props.file.path()}`
|
|
73
|
+
const editorId = `simulationExperimentEditor_${props.file.path()}`
|
|
74
|
+
const instance = props.file.instance()
|
|
75
|
+
const instanceTask = instance.task(0)
|
|
76
|
+
|
|
77
|
+
const parameters = vue.ref<string[]>([])
|
|
78
|
+
const xParameter = vue.ref(instanceTask.voiName())
|
|
79
|
+
const yParameter = vue.ref(instanceTask.stateName(0))
|
|
80
|
+
const plots = vue.ref<IGraphPanelPlot[]>([])
|
|
81
|
+
const consoleContents = vue.ref<string>(`<b>${props.file.path()}</b>`)
|
|
82
|
+
|
|
83
|
+
function addParameter(param: string): void {
|
|
84
|
+
parameters.value.push(param)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
addParameter(instanceTask.voiName())
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < instanceTask.stateCount(); i++) {
|
|
90
|
+
addParameter(instanceTask.stateName(i))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < instanceTask.rateCount(); i++) {
|
|
94
|
+
addParameter(instanceTask.rateName(i))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < instanceTask.constantCount(); i++) {
|
|
98
|
+
addParameter(instanceTask.constantName(i))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < instanceTask.computedConstantCount(); i++) {
|
|
102
|
+
addParameter(instanceTask.computedConstantName(i))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (let i = 0; i < instanceTask.algebraicCount(); i++) {
|
|
106
|
+
addParameter(instanceTask.algebraicName(i))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function onRun(): void {
|
|
110
|
+
// Run the instance, output the simulation time to the console, and update the plot.
|
|
111
|
+
|
|
112
|
+
const simulationTime = instance.run()
|
|
113
|
+
|
|
114
|
+
consoleContents.value += `<br/> <b>Simulation time:</b> ${common.formatTime(simulationTime)}`
|
|
115
|
+
|
|
116
|
+
void vue.nextTick().then(() => {
|
|
117
|
+
const consoleElement = document.getElementById(editorId)?.getElementsByClassName('ql-editor')[0]
|
|
118
|
+
|
|
119
|
+
if (consoleElement !== undefined) {
|
|
120
|
+
consoleElement.scrollTop = consoleElement.scrollHeight
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
updatePlot()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const xInfo = vue.computed(() => locCommon.simulationDataInfo(instanceTask, xParameter.value))
|
|
128
|
+
const yInfo = vue.computed(() => locCommon.simulationDataInfo(instanceTask, yParameter.value))
|
|
129
|
+
|
|
130
|
+
function updatePlot() {
|
|
131
|
+
plots.value = [
|
|
132
|
+
{
|
|
133
|
+
x: {
|
|
134
|
+
data: locCommon.simulationData(instanceTask, xInfo.value)
|
|
135
|
+
},
|
|
136
|
+
y: {
|
|
137
|
+
data: locCommon.simulationData(instanceTask, yInfo.value)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// "Initialise" our plot.
|
|
144
|
+
|
|
145
|
+
vue.onMounted(() => {
|
|
146
|
+
updatePlot()
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// Track the height of our file tablist toolbar.
|
|
150
|
+
|
|
151
|
+
vueCommon.trackElementHeight(toolbarId)
|
|
152
|
+
|
|
153
|
+
// Keyboard shortcuts.
|
|
154
|
+
|
|
155
|
+
if (!common.isMobile()) {
|
|
156
|
+
vueusecore.onKeyStroke((event: KeyboardEvent) => {
|
|
157
|
+
if (props.isActiveFile && !event.ctrlKey && !event.shiftKey && !event.metaKey && event.code === 'F9') {
|
|
158
|
+
event.preventDefault()
|
|
159
|
+
|
|
160
|
+
onRun()
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
</script>
|
|
165
|
+
|
|
166
|
+
<style scoped>
|
|
167
|
+
:deep(.p-button) {
|
|
168
|
+
transition: none;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
:deep(.p-button-icon) {
|
|
172
|
+
font-size: 1.5rem;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
:deep(.p-button-icon-only) {
|
|
176
|
+
width: 2rem;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
:deep(.p-button-label) {
|
|
180
|
+
height: 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
:deep(.p-editor-content) {
|
|
184
|
+
border: none !important;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
:deep(.p-editor-toolbar) {
|
|
188
|
+
display: none;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.p-toolbar {
|
|
192
|
+
border: none;
|
|
193
|
+
border-radius: 0;
|
|
194
|
+
border-bottom: 1px solid var(--p-content-border-color);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
:deep(.ql-editor) {
|
|
198
|
+
padding: 0.25rem 0.5rem;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
:deep(.ql-editor > *) {
|
|
202
|
+
cursor: default;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.simulation-experiment-only {
|
|
206
|
+
height: calc(100vh - var(--simulation-experiment-toolbar-height));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.simulation-experiment {
|
|
210
|
+
height: calc(
|
|
211
|
+
100vh - var(--main-menu-height) - var(--file-tablist-height) - var(--simulation-experiment-toolbar-height)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
</style>
|