Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"url": "git+https://github.com/opencor/webapp.git"
},
"type": "module",
"version": "0.20260303.1",
"version": "0.20260304.0",
"engines": {
"bun": ">=1.2.0"
},
Expand Down Expand Up @@ -82,7 +82,7 @@
"electron-vite": "^5.0.0",
"esbuild": "^0.27.3",
"node-addon-api": "^8.6.0",
"rollup-plugin-visualizer": "^7.0.0",
"rollup-plugin-visualizer": "^7.0.1",
"stylelint": "^17.4.0",
"stylelint-config-standard": "^40.0.0",
"tailwindcss": "^4.2.1",
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/renderer/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { IOpenCORProps, Theme } from './index.ts';
export { IOpenCORProps, IOpenCORSimulationData, OpenCORSimulationData, OpenCORTheme } from './index.ts';

export declare const OpenCOR: import('vue').Component<IOpenCORProps>;
export default OpenCOR;
11 changes: 9 additions & 2 deletions src/renderer/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
export type Theme = 'light' | 'dark' | 'system';
export type OpenCORTheme = 'light' | 'dark' | 'system';

export interface IOpenCORProps {
omex?: string | Uint8Array;
theme?: Theme;
theme?: OpenCORTheme;
}

export type OpenCORSimulationData = Record<string, Float64Array | undefined>;

export interface IOpenCORSimulationData {
simulationData: OpenCORSimulationData;
issues: string[];
}

