From 4b21745fc77cdfeb8f32b380255c45ee001552b9 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Mon, 18 Mar 2024 20:27:58 +0100 Subject: [PATCH] refactor: download files from interface (#831) --- apps/client/src/common/api/db.ts | 70 ++++++++++++++++--- apps/client/src/common/api/utils.ts | 61 ++++------------ .../app-settings/panel/Panel.module.scss | 4 -- .../panel/project-panel/ProjectListItem.tsx | 4 +- .../project-panel/ProjectPanel.module.scss | 2 +- 5 files changed, 75 insertions(+), 66 deletions(-) diff --git a/apps/client/src/common/api/db.ts b/apps/client/src/common/api/db.ts index 03a4b2937..2f6a79615 100644 --- a/apps/client/src/common/api/db.ts +++ b/apps/client/src/common/api/db.ts @@ -10,27 +10,54 @@ import { } from 'ontime-types'; import { ImportMap } from 'ontime-utils'; +import { makeCSV, makeTable } from '../../features/cuesheet/cuesheetUtils'; + import { apiEntryUrl } from './constants'; -import fileDownload from './utils'; +import { createBlob, downloadBlob } from './utils'; const dbPath = `${apiEntryUrl}/db`; /** - * HTTP request to download db in JSON format + * HTTP request to the current DB */ -export async function downloadRundown(fileName?: string) { - return fileDownload( - dbPath, - { name: fileName ?? 'rundown', type: 'json' }, - { type: 'application/json;charset=utf-8;' }, - ); +async function getDb(): Promise> { + return axios.get(`${dbPath}/download`); } /** - * HTTP request to download db in CSV format + * Request download of the current project file + * @param fileName */ -export async function downloadCSV(fileName?: string) { - return fileDownload(dbPath, { name: fileName ?? 'rundown', type: 'csv' }, { type: 'text/csv;charset=utf-8;' }); +export async function downloadProject(fileName: string = 'ontime-project') { + try { + const { data, name } = await fileDownload(fileName); + + const fileContent = JSON.stringify(data, null, 2); + + const blob = createBlob(fileContent, 'application/json;charset=utf-8;'); + downloadBlob(blob, `${name}.json`); + } catch (error) { + console.error(error); + } +} + +/** + * Request download of the current rundown as a CSV file + * @param fileName + */ +export async function downloadCSV(fileName: string = 'rundown') { + try { + const { data, name } = await fileDownload(fileName); + const { project, rundown, customFields } = data; + + const sheetData = makeTable(project, rundown, customFields); + const fileContent = makeCSV(sheetData); + + const blob = createBlob(fileContent, 'text/csv;charset=utf-8;'); + downloadBlob(blob, `${name}.csv`); + } catch (error) { + console.error(error); + } } /** @@ -153,3 +180,24 @@ export async function importSpreadsheetPreview(file: File, options: ImportMap): return response.data; } + +/** + * Utility function gets project from db + * @param fileName + * @returns + */ +async function fileDownload(fileName: string): Promise<{ data: DatabaseModel; name: string }> { + const response = await getDb(); + + const headerLine = response.headers['Content-Disposition']; + + // try and get the filename from the response + let name = fileName; + if (headerLine != null) { + const startFileNameIndex = headerLine.indexOf('"') + 1; + const endFileNameIndex = headerLine.lastIndexOf('"'); + name = headerLine.substring(startFileNameIndex, endFileNameIndex); + } + + return { data: response.data, name }; +} diff --git a/apps/client/src/common/api/utils.ts b/apps/client/src/common/api/utils.ts index 623f0a0b2..cc9f7a79b 100644 --- a/apps/client/src/common/api/utils.ts +++ b/apps/client/src/common/api/utils.ts @@ -2,7 +2,6 @@ import axios, { AxiosError } from 'axios'; import { LogLevel } from 'ontime-types'; import { generateId, millisToString } from 'ontime-utils'; -import { makeCSV, makeTable } from '../../features/cuesheet/cuesheetUtils'; import { ontimeQueryClient } from '../queryClient'; import { addLog } from '../stores/logger'; import { nowInMillis } from '../utils/time'; @@ -56,63 +55,29 @@ export async function invalidateAllCaches() { await ontimeQueryClient.invalidateQueries(); } -type FileOptions = { - name: string; - type: string; -}; - -type BlobOptions = { - type: string; -}; - /** - * Gets DB from backend and prepares a file to be downloaded - * @param url - * @param fileOptions - * @param blobOptions + * Creates blob from content + * @param fileContent + * @param type * @returns */ -export default async function fileDownload(url: string, fileOptions: FileOptions, blobOptions: BlobOptions) { - const response = await axios({ - url: `${url}/db`, - method: 'GET', - }); +export function createBlob(fileContent: string, type: string): Blob { + return new Blob([fileContent], { type }); +} - const headerLine = response.headers['Content-Disposition']; - let { name: fileName } = fileOptions; - const { type: fileType } = fileOptions; - const { project, rundown, customFields } = response.data; - - // try and get the filename from the response - if (headerLine != null) { - const startFileNameIndex = headerLine.indexOf('"') + 1; - const endFileNameIndex = headerLine.lastIndexOf('"'); - fileName = headerLine.substring(startFileNameIndex, endFileNameIndex); - } - - let fileContent = ''; - - if (fileType === 'json') { - fileContent = JSON.stringify(response.data); - fileName += '.json'; - } - - if (fileType === 'csv') { - const sheetData = makeTable(project, rundown, customFields); - fileContent = makeCSV(sheetData); - fileName += '.csv'; - } - - const blob = new Blob([fileContent], { type: blobOptions.type }); +/** + * downloads a blob + * @param downloadUrl + * @param fileName + */ +export function downloadBlob(blob: Blob, fileName: string) { const downloadUrl = URL.createObjectURL(blob); - const link = document.createElement('a'); link.setAttribute('href', downloadUrl); link.setAttribute('download', fileName); document.body.appendChild(link); link.click(); + // Clean up the URL.createObjectURL to release resources URL.revokeObjectURL(downloadUrl); - return; } - diff --git a/apps/client/src/features/app-settings/panel/Panel.module.scss b/apps/client/src/features/app-settings/panel/Panel.module.scss index 634b24cd2..f58e8e68e 100644 --- a/apps/client/src/features/app-settings/panel/Panel.module.scss +++ b/apps/client/src/features/app-settings/panel/Panel.module.scss @@ -75,10 +75,6 @@ $inner-padding: 1rem; box-shadow: 0 1px $white-10; } - tr { - padding: 1rem 0; - } - th { font-weight: 400; color: $gray-400; diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx b/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx index 38d353582..a8b91cc13 100644 --- a/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectListItem.tsx @@ -5,7 +5,7 @@ import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHoriz import { deleteProject, downloadCSV, - downloadRundown, + downloadProject, duplicateProject, loadProject, renameProject, @@ -148,7 +148,7 @@ function ActionMenu({ }; const handleDownload = async () => { - await downloadRundown(filename); + await downloadProject(filename); }; const handleExportCSV = async () => { diff --git a/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.module.scss b/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.module.scss index cbfd6b176..784d86931 100644 --- a/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.module.scss +++ b/apps/client/src/features/app-settings/panel/project-panel/ProjectPanel.module.scss @@ -1,5 +1,5 @@ .current { - background-color: $blue-900; + background-color: $blue-1100; } .actionButton {