Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cccf820
New version.
agarny Feb 24, 2026
1daa7cb
Updated our dependencies.
agarny Feb 24, 2026
02d8670
Scripts: some minor cleaning up.
agarny Feb 24, 2026
4e6a83e
Scripts: check that libOpenCOR's native Node.js module is present or …
agarny Feb 24, 2026
9414427
Common: cache the OS name.
agarny Feb 25, 2026
4b26c43
Graph Panel widget: some minor cleaning up.
agarny Feb 25, 2026
deb4bd4
Graph Panel widget: keep track of the theme mode in which we are.
agarny Feb 25, 2026
e2f1a5b
Simulation Experiment view: some minor optimisations.
agarny Feb 25, 2026
b2b3f20
Simulation Experiment view: small safeguard.
agarny Feb 25, 2026
66f71c0
Simulation Experiment view: optimised the use of our Math.js parser.
agarny Feb 25, 2026
cf3b093
Settings: optimised the saving our settings.
agarny Feb 25, 2026
dbf3bad
OpenCOR: prevent post initialisation from running more than once.
agarny Feb 25, 2026
6d62759
Linux: set up Linux desktop integration in the background so that it …
agarny Feb 25, 2026
c0e49a2
Graph Panel widget: some minor optimisations.
agarny Feb 25, 2026
b7f344e
Simulation Experiment view: some minor optimisations.
agarny Feb 25, 2026
75a5c91
Simulation Experiment view: fixed a small issue with a run's tooltip.
agarny Feb 26, 2026
9ac51ae
Main window: some minor optimisations.
agarny Feb 26, 2026
2d44abc
Simulation Experiment view: some minor optimisations.
agarny Feb 26, 2026
83f918b
libOpenCOR: upgraded to version 0.20260226.0.
agarny Feb 26, 2026
efbadad
Simulation Experiment view: optimised the plotting of simulation resu…
agarny Feb 26, 2026
09ce260
Some minor cleaning up.
agarny Feb 26, 2026
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
2 changes: 0 additions & 2 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ jobs:
- name: OpenCOR dependencies (Windows ARM only)
if: ${{ matrix.name == 'Windows (ARM)' }}
run: bun install --cpu=arm64
- name: Build libOpenCOR
run: bun libopencor
- name: Build OpenCOR
env:
VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }}
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ jobs:
- name: OpenCOR dependencies (Windows ARM only)
if: ${{ matrix.name == 'Windows (ARM)' }}
run: bun install --cpu=arm64
- name: Build libOpenCOR
if: ${{ matrix.name != 'Code formatting' && matrix.name != 'Linting' }}
run: bun libopencor
- name: Build OpenCOR
if: ${{ matrix.name != 'Code formatting' && matrix.name != 'Linting' }}
run: bun run build
Expand Down
11 changes: 2 additions & 9 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,7 @@ npm install -g bun
bun install
```

2. **Build libOpenCOR's native Node.js module:**

```bash
bun libopencor
```

3. **Start the development version of OpenCOR:**
2. **Start the development version of OpenCOR:**
- **Desktop version:**

```bash
Expand All @@ -57,7 +51,7 @@ npm install -g bun
bun dev:web
```

4. **Test OpenCOR:**
3. **Test OpenCOR:**
- **Desktop version:** the application will open automatically; and
- **Web app version:** open your browser and navigate to the local development URL (typically http://localhost:5173).

Expand All @@ -74,7 +68,6 @@ npm install -g bun
| `dev:web` | (Build and) start OpenCOR's Web app with hot reload |
| `format` | Format the code |
| `format:check` | Check code formatting without making changes |
| `libopencor` | Build libOpenCOR's native Node.js module |
| `lint` | Lint and automatically fix issues |
| `release` | Release OpenCOR for the current platform |
| `release:local` | Release OpenCOR for the current platform without code signing |
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ endforeach()

# Project details.

project(libOpenCOR VERSION 0.20260211.0)
project(libOpenCOR VERSION 0.20260226.0)

# Enable C++20.

Expand Down
48 changes: 25 additions & 23 deletions bun.lock

Large diffs are not rendered by default.

23 changes: 11 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,21 @@
"url": "git+https://github.com/opencor/webapp.git"
},
"type": "module",
"version": "0.20260224.1",
"version": "0.20260227.0",
"scripts": {
"archive:web": "bun src/renderer/scripts/archive.web.js",
"build": "electron-vite build",
"build": "bun src/renderer/scripts/libopencor.js && electron-vite build",
"build:web": "cd ./src/renderer && vite build && bun scripts/generate.version.js",
"clean": "bun src/renderer/scripts/clean.js",
"dependencies:update": "bun clean && bun update -i && bun install && bun clean && cd ./src/renderer && bun clean && bun update -i && bun install && bun clean",
"dev": "electron-vite dev --watch",
"dev": "bun src/renderer/scripts/libopencor.js && electron-vite dev --watch",
"dev:web": "cd ./src/renderer && vite dev",
"format": "bunx --bun biome format --fix --max-diagnostics=none && clang-format -i src/renderer/src/libopencor/src/*",
"format:check": "bunx --bun biome format --max-diagnostics=none && clang-format --dry-run -Werror src/renderer/src/libopencor/src/*",
"libopencor": "cmake-js build -B Release -O ./dist/libOpenCOR",
"lint": "bunx --bun biome lint --fix --error-on-warnings --max-diagnostics=none && bunx stylelint '**/*.css' --fix",
"release": "electron-builder",
"release:local": "CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --config.mac.notarize=false --publish=never",
"start": "electron-vite preview",
"release": "bun src/renderer/scripts/libopencor.js && electron-builder",
"release:local": "bun src/renderer/scripts/libopencor.js && CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --config.mac.notarize=false --publish=never",
"start": "bun src/renderer/scripts/libopencor.js && electron-vite preview",
"start:web": "bun build:web && cd ./src/renderer && vite preview",
"version:new": "bun src/renderer/scripts/version.js"
},
Expand Down Expand Up @@ -67,22 +66,22 @@
"@electron-toolkit/utils": "^4.0.0",
"@tailwindcss/postcss": "^4.2.1",
"@tailwindcss/vite": "^4.2.1",
"@types/node": "^25.3.0",
"@types/node": "^25.3.1",
"@types/plotly.js": "^3.0.10",
"@vitejs/plugin-vue": "^6.0.4",
"@vue/tsconfig": "^0.8.1",
"@wasm-fmt/clang-format": "^21.1.8",
"autoprefixer": "^10.4.24",
"@wasm-fmt/clang-format": "^22.1.0",
"autoprefixer": "^10.4.27",
"cmake-js": "^8.0.0",
"electron": "^40.6.0",
"electron": "^40.6.1",
"electron-builder": "^26.8.1",
"electron-conf": "^1.3.0",
"electron-updater": "^6.8.3",
"electron-vite": "^5.0.0",
"esbuild": "^0.27.3",
"node-addon-api": "^8.5.0",
"rollup-plugin-visualizer": "^7.0.0",
"stylelint": "^17.3.0",
"stylelint": "^17.4.0",
"stylelint-config-standard": "^40.0.0",
"tailwindcss": "^4.2.1",
"tailwindcss-primeui": "^0.6.1",
Expand Down
62 changes: 44 additions & 18 deletions src/main/MainWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ export const resetAll = (): void => {

let recentFilePaths: string[] = [];

const removeRecentFilePath = (filePath: string): void => {
for (let i = recentFilePaths.length - 1; i >= 0; --i) {
if (recentFilePaths[i] === filePath) {
recentFilePaths.splice(i, 1);
}
}
};

export const clearRecentFiles = (): void => {
recentFilePaths = [];

Expand All @@ -94,20 +102,25 @@ export const fileClosed = (filePath: string): void => {
return;
}

removeRecentFilePath(filePath);

recentFilePaths.unshift(filePath);
recentFilePaths = recentFilePaths.slice(0, 10);

if (recentFilePaths.length > 10) {
recentFilePaths.length = 10;
}

updateReopenMenu(recentFilePaths);
};

export const fileIssue = (filePath: string): void => {
recentFilePaths = recentFilePaths.filter((recentFilePath) => recentFilePath !== filePath);
removeRecentFilePath(filePath);

updateReopenMenu(recentFilePaths);
};

export const fileOpened = (filePath: string): void => {
recentFilePaths = recentFilePaths.filter((recentFilePath) => recentFilePath !== filePath);
removeRecentFilePath(filePath);

updateReopenMenu(recentFilePaths);

Expand Down Expand Up @@ -142,6 +155,7 @@ export class MainWindow extends ApplicationWindow {
private _splashScreenWindowClosed = false;

private _openedFilePaths: string[] = [];
private _openedFilePathIndex = 0;
private _selectedFilePath = '';

// Constructor.
Expand Down Expand Up @@ -209,6 +223,7 @@ export class MainWindow extends ApplicationWindow {
// Reopen previously opened files, if any, and select the previously selected file.

this._openedFilePaths = electronConf.get('app.files.opened');
this._openedFilePathIndex = 0;
this._selectedFilePath = electronConf.get('app.files.selected');

this.reopenFilePathsAndSelectFilePath();
Expand Down Expand Up @@ -360,16 +375,21 @@ export class MainWindow extends ApplicationWindow {
// reopen. So, we need to wait for the file to be reopened before reopening the next one.

reopenFilePathsAndSelectFilePath(): void {
if (this._openedFilePaths.length) {
const filePath = this._openedFilePaths[0];
if (this._openedFilePathIndex < this._openedFilePaths.length) {
const filePath = this._openedFilePaths[this._openedFilePathIndex];

this.webContents.send('open', filePath);
if (filePath) {
this.webContents.send('open', filePath);
}

this._openedFilePaths = this._openedFilePaths.slice(1);
++this._openedFilePathIndex;

if (this._openedFilePaths.length) {
if (this._openedFilePathIndex < this._openedFilePaths.length) {
return;
}

this._openedFilePaths = [];
this._openedFilePathIndex = 0;
}

if (this._selectedFilePath) {
Expand All @@ -394,21 +414,27 @@ export class MainWindow extends ApplicationWindow {
return;
}

commandLine.forEach((argument: string) => {
for (let argument of commandLine) {
if (this.isAction(argument)) {
this.webContents.send('action', argument.slice(FULL_URI_SCHEME.length));
} else if (argument !== '--allow-file-access-from-files' && argument !== '--enable-avfoundation') {
// The argument is not an action (and not --allow-file-access-from-files or --enable-avfoundation either), so it
// must be a file to open. But, first, check whether the argument is a relative path and, if so, convert it to
// an absolute path.

if (!path.isAbsolute(argument)) {
argument = path.resolve(argument);
}
continue;
}

this.webContents.send('open', argument);
if (argument === '--allow-file-access-from-files' || argument === '--enable-avfoundation') {
continue;
}
});

// The argument is not an action (and not --allow-file-access-from-files or --enable-avfoundation either), so it
// must be a file to open. But, first, check whether the argument is a relative path and, if so, convert it to
// an absolute path.

if (!path.isAbsolute(argument)) {
argument = path.resolve(argument);
}

this.webContents.send('open', argument);
}
}

// Enable/disable our UI.
Expand Down
80 changes: 55 additions & 25 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,44 +90,68 @@ electron.app.on('second-instance', (_event, argv) => {

electron.app.setAsDefaultProtocolClient(URI_SCHEME, isWindows() ? process.execPath : undefined);

if (isLinux()) {
// Make our application icon available so that it can be referenced by our desktop file.
// Set up Linux desktop integration by creating a desktop file and making our application icon available.
// Note: this is not needed on Windows and macOS since they automatically pick up the necessary information from
// OpenCOR.

const localShareFolder = path.join(electron.app.getPath('home'), '.local/share');
const localShareOpencorFolder = path.join(localShareFolder, URI_SCHEME);
const setupLinuxDesktopIntegration = async (): Promise<void> => {
try {
// Make our application icon available so that it can be referenced by our desktop file.

// Check whether localShareOpencorFolder exists and, if not, create it.
const localShareFolder = path.join(electron.app.getPath('home'), '.local/share');
const localShareOpencorFolder = path.join(localShareFolder, URI_SCHEME);
const iconSourcePath = path.join(import.meta.dirname, '../../src/main/assets/icon.png');
const iconTargetPath = path.join(localShareOpencorFolder, 'icon.png');

if (!fs.existsSync(localShareOpencorFolder)) {
fs.mkdirSync(localShareOpencorFolder);
}
await fs.promises.mkdir(localShareOpencorFolder, { recursive: true });
await fs.promises.copyFile(iconSourcePath, iconTargetPath);

// Create a desktop file for OpenCOR and its URI scheme.

fs.copyFileSync(
path.join(import.meta.dirname, '../../src/main/assets/icon.png'),
path.join(`${localShareOpencorFolder}/icon.png`)
);
const localApplicationsFolder = path.join(localShareFolder, 'applications');

// Create a desktop file for OpenCOR and its URI scheme.
await fs.promises.mkdir(localApplicationsFolder, { recursive: true });

fs.writeFileSync(
path.join(`${localShareFolder}/applications/${URI_SCHEME}.desktop`),
`[Desktop Entry]
const desktopFilePath = path.join(localApplicationsFolder, `${URI_SCHEME}.desktop`);
const desktopFileContents = `[Desktop Entry]
Type=Application
Name=OpenCOR
Exec=${process.execPath} %u
Icon=${localShareOpencorFolder}/icon.png
Icon=${iconTargetPath}
Terminal=false
MimeType=x-scheme-handler/${URI_SCHEME}`
);
MimeType=x-scheme-handler/${URI_SCHEME}`;
let updateDesktopDatabase: boolean = false;

try {
const currentDesktopFileContents = await fs.promises.readFile(desktopFilePath, { encoding: 'utf8' });

if (currentDesktopFileContents !== desktopFileContents) {
await fs.promises.writeFile(desktopFilePath, desktopFileContents);

// Update the desktop database.
updateDesktopDatabase = true;
}
} catch {
await fs.promises.writeFile(desktopFilePath, desktopFileContents);

nodeChildProcess.exec('update-desktop-database ~/.local/share/applications', (error) => {
if (error) {
console.error('Failed to update the desktop database:', error);
updateDesktopDatabase = true;
}
});
}

// Update the desktop database.

if (updateDesktopDatabase) {
nodeChildProcess.exec(
'update-desktop-database ~/.local/share/applications',
(error: nodeChildProcess.ExecException | null) => {
if (error) {
console.error('Failed to update the desktop database:', formatError(error));
}
}
);
}
} catch (error: unknown) {
console.error('Failed to set up Linux desktop integration:', formatError(error));
}
};

// Handle the clicking of an opencor:// link.

Expand All @@ -144,6 +168,12 @@ electron.app.on('open-url', (_event, url) => {
electron.app
.whenReady()
.then(() => {
// Set up Linux desktop integration.

if (isLinux()) {
setupLinuxDesktopIntegration();
}

// Set process.env.NODE_ENV to 'production' if we are not the default app.
// Note: we do this because some packages rely on the value of process.env.NODE_ENV to determine whether they
// should run in development mode (default) or production mode.
Expand Down
Loading