From 9b7b650d8bd77523f1c74e5ca2d4dbeb7188e5f7 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 30 May 2024 15:05:31 +0200 Subject: [PATCH] table small improvements --- .../src/lib/components/DisplayResult.svelte | 21 ++- .../lib/components/table/AutoDataTable.svelte | 178 +++++++----------- .../lib/components/table/DownloadCsv.svelte | 32 ++++ .../src/lib/components/table/tableUtils.ts | 45 +++++ 4 files changed, 162 insertions(+), 114 deletions(-) create mode 100644 frontend/src/lib/components/table/DownloadCsv.svelte diff --git a/frontend/src/lib/components/DisplayResult.svelte b/frontend/src/lib/components/DisplayResult.svelte index 9c8bc39888..ea0d7af912 100644 --- a/frontend/src/lib/components/DisplayResult.svelte +++ b/frontend/src/lib/components/DisplayResult.svelte @@ -27,6 +27,8 @@ import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte' import MapResult from './MapResult.svelte' import Popover from './Popover.svelte' + import DownloadCsv from './table/DownloadCsv.svelte' + import { convertJsonToCsv } from './table/tableUtils' export let result: any export let requireHtmlApproval = false @@ -110,6 +112,7 @@ } let is_render_all = false + let download_as_csv = false function inferResultKind(result: any) { if (result == 'WINDMILL_TOO_BIG') { largeObject = true @@ -117,6 +120,7 @@ } if (result !== undefined) { + download_as_csv = false if (typeof result === 'string') { length = 0 largeObject = false @@ -136,12 +140,17 @@ return keys[0] as 'png' | 'svg' | 'jpeg' | 'html' | 'gif' } - const tableLargeObject = roughSizeOfObject(result) > TABLE_MAX_SIZE + let size = roughSizeOfObject(result) // Otherwise, check if the result is too large (10kb) for json - largeObject = tableLargeObject || roughSizeOfObject(result) > DISPLAY_MAX_SIZE - if (tableLargeObject) { + if (size > TABLE_MAX_SIZE) { + largeObject = true + if (Array.isArray(result) && isTableRowObject(result)) { + download_as_csv = true + } return 'json' + } else { + largeObject = size > DISPLAY_MAX_SIZE } if (Array.isArray(result)) { @@ -629,6 +638,12 @@ > Download {filename ? '' : 'as JSON'} + {#if download_as_csv} + convertJsonToCsv(result)} + customText="Download as CSV" + /> + {/if}
diff --git a/frontend/src/lib/components/table/AutoDataTable.svelte b/frontend/src/lib/components/table/AutoDataTable.svelte index ce05447d0a..76e18ca239 100644 --- a/frontend/src/lib/components/table/AutoDataTable.svelte +++ b/frontend/src/lib/components/table/AutoDataTable.svelte @@ -5,51 +5,34 @@ import DataTable from './DataTable.svelte' import Head from './Head.svelte' import Row from './Row.svelte' - import { pluralize, sendUserToast } from '$lib/utils' + import { pluralize } from '$lib/utils' import Badge from '$lib/components/common/badge/Badge.svelte' - import { isEmail, isLink } from './tableUtils' + import { + computeStructuredObjectsAndHeaders, + convertJsonToCsv, + isEmail, + isLink + } from './tableUtils' import type { BadgeColor } from '../common' import Popover from '../Popover.svelte' import DarkModeObserver from '../DarkModeObserver.svelte' - import Button from '../common/button/Button.svelte' - import { Parser } from '@json2csv/plainjs' + import DownloadCsv from './DownloadCsv.svelte' export let objects: Array> = [] let currentPage = 1 let perPage = 25 let search: string = '' - $: structuredObjects = computeStructuredObjects(objects) - + let structuredObjects: { + _id: number + rowData: Record + }[] = [] let headers: string[] = [] - function computeStructuredObjects(objects: Array>) { - if (Array.isArray(objects)) { - let nextId = 1 + $: recomputeObjectsAndHeaders(objects) - let hds: string[] = [] - let objs = objects.map((obj) => { - let rowData = obj && typeof obj == 'object' ? obj : {} - if (Array.isArray(rowData)) { - rowData = Object.fromEntries(rowData.map((x, i) => ['col' + i, x])) - } - let ks = Object.keys(rowData) - ks.forEach((x) => { - if (!hds.includes(x)) { - hds.push(x) - } - }) - return { - _id: nextId++, - rowData - } - }) - headers = hds - return objs - } else { - headers = [] - return [] - } + function recomputeObjectsAndHeaders(objects: Array>) { + ;[headers, structuredObjects] = computeStructuredObjectsAndHeaders(objects) } function adjustCurrentPage() { @@ -62,42 +45,52 @@ $: perPage && adjustCurrentPage() - $: data = structuredObjects - .filter( - ({ rowData }) => - search == undefined || - search == '' || - Object.values(rowData).some((value) => + $: data = computeData(structuredObjects, activeSorting, search) + + type ActiveSorting = { + column: string + direction: 'asc' | 'desc' + } + + let activeSorting: ActiveSorting | undefined = undefined + + function computeData( + structuredObjects: Array>, + activeSorting: ActiveSorting | undefined, + search: string + ): Array> { + let objects = structuredObjects + if (search != undefined && search != '') { + objects = objects.filter((obj) => + Object.values(obj.rowData).some((value) => JSON.stringify(value).toLowerCase().includes(search.toLowerCase()) ) - ) - .sort((a, b) => { - if (!activeSorting) return 0 - const valA = a.rowData[activeSorting.column] - const valB = b.rowData[activeSorting.column] - const isAsc = activeSorting.direction === 'asc' - if (valA == undefined || valA == null) { - return isAsc ? -1 : 1 - } - if (valB == undefined || valB == null) { - return isAsc ? 1 : -1 - } - if (isAsc) { - return valA > valB ? 1 : -1 - } else { - return valA > valB ? -1 : 1 - } - }) + ) + } + if (activeSorting) { + objects = objects.sort((a, b) => { + if (!activeSorting) return 0 + const valA = a.rowData[activeSorting.column] + const valB = b.rowData[activeSorting.column] + const isAsc = activeSorting.direction === 'asc' + if (valA == undefined || valA == null) { + return isAsc ? -1 : 1 + } + if (valB == undefined || valB == null) { + return isAsc ? 1 : -1 + } + if (isAsc) { + return valA > valB ? 1 : -1 + } else { + return valA > valB ? -1 : 1 + } + }) + } + return objects + } $: slicedData = data.slice((currentPage - 1) * perPage, currentPage * perPage) - let activeSorting: - | { - column: string - direction: 'asc' | 'desc' - } - | undefined = undefined - let selection = [] as Array // Function to handle individual row checkbox change @@ -141,16 +134,6 @@ // let typof = typeof value // return (value != undefined && typof === 'string') || typof === 'number' || typof === 'boolean' // } - - function convertJsonToCsv(arr: Array>): string { - try { - const parser = new Parser({}) - const csv = parser.parse(arr) - return csv - } catch (err) { - throw new Error('An error occured when generating CSV:' + err) - } - } @@ -167,45 +150,18 @@ {/if}
- + customText={selection.length > 0 ? 'Download selected as CSV' : undefined} + /> { const actions = [ diff --git a/frontend/src/lib/components/table/DownloadCsv.svelte b/frontend/src/lib/components/table/DownloadCsv.svelte new file mode 100644 index 0000000000..fa4b041074 --- /dev/null +++ b/frontend/src/lib/components/table/DownloadCsv.svelte @@ -0,0 +1,32 @@ + + + diff --git a/frontend/src/lib/components/table/tableUtils.ts b/frontend/src/lib/components/table/tableUtils.ts index 25d72ee014..0068f802cc 100644 --- a/frontend/src/lib/components/table/tableUtils.ts +++ b/frontend/src/lib/components/table/tableUtils.ts @@ -1,3 +1,5 @@ +import { Parser } from '@json2csv/plainjs' + export function isLink(value: string) { return value?.startsWith('http://') || value?.startsWith('https://') } @@ -5,3 +7,46 @@ export function isLink(value: string) { export function isEmail(value: string) { return value?.includes('@') } + +export function computeStructuredObjectsAndHeaders(objects: Array>): [ + string[], + { + _id: number + rowData: Record + }[] +] { + if (Array.isArray(objects)) { + let nextId = 1 + + let hds: string[] = [] + let objs = objects.map((obj) => { + let rowData = obj && typeof obj == 'object' ? obj : {} + if (Array.isArray(rowData)) { + rowData = Object.fromEntries(rowData.map((x, i) => ['col' + i, x])) + } + let ks = Object.keys(rowData) + ks.forEach((x) => { + if (!hds.includes(x)) { + hds.push(x) + } + }) + return { + _id: nextId++, + rowData + } + }) + return [hds, objs] + } else { + return [[], []] + } +} + +export function convertJsonToCsv(arr: Array>): string { + try { + const parser = new Parser({}) + const csv = parser.parse(arr) + return csv + } catch (err) { + throw new Error('An error occured when generating CSV:' + err) + } +}