diff --git a/packages/api/src/controllers/archive.js b/packages/api/src/controllers/archive.js index 55869487e..c3768e114 100644 --- a/packages/api/src/controllers/archive.js +++ b/packages/api/src/controllers/archive.js @@ -1,7 +1,11 @@ const fs = require('fs-extra'); +const stream = require('stream'); +const readline = require('readline'); const path = require('path'); +const { formatWithOptions } = require('util'); const { archivedir } = require('../utility/directories'); const socket = require('../utility/socket'); +const JsonLinesDatastore = require('../utility/JsonLinesDatastore'); module.exports = { folders_meta: 'get', @@ -61,4 +65,37 @@ module.exports = { await fs.rmdir(path.join(archivedir(), folder), { recursive: true }); socket.emitChanged(`archive-folders-changed`); }, + + saveFreeTable_meta: 'post', + async saveFreeTable({ folder, file, data }) { + const { structure, rows } = data; + const fileStream = fs.createWriteStream(path.join(archivedir(), folder, `${file}.jsonl`)); + await fileStream.write(JSON.stringify(structure) + '\n'); + for (const row of rows) { + await fileStream.write(JSON.stringify(row) + '\n'); + } + await fileStream.close(); + return true; + }, + + loadFreeTable_meta: 'post', + async loadFreeTable({ folder, file }) { + return new Promise((resolve, reject) => { + const fileStream = fs.createReadStream(path.join(archivedir(), folder, `${file}.jsonl`)); + const liner = readline.createInterface({ + input: fileStream, + }); + let structure = null; + const rows = []; + liner.on('line', (line) => { + const data = JSON.parse(line); + if (structure) rows.push(data); + else structure = data; + }); + liner.on('close', () => { + resolve({ structure, rows }); + fileStream.close(); + }); + }); + }, }; diff --git a/packages/datalib/src/FreeTableGridDisplay.ts b/packages/datalib/src/FreeTableGridDisplay.ts index 9652790d8..5b7ac48a4 100644 --- a/packages/datalib/src/FreeTableGridDisplay.ts +++ b/packages/datalib/src/FreeTableGridDisplay.ts @@ -14,8 +14,8 @@ export class FreeTableGridDisplay extends GridDisplay { ) { super(config, setConfig, cache, setCache); this.columns = this.getDisplayColumns(model); - this.filterable = true; - this.sortable = true; + this.filterable = false; + this.sortable = false; this.editable = true; } diff --git a/packages/web/src/appobj/archiveFileAppObject.js b/packages/web/src/appobj/archiveFileAppObject.js index 82a6886b6..d6b2e2fd7 100644 --- a/packages/web/src/appobj/archiveFileAppObject.js +++ b/packages/web/src/appobj/archiveFileAppObject.js @@ -7,13 +7,46 @@ import { openNewTab } from '../utility/common'; import { filterName } from '@dbgate/datalib'; import axios from '../utility/axios'; +function openArchive(setOpenedTabs, fileName, folderName) { + openNewTab(setOpenedTabs, { + title: fileName, + icon: 'archtable.svg', + tooltip: `${folderName}\n${fileName}`, + tabComponent: 'ArchiveFileTab', + props: { + archiveFile: fileName, + archiveFolder: folderName, + }, + }); +} + function Menu({ data, setOpenedTabs }) { const handleDelete = () => { axios.post('archive/delete-file', { file: data.fileName, folder: data.folderName }); // setOpenedTabs((tabs) => tabs.filter((x) => x.tabid != data.tabid)); }; + const handleOpenRead = () => { + openArchive(setOpenedTabs, data.fileName, data.folderName); + }; + const handleOpenWrite = async () => { + const resp = await axios.post('archive/load-free-table', { file: data.fileName, folder: data.folderName }); + + openNewTab(setOpenedTabs, { + title: data.fileName, + icon: 'freetable.svg', + tabComponent: 'FreeTableTab', + props: { + initialData: resp.data, + archiveFile: data.fileName, + archiveFolder: data.folderName, + }, + }); + }; + return ( <> + Open (readonly) + Open in free table editor Delete ); @@ -24,16 +57,7 @@ const archiveFileAppObject = () => ({ fileName, folderName }, { setOpenedTabs }) // const Icon = (props) => ; const Icon = ArchiveTableIcon; const onClick = () => { - openNewTab(setOpenedTabs, { - title: fileName, - icon: 'archtable.svg', - tooltip: `${folderName}\n${fileName}`, - tabComponent: 'ArchiveFileTab', - props: { - archiveFile: fileName, - archiveFolder: folderName, - }, - }); + openArchive(setOpenedTabs, fileName, folderName); }; const matcher = (filter) => filterName(filter, fileName); diff --git a/packages/web/src/freetable/useNewFreeTable.js b/packages/web/src/freetable/useNewFreeTable.js index 37a667110..9cbb8daf9 100644 --- a/packages/web/src/freetable/useNewFreeTable.js +++ b/packages/web/src/freetable/useNewFreeTable.js @@ -1,5 +1,5 @@ import _ from 'lodash'; -import { useSetOpenedTabs, useCurrentDatabase } from '../utility/globalState'; +import { useSetOpenedTabs } from '../utility/globalState'; import { openNewTab } from '../utility/common'; export default function useNewFreeTable() { @@ -10,6 +10,6 @@ export default function useNewFreeTable() { title: title || 'Table', icon: 'freetable.svg', tabComponent: 'FreeTableTab', - props: {}, + props, }); } diff --git a/packages/web/src/modals/SaveArchiveModal.js b/packages/web/src/modals/SaveArchiveModal.js new file mode 100644 index 000000000..511b9312f --- /dev/null +++ b/packages/web/src/modals/SaveArchiveModal.js @@ -0,0 +1,44 @@ +import React from 'react'; +import ModalBase from './ModalBase'; +import { FormTextField, FormSubmit, FormArchiveFolderSelect, FormRow, FormLabel } from '../utility/forms'; +import { Formik, Form } from 'formik'; +import styled from 'styled-components'; +import ModalHeader from './ModalHeader'; +import ModalContent from './ModalContent'; +import ModalFooter from './ModalFooter'; + +const SelectWrapper = styled.div` + width: 150px; + position: relative; + flex: 1; +`; + +export default function SaveArchiveModal({ file = 'new-table', folder = 'default', modalState, onSave }) { + const handleSubmit = async (values) => { + const { file, folder } = values; + modalState.close(); + if (onSave) onSave(folder, file); + }; + return ( + + Save to archive + +
+ + {/* */} + + Folder + + + + + + + + + +
+
+
+ ); +} diff --git a/packages/web/src/tabs/FreeTableTab.js b/packages/web/src/tabs/FreeTableTab.js index 89185047d..a54bd35b1 100644 --- a/packages/web/src/tabs/FreeTableTab.js +++ b/packages/web/src/tabs/FreeTableTab.js @@ -2,15 +2,21 @@ import React from 'react'; import { createGridCache, createChangeSet, createGridConfig, createFreeTableModel } from '@dbgate/datalib'; import useUndoReducer from '../utility/useUndoReducer'; import usePropsCompare from '../utility/usePropsCompare'; -import { useUpdateDatabaseForTab } from '../utility/globalState'; +import { useSetOpenedTabs, useUpdateDatabaseForTab } from '../utility/globalState'; import TableDataGrid from '../datagrid/TableDataGrid'; import useGridConfig from '../utility/useGridConfig'; import FreeTableGrid from '../freetable/FreeTableGrid'; +import SaveArchiveModal from '../modals/SaveArchiveModal'; +import useModalState from '../modals/useModalState'; +import axios from '../utility/axios'; +import { changeTab } from '../utility/common'; -export default function FreeDataTab({ conid, database, schemaName, pureName, tabVisible, toolbarPortalRef, tabid }) { +export default function FreeDataTab({ archiveFolder, archiveFile, tabVisible, toolbarPortalRef, tabid, initialData }) { const [config, setConfig] = useGridConfig(tabid); - const [modelState, dispatchModel] = useUndoReducer(createFreeTableModel()); + const [modelState, dispatchModel] = useUndoReducer(initialData || createFreeTableModel()); const storageKey = `tabdata_freetable_${tabid}`; + const saveSqlFileModalState = useModalState(); + const setOpenedTabs = useSetOpenedTabs(); React.useEffect(() => { const existingData = localStorage.getItem(storageKey); @@ -25,15 +31,32 @@ export default function FreeDataTab({ conid, database, schemaName, pureName, tab localStorage.setItem(storageKey, JSON.stringify(modelState.value)); }, [modelState]); + const handleSave = async (folder, file) => { + await axios.post('archive/save-free-table', { folder, file, data: modelState.value }); + changeTab(tabid, setOpenedTabs, (tab) => ({ + ...tab, + title: file, + props: { archiveFIle: file, archiveFolder: folder }, + })); + }; + return ( - + <> + saveSqlFileModalState.open()} + /> + + ); } diff --git a/packages/web/src/utility/forms.js b/packages/web/src/utility/forms.js index 9079c3dbe..7020c8238 100644 --- a/packages/web/src/utility/forms.js +++ b/packages/web/src/utility/forms.js @@ -196,7 +196,7 @@ export function FormArchiveFilesSelect({ folderName, name }) { return ; } -export function FormArchiveFolderSelect({ name }) { +export function FormArchiveFolderSelect({ name, ...other }) { const { setFieldValue } = useFormikContext(); const folders = useArchiveFolders(); const folderOptions = React.useMemo( @@ -214,6 +214,12 @@ export function FormArchiveFolderSelect({ name }) { }; return ( - + ); }