diff --git a/packages/api/src/controllers/files.js b/packages/api/src/controllers/files.js index 74e6c81ed..828730ce0 100644 --- a/packages/api/src/controllers/files.js +++ b/packages/api/src/controllers/files.js @@ -27,11 +27,25 @@ module.exports = { return files; }, + listAll_meta: 'get', + async listAll() { + const folders = await fs.readdir(filesdir()); + const res = []; + for (const folder of folders) { + if (!hasPermission(`files/${folder}/read`)) continue; + const dir = path.join(filesdir(), folder); + const files = (await fs.readdir(dir)).map((file) => ({ folder, file })); + res.push(...files); + } + return res; + }, + delete_meta: 'post', async delete({ folder, file }) { if (!hasPermission(`files/${folder}/write`)) return; await fs.unlink(path.join(filesdir(), folder, file)); socket.emitChanged(`files-changed-${folder}`); + socket.emitChanged(`all-files-changed`); }, rename_meta: 'post', @@ -39,6 +53,7 @@ module.exports = { if (!hasPermission(`files/${folder}/write`)) return; await fs.rename(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile)); socket.emitChanged(`files-changed-${folder}`); + socket.emitChanged(`all-files-changed`); }, load_meta: 'post', @@ -57,6 +72,7 @@ module.exports = { } await fs.writeFile(path.join(dir, file), serialize(format, data)); socket.emitChanged(`files-changed-${folder}`); + socket.emitChanged(`all-files-changed`); if (folder == 'shell') { scheduler.reload(); } diff --git a/packages/web/src/appobj/SavedFileAppObject.js b/packages/web/src/appobj/SavedFileAppObject.js index e425ca0b1..076d692d2 100644 --- a/packages/web/src/appobj/SavedFileAppObject.js +++ b/packages/web/src/appobj/SavedFileAppObject.js @@ -205,6 +205,45 @@ export function SavedChartFileAppObject({ data, commonProps }) { ); } +export function SavedQueryFileAppObject({ data, commonProps }) { + const { file, folder } = data; + const openNewTab = useOpenNewTab(); + + const currentDatabase = useCurrentDatabase(); + + const connection = _.get(currentDatabase, 'connection') || {}; + const database = _.get(currentDatabase, 'name'); + + const tooltip = `${connection.displayName || connection.server}\n${database}`; + + return ( + { + openNewTab( + { + title: file, + icon: 'img query-design', + tooltip, + props: { + conid: connection._id, + database, + savedFile: file, + savedFolder: 'query', + savedFormat: 'json', + }, + tabComponent: 'QueryDesignTab', + }, + { editor: data } + ); + }} + /> + ); +} + export function SavedMarkdownFileAppObject({ data, commonProps }) { const { file, folder } = data; const openNewTab = useOpenNewTab(); @@ -247,7 +286,29 @@ export function SavedMarkdownFileAppObject({ data, commonProps }) { ); } -[SavedSqlFileAppObject, SavedShellFileAppObject, SavedChartFileAppObject, SavedMarkdownFileAppObject].forEach((fn) => { +export function SavedFileAppObject({ data, commonProps }) { + const { folder } = data; + const folderTypes = { + sql: SavedSqlFileAppObject, + shell: SavedShellFileAppObject, + charts: SavedChartFileAppObject, + markdown: SavedMarkdownFileAppObject, + query: SavedQueryFileAppObject, + }; + const AppObject = folderTypes[folder]; + if (AppObject) { + return ; + } + return null; +} + +[ + SavedSqlFileAppObject, + SavedShellFileAppObject, + SavedChartFileAppObject, + SavedMarkdownFileAppObject, + SavedFileAppObject, +].forEach((fn) => { // @ts-ignore fn.extractKey = (data) => data.file; }); diff --git a/packages/web/src/utility/metadataLoaders.js b/packages/web/src/utility/metadataLoaders.js index ff35eea7c..1ac92cebf 100644 --- a/packages/web/src/utility/metadataLoaders.js +++ b/packages/web/src/utility/metadataLoaders.js @@ -105,6 +105,11 @@ const filesLoader = ({ folder }) => ({ params: { folder }, reloadTrigger: `files-changed-${folder}`, }); +const allFilesLoader = () => ({ + url: 'files/list-all', + params: {}, + reloadTrigger: `all-files-changed`, +}); async function getCore(loader, args) { const { url, params, reloadTrigger, transform } = loader(args); @@ -276,6 +281,13 @@ export function useFiles(args) { return useCore(filesLoader, args); } +export function getAllFiles(args) { + return getCore(allFilesLoader, args); +} +export function useAllFiles(args) { + return useCore(allFilesLoader, args); +} + export function getFavorites(args) { return getCore(favoritesLoader, args); } diff --git a/packages/web/src/widgets/FilesWidget.js b/packages/web/src/widgets/FilesWidget.js index 3a76fe371..3cc4f1fd9 100644 --- a/packages/web/src/widgets/FilesWidget.js +++ b/packages/web/src/widgets/FilesWidget.js @@ -2,92 +2,45 @@ import React from 'react'; import _ from 'lodash'; import { AppObjectList } from '../appobj/AppObjectList'; -import { useOpenedTabs } from '../utility/globalState'; -import ClosedTabAppObject from '../appobj/ClosedTabAppObject'; import { WidgetsInnerContainer } from './WidgetStyles'; -import { - SavedSqlFileAppObject, - SavedShellFileAppObject, - SavedChartFileAppObject, - SavedMarkdownFileAppObject, -} from '../appobj/SavedFileAppObject'; +import { SavedFileAppObject } from '../appobj/SavedFileAppObject'; import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar'; import { useFiles } from '../utility/metadataLoaders'; -import useHasPermission from '../utility/useHasPermission'; +function SavedFilesList() { + const sqlFiles = useFiles({ folder: 'sql' }); + const shellFiles = useFiles({ folder: 'shell' }); + const markdownFiles = useFiles({ folder: 'markdown' }); + const chartFiles = useFiles({ folder: 'charts' }); + const queryFiles = useFiles({ folder: 'query' }); -function SavedSqlFilesList() { - const files = useFiles({ folder: 'sql' }); + const files = [ + ...(sqlFiles || []), + ...(shellFiles || []), + ...(markdownFiles || []), + ...(chartFiles || []), + ...(queryFiles || []), + ]; return ( <> - - - - ); -} - -function SavedShellFilesList() { - const files = useFiles({ folder: 'shell' }); - - return ( - <> - - - - - ); -} - -function SavedChartFilesList() { - const files = useFiles({ folder: 'charts' }); - - return ( - <> - - - - - ); -} - -function SavedMarkdownFilesList() { - const files = useFiles({ folder: 'markdown' }); - - return ( - <> - - + _.startCase(data.folder)} + /> ); } export default function FilesWidget() { - const hasPermission = useHasPermission(); return ( - {hasPermission('files/sql/read') && ( - - - - )} - {hasPermission('files/shell/read') && ( - - - - )} - {hasPermission('files/charts/read') && ( - - - - )} - {hasPermission('files/markdown/read') && ( - - - - )} + + + ); } diff --git a/packages/web/src/widgets/WidgetColumnBar.js b/packages/web/src/widgets/WidgetColumnBar.js index e241bf2e8..43822a54d 100644 --- a/packages/web/src/widgets/WidgetColumnBar.js +++ b/packages/web/src/widgets/WidgetColumnBar.js @@ -47,6 +47,9 @@ export default function WidgetColumnBar({ children }) { childArray.filter((x) => x && x.props.collapsed).map((x) => x.props.name) ); const toggleCollapsed = (name) => { + // skip collapse last uncollapsed item + if (!childArray.find((x) => x.props.name != name && !collapsedWidgets.includes(x.props.name))) return; + if (collapsedWidgets.includes(name)) setCollapsedWidgets(collapsedWidgets.filter((x) => x != name)); else setCollapsedWidgets([...collapsedWidgets, name]); };