From cb468f0ad1cf99ecfddde6fcc06bbb85cd779ea0 Mon Sep 17 00:00:00 2001 From: Seanghay Yath Date: Mon, 16 Oct 2023 12:32:17 +0700 Subject: [PATCH 1/5] feat: Add JSON as an export option --- src/libs/SaveJSONFile.ts | 6 +++ src/main/ipc/ipc_file_system.ts | 4 ++ src/main/preload.ts | 3 ++ .../DatabaseScreen/ExportModal/index.tsx | 51 +++++++++++++------ 4 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 src/libs/SaveJSONFile.ts diff --git a/src/libs/SaveJSONFile.ts b/src/libs/SaveJSONFile.ts new file mode 100644 index 00000000..2c6231eb --- /dev/null +++ b/src/libs/SaveJSONFile.ts @@ -0,0 +1,6 @@ +import fs from 'fs'; + +export default function saveJsonFile(fileName: string, records: object[]) { + const jsonString = JSON.stringify(records); + fs.writeFileSync(fileName, jsonString); +} diff --git a/src/main/ipc/ipc_file_system.ts b/src/main/ipc/ipc_file_system.ts index 6c2877fa..ab2c5855 100644 --- a/src/main/ipc/ipc_file_system.ts +++ b/src/main/ipc/ipc_file_system.ts @@ -6,6 +6,7 @@ import { OpenDialogOptions, } from 'electron'; import fs from 'fs'; +import saveJsonFile from '../../libs/SaveJSONFile'; import saveCsvFile from '../../libs/SaveCSVFile'; import saveExcelFile from '../../libs/SaveExcelFile'; import CommunicateHandler from './../CommunicateHandler'; @@ -35,6 +36,9 @@ CommunicateHandler.handle( } }, ) + .handle('save-json-file', ([fileName, records]: [string, object[]]) => { + saveJsonFile(fileName, records); + }) .handle('save-csv-file', ([fileName, records]: [string, object[]]) => { saveCsvFile(fileName, records); }) diff --git a/src/main/preload.ts b/src/main/preload.ts index cf4ca19e..2fecf203 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -74,6 +74,9 @@ const electronHandler = { showFileInFolder: (fileName: string) => ipcRenderer.invoke('show-item-in-folder', [fileName]), + saveJsonFile: (fileName: string, records: object[]) => + ipcRenderer.invoke('save-json-file', [fileName, records]), + saveCsvFile: (fileName: string, records: object[]) => ipcRenderer.invoke('save-csv-file', [fileName, records]), diff --git a/src/renderer/screens/DatabaseScreen/ExportModal/index.tsx b/src/renderer/screens/DatabaseScreen/ExportModal/index.tsx index 02691598..449b08b8 100644 --- a/src/renderer/screens/DatabaseScreen/ExportModal/index.tsx +++ b/src/renderer/screens/DatabaseScreen/ExportModal/index.tsx @@ -1,6 +1,7 @@ import { faFileExcel, faFileCsv, + faFileText, faCircleCheck, faTimesCircle, faSpinner, @@ -16,18 +17,35 @@ import Stack from 'renderer/components/Stack'; import TextField from 'renderer/components/TextField'; import { QueryResult } from 'types/SqlResult'; +function combineExportOptionText(name: string, extensions: string[]): string { + const ext = extensions.map(x => `*.${x}`).join(", "); + return `${name} (${ext})` +} + const EXPORT_OPTIONS = [ { - text: 'Excel (*.xlsx)', + name: 'Excel', icon: , value: 'excel', + extensions: ['xlsx'], }, { - text: 'Comma Separated Value (*.csv)', + name: 'Comma Separated Value', icon: , value: 'csv', + extensions: ['csv'], }, -]; + { + name: 'JSON', + icon: , + value: 'json', + extensions: ['json'], + } +].map((item) => ({...item, text: combineExportOptionText(item.name, item.extensions) })); + +const EXPORT_OPTIONS_FILE_FILTERS = Object.fromEntries( + EXPORT_OPTIONS.map(({ value, extensions, name }) => [value, { extensions, name }]) +); interface ExportModalProps { data: QueryResult; @@ -43,20 +61,10 @@ function ExportModalConfig({ const [fileName, setFileName] = useState(''); const onBrowseFileClicked = useCallback(() => { + const filter = EXPORT_OPTIONS_FILE_FILTERS[format]; + if (!filter) return; window.electron - .showSaveDialog({ - filters: [ - format === 'excel' - ? { - name: 'Excel', - extensions: ['xlsx'], - } - : { - name: 'CSV', - extensions: ['csv'], - }, - ], - }) + .showSaveDialog({ filters: [filter] }) .then((value) => setFileName(value ?? '')); }, [format]); @@ -213,6 +221,17 @@ export default function ExportModal({ data, onClose }: ExportModalProps) { console.error(e); setStage('ERROR'); }); + } else if (format === "json") { + window.electron + .saveJsonFile( + fileName, + getDisplayableFromDatabaseRows(data.rows, data.headers) + ) + .then(() => setStage('SUCCESS')) + .catch((e) => { + console.error(e); + setStage('ERROR'); + }); } else { setStage('ERROR'); } From 68863202d193ce97538abc40448da5698ed624c3 Mon Sep 17 00:00:00 2001 From: Seanghay Yath Date: Mon, 16 Oct 2023 12:43:22 +0700 Subject: [PATCH 2/5] doc: update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a83dfa7c..e3da0b0f 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Effortlessly move data in and out of your database: - ✅ Export data to CSV. - ✅ Export data to Microsoft Excel. -- ❌ Export data to JSON (in progress). +- ✅ Export data to JSON (in progress). - ❌ Export data to SQL (in progress). - ❌ Export data to XML (in progress). - ❌ Export to Clipboard (in progress). From 89bd999fd787f97cf1551c16640018f5966fe109 Mon Sep 17 00:00:00 2001 From: Seanghay Yath Date: Mon, 16 Oct 2023 12:43:56 +0700 Subject: [PATCH 3/5] doc: update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e3da0b0f..54dfea63 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Effortlessly move data in and out of your database: - ✅ Export data to CSV. - ✅ Export data to Microsoft Excel. -- ✅ Export data to JSON (in progress). +- ✅ Export data to JSON. - ❌ Export data to SQL (in progress). - ❌ Export data to XML (in progress). - ❌ Export to Clipboard (in progress). From 691cdc9d8404995a175bad841207d52519df893b Mon Sep 17 00:00:00 2001 From: Seanghay Yath Date: Sat, 21 Oct 2023 19:59:17 +0700 Subject: [PATCH 4/5] refactor: use saveStructuredTextFile() instead of individual function for each file type --- src/libs/SaveJSONFile.ts | 6 ------ src/libs/StructuredTextFile.ts | 20 +++++++++++++++++++ src/main/ipc/ipc_file_system.ts | 6 +++--- .../DatabaseScreen/ExportModal/index.tsx | 3 ++- 4 files changed, 25 insertions(+), 10 deletions(-) delete mode 100644 src/libs/SaveJSONFile.ts create mode 100644 src/libs/StructuredTextFile.ts diff --git a/src/libs/SaveJSONFile.ts b/src/libs/SaveJSONFile.ts deleted file mode 100644 index 2c6231eb..00000000 --- a/src/libs/SaveJSONFile.ts +++ /dev/null @@ -1,6 +0,0 @@ -import fs from 'fs'; - -export default function saveJsonFile(fileName: string, records: object[]) { - const jsonString = JSON.stringify(records); - fs.writeFileSync(fileName, jsonString); -} diff --git a/src/libs/StructuredTextFile.ts b/src/libs/StructuredTextFile.ts new file mode 100644 index 00000000..3a14d0d3 --- /dev/null +++ b/src/libs/StructuredTextFile.ts @@ -0,0 +1,20 @@ +import fs from 'fs'; + +export type SupportedStructuredFileType = 'json' + +function jsonSerializer(data: any) { + return JSON.stringify(data, undefined, 2); +} + +export default function saveStructuredTextFile( + fileName: string, + type: SupportedStructuredFileType, + records: object[] +) { + + const serializers = { + json: jsonSerializer, + } + + fs.writeFileSync(fileName, serializers[type](records)) +} diff --git a/src/main/ipc/ipc_file_system.ts b/src/main/ipc/ipc_file_system.ts index ab2c5855..e7a566b8 100644 --- a/src/main/ipc/ipc_file_system.ts +++ b/src/main/ipc/ipc_file_system.ts @@ -6,10 +6,10 @@ import { OpenDialogOptions, } from 'electron'; import fs from 'fs'; -import saveJsonFile from '../../libs/SaveJSONFile'; import saveCsvFile from '../../libs/SaveCSVFile'; import saveExcelFile from '../../libs/SaveExcelFile'; import CommunicateHandler from './../CommunicateHandler'; +import saveStructuredTextFile, { SupportedStructuredFileType } from '../../libs/StructuredTextFile'; CommunicateHandler.handle( 'show-message-box', @@ -36,8 +36,8 @@ CommunicateHandler.handle( } }, ) - .handle('save-json-file', ([fileName, records]: [string, object[]]) => { - saveJsonFile(fileName, records); + .handle('save-structured-text-file', ([fileName, type, records]: [string, string, object[]]) => { + saveStructuredTextFile(fileName, type as SupportedStructuredFileType, records); }) .handle('save-csv-file', ([fileName, records]: [string, object[]]) => { saveCsvFile(fileName, records); diff --git a/src/renderer/screens/DatabaseScreen/ExportModal/index.tsx b/src/renderer/screens/DatabaseScreen/ExportModal/index.tsx index 449b08b8..7e7267aa 100644 --- a/src/renderer/screens/DatabaseScreen/ExportModal/index.tsx +++ b/src/renderer/screens/DatabaseScreen/ExportModal/index.tsx @@ -223,8 +223,9 @@ export default function ExportModal({ data, onClose }: ExportModalProps) { }); } else if (format === "json") { window.electron - .saveJsonFile( + .saveStructuredTextFile( fileName, + format, getDisplayableFromDatabaseRows(data.rows, data.headers) ) .then(() => setStage('SUCCESS')) From 41989306fdd70baf4204a98658eb731689d94748 Mon Sep 17 00:00:00 2001 From: Seanghay Yath Date: Sat, 21 Oct 2023 20:05:11 +0700 Subject: [PATCH 5/5] fix: add preload and cast type --- src/main/preload.ts | 5 +++-- src/renderer/screens/DatabaseScreen/ExportModal/index.tsx | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/preload.ts b/src/main/preload.ts index 2fecf203..ccd3531f 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -16,6 +16,7 @@ import { UpdateDownloadedEvent, UpdateInfo, } from 'electron-updater'; +import { SupportedStructuredFileType } from 'libs/StructuredTextFile.js'; export type Channels = 'ipc-example' | 'create-connection'; @@ -74,8 +75,8 @@ const electronHandler = { showFileInFolder: (fileName: string) => ipcRenderer.invoke('show-item-in-folder', [fileName]), - saveJsonFile: (fileName: string, records: object[]) => - ipcRenderer.invoke('save-json-file', [fileName, records]), + saveStructuredTextFile: (fileName: string, type: SupportedStructuredFileType, records: object[]) => + ipcRenderer.invoke('save-structured-text-file', [fileName, type, records]), saveCsvFile: (fileName: string, records: object[]) => ipcRenderer.invoke('save-csv-file', [fileName, records]), diff --git a/src/renderer/screens/DatabaseScreen/ExportModal/index.tsx b/src/renderer/screens/DatabaseScreen/ExportModal/index.tsx index 7e7267aa..6c313ffb 100644 --- a/src/renderer/screens/DatabaseScreen/ExportModal/index.tsx +++ b/src/renderer/screens/DatabaseScreen/ExportModal/index.tsx @@ -8,6 +8,7 @@ import { faEllipsis, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { SupportedStructuredFileType } from 'libs/StructuredTextFile'; import { getDisplayableFromDatabaseRows } from 'libs/TransformResult'; import { useCallback, useState } from 'react'; import Button from 'renderer/components/Button'; @@ -225,7 +226,7 @@ export default function ExportModal({ data, onClose }: ExportModalProps) { window.electron .saveStructuredTextFile( fileName, - format, + format as SupportedStructuredFileType, getDisplayableFromDatabaseRows(data.rows, data.headers) ) .then(() => setStage('SUCCESS'))