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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
"url": "git+https://github.com/opencor/webapp.git"
},
"type": "module",
"version": "0.20260208.0",
"version": "0.20260209.0",
"scripts": {
"archive:web": "bun src/renderer/scripts/archive.web.js",
"build": "electron-vite build",
"build:web": "cd ./src/renderer && 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",
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
},
"./style.css": "./dist/opencor.css"
},
"version": "0.20260208.0",
"version": "0.20260209.0",
"scripts": {
"build": "vite build",
"build": "vite build && bun scripts/generate.version.js",
"build:lib": "vite build --config vite.lib.config.ts && cp index.d.ts dist/index.d.ts",
"clean": "bun scripts/clean.js",
"dependencies:update": "cd ../.. && bun clean && bun update -i && bun install && bun clean && cd src/renderer && bun clean && bun update -i && bun install && bun clean",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/scripts/archive.web.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ if (!fs.existsSync('dist')) {
fs.mkdirSync('dist');
}

const version = JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
const version = JSON.parse(fs.readFileSync('package.json')).version;

tar.c(
{
Expand Down
26 changes: 26 additions & 0 deletions src/renderer/scripts/generate.version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

// Retrieve the version from the package.json file.

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const packageJsonPath = path.join(__dirname, '../package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath));
const { version } = packageJson;

// Make sure that the dist/assets folder exists.

const distAssetsPath = path.join(__dirname, '../dist/assets');

if (!fs.existsSync(distAssetsPath)) {
fs.mkdirSync(distAssetsPath, { recursive: true });
}

// Write the version file.

fs.writeFileSync(path.join(distAssetsPath, 'version.json'), JSON.stringify({ version }, null, 2));

// Log the generated version.

console.log(`Generated version.json with version ${version}.`);
2 changes: 1 addition & 1 deletion src/renderer/src/common/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Settings {
// Start with some default settings and then load them.
// Note: to have default settings is critical when running the desktop version of OpenCOR since they are loaded
// asynchronously, so we need to ensure that our settings are always defined. This is not the case for the
// web version of OpenCOR, where we can load our settings directly from the cookies.
// Web version of OpenCOR, where we can load our settings directly from the cookies.

this.reset();
this.load();
Expand Down
122 changes: 122 additions & 0 deletions src/renderer/src/common/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import * as vue from 'vue';

import packageJson from '../../package.json' with { type: 'json' };

Comment on lines +1 to +4
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import ... with { type: 'json' } introduces import attributes syntax that isn’t used elsewhere in the renderer (other files import package.json directly) and can be less compatible with the current Vite/TS pipeline. Consider switching to the same JSON import style used in the Vue components (or otherwise standardize on one approach) to avoid build/runtime differences.

Copilot uses AI. Check for mistakes.
import { electronApi } from './electronApi.ts';

const { version } = packageJson;

// State to track whether an update is available and the latest version.

const updateAvailable = vue.ref(false);
const latestVersion = vue.ref<string>('');

// Check if a new version is available.

interface IVersionInfo {
version: string;
}

const checkForUpdates = async (): Promise<boolean> => {
// Make sure that we are not running the desktop version of OpenCOR.

if (electronApi) {
return false;
}

// Get the latest version information from the server and compare it with the current version.

try {
// Fetch the version.json file from the server with cache busting (i.e. by adding a timestamp query parameter to the
// URL so that the browser doesn't serve a cached version).

const response = await fetch(`./assets/version.json?t=${Date.now()}`);

if (!response.ok) {
console.warn('Failed to fetch the version information.');

updateAvailable.value = false;
latestVersion.value = '';

return false;
}

const versionInfo: IVersionInfo = await response.json();

latestVersion.value = versionInfo.version;

// Compare versions.

const isNewer = isNewerVersion(latestVersion.value, version);

updateAvailable.value = isNewer;

return isNewer;
} catch (error) {
console.error('Failed to check for updates:', error);

updateAvailable.value = false;
latestVersion.value = '';

return false;
}
};

// Return whether the first version is newer than the second version.

const isNewerVersion = (versionA: string, versionB: string): boolean => {
const partsA = versionA.split('.').map(Number);
const partsB = versionB.split('.').map(Number);

for (let i = 0; i < Math.max(partsA.length, partsB.length); ++i) {
const partA = partsA[i] || 0;
const partB = partsB[i] || 0;

if (partA > partB) {
return true;
}

if (partA < partB) {
return false;
}
}

return false;
};

// Start periodic version checking (every 5 minutes).

let checkInterval: number | null = null;

const startCheck = (): void => {
// Make sure that we are not running the desktop version of OpenCOR.

if (electronApi) {
return;
}

// Check immediately on start.

checkForUpdates();

// Then check every 5 minutes.

if (!checkInterval) {
checkInterval = window.setInterval(
() => {
checkForUpdates();
},
5 * 60 * 1000 // Every 5 minutes.
);
}
};

// Reload the Web app to get the latest version (force cache bypass).

const reloadApp = (): void => {
window.location.replace(window.location.href);
};

// Export the version checking functions and state.

export { latestVersion, reloadApp, startCheck, updateAvailable };
3 changes: 2 additions & 1 deletion src/renderer/src/components/BackgroundComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
* - Height: 257.92px + 8px = 266.92px.
*/

import { COPYRIGHT } from '../common/constants.ts';
import { version } from '../../package.json';

import { COPYRIGHT } from '../common/constants.ts';
</script>

<style scoped>
Expand Down
46 changes: 45 additions & 1 deletion src/renderer/src/components/MainMenu.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<Menubar ref="menuBar" :model="items">
<Menubar ref="menuBar" :id="props.id" :model="items">
<template #item="{ item, props }">
<a v-bind="props.action">
<div class="p-menubar-item-label">{{ item.label }}</div>
Expand All @@ -20,7 +20,23 @@
</div>
</a>
</template>
<template #end>
<button v-if="updateAvailable" class="update-link flex mr-1 text-xs px-1.5 py-0.5 cursor-pointer rounded-sm border border-transparent bg-transparent"
type="button" title="Click to reload and update"
@click="onUpdateClick"
>
New version available!
</button>
</template>
</Menubar>

<YesNoQuestionDialog
v-model:visible="updateConfirmVisible"
title="Update Available..."
:question="'Version ' + latestVersion + ' is available. Do you want to reload and update now?'"
@yes="onUpdate"
@no="updateConfirmVisible = false"
/>
</template>

<script setup lang="ts">
Expand All @@ -30,8 +46,10 @@ import type Menubar from 'primevue/menubar';
import * as vue from 'vue';

import * as common from '../common/common.ts';
import * as version from '../common/version.ts';

const props = defineProps<{
id?: string;
isActive: boolean;
hasFiles: boolean;
uiEnabled: boolean;
Expand All @@ -46,6 +64,7 @@ const emit = defineEmits<{
(event: 'openSampleLorenz'): void;
(event: 'settings'): void;
}>();

const isWindowsOrLinux = common.isWindows() || common.isLinux();
const isMacOs = common.isMacOs();

Expand Down Expand Up @@ -137,6 +156,22 @@ const items = [
}
];

// Handle the click on the update link button by showing a Yes/No dialog.

const updateConfirmVisible = vue.ref(false);
const updateAvailable = version.updateAvailable;
const latestVersion = version.latestVersion;

const onUpdateClick = () => {
updateConfirmVisible.value = true;
};

const onUpdate = () => {
updateConfirmVisible.value = false;

version.reloadApp();
};

// A few things that can only be done when the component is mounted.

const menuBar = vue.ref<(vue.ComponentPublicInstance<typeof Menubar> & { hide: () => void }) | null>(null);
Expand Down Expand Up @@ -261,4 +296,13 @@ if (common.isDesktop()) {
background-color: var(--p-content-hover-background);
color: var(--p-text-muted-color);
}

.update-link {
color: var(--p-primary-color);
}

.update-link:hover {
background-color: var(--p-content-hover-background);
border-color: var(--p-content-border-color);
}
</style>
5 changes: 5 additions & 0 deletions src/renderer/src/components/OpenCOR.vue
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ import { electronApi } from '../common/electronApi.ts';
import firebaseConfig, { missingFirebaseKeys } from '../common/firebaseConfig';
*/
import * as locCommon from '../common/locCommon.ts';
import * as version from '../common/version.ts';
import * as vueCommon from '../common/vueCommon.ts';
import type IContentsComponent from '../components/ContentsComponent.vue';
import * as locApi from '../libopencor/locApi.ts';
Expand Down Expand Up @@ -262,6 +263,10 @@ if (!window.locApi) {
vue.watch(locApiInitialised, (newLocApiInitialised: boolean) => {
if (newLocApiInitialised) {
loadingOpencorMessageVisible.value = false;

// We are now officially loaded, so start checking for a newer version of OpenCOR.

version.startCheck();
}
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/src/components/dialogs/AboutDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@
</template>

<script setup lang="ts">
import { version } from '../../../package.json';

import { COPYRIGHT } from '../../common/constants.ts';
import { electronApi } from '../../common/electronApi.ts';
import * as locApi from '../../libopencor/locApi.ts';

defineEmits<(event: 'close') => void>();

import { version } from '../../../package.json';
</script>
Loading