From 81b05e5bbcfa3d42a1212e90e79befae4fdf0fca Mon Sep 17 00:00:00 2001 From: Candid Dauth Date: Thu, 7 Mar 2024 03:30:24 +0100 Subject: [PATCH] Add CSV export option --- .eslintrc.cjs | 1 + client/package.json | 10 +- frontend/package.json | 28 +- frontend/src/lib/components/export-dialog.vue | 64 +- .../lib/components/line-info/line-info.vue | 2 +- .../components/marker-info/marker-info.vue | 2 +- .../ui/validated-form/validated-field.vue | 4 +- .../ui/validated-form/validated-form.vue | 22 +- frontend/src/table/table.ejs | 2 +- integration-tests/package.json | 10 +- leaflet/package.json | 27 +- package.json | 10 +- server/package.json | 31 +- server/src/export/csv.ts | 25 + server/src/export/tabular.ts | 64 + server/src/utils/streams.ts | 20 + server/src/webserver.ts | 51 +- types/package.json | 8 +- types/src/base.ts | 3 +- utils/package.json | 16 +- utils/src/__tests__/format.test.ts | 30 + utils/src/format.ts | 97 +- yarn.lock | 1285 ++++++++++------- 23 files changed, 1151 insertions(+), 661 deletions(-) create mode 100644 server/src/export/csv.ts create mode 100644 server/src/export/tabular.ts create mode 100644 utils/src/__tests__/format.test.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 68bb8d6f..8bb0513d 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -39,6 +39,7 @@ module.exports = { "vue/multi-word-component-names": ["off"], "@typescript-eslint/no-base-to-string": ["error"], "@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }], + "vue/return-in-computed-property": ["off"], "constructor-super": ["error"], "for-direction": ["error"], diff --git a/client/package.json b/client/package.json index 09c6e642..c11a1ccc 100644 --- a/client/package.json +++ b/client/package.json @@ -34,13 +34,13 @@ }, "dependencies": { "facilmap-types": "workspace:^", - "socket.io-client": "^4.7.2" + "socket.io-client": "^4.7.4" }, "devDependencies": { - "@types/geojson": "^7946.0.13", + "@types/geojson": "^7946.0.14", "rimraf": "^5.0.5", - "typescript": "^5.3.3", - "vite": "^5.0.12", - "vite-plugin-dts": "^3.7.0" + "typescript": "^5.4.2", + "vite": "^5.1.5", + "vite-plugin-dts": "^3.7.3" } } diff --git a/frontend/package.json b/frontend/package.json index 2aa0093e..72c64ef7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,9 +43,9 @@ "dependencies": { "@ckpack/vue-color": "^1.5.0", "@tmcw/togeojson": "^5.8.1", - "@vitejs/plugin-vue": "^5.0.1", + "@vitejs/plugin-vue": "^5.0.4", "blob": "^0.1.0", - "bootstrap": "^5.3.2", + "bootstrap": "^5.3.3", "copy-to-clipboard": "^3.3.3", "decode-uri-component": "^0.4.1", "facilmap-client": "workspace:^", @@ -70,10 +70,10 @@ "popper-max-size-modifier": "^0.2.0", "qrcode.vue": "^3.4.1", "tablesorter": "^2.31.3", - "vite": "^5.0.12", - "vite-plugin-css-injected-by-js": "^3.3.1", - "vite-plugin-dts": "^3.7.0", - "vue": "^3.4.0", + "vite": "^5.1.5", + "vite-plugin-css-injected-by-js": "^3.4.0", + "vite-plugin-dts": "^3.7.3", + "vue": "^3.4.21", "vuedraggable": "^4.1.0" }, "devDependencies": { @@ -87,14 +87,14 @@ "@types/leaflet.locatecontrol": "^0.74.4", "@types/lodash-es": "^4.17.12", "@types/pluralize": "^0.0.33", - "happy-dom": "^12.10.3", + "happy-dom": "^13.6.2", "rimraf": "^5.0.5", - "sass": "^1.69.6", - "svgo": "^3.1.0", - "tsx": "^4.7.0", - "typescript": "^5.3.3", - "vite-tsconfig-paths": "^4.2.3", - "vitest": "^1.1.0", - "vue-tsc": "^1.8.27" + "sass": "^1.71.1", + "svgo": "^3.2.0", + "tsx": "^4.7.1", + "typescript": "^5.4.2", + "vite-tsconfig-paths": "^4.3.1", + "vitest": "^1.3.1", + "vue-tsc": "^2.0.5" } } diff --git a/frontend/src/lib/components/export-dialog.vue b/frontend/src/lib/components/export-dialog.vue index f36fd59e..66b49124 100644 --- a/frontend/src/lib/components/export-dialog.vue +++ b/frontend/src/lib/components/export-dialog.vue @@ -6,7 +6,8 @@ import HelpPopover from "./ui/help-popover.vue"; import CopyToClipboardInput from "./ui/copy-to-clipboard-input.vue"; import type { ComponentProps } from "../utils/vue"; - + import type { ID } from "facilmap-types"; + import validatedField from "./ui/validated-form/validated-field.vue"; const emit = defineEmits<{ hidden: []; @@ -25,7 +26,8 @@ const formatOptions = { gpx: "GPX", geojson: "GeoJSON", - table: "HTML" + table: "HTML", + csv: "CSV" }; const hideOptions = computed(() => new Set([ @@ -41,6 +43,7 @@ const useTracks = ref<"1" | "0">("1"); const filter = ref(true); const hide = ref(new Set()); + const typeId = ref(); const methodOptions = computed(() => ({ download: format.value === "table" ? "Open file" : "Download file", @@ -49,12 +52,32 @@ const method = ref((Object.keys(methodOptions.value) as Array)[0]); + const resolvedTypeId = computed(() => typeId.value != null && client.value.types[typeId.value] ? typeId.value : undefined); + + const canSelectUseTracks = computed(() => format.value === "gpx"); + const canSelectType = computed(() => format.value === "csv"); + const mustSelectType = computed(() => format.value === "csv"); + const canSelectHide = computed(() => ["table", "csv"].includes(format.value)); + const validateImmediate = computed(() => method.value === "link"); // No submit button + + function validateTypeId(typeId: ID | undefined) { + if (mustSelectType.value && resolvedTypeId.value == null) { + return "Please select a type."; + } + } + const url = computed(() => { const params = new URLSearchParams(); - if (format.value === "gpx") { + if (canSelectUseTracks.value) { params.set("useTracks", useTracks.value); } - if (format.value === "table" && hide.value.size > 0) { + if (canSelectType.value) { + if (resolvedTypeId.value == null) { + return undefined; + } + params.set("typeId", `${resolvedTypeId.value}`); + } + if (canSelectHide.value && hide.value.size > 0) { params.set("hide", [...hide.value].join(",")); } if (mapContext.value.filter) { @@ -117,6 +140,10 @@ attributes of all markers and lines. This table can also be copy&pasted into a spreadsheet application for further processing.

+

+ CSV files can be imported into most spreadsheet applications and only contain the data attributes of the objects + one type of marker or line. +

@@ -126,7 +153,7 @@
-
+
-
+
+ + + + +
+ +