diff --git a/packages/api/src/controllers/files.js b/packages/api/src/controllers/files.js new file mode 100644 index 000000000..2a6f4ee06 --- /dev/null +++ b/packages/api/src/controllers/files.js @@ -0,0 +1,46 @@ +const fs = require('fs-extra'); +const path = require('path'); +const { filesdir } = require('../utility/directories'); +const socket = require('../utility/socket'); + +function serialize(folder, data) { + if (folder == 'sql') return data; + return JSON.stringify(data); +} + +function deserialize(folder, text) { + if (folder == 'sql') return text; + return JSON.parse(text); +} + +module.exports = { + list_meta: 'get', + async list({ folder }) { + const dir = path.join(filesdir(), folder); + if (!(await fs.exists(dir))) return []; + const files = (await fs.readdir(dir)).map((name) => ({ name })); + return files; + }, + + delete_meta: 'post', + async delete({ folder, file }) { + await fs.unlink(path.join(filesdir(), folder, file)); + socket.emitChanged(`files-changed-${folder}`); + }, + + load_meta: 'post', + async load({ folder, file }) { + const text = await fs.readFile(path.join(filesdir(), folder, file), { encoding: 'utf-8' }); + return deserialize(folder, text); + }, + + save_meta: 'post', + async save({ folder, file, data }) { + const dir = path.join(filesdir(), folder); + if (!(await fs.exists(dir))) { + await fs.mkdir(dir); + } + await fs.writeFile(path.join(dir, file), serialize(folder, data)); + socket.emitChanged(`files-changed-${folder}`); + }, +}; diff --git a/packages/api/src/main.js b/packages/api/src/main.js index 37841c0cf..cbc0eaed8 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -23,6 +23,7 @@ const config = require('./controllers/config'); const archive = require('./controllers/archive'); const uploads = require('./controllers/uploads'); const plugins = require('./controllers/plugins'); +const files = require('./controllers/files'); const { rundir } = require('./utility/directories'); @@ -67,6 +68,7 @@ function start(argument = null) { useController(app, '/archive', archive); useController(app, '/uploads', uploads); useController(app, '/plugins', plugins); + useController(app, '/files', files); if (process.env.PAGES_DIRECTORY) { app.use('/pages', express.static(process.env.PAGES_DIRECTORY)); diff --git a/packages/api/src/utility/directories.js b/packages/api/src/utility/directories.js index 4f0ace4b9..f1c41ac50 100644 --- a/packages/api/src/utility/directories.js +++ b/packages/api/src/utility/directories.js @@ -37,6 +37,7 @@ const rundir = dirFunc('run', true); const uploadsdir = dirFunc('uploads', true); const pluginsdir = dirFunc('plugins'); const archivedir = dirFunc('archive'); +const filesdir = dirFunc('files'); module.exports = { datadir, @@ -46,4 +47,5 @@ module.exports = { archivedir, ensureDirectory, pluginsdir, + filesdir, }; diff --git a/packages/web/src/App.js b/packages/web/src/App.js index 28426f75f..8ad18373a 100644 --- a/packages/web/src/App.js +++ b/packages/web/src/App.js @@ -5,7 +5,6 @@ import { CurrentWidgetProvider, CurrentDatabaseProvider, OpenedTabsProvider, - SavedSqlFilesProvider, OpenedConnectionsProvider, LeftPanelWidthProvider, CurrentArchiveProvider, @@ -25,28 +24,26 @@ function App() { - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/src/appobj/savedSqlFileAppObject.js b/packages/web/src/appobj/savedSqlFileAppObject.js index 2d8054398..a5525c05c 100644 --- a/packages/web/src/appobj/savedSqlFileAppObject.js +++ b/packages/web/src/appobj/savedSqlFileAppObject.js @@ -1,10 +1,11 @@ import React from 'react'; +import axios from '../utility/axios'; import _ from 'lodash'; import { DropDownMenuItem } from '../modals/DropDownMenu'; -function Menu({ data, setSavedSqlFiles }) { +function Menu({ data }) { const handleDelete = () => { - setSavedSqlFiles((files) => files.filter((x) => x.storageKey != data.storageKey)); + axios.post('files/delete', { folder: 'sql', file: data.name }); }; return ( <> @@ -13,26 +14,17 @@ function Menu({ data, setSavedSqlFiles }) { ); } -const savedSqlFileAppObject = () => ({ name, storageKey }, { setOpenedTabs, newQuery, openedTabs }) => { - const key = storageKey; +const savedSqlFileAppObject = () => ({ name }, { setOpenedTabs, newQuery, openedTabs }) => { + const key = name; const title = name; const icon = 'img sql-file'; - const onClick = () => { - const existing = openedTabs.find((x) => x.props && x.props.storageKey == storageKey); - if (existing) { - setOpenedTabs( - openedTabs.map((x) => ({ - ...x, - selected: x == existing, - })) - ); - } else { - newQuery({ - title, - storageKey, - }); - } + const onClick = async () => { + const resp = await axios.post('files/load', { folder: 'sql', file: name }); + newQuery({ + title: name, + initialScript: resp.data, + }); }; return { title, key, icon, onClick, Menu }; diff --git a/packages/web/src/modals/ChangeDownloadUrlModal.js b/packages/web/src/modals/ChangeDownloadUrlModal.js index 0ef4fe1d7..5501562d2 100644 --- a/packages/web/src/modals/ChangeDownloadUrlModal.js +++ b/packages/web/src/modals/ChangeDownloadUrlModal.js @@ -4,7 +4,6 @@ import ModalBase from './ModalBase'; import { FormButtonRow, FormButton, FormTextField, FormSelectField, FormSubmit } from '../utility/forms'; import { TextField } from '../utility/inputs'; import { Formik, Form } from 'formik'; -import { useSetSavedSqlFiles } from '../utility/globalState'; import ModalHeader from './ModalHeader'; import ModalContent from './ModalContent'; import ModalFooter from './ModalFooter'; diff --git a/packages/web/src/modals/SaveSqlFileModal.js b/packages/web/src/modals/SaveSqlFileModal.js index 935d732ff..e680cd5d4 100644 --- a/packages/web/src/modals/SaveSqlFileModal.js +++ b/packages/web/src/modals/SaveSqlFileModal.js @@ -4,23 +4,15 @@ import ModalBase from './ModalBase'; import { FormButtonRow, FormButton, FormTextField, FormSelectField, FormSubmit } from '../utility/forms'; import { TextField } from '../utility/inputs'; import { Formik, Form } from 'formik'; -import { useSetSavedSqlFiles } from '../utility/globalState'; import ModalHeader from './ModalHeader'; import ModalContent from './ModalContent'; import ModalFooter from './ModalFooter'; // import FormikForm from '../utility/FormikForm'; export default function SaveSqlFileModal({ storageKey, modalState, name, onSave = undefined }) { - const setSavedSqlFiles = useSetSavedSqlFiles(); const handleSubmit = async (values) => { const { name } = values; - setSavedSqlFiles((files) => [ - ...files.filter((x) => x.storageKey != storageKey), - { - name, - storageKey, - }, - ]); + await axios.post('files/save', { folder: 'sql', file: name, data: localStorage.getItem(storageKey) }); modalState.close(); if (onSave) onSave(name); }; diff --git a/packages/web/src/utility/globalState.js b/packages/web/src/utility/globalState.js index d47c4666c..811aa82c8 100644 --- a/packages/web/src/utility/globalState.js +++ b/packages/web/src/utility/globalState.js @@ -85,7 +85,6 @@ export function useAppObjectParams() { const currentDatabase = useCurrentDatabase(); const newQuery = useNewQuery(); const openedTabs = useOpenedTabs(); - const setSavedSqlFiles = useSetSavedSqlFiles(); const openedConnections = useOpenedConnections(); const setOpenedConnections = useSetOpenedConnections(); const currentArchive = useCurrentArchive(); @@ -99,7 +98,6 @@ export function useAppObjectParams() { currentArchive, newQuery, openedTabs, - setSavedSqlFiles, openedConnections, setOpenedConnections, config, @@ -108,9 +106,6 @@ export function useAppObjectParams() { }; } -const [SavedSqlFilesProvider, useSavedSqlFiles, useSetSavedSqlFiles] = createStorageState('savedSqlFiles', []); -export { SavedSqlFilesProvider, useSavedSqlFiles, useSetSavedSqlFiles }; - const [OpenedConnectionsProvider, useOpenedConnections, useSetOpenedConnections] = createGlobalState([]); export { OpenedConnectionsProvider, useOpenedConnections, useSetOpenedConnections }; diff --git a/packages/web/src/utility/metadataLoaders.js b/packages/web/src/utility/metadataLoaders.js index ffe8fc6c9..e3f46cc73 100644 --- a/packages/web/src/utility/metadataLoaders.js +++ b/packages/web/src/utility/metadataLoaders.js @@ -94,6 +94,12 @@ const installedPluginsLoader = () => ({ reloadTrigger: `installed-plugins-changed`, }); +const filesLoader = ({ folder }) => ({ + url: 'files/list', + params: { folder }, + reloadTrigger: `files-changed-${folder}`, +}); + async function getCore(loader, args) { const { url, params, reloadTrigger, transform } = loader(args); const key = stableStringify({ url, ...params }); @@ -256,3 +262,10 @@ export function getInstalledPlugins(args) { export function useInstalledPlugins(args) { return useCore(installedPluginsLoader, args) || []; } + +export function getFiles(args) { + return getCore(filesLoader, args); +} +export function useFiles(args) { + return useCore(filesLoader, args); +} diff --git a/packages/web/src/widgets/FilesWidget.js b/packages/web/src/widgets/FilesWidget.js index 84277755c..01181bccf 100644 --- a/packages/web/src/widgets/FilesWidget.js +++ b/packages/web/src/widgets/FilesWidget.js @@ -1,19 +1,13 @@ import React from 'react'; -import styled from 'styled-components'; import _ from 'lodash'; import { AppObjectList } from '../appobj/AppObjectList'; -import { useOpenedTabs, useSavedSqlFiles } from '../utility/globalState'; +import { useOpenedTabs } from '../utility/globalState'; import closedTabAppObject from '../appobj/closedTabAppObject'; -import { - SearchBoxWrapper, - WidgetsInnerContainer, - WidgetsMainContainer, - WidgetsOuterContainer, - WidgetTitle, -} from './WidgetStyles'; +import { WidgetsInnerContainer } from './WidgetStyles'; import savedSqlFileAppObject from '../appobj/savedSqlFileAppObject'; import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar'; +import { useFiles } from '../utility/metadataLoaders'; function ClosedTabsList() { const tabs = useOpenedTabs(); @@ -34,7 +28,7 @@ function ClosedTabsList() { } function SavedSqlFilesList() { - const files = useSavedSqlFiles(); + const files = useFiles({ folder: 'sql' }); return ( <>