export { default, default as OpenCOR } from './src/components/OpenCOR.vue';
2 changes: 1 addition & 1 deletion src/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"./style.css": "./dist/opencor.css"
},
"version": "0.20260303.1",
"version": "0.20260304.0",
"scripts": {
"build": "vite build && bun scripts/generate.version.js",
"build:lib": "vite build --config vite.lib.config.ts && bun scripts/copy.indexdts.js",
Expand Down
27 changes: 17 additions & 10 deletions src/renderer/src/AppWithSimulationData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,24 @@ const opencorRef = vue.ref<InstanceType<typeof OpenCOR> | null>(null);

vue.onMounted(() => {
if (opencorRef.value) {
for (const modelParameter of ['VOI', 'main/t', 'main/x', 'main/y', 'main/z', 'unknown']) {
opencorRef.value
.simulationData(modelParameter)
.then((simulationData: Float64Array) => {
console.log(`Simulation data for "${modelParameter}":`);
console.log(simulationData);
})
.catch((error) => {
console.error(error);
const params = ['VOI', 'main/t', 'main/x', 'main/y', 'main/z', 'unknown'];

opencorRef.value.simulationData(params).then((res) => {
for (const modelParameter of params) {
console.log(`Simulation data for "${modelParameter}":`);
console.log(res.simulationData[modelParameter]);
}

if (res.issues.length > 0) {
console.log('Issues:');

res.issues.forEach((issue) => {
console.log(` - ${issue}`);
});
}
} else {
console.log('No issues.');
}
});
}
});
</script>
Expand Down
14 changes: 14 additions & 0 deletions src/renderer/src/common/common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { OpenCORSimulationData } from '../../index.ts';

import * as dependencies from './dependencies.ts';
import { electronApi } from './electronApi.ts';

Expand Down Expand Up @@ -212,3 +214,15 @@ export const fileName = (filePath: string): string => {
export const sleep = (ms: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, ms));
};

// A method to get an object with undefined values for a list of model parameters.

export const undefinedSimulationData = (modelParameters: string[]): OpenCORSimulationData => {
const res: OpenCORSimulationData = {};

for (const modelParameter of modelParameters) {
res[modelParameter] = undefined;
}

return res;
};
8 changes: 4 additions & 4 deletions src/renderer/src/common/vueCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as vueusecore from '@vueuse/core';

import * as vue from 'vue';

import type { Theme } from '../../index.ts';
import type { OpenCORTheme } from '../../index.ts';

// A constant to know the UID of the active instance of OpenCOR.

Expand All @@ -14,7 +14,7 @@ export const useTheme = vueusecore.createGlobalState(() => {
const prefersColorScheme = window.matchMedia('(prefers-color-scheme: light)');
const isLightMode = vue.ref(prefersColorScheme.matches);
const isDarkMode = vue.ref(!prefersColorScheme.matches);
const _theme = vue.ref<Theme>('system');
const _theme = vue.ref<OpenCORTheme>('system');

const updateLightAndDarkModes = (prefersColorScheme: MediaQueryList | MediaQueryListEvent) => {
isLightMode.value = prefersColorScheme.matches;
Expand All @@ -32,11 +32,11 @@ export const useTheme = vueusecore.createGlobalState(() => {
}
});

const theme = (): Theme => {
const theme = (): OpenCORTheme => {
return _theme.value;
};

const setTheme = (newTheme: Theme | undefined) => {
const setTheme = (newTheme: OpenCORTheme | undefined) => {
_theme.value = newTheme ?? 'system';

if (_theme.value === 'light') {
Expand Down
27 changes: 18 additions & 9 deletions src/renderer/src/components/ContentsComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import { electronApi } from '../common/electronApi.ts';
import * as locApi from '../libopencor/locApi.ts';

import SimulationExperimentView from './views/SimulationExperimentView.vue';
import type { IOpenCORSimulationData } from '../../index.ts';

interface IFileTab {
file: locApi.File;
Expand Down Expand Up @@ -173,31 +174,39 @@ const closeAllFiles = (): void => {
}
};

const simulationData = (modelParameter: string, attempt: number = 0): Promise<Float64Array> => {
const simulationData = (modelParameters: string[], attempt: number = 0): Promise<IOpenCORSimulationData> => {
if (!props.simulationOnly) {
return Promise.reject(new Error('Simulation data can only be retrieved in simulation only mode.'));
return Promise.resolve({
simulationData: common.undefinedSimulationData(modelParameters),
issues: ['Simulation data can only be retrieved in simulation only mode.']
});
}

const simulationExperimentViews = simulationExperimentViewRef.value;

if (!simulationExperimentViews.length) {
// In simulation only mode, there should always be a simulation experiment view available, but we add this check
// just in case. If there is no simulation experiment view available, we retry a few times with a delay to give
// it some time to load before giving up.
// In simulation only mode, there should always be a simulation experiment view available, but we retry a few times
// with a delay to give it some time to load before giving up.

if (attempt < 3) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(simulationData(modelParameter, attempt + 1));
resolve(simulationData(modelParameters, attempt + 1));
}, HUGE_DELAY);
});
}

return Promise.reject(new Error('No simulation experiment view available.'));
return Promise.resolve({
simulationData: common.undefinedSimulationData(modelParameters),
issues: ['No simulation experiment view available.']
});
}

return simulationExperimentViews[0].simulationData(modelParameter).catch((error: unknown) => {
throw new Error(common.formatError(error));
return simulationExperimentViews[0].simulationData(modelParameters).catch((error: unknown) => {
return {
simulationData: common.undefinedSimulationData(modelParameters),
issues: [common.formatError(error)]
};
});
};

Expand Down
16 changes: 11 additions & 5 deletions src/renderer/src/components/OpenCOR.vue
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ import primeVueToastService from 'primevue/toastservice';
import { useToast } from 'primevue/usetoast';
import * as vue from 'vue';

import type { IOpenCORProps } from '../../index.ts';
import type { IOpenCORProps, IOpenCORSimulationData } from '../../index.ts';

import '../assets/app.css';
import '../assets/primeicons-assets.ts';
Expand All @@ -139,15 +139,21 @@ import MainMenu from './MainMenu.vue';

const props = defineProps<IOpenCORProps>();

const simulationData = (modelParameter: string): Promise<Float64Array> => {
const simulationData = (modelParameters: string[]): Promise<IOpenCORSimulationData> => {
const contents = contentsRef.value;

if (!contents) {
return Promise.reject(new Error('No contents available.'));
return Promise.resolve({
simulationData: common.undefinedSimulationData(modelParameters),
issues: ['No contents available.']
});
}

return contents.simulationData(modelParameter).catch((error: unknown) => {
throw new Error(common.formatError(error));
return contents.simulationData(modelParameters).catch((error: unknown) => {
return {
simulationData: common.undefinedSimulationData(modelParameters),
issues: [common.formatError(error)]
};
});
};

Expand Down
46 changes: 31 additions & 15 deletions src/renderer/src/components/views/SimulationExperimentView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ import * as vueusecore from '@vueuse/core';
import Popover from 'primevue/popover';
import * as vue from 'vue';

import type { IOpenCORSimulationData } from '../../../index.ts';

import * as colors from '../../common/colors.ts';
import * as common from '../../common/common.ts';
import * as dependencies from '../../common/dependencies.ts';
Expand Down Expand Up @@ -659,30 +661,44 @@ const interactiveSettings = vue.computed<ISimulationExperimentViewSettings>(() =
}));
const interactiveOldSettings = vue.ref<string>(JSON.stringify(vue.toRaw(interactiveSettings.value)));

// A helper function to get the simulation data for a given model parameter.
// A helper function to retrieve simulation data for one or more model parameters.

const simulationData = (modelParameters: string[]): Promise<IOpenCORSimulationData> => {
const res: IOpenCORSimulationData = {
simulationData: common.undefinedSimulationData(modelParameters),
issues: []
};

const simulationData = (modelParameter: string): Promise<Float64Array> => {
if (!interactiveInstanceTask) {
return Promise.reject(new Error('No SED-ML instance task available.'));
res.issues.push('No SED-ML instance task available.');

return Promise.resolve(res);
}

const instanceTask = interactiveInstanceTask as locSedApi.SedInstanceTask;
const info = locCommon.simulationDataInfo(
instanceTask,
modelParameter === 'VOI' ? instanceTask.voiName() : modelParameter
);

if (isNoSimulationDataInfo(info)) {
return Promise.reject(
new Error(`No simulation data information was found for model parameter "${modelParameter}".`)
for (const modelParameter of modelParameters) {
const info = locCommon.simulationDataInfo(
instanceTask,
modelParameter === 'VOI' ? instanceTask.voiName() : modelParameter
);
}

try {
return Promise.resolve(locCommon.simulationData(instanceTask, info));
} catch (error: unknown) {
return Promise.reject(new Error(common.formatError(error)));
if (isNoSimulationDataInfo(info)) {
res.simulationData[modelParameter] = undefined;
res.issues.push(`No simulation data information was found for model parameter "${modelParameter}".`);

continue;
}

try {
res.simulationData[modelParameter] = locCommon.simulationData(instanceTask, info);
} catch (error: unknown) {
res.simulationData[modelParameter] = undefined;
res.issues.push(`Error for model parameter "${modelParameter}": ${common.formatError(error)}`);
}
}

return Promise.resolve(res);
};

defineExpose({
Expand Down