diff --git a/app/src/electron.js b/app/src/electron.js index a61f372dc..9eb6b9707 100644 --- a/app/src/electron.js +++ b/app/src/electron.js @@ -51,6 +51,12 @@ function buildMenu() { mainWindow.webContents.executeJavaScript(`dbgate_newQuery()`); }, }, + { + label: 'Open file', + click() { + mainWindow.webContents.executeJavaScript(`dbgate_openFile()`); + }, + }, { label: 'Close all tabs', click() { diff --git a/packages/api/src/controllers/files.js b/packages/api/src/controllers/files.js index efb75d1bc..ad3e91cb2 100644 --- a/packages/api/src/controllers/files.js +++ b/packages/api/src/controllers/files.js @@ -78,6 +78,11 @@ module.exports = { } }, + saveAs_meta: 'post', + async saveAs({ filePath, data, format }) { + await fs.writeFile(filePath, serialize(format, data)); + }, + favorites_meta: 'get', async favorites() { if (!hasPermission(`files/favorites/read`)) return []; diff --git a/packages/web/src/TabsPanel.js b/packages/web/src/TabsPanel.js index c976e50e2..1da14e220 100644 --- a/packages/web/src/TabsPanel.js +++ b/packages/web/src/TabsPanel.js @@ -124,6 +124,15 @@ function getDbIcon(key) { return 'icon file'; } +function buildTooltip(tab) { + let res = tab.tooltip; + if (tab.props && tab.props.savedFilePath) { + if (res) res += '\n'; + res += tab.props.savedFilePath; + } + return res; +} + export default function TabsPanel() { // const formatDbKey = (conid, database) => `${database}-${conid}`; const theme = useTheme(); @@ -252,7 +261,7 @@ export default function TabsPanel() { {_.sortBy(tabsByDb[dbKey], ['title', 'tabid']).map(tab => ( handleTabClick(e, tab.tabid)} diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.js b/packages/web/src/appobj/DatabaseObjectAppObject.js index 88fc4badc..e091eff4e 100644 --- a/packages/web/src/appobj/DatabaseObjectAppObject.js +++ b/packages/web/src/appobj/DatabaseObjectAppObject.js @@ -149,7 +149,7 @@ export async function openDatabaseObjectDetail( openNewTab( { - title: pureName, + title: 'Query #', tooltip, icon: sqlTemplate ? 'img sql-file' : icons[objectTypeField], tabComponent: sqlTemplate ? 'QueryTab' : tabComponent, diff --git a/packages/web/src/modals/SaveFileModal.js b/packages/web/src/modals/SaveFileModal.js index 583fb8078..829eea28c 100644 --- a/packages/web/src/modals/SaveFileModal.js +++ b/packages/web/src/modals/SaveFileModal.js @@ -6,14 +6,52 @@ import ModalHeader from './ModalHeader'; import ModalContent from './ModalContent'; import ModalFooter from './ModalFooter'; import { FormProvider } from '../utility/FormProvider'; +import FormStyledButton from '../widgets/FormStyledButton'; +import getElectron from '../utility/getElectron'; + +export default function SaveFileModal({ + data, + folder, + format, + modalState, + name, + fileExtension, + filePath, + onSave = undefined, +}) { + const electron = getElectron(); -export default function SaveFileModal({ data, folder, format, modalState, name, onSave = undefined }) { const handleSubmit = async values => { const { name } = values; await axios.post('files/save', { folder, file: name, data, format }); modalState.close(); - if (onSave) onSave(name); + if (onSave) { + onSave(name, { + savedFile: name, + savedFolder: folder, + savedFilePath: null, + }); + } }; + + const handleSaveAs = async filePath => { + const path = window.require('path'); + + const parsed = path.parse(filePath); + if (!parsed.ext) filePath += `.${fileExtension}`; + + await axios.post('files/save-as', { filePath, data, format }); + modalState.close(); + + if (onSave) { + onSave(parsed.name, { + savedFile: null, + savedFolder: null, + savedFilePath: filePath, + }); + } + }; + return ( Save file @@ -23,6 +61,21 @@ export default function SaveFileModal({ data, folder, format, modalState, name, + {electron && ( + { + const file = electron.remote.dialog.showSaveDialogSync(electron.remote.getCurrentWindow(), { + filters: { name: `${fileExtension.toUpperCase()} files`, extensions: [fileExtension] }, + defaultPath: filePath, + }); + if (file) { + handleSaveAs(file); + } + }} + /> + )} diff --git a/packages/web/src/modals/SaveTabModal.js b/packages/web/src/modals/SaveTabModal.js index d581649b4..0111d1114 100644 --- a/packages/web/src/modals/SaveTabModal.js +++ b/packages/web/src/modals/SaveTabModal.js @@ -4,22 +4,22 @@ import { useOpenedTabs, useSetOpenedTabs } from '../utility/globalState'; import keycodes from '../utility/keycodes'; import SaveFileModal from './SaveFileModal'; -export default function SaveTabModal({ data, folder, format, modalState, tabid, tabVisible }) { +export default function SaveTabModal({ data, folder, format, modalState, tabid, tabVisible, fileExtension }) { const setOpenedTabs = useSetOpenedTabs(); const openedTabs = useOpenedTabs(); - const { savedFile } = openedTabs.find(x => x.tabid == tabid).props || {}; - const onSave = name => + const { savedFile, savedFilePath } = openedTabs.find(x => x.tabid == tabid).props || {}; + const onSave = (title, newProps) => { changeTab(tabid, setOpenedTabs, tab => ({ ...tab, - title: name, + title, props: { ...tab.props, - savedFile: name, - savedFolder: folder, savedFormat: format, + ...newProps, }, })); + }; const handleKeyboard = React.useCallback( e => { @@ -47,6 +47,8 @@ export default function SaveTabModal({ data, folder, format, modalState, tabid, format={format} modalState={modalState} name={savedFile || 'newFile'} + filePath={savedFilePath} + fileExtension={fileExtension} onSave={onSave} /> ); diff --git a/packages/web/src/tabs/ChartTab.js b/packages/web/src/tabs/ChartTab.js index 3a3882dc4..61a880a29 100644 --- a/packages/web/src/tabs/ChartTab.js +++ b/packages/web/src/tabs/ChartTab.js @@ -63,6 +63,7 @@ export default function ChartTab({ tabVisible, toolbarPortalRef, conid, database format="json" folder="charts" tabid={tabid} + fileExtension='chart' /> {toolbarPortalRef && toolbarPortalRef.current && diff --git a/packages/web/src/tabs/MarkdownEditorTab.js b/packages/web/src/tabs/MarkdownEditorTab.js index b45f1e936..4725464a6 100644 --- a/packages/web/src/tabs/MarkdownEditorTab.js +++ b/packages/web/src/tabs/MarkdownEditorTab.js @@ -75,6 +75,7 @@ export default function MarkdownEditorTab({ tabid, tabVisible, toolbarPortalRef, format="text" folder="markdown" tabid={tabid} + fileExtension='md' /> ); diff --git a/packages/web/src/tabs/QueryDesignTab.js b/packages/web/src/tabs/QueryDesignTab.js index 1d03eeea7..deb2f16d1 100644 --- a/packages/web/src/tabs/QueryDesignTab.js +++ b/packages/web/src/tabs/QueryDesignTab.js @@ -225,6 +225,7 @@ export default function QueryDesignTab({ format="json" folder="query" tabid={tabid} + fileExtension='qdesign' /> ); diff --git a/packages/web/src/tabs/QueryTab.js b/packages/web/src/tabs/QueryTab.js index 4801f29c1..fcf6a8787 100644 --- a/packages/web/src/tabs/QueryTab.js +++ b/packages/web/src/tabs/QueryTab.js @@ -228,6 +228,7 @@ export default function QueryTab({ format="text" folder="sql" tabid={tabid} + fileExtension='sql' /> ); diff --git a/packages/web/src/tabs/ShellTab.js b/packages/web/src/tabs/ShellTab.js index c7cd3766b..f541704cb 100644 --- a/packages/web/src/tabs/ShellTab.js +++ b/packages/web/src/tabs/ShellTab.js @@ -145,6 +145,7 @@ export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, statusba format="text" folder="shell" tabid={tabid} + fileExtension='js' /> ); diff --git a/packages/web/src/utility/useOpenElectronFile.js b/packages/web/src/utility/useOpenElectronFile.js new file mode 100644 index 000000000..b3133f5df --- /dev/null +++ b/packages/web/src/utility/useOpenElectronFile.js @@ -0,0 +1,31 @@ +import useNewQuery from '../query/useNewQuery'; +import getElectron from './getElectron'; + +export default function useOpenElectronFile() { + const electron = getElectron(); + + const newQuery = useNewQuery(); + + return () => { + const filePaths = electron.remote.dialog.showOpenDialogSync(electron.remote.getCurrentWindow(), { + filters: { name: `SQL files`, extensions: ['sql'] }, + }); + const filePath = filePaths && filePaths[0]; + if (filePath) { + if (filePath.match(/.sql$/i)) { + const path = window.require('path'); + const fs = window.require('fs'); + const parsed = path.parse(filePath); + const data = fs.readFileSync(filePath, { encoding: 'utf-8' }); + + newQuery({ + title: parsed.name, + initialData: data, + // @ts-ignore + savedFilePath: filePath, + savedFormat: 'text', + }); + } + } + }; +} diff --git a/packages/web/src/utility/useOpenNewTab.js b/packages/web/src/utility/useOpenNewTab.js index dc997eb1b..2c20dc0d7 100644 --- a/packages/web/src/utility/useOpenNewTab.js +++ b/packages/web/src/utility/useOpenNewTab.js @@ -23,11 +23,16 @@ export default function useOpenNewTab() { async (newTab, initialData = undefined, options) => { let existing = null; - const { savedFile } = newTab.props || {}; - if (savedFile) { + const { savedFile, savedFolder, savedFilePath } = newTab.props || {}; + if (savedFile || savedFilePath) { existing = openedTabs.find( x => - x.props && x.tabComponent == newTab.tabComponent && x.closedTime == null && x.props.savedFile == savedFile + x.props && + x.tabComponent == newTab.tabComponent && + x.closedTime == null && + x.props.savedFile == savedFile && + x.props.savedFolder == savedFolder && + x.props.savedFilePath == savedFilePath ); } diff --git a/packages/web/src/widgets/Toolbar.js b/packages/web/src/widgets/Toolbar.js index e5b835995..3873c1edc 100644 --- a/packages/web/src/widgets/Toolbar.js +++ b/packages/web/src/widgets/Toolbar.js @@ -25,6 +25,7 @@ import tabs from '../tabs'; import FavoriteModal from '../modals/FavoriteModal'; import { useOpenFavorite } from '../appobj/FavoriteFileAppObject'; import ErrorMessageModal from '../modals/ErrorMessageModal'; +import useOpenElectronFile from '../utility/useOpenElectronFile'; const ToolbarContainer = styled.div` display: flex; @@ -48,6 +49,7 @@ export default function ToolBar({ toolbarPortalRef }) { const electron = getElectron(); const favorites = useFavorites(); const openFavorite = useOpenFavorite(); + const openElectronFile = useOpenElectronFile(); const currentTab = openedTabs.find(x => x.selected); @@ -58,6 +60,7 @@ export default function ToolBar({ toolbarPortalRef }) { window['dbgate_newQuery'] = newQuery; window['dbgate_closeAll'] = () => setOpenedTabs([]); window['dbgate_showAbout'] = showAbout; + window['dbgate_openFile'] = openElectronFile; }); const showAbout = () => {