From 1956cac6e3d648241b090d1cf12afe1853ffce8f Mon Sep 17 00:00:00 2001 From: Jonah Bonner Date: Sun, 2 Jan 2022 12:12:28 -0500 Subject: [PATCH] Various feature additions --- CHANGELOG.md | 5 +- main.js | 27 +++-- package-lock.json | 100 ++++++------------ package.json | 2 +- preload/downloadPreload.js | 12 +-- preload/indexPreload.js | 25 +++++ www/csvWorker.js | 46 ++++++++ www/download.css | 8 ++ www/download.html | 1 + www/download.js | 21 +++- www/index.js | 37 ++++++- www/modules/log.mjs | 28 +++-- www/modules/sideBar.mjs | 13 ++- .../tabControllers/metadataController.mjs | 2 +- 14 files changed, 225 insertions(+), 102 deletions(-) create mode 100644 www/csvWorker.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c27a46f..3314a2f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,4 @@ -Fixed error when downloading a single log file. \ No newline at end of file +* You can now export the opened log as a CSV file! +* A progress bar is now shown when downloading logs over SFTP. +* The field count shown in the side bar no longer includes individual array items. +* The name of ".rlog" files registered with the OS is now "Robot Logs" instead of "Robot logs" (to better match other formats). \ No newline at end of file diff --git a/main.js b/main.js index d35707aa..9bdcc105 100644 --- a/main.js +++ b/main.js @@ -268,13 +268,9 @@ function setupMenu() { label: "Export as CSV...", accelerator: "CmdOrCtrl+E", click() { - dialog.showMessageBox({ - type: "info", - title: "Coming soon...", - message: "Coming soon...", - detail: "This feature is not available yet.", - icon: iconPath - }) + var window = BrowserWindow.getFocusedWindow() + if (!window.webContents.getURL().endsWith("index.html")) return + window.webContents.send("export-csv") } }, { type: "separator" }, @@ -821,4 +817,21 @@ ipcMain.on("update-odometry-popup", (_, id, command) => { } }) } +}) + +ipcMain.on("export-csv-dialog", (_, path) => { + var csvPath = path.substring(0, path.length - 4) + "csv" + var result = dialog.showSaveDialog(BrowserWindow.getFocusedWindow(), { + title: "Select export location for robot log", + defaultPath: csvPath, + properties: ["createDirectory", "showOverwriteConfirmation", "dontAddToRecent"], + filters: [ + { name: "Comma-separated values", extensions: ["csv"] } + ] + }) + result.then(response => { + if (!response.canceled) { + BrowserWindow.getFocusedWindow().send("export-csv-dialog-response", response.filePath) + } + }) }) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d7020649..e77d2bed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "advantage-scope", - "version": "1.9.8", + "version": "1.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "advantage-scope", - "version": "1.9.0", + "version": "1.10.0", "license": "MIT", "dependencies": { "date-holidays": "^3.12.1", @@ -224,9 +224,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "14.18.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz", - "integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ==", + "version": "14.18.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.3.tgz", + "integrity": "sha512-GtTH2crF4MtOIrrAa+jgTV9JX/PfoUCYr6MiZw7O/dkZu5b6gm5dc1nAL0jwGo4ortSBBtGyeVaxdC8X6V+pLg==", "dev": true }, "node_modules/@types/plist": { @@ -248,9 +248,9 @@ "optional": true }, "node_modules/@types/yargs": { - "version": "17.0.7", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.7.tgz", - "integrity": "sha512-OvLKmpKdea1aWtqHv9bxVVcMoT6syAeK+198dfETIFkAevYRGwqh4H+KFxfjUETZuUuE5sQCAFwdOdoHUdo8eg==", + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.8.tgz", + "integrity": "sha512-wDeUwiUmem9FzsyysEwRukaEdDNcwbROvQ9QGRKaLI6t+IltNzbn4/i4asmB10auvZGQCzSQ6t0GSczEThlUXw==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -982,19 +982,6 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, - "node_modules/cpu-features": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.2.tgz", - "integrity": "sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "nan": "^2.14.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", @@ -1056,9 +1043,9 @@ } }, "node_modules/date-holidays": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/date-holidays/-/date-holidays-3.12.2.tgz", - "integrity": "sha512-RmBkD7vg3UWeF1lOsSOrpi9kc5Wlq3h4k82F3lKzPgKqWvt0ca2eUS1fDvNXWkuRVyoPQC2bVSQKg37QDzLDnQ==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/date-holidays/-/date-holidays-3.13.0.tgz", + "integrity": "sha512-o1+LfD4oA6cydeYgH1Xhz+xUOb+gPicvYFb6gg9zNr2BxT8HEac6L9yK5zCF/fLt4zQYFx5VslDJhsDCugZuSw==", "dependencies": { "date-holidays-parser": "^3.2.3", "js-yaml": "^4.1.0", @@ -1313,9 +1300,9 @@ } }, "node_modules/electron": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.4.tgz", - "integrity": "sha512-IptwmowvMP1SFOmZLh6rrURwfnOxbDBXBRBcaOdfBM5I+B9mgtdNwzNC3ymFFNzEkZUwdOyg9fu3iyjAAQIQgw==", + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.5.tgz", + "integrity": "sha512-TgQXWmEGQ3uH2P2JDq5GyJDEu/fimRgqp1iNisARtGreU1k3630PqWlR+4SPnSEHN9NuSv92ng6NWxtefeFzxg==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2416,12 +2403,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "optional": true - }, "node_modules/node-addon-api": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", @@ -3334,9 +3315,9 @@ "dev": true }, "node_modules/yargs": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.0.tgz", - "integrity": "sha512-GQl1pWyDoGptFPJx9b9L6kmR33TGusZvXIZUT+BOz9f7X2L94oeAskFYLEg/FkhV06zZPBYLvLZRWeYId29lew==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", + "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", "dev": true, "dependencies": { "cliui": "^7.0.2", @@ -3531,9 +3512,9 @@ "dev": true }, "@types/node": { - "version": "14.18.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz", - "integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ==", + "version": "14.18.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.3.tgz", + "integrity": "sha512-GtTH2crF4MtOIrrAa+jgTV9JX/PfoUCYr6MiZw7O/dkZu5b6gm5dc1nAL0jwGo4ortSBBtGyeVaxdC8X6V+pLg==", "dev": true }, "@types/plist": { @@ -3555,9 +3536,9 @@ "optional": true }, "@types/yargs": { - "version": "17.0.7", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.7.tgz", - "integrity": "sha512-OvLKmpKdea1aWtqHv9bxVVcMoT6syAeK+198dfETIFkAevYRGwqh4H+KFxfjUETZuUuE5sQCAFwdOdoHUdo8eg==", + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.8.tgz", + "integrity": "sha512-wDeUwiUmem9FzsyysEwRukaEdDNcwbROvQ9QGRKaLI6t+IltNzbn4/i4asmB10auvZGQCzSQ6t0GSczEThlUXw==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -4129,15 +4110,6 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, - "cpu-features": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.2.tgz", - "integrity": "sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA==", - "optional": true, - "requires": { - "nan": "^2.14.1" - } - }, "crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", @@ -4184,9 +4156,9 @@ "integrity": "sha512-5uAPmiO5tlztgaJrAy8eQCbsVrvYap12JGQEULi7HtLFRML65Jrop74ylHsA6OrqzcEyd6b9WQRIG/kvNzqC8g==" }, "date-holidays": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/date-holidays/-/date-holidays-3.12.2.tgz", - "integrity": "sha512-RmBkD7vg3UWeF1lOsSOrpi9kc5Wlq3h4k82F3lKzPgKqWvt0ca2eUS1fDvNXWkuRVyoPQC2bVSQKg37QDzLDnQ==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/date-holidays/-/date-holidays-3.13.0.tgz", + "integrity": "sha512-o1+LfD4oA6cydeYgH1Xhz+xUOb+gPicvYFb6gg9zNr2BxT8HEac6L9yK5zCF/fLt4zQYFx5VslDJhsDCugZuSw==", "requires": { "date-holidays-parser": "^3.2.3", "js-yaml": "^4.1.0", @@ -4378,9 +4350,9 @@ } }, "electron": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.4.tgz", - "integrity": "sha512-IptwmowvMP1SFOmZLh6rrURwfnOxbDBXBRBcaOdfBM5I+B9mgtdNwzNC3ymFFNzEkZUwdOyg9fu3iyjAAQIQgw==", + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.5.tgz", + "integrity": "sha512-TgQXWmEGQ3uH2P2JDq5GyJDEu/fimRgqp1iNisARtGreU1k3630PqWlR+4SPnSEHN9NuSv92ng6NWxtefeFzxg==", "dev": true, "requires": { "@electron/get": "^1.13.0", @@ -5234,12 +5206,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "optional": true - }, "node-addon-api": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", @@ -5967,9 +5933,9 @@ "dev": true }, "yargs": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.0.tgz", - "integrity": "sha512-GQl1pWyDoGptFPJx9b9L6kmR33TGusZvXIZUT+BOz9f7X2L94oeAskFYLEg/FkhV06zZPBYLvLZRWeYId29lew==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", + "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", "dev": true, "requires": { "cliui": "^7.0.2", @@ -5998,4 +5964,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 8f1052f9..2930ecf6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "advantage-scope", "productName": "Advantage Scope", - "version": "1.9.8", + "version": "1.10.0", "description": "Logging tool from FRC Team 6328.", "main": "main.js", "scripts": { diff --git a/preload/downloadPreload.js b/preload/downloadPreload.js index 4e1d6ace..6ed51de6 100644 --- a/preload/downloadPreload.js +++ b/preload/downloadPreload.js @@ -96,22 +96,18 @@ ipcRenderer.on("download-save", (_, files, savePath) => { if (error) { sendError(error.message) } else { + window.dispatchEvent(new CustomEvent("status-progress", { detail: null })) if (files.length == 1) { // Single file sftp.fastGet(fullRioPath + files[0], savePath, error => { if (error) { sendError(error.message) } else { + window.dispatchEvent(new CustomEvent("status-progress", { detail: 1.0 })) ipcRenderer.send("prompt-download-auto-open", savePath) } }) } else { // Multiple files - if (files.length > 10) { - window.dispatchEvent(new CustomEvent("status-alert", { - detail: "Downloading " + files.length.toString() + " logs..." - })) - } - var completeCount = 0 var skipCount = 0 files.forEach(file => { @@ -128,6 +124,10 @@ ipcRenderer.on("download-save", (_, files, savePath) => { sendError(error.message) } else { completeCount++ + var progress = (completeCount - skipCount) / (files.length - skipCount) + window.dispatchEvent(new CustomEvent("status-progress", { detail: progress })) + console.log(progress) + if (completeCount >= files.length) { if (skipCount > 0) { var newCount = completeCount - skipCount diff --git a/preload/indexPreload.js b/preload/indexPreload.js index def661a5..4ccb4b99 100644 --- a/preload/indexPreload.js +++ b/preload/indexPreload.js @@ -196,4 +196,29 @@ window.addEventListener("stop-live-socket", () => { client.destroy() client = null } +}) + +// Manage exporting as CSV +ipcRenderer.on("export-csv", () => { + window.dispatchEvent(new Event("export-csv")) +}) + +window.addEventListener("export-csv-dialog", event => { + ipcRenderer.send("export-csv-dialog", event.detail) +}) + +ipcRenderer.on("export-csv-dialog-response", (_, path) => { + window.dispatchEvent(new CustomEvent("export-csv-dialog-response", { + detail: path + })) +}) + +window.addEventListener("save-csv-data", event => { + fs.writeFile(event.detail.path, event.detail.data, err => { + if (err) + throw err + else { + window.dispatchEvent(new Event("save-csv-data-response")) + } + }) }) \ No newline at end of file diff --git a/www/csvWorker.js b/www/csvWorker.js new file mode 100644 index 00000000..2f83aaa2 --- /dev/null +++ b/www/csvWorker.js @@ -0,0 +1,46 @@ +import { Log } from "./modules/log.mjs" + +// Encodes the data from a Log to an array savable as a CSV. +onmessage = function (event) { + var log = new Log() + log.rawData = event.data + + // Get list of fields + var fields = [] + var processTree = data => { + Object.keys(data).sort().forEach(key => { + if (data[key].field != null) { + fields.push(data[key].field) + } + if (Object.keys(data[key].children).length > 0) { + processTree(data[key].children) + } + }) + } + processTree(log.getFieldTree(false)) + + // Record timestamps + var data = [["Timestamp"]] + log.getTimestamps().forEach(timestamp => { + data.push([[timestamp]]) + }) + + // Retrieve data + fields.forEach(id => { + data[0].push(log.getFieldInfo(id).displayKey) + var fieldData = log.getDataInRange(id, -Infinity, Infinity) + log.getTimestamps().forEach((_, index) => { + var nextIndex = fieldData.timestampIndexes.findIndex(value => value > index) + if (nextIndex == -1) nextIndex = fieldData.timestampIndexes.length + if (nextIndex == 0) { + var value = null + } else { + var value = fieldData.values[nextIndex - 1] + } + data[index + 1].push(JSON.stringify(value).replaceAll(",", "_")) + }) + }) + + // Convert to string + this.postMessage(data.map(x => x.join(",")).join("\n")) +} \ No newline at end of file diff --git a/www/download.css b/www/download.css index dc10a467..03507411 100644 --- a/www/download.css +++ b/www/download.css @@ -188,6 +188,14 @@ div.alert-text { text-overflow: ellipsis; } +progress { + position: absolute; + bottom: 14px; + height: 18px; + left: 12px; + width: calc(100% - 12px - 85px); +} + button { position: absolute; bottom: 8px; diff --git a/www/download.html b/www/download.html index 907ca3f5..88e1ce56 100644 --- a/www/download.html +++ b/www/download.html @@ -24,6 +24,7 @@ +