diff --git a/src/renderer/src/components/OpenCOR.vue b/src/renderer/src/components/OpenCOR.vue index 20c5aade..4a43ea22 100644 --- a/src/renderer/src/components/OpenCOR.vue +++ b/src/renderer/src/components/OpenCOR.vue @@ -249,7 +249,7 @@ const locApiInitialised = vue.ref(false); const compBackgroundVisible = vue.computed(() => { return ( (loadingOpencorMessageVisible.value || loadingModelMessageVisible.value || progressMessageVisible.value) && - props.omex + !!props.omex ); }); const loadingOpencorMessageVisible = vue.ref(false); diff --git a/src/renderer/src/components/widgets/GraphPanelWidget.vue b/src/renderer/src/components/widgets/GraphPanelWidget.vue index 476eaf78..40fcbefb 100644 --- a/src/renderer/src/components/widgets/GraphPanelWidget.vue +++ b/src/renderer/src/components/widgets/GraphPanelWidget.vue @@ -272,91 +272,102 @@ async function exportToCsv(): Promise { progressMessage.show('Exporting data to CSV...'); } - // Allow the UI to update before actually starting the export to CSV. + let successfulExport: boolean = true; - await common.sleep(SHORT_DELAY); + try { + // Allow the UI to update before actually starting the export to CSV. - // Perform the export itself. - // Note: to efficiently export to CSV, we build an array of CSV lines and only join them together at the end. This is - // much more efficient than concatenating strings together repeatedly. + await common.sleep(SHORT_DELAY); - const csvLines: string[] = []; + // Perform the export itself. + // Note: to efficiently export to CSV, we build an array of CSV lines and only join them together at the end. This is + // much more efficient than concatenating strings together repeatedly. - // Headers. + const csvLines: string[] = []; - const allXValuesEqual = props.data.traces.every((trace) => trace.xValue === firstTrace.xValue); - const headerParts: string[] = [allXValuesEqual ? firstTrace.xValue : 'X']; + // Headers. - props.data.traces.forEach((trace) => { - headerParts.push(trace.name.replace(/<[^>]*>|,/g, '') || trace.yValue); - // Note: we remove any HTML tags and commas to ensure the CSV is well-formed. - }); + const allXValuesEqual = props.data.traces.every((trace) => trace.xValue === firstTrace.xValue); + const headerParts: string[] = [allXValuesEqual ? firstTrace.xValue : 'X']; - csvLines.push(headerParts.join(',')); + props.data.traces.forEach((trace) => { + headerParts.push(trace.name.replace(/<[^>]*>|,/g, '') || trace.yValue); + // Note: we remove any HTML tags and commas to ensure the CSV is well-formed. + }); - // Data rows: collect all unique X values and build value maps. + csvLines.push(headerParts.join(',')); - const allXValues = new Set(); - const traceMaps = props.data.traces.map((trace) => { - const map = new Map(); + // Data rows: collect all unique X values and build value maps. - for (let i = 0; i < trace.x.length; ++i) { - const xValue = trace.x[i]; - const yValue = trace.y[i]; + const allXValues = new Set(); + const traceMaps = props.data.traces.map((trace) => { + const map = new Map(); - if (xValue !== undefined && yValue !== undefined) { - allXValues.add(xValue); + for (let i = 0; i < trace.x.length; ++i) { + const xValue = trace.x[i]; + const yValue = trace.y[i]; - map.set(xValue, yValue); + if (xValue !== undefined && yValue !== undefined) { + allXValues.add(xValue); + + map.set(xValue, yValue); + } } - } - return map; - }); + return map; + }); - // Process the rows and update the progress message at regular intervals to keep the UI responsive. + // Process the rows and update the progress message at regular intervals to keep the UI responsive. - const sortedXValues = Array.from(allXValues).sort((a, b) => a - b); - const chunkSize = Math.max(1, Math.floor(0.01 * sortedXValues.length)); - const percentPerRow = 100 / sortedXValues.length; - let processedRows = 0; + const sortedXValues = Array.from(allXValues).sort((a, b) => a - b); + const chunkSize = Math.max(1, Math.floor(0.01 * sortedXValues.length)); + const percentPerRow = 100 / sortedXValues.length; + let processedRows = 0; - for (const sortedXValue of sortedXValues) { - const rowParts: string[] = [String(sortedXValue)]; + for (const sortedXValue of sortedXValues) { + const rowParts: string[] = [String(sortedXValue)]; - props.data.traces.forEach((_trace, traceIndex) => { - const yValue = traceMaps[traceIndex]?.get(sortedXValue); + props.data.traces.forEach((_trace, traceIndex) => { + const yValue = traceMaps[traceIndex]?.get(sortedXValue); - rowParts.push(yValue !== undefined ? String(yValue) : ''); - }); + rowParts.push(yValue !== undefined ? String(yValue) : ''); + }); - csvLines.push(rowParts.join(',')); + csvLines.push(rowParts.join(',')); - ++processedRows; + ++processedRows; - if (progressMessage && (processedRows % chunkSize === 0 || processedRows === sortedXValues.length)) { - progressMessage.update(Math.floor(percentPerRow * processedRows)); + if (progressMessage && (processedRows % chunkSize === 0 || processedRows === sortedXValues.length)) { + progressMessage.update(Math.floor(percentPerRow * processedRows)); - await common.sleep(NO_DELAY); + await common.sleep(NO_DELAY); + } } - } - // Make sure that we show 100% before finishing. + // Make sure that we show 100% before finishing. - if (progressMessage) { - progressMessage.update(100); - } + if (progressMessage) { + progressMessage.update(100); + } - // Create and download the CSV file. + // Create and download the CSV file. - common.downloadFile('data.csv', csvLines.join('\n'), 'text/csv;charset=utf-8;'); + common.downloadFile('data.csv', csvLines.join('\n'), 'text/csv;charset=utf-8;'); + } catch (error: unknown) { + successfulExport = false; - // Hide the progress message after a short delay so that the user has time to see that we reached 100%. + console.error('Failed to export to CSV:', common.formatError(error)); + } finally { + // Hide the progress message. If the export succeeded then delay briefly so that the user can see that we reached + // 100%. Otherwise, hide immediately to avoid blocking the UI unnecessarily. - if (progressMessage) { - await common.sleep(LONG_DELAY); + if (progressMessage) { + if (successfulExport) { + await common.sleep(LONG_DELAY); + } - progressMessage.hide(); + progressMessage.hide(); + } } }