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
16 changes: 14 additions & 2 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.20260129.0",
"version": "0.20260129.1",
"scripts": {
"archive:web": "bun src/renderer/scripts/archive.web.js",
"build": "electron-vite build",
Expand Down Expand Up @@ -67,7 +67,7 @@
"@tailwindcss/postcss": "^4.1.18",
"@tailwindcss/vite": "^4.1.18",
"@types/crypto-js": "^4.2.2",
"@types/node": "^25.0.10",
"@types/node": "^25.1.0",
"@types/plotly.js": "^3.0.9",
"@vitejs/plugin-vue": "^6.0.3",
"@vue/tsconfig": "^0.8.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.

4 changes: 2 additions & 2 deletions 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.20260129.0",
"version": "0.20260129.1",
"scripts": {
"build": "vite build",
"build:lib": "vite build --config vite.lib.config.ts && cp index.d.ts dist/index.d.ts",
Expand Down Expand Up @@ -74,7 +74,7 @@
"@tailwindcss/postcss": "^4.1.18",
"@tailwindcss/vite": "^4.1.18",
"@types/crypto-js": "^4.2.2",
"@types/node": "^25.0.10",
"@types/node": "^25.1.0",
"@types/plotly.js": "^3.0.9",
"@vitejs/plugin-vue": "^6.0.3",
"@vue/tsconfig": "^0.8.1",
Expand Down
4 changes: 1 addition & 3 deletions src/renderer/src/components/dialogs/AboutDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ import { COPYRIGHT } from '../../common/constants.ts';
import { electronApi } from '../../common/electronApi.ts';
import * as locApi from '../../libopencor/locApi.ts';

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

import { version } from '../../../package.json';
</script>
4 changes: 1 addition & 3 deletions src/renderer/src/components/dialogs/BaseDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ import * as vue from 'vue';

import { enableDisableMainMenu } from '../../common/common.ts';

const emit = defineEmits<{
(event: 'cancel'): void;
}>();
const emit = defineEmits<(event: 'cancel') => void>();

const { incrementDialogs, decrementDialogs } = useDialogState();

Expand Down
4 changes: 1 addition & 3 deletions src/renderer/src/components/dialogs/OkMessageDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
</template>

<script setup lang="ts">
defineEmits<{
(event: 'ok'): void;
}>();
defineEmits<(event: 'ok') => void>();
defineProps<{
title: string;
message: string;
Expand Down
4 changes: 1 addition & 3 deletions src/renderer/src/components/dialogs/SettingsDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ import * as vue from 'vue';

import { settings } from '../../common/settings.ts';

const emit = defineEmits<{
(event: 'close'): void;
}>();
const emit = defineEmits<(event: 'close') => void>();

const checkForUpdatesAtStartup = vue.ref(settings.general.checkForUpdatesAtStartup);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,20 @@

<div class="settings-form">
<div class="form-row">
<FloatLabel variant="on" class="form-field">
<InputNumber v-model="localSettings.simulation.startingPoint" class="w-full" size="small" :maxFractionDigits="15" />
<label>Starting point ({{ localSettings.extra.voiUnit }})</label>
</FloatLabel>
<FloatLabel variant="on" class="form-field">
<InputNumber v-model="localSettings.simulation.endingPoint" class="w-full" size="small" :maxFractionDigits="15" />
<label>Ending point ({{ localSettings.extra.voiUnit }})</label>
</FloatLabel>
<InputScientificNumber v-model="localSettings.simulation.startingPoint" class="form-field"
:label="`Starting point (${localSettings.extra.voiUnit})`"
size="small"
/>
<InputScientificNumber v-model="localSettings.simulation.endingPoint" class="form-field"
:label="`Ending point (${localSettings.extra.voiUnit})`"
size="small"
/>
</div>
<div class="form-row">
<FloatLabel variant="on" class="form-field">
<InputNumber v-model="localSettings.simulation.pointInterval" class="w-full" size="small" :maxFractionDigits="15" />
<label>Point interval ({{ localSettings.extra.voiUnit }})</label>
</FloatLabel>
<InputScientificNumber v-model="localSettings.simulation.pointInterval" class="form-field"
:label="`Point interval (${localSettings.extra.voiUnit})`"
size="small"
/>
<div class="form-field self-stretch">
<div v-if="!simulationSettingsIssues.length" class="form-field items-center text-muted-color text-sm">
<i class="pi pi-info-circle mr-2"></i>
Expand Down Expand Up @@ -81,10 +81,10 @@

<div class="settings-form">
<div class="form-row">
<FloatLabel variant="on" class="form-field">
<InputNumber v-model="localSettings.solvers.cvodeMaximumStep" class="w-full" size="small" :maxFractionDigits="15" />
<label>CVODE's maximum step ({{ localSettings.extra.voiUnit }})</label>
</FloatLabel>
<InputScientificNumber v-model="localSettings.solvers.cvodeMaximumStep" class="form-field"
:label="`CVODE's maximum step (${localSettings.extra.voiUnit})`"
size="small"
/>
<div class="form-field self-stretch flex items-center">
<div class="form-field items-center text-muted-color text-sm">
<i class="pi pi-info-circle mr-2"></i>
Expand Down Expand Up @@ -184,10 +184,10 @@
</FloatLabel>
</div>
<div class="form-row">
<FloatLabel variant="on" class="form-field">
<InputNumber v-model="input.defaultValue" class="w-full" size="small" :maxFractionDigits="15" />
<label>Default value</label>
</FloatLabel>
<InputScientificNumber v-model="input.defaultValue" class="form-field"
label="Default value"
size="small"
/>
<FloatLabel variant="on" class="form-field">
<InputText v-model="input.visible" class="w-full" size="small" />
<label>Visible (optional)</label>
Expand All @@ -214,18 +214,19 @@
<!-- Scalar input fields -->

<div v-if="locApi.isScalarInput(input)" class="form-row">
<FloatLabel variant="on" class="form-field">
<InputNumber v-model="input.minimumValue" class="w-full" size="small" :maxFractionDigits="15" />
<label>Minimum value</label>
</FloatLabel>
<FloatLabel variant="on" class="form-field">
<InputNumber v-model="input.maximumValue" class="w-full" size="small" :maxFractionDigits="15" />
<label>Maximum value</label>
</FloatLabel>
<FloatLabel variant="on" class="form-field">
<InputNumber v-model="input.stepValue" class="w-full" size="small" :maxFractionDigits="15" />
<label>Step value (optional)</label>
</FloatLabel>
<InputScientificNumber v-model="input.minimumValue" class="form-field"
label="Minimum value"
size="small"
/>
<InputScientificNumber v-model="input.maximumValue" class="form-field"
label="Maximum value"
size="small"
/>
<InputScientificNumber v-model="input.stepValue" class="form-field"
label="Step value (optional)"
:allowEmpty="true"
size="small"
/>
</div>

<!-- Discrete input fields -->
Expand All @@ -249,10 +250,10 @@
<InputText v-model="possibleValue.name" class="w-full" size="small" />
<label>Name</label>
</FloatLabel>
<FloatLabel variant="on" class="flex-1">
<InputNumber v-model="possibleValue.value" class="w-full" size="small" />
<label>Value</label>
</FloatLabel>
<InputScientificNumber v-model="possibleValue.value" class="form-field"
label="Value"
size="small"
/>
<Button
icon="pi pi-times"
severity="secondary"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ const props = withDefaults(
}
);
const columnWidth = `width: calc(100% / ${props.hasUnits ? '3' : '2'});`;
const emit = defineEmits<{
(event: 'propertyUpdated', index: number, newValue: number): void;
}>();
const emit = defineEmits<(event: 'propertyUpdated', index: number, newValue: number) => void>();

function onCellEditComplete(event: DataTableCellEditCompleteEvent): void {
const { data, newValue, field } = event;
Expand Down
133 changes: 133 additions & 0 deletions src/renderer/src/components/widgets/InputScientificNumber.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<template>
<FloatLabel variant="on">
<InputText :model-value="internalValue" class="w-full"
v-bind="$attrs"
v-keyfilter="{ pattern: /^[+-]?(\d*(\.\d*)?|\.\d*)([eE][+-]?\d*)?$/, validateOnly: true }"
@update:model-value="onUpdateModelValue"
@blur="onBlur"
@keydown.enter="onKeydownEnter"
@paste="onPaste"
/>
<label>{{ label }}</label>
</FloatLabel>
</template>

<script setup lang="ts">
import * as vue from 'vue';

const props = defineProps<{
modelValue: number | undefined;
label: string;
allowEmpty?: boolean;
}>();
const emit = defineEmits<(event: 'update:modelValue', value: number | undefined) => void>();

const internalValue = vue.ref<string>('');
const isEditing = vue.ref<boolean>(false);

function updateInternalValue(value: number | undefined) {
internalValue.value = value !== undefined ? String(value) : '';
}

vue.watch(
() => props.modelValue,
(newValue) => {
if (!isEditing.value) {
updateInternalValue(newValue);
}
},
{ immediate: true }
);

function onUpdateModelValue(value: string) {
isEditing.value = true;
internalValue.value = value;
}

function onPaste(event: ClipboardEvent) {
const clipboardData = event.clipboardData ?? (window as unknown as { clipboardData?: DataTransfer }).clipboardData;

if (!clipboardData) {
return;
}

event.preventDefault();

const pastedValue = clipboardData.getData('text') || '';

if (pastedValue === '') {
return;
}

// Try to extract a valid scientific-number pattern from the pasted text. Failing that, try to sanitise the pasted
// text first by removing invalid characters, then extracting a scientific-number pattern from it.
// Note: the pattern used in the keyfilter is suitable for validating a scientific number when typing it in, but here
// we want to extract a matching substring from arbitrary pasted text, so we use a slightly different pattern.

const scientificNumberPattern = /[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/;
let match = pastedValue.match(scientificNumberPattern);
let toInsert: string | null = match?.[0] ?? null;

if (!toInsert) {
const sanitisedValue = pastedValue.replace(/[^0-9eE+\-.]/g, '');

if (sanitisedValue === '') {
return;
}

match = sanitisedValue.match(scientificNumberPattern);

if (!match || !match[0]) {
return;
}

toInsert = match[0];
}

const inputElement = event.target as HTMLInputElement | null;

if (!inputElement) {
return;
}

const selectionStart = inputElement.selectionStart ?? 0;
const selectionEnd = inputElement.selectionEnd ?? 0;
const newValue = internalValue.value.slice(0, selectionStart) + toInsert + internalValue.value.slice(selectionEnd);

onUpdateModelValue(newValue);
}

function parseAndEmit() {
isEditing.value = false;

if (internalValue.value === '') {
updateInternalValue(props.modelValue);

if (props.allowEmpty) {
emit('update:modelValue', undefined);
}

return;
}

const parsedValue = parseFloat(internalValue.value);

if (Number.isNaN(parsedValue)) {
updateInternalValue(props.modelValue);

return;
}

updateInternalValue(parsedValue);

emit('update:modelValue', parsedValue);
}

function onBlur() {
parseAndEmit();
}

function onKeydownEnter(event: KeyboardEvent) {
(event.target as HTMLInputElement).blur();
}
</script>
Loading