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
2 changes: 1 addition & 1 deletion 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.20260204.0",
"version": "0.20260204.1",
"scripts": {
"archive:web": "bun src/renderer/scripts/archive.web.js",
"build": "electron-vite build",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
},
"./style.css": "./dist/opencor.css"
},
"version": "0.20260204.0",
"version": "0.20260204.1",
"scripts": {
"build": "vite build",
"build:lib": "vite build --config vite.lib.config.ts && cp index.d.ts dist/index.d.ts",
Expand Down
82 changes: 41 additions & 41 deletions src/renderer/src/common/locCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,17 @@ import { electronApi } from './electronApi.ts';

export interface IDataUriInfo {
res: boolean;
data: Uint8Array | null;
error: string | null;
fileName?: string;
data?: Uint8Array;
error?: string;
}

function zipDataFromDataUrl(dataUrl: string | Uint8Array | File, mimeType: string): IDataUriInfo {
// Make sure that we have a string data URL.

if (dataUrl instanceof Uint8Array || dataUrl instanceof File) {
return {
res: false,
data: null,
error: null
res: false
};
}

Expand All @@ -31,9 +30,7 @@ function zipDataFromDataUrl(dataUrl: string | Uint8Array | File, mimeType: strin

if (!res) {
return {
res: false,
data: null,
error: null
res: false
};
}

Expand All @@ -44,7 +41,6 @@ function zipDataFromDataUrl(dataUrl: string | Uint8Array | File, mimeType: strin
} catch (error: unknown) {
return {
res: true,
data: null,
error: `The data URL contains invalid Base64 encoding (${formatMessage(formatError(error), false)}).`
};
}
Expand All @@ -60,15 +56,13 @@ function zipDataFromDataUrl(dataUrl: string | Uint8Array | File, mimeType: strin
) {
return {
res: true,
data: null,
error: `The data URL of MIME type ${mimeType} does not contain a ZIP file.`
};
}

return {
res: true,
data,
error: null
data
};
}

Expand All @@ -79,7 +73,7 @@ export async function zipCellmlDataUrl(dataUrl: string | Uint8Array | File): Pro
const zipDataUrl = zipDataFromDataUrl(dataUrl, mimeType);

if (zipDataUrl.res) {
if (zipDataUrl.data === null) {
if (!zipDataUrl.data) {
return zipDataUrl;
}

Expand All @@ -96,7 +90,6 @@ export async function zipCellmlDataUrl(dataUrl: string | Uint8Array | File): Pro
if (fileNames.length !== 1) {
return {
res: true,
data: null,
error: `The data URL of MIME type ${mimeType} does not contain exactly one (CellML) file.`
};
}
Expand All @@ -109,7 +102,6 @@ export async function zipCellmlDataUrl(dataUrl: string | Uint8Array | File): Pro
if (!file || file.dir) {
return {
res: true,
data: null,
error: `The data URL of MIME type ${mimeType} does not contain a valid file.`
};
}
Expand All @@ -118,13 +110,12 @@ export async function zipCellmlDataUrl(dataUrl: string | Uint8Array | File): Pro

return {
res: true,
data: await file.async('uint8array'),
error: null
fileName,
data: await file.async('uint8array')
};
} catch (error: unknown) {
return {
res: true,
data: null,
error: `The data URL of MIME type ${mimeType} contains an invalid ZIP file (${formatMessage(formatError(error), false)}).`
};
}
Expand All @@ -133,9 +124,7 @@ export async function zipCellmlDataUrl(dataUrl: string | Uint8Array | File): Pro
// Not a data URL for a zipped CellML file.

return {
res: false,
data: null,
error: null
res: false
};
}

Expand All @@ -146,44 +135,48 @@ export function combineArchiveDataUrl(dataUrl: string | Uint8Array | File): IDat
const zipDataUrl = zipDataFromDataUrl(dataUrl, mimeType);

if (zipDataUrl.res) {
if (zipDataUrl.data === null) {
if (!zipDataUrl.data) {
return zipDataUrl;
}

return {
res: true,
data: zipDataUrl.data,
error: null
data: zipDataUrl.data
};
}

// Not a data URL for a COMBINE archive.

return {
res: false,
data: null,
error: null
res: false
};
}

export function isRemoteFilePath(filePath: string): boolean {
return filePath.startsWith('http://') || filePath.startsWith('https://');
}

export function filePath(fileFilePathOrFileContents: string | Uint8Array | File, dataUrlCounter: number): string {
return dataUrlCounter
? `Data URL ${dataUrlCounter}`
: fileFilePathOrFileContents instanceof File
? electronApi
? electronApi.filePath(fileFilePathOrFileContents)
: fileFilePathOrFileContents.name
: typeof fileFilePathOrFileContents === 'string'
? fileFilePathOrFileContents
: sha256(fileFilePathOrFileContents);
export function filePath(
fileFilePathOrFileContents: string | Uint8Array | File,
dataUrlFileName: string,
dataUrlCounter: number
): string {
return dataUrlFileName
? dataUrlFileName
: dataUrlCounter
? `OMEX #${dataUrlCounter}`
: fileFilePathOrFileContents instanceof File
? electronApi
? electronApi.filePath(fileFilePathOrFileContents)
: fileFilePathOrFileContents.name
: typeof fileFilePathOrFileContents === 'string'
? fileFilePathOrFileContents
: sha256(fileFilePathOrFileContents);
}

export function file(
fileFilePathOrFileContents: string | Uint8Array | File,
dataUrlFileName: string,
dataUrlCounter: number
): Promise<locApi.File> {
if (typeof fileFilePathOrFileContents === 'string') {
Expand Down Expand Up @@ -222,7 +215,9 @@ export function file(
.then((arrayBuffer) => {
const fileContents = new Uint8Array(arrayBuffer);

resolve(new locApi.File(filePath(fileFilePathOrFileContents, dataUrlCounter), fileContents));
resolve(
new locApi.File(filePath(fileFilePathOrFileContents, dataUrlFileName, dataUrlCounter), fileContents)
);
})
.catch((error: unknown) => {
reject(new Error(formatError(error)));
Expand All @@ -232,7 +227,7 @@ export function file(

return new Promise((resolve, reject) => {
if (electronApi) {
resolve(new locApi.File(filePath(fileFilePathOrFileContents, dataUrlCounter)));
resolve(new locApi.File(filePath(fileFilePathOrFileContents, dataUrlFileName, dataUrlCounter)));
} else {
reject(new Error('Local files cannot be opened.'));
}
Expand All @@ -241,7 +236,12 @@ export function file(

if (fileFilePathOrFileContents instanceof Uint8Array) {
return new Promise((resolve) => {
resolve(new locApi.File(filePath(fileFilePathOrFileContents, dataUrlCounter), fileFilePathOrFileContents));
resolve(
new locApi.File(
filePath(fileFilePathOrFileContents, dataUrlFileName, dataUrlCounter),
fileFilePathOrFileContents
)
);
});
}

Expand All @@ -251,7 +251,7 @@ export function file(
.then((arrayBuffer) => {
const fileContents = new Uint8Array(arrayBuffer);

resolve(new locApi.File(filePath(fileFilePathOrFileContents, dataUrlCounter), fileContents));
resolve(new locApi.File(filePath(fileFilePathOrFileContents, dataUrlFileName, dataUrlCounter), fileContents));
})
.catch((error: unknown) => {
reject(new Error(formatError(error)));
Expand Down
27 changes: 20 additions & 7 deletions src/renderer/src/components/OpenCOR.vue
Original file line number Diff line number Diff line change
Expand Up @@ -428,12 +428,13 @@ function onSettingsMenu(): void {

// Open a file.

let globalDataUrlCounter = 0;
let globalOmexDataUrlCounter = 0;

function openFile(fileFilePathOrFileContents: string | Uint8Array | File): void {
// Check whether we were passed a ZIP-CellML data URL.

let dataUrlCounter = 0;
let cellmlDataUrlFileName: string = '';
let omexDataUrlCounter: number = 0;

locCommon.zipCellmlDataUrl(fileFilePathOrFileContents).then((zipCellmlDataUriInfo: locCommon.IDataUriInfo) => {
if (zipCellmlDataUriInfo.res) {
Expand All @@ -442,29 +443,41 @@ function openFile(fileFilePathOrFileContents: string | Uint8Array | File): void
severity: 'error',
group: toastId.value,
summary: 'Opening a file',
detail: `${zipCellmlDataUriInfo.error}`,
detail: zipCellmlDataUriInfo.error,
life: TOAST_LIFE
});

return;
}

dataUrlCounter = ++globalDataUrlCounter;
cellmlDataUrlFileName = zipCellmlDataUriInfo.fileName as string;
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

Using as string type assertion here is potentially unsafe. While the logic ensures that fileName should be defined when there's no error, TypeScript's type system indicates it's optional. Consider using optional chaining with a default value for better type safety: cellmlDataUrlFileName = zipCellmlDataUriInfo.fileName ?? ''; This makes the code more defensive against potential future changes in the zipCellmlDataUrl function.

Suggested change
cellmlDataUrlFileName = zipCellmlDataUriInfo.fileName as string;
cellmlDataUrlFileName = zipCellmlDataUriInfo.fileName ?? '';

Copilot uses AI. Check for mistakes.
fileFilePathOrFileContents = zipCellmlDataUriInfo.data as Uint8Array;
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

Using as Uint8Array type assertion here is potentially unsafe. While the logic ensures that data should be defined when there's no error, TypeScript's type system indicates it's optional. Consider using a more defensive approach with optional chaining or an explicit check to ensure type safety and guard against potential future changes.

Copilot uses AI. Check for mistakes.
} else {
// Check whether we were passed a COMBINE archive data URL.

const combineArchiveDataUriInfo = locCommon.combineArchiveDataUrl(fileFilePathOrFileContents);

if (combineArchiveDataUriInfo.res) {
dataUrlCounter = ++globalDataUrlCounter;
if (combineArchiveDataUriInfo.error) {
toast.add({
severity: 'error',
group: toastId.value,
summary: 'Opening a file',
detail: combineArchiveDataUriInfo.error,
life: TOAST_LIFE
});

return;
}

omexDataUrlCounter = ++globalOmexDataUrlCounter;
fileFilePathOrFileContents = combineArchiveDataUriInfo.data as Uint8Array;
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

Using as Uint8Array type assertion here is potentially unsafe. While the logic ensures that data should be defined when there's no error, TypeScript's type system indicates it's optional. Consider using a more defensive approach with optional chaining or an explicit check to ensure type safety and guard against potential future changes.

Suggested change
fileFilePathOrFileContents = combineArchiveDataUriInfo.data as Uint8Array;
if (!(combineArchiveDataUriInfo.data instanceof Uint8Array)) {
toast.add({
severity: 'error',
group: toastId.value,
summary: 'Opening a file',
detail: 'Unexpected COMBINE archive data format.',
life: TOAST_LIFE
});
return;
}
fileFilePathOrFileContents = combineArchiveDataUriInfo.data;

Copilot uses AI. Check for mistakes.
}
}

// Check whether the file is already open and if so then select it.

const filePath = locCommon.filePath(fileFilePathOrFileContents, dataUrlCounter);
const filePath = locCommon.filePath(fileFilePathOrFileContents, cellmlDataUrlFileName, omexDataUrlCounter);

if (contents.value?.hasFile(filePath) ?? false) {
contents.value?.selectFile(filePath);
Expand All @@ -479,7 +492,7 @@ function openFile(fileFilePathOrFileContents: string | Uint8Array | File): void
}

locCommon
.file(fileFilePathOrFileContents, dataUrlCounter)
.file(fileFilePathOrFileContents, cellmlDataUrlFileName, omexDataUrlCounter)
.then((file) => {
const fileType = file.type();

Expand Down
Loading