mirror of
https://github.com/Fabio286/antares.git
synced 2025-02-16 03:30:55 +01:00
feat: open,save, and save as file in query tab
This commit is contained in:
parent
f7204dc0ae
commit
c1e58eb695
@ -1,5 +1,6 @@
|
||||
import { app, dialog, ipcMain, safeStorage } from 'electron';
|
||||
import * as Store from 'electron-store';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { validateSender } from '../libs/misc/validateSender';
|
||||
import { ShortcutRegister } from '../libs/ShortcutRegister';
|
||||
@ -52,6 +53,11 @@ export default () => {
|
||||
return dialog.showOpenDialog(options);
|
||||
});
|
||||
|
||||
ipcMain.handle('show-save-dialog', (event, options) => {
|
||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||
return dialog.showSaveDialog(options);
|
||||
});
|
||||
|
||||
ipcMain.handle('get-download-dir-path', (event) => {
|
||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||
return app.getPath('downloads');
|
||||
@ -80,4 +86,26 @@ export default () => {
|
||||
const shortCutRegister = ShortcutRegister.getInstance();
|
||||
shortCutRegister.unregister();
|
||||
});
|
||||
|
||||
ipcMain.handle('read-file', (event, filePath) => {
|
||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
return content;
|
||||
}
|
||||
catch (error) {
|
||||
return { status: 'error', response: error.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('write-file', (event, filePath, content) => {
|
||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||
try {
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (error) {
|
||||
return { status: 'error', response: error.toString() };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -46,7 +46,7 @@
|
||||
:size="18"
|
||||
/>
|
||||
<span>
|
||||
<span>{{ cutText(element.content || 'Query', 20, true) }} #{{ element.index }}</span>
|
||||
<span>{{ cutText(element.elementName || element.content || 'Query', 20, true) }} #{{ element.index }}</span>
|
||||
<span
|
||||
class="btn btn-clear"
|
||||
:title="t('general.close')"
|
||||
|
@ -120,6 +120,30 @@
|
||||
<BaseIcon icon-name="mdiStarOutline" :size="24" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-dark btn-sm mr-0"
|
||||
:disabled="!filePath"
|
||||
:title="t('application.saveFile')"
|
||||
@click="saveFile()"
|
||||
>
|
||||
<BaseIcon icon-name="mdiContentSaveCheckOutline" :size="24" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm mr-0"
|
||||
:title="t('application.saveFileAs')"
|
||||
@click="saveFileAs()"
|
||||
>
|
||||
<BaseIcon icon-name="mdiContentSavePlusOutline" :size="24" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:title="t('application.openFile')"
|
||||
@click="openFile()"
|
||||
>
|
||||
<BaseIcon icon-name="mdiFolderOpenOutline" :size="24" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown table-dropdown pr-2">
|
||||
<button
|
||||
:disabled="!hasResults || isQuering"
|
||||
@ -262,6 +286,7 @@ import QueryEditor from '@/components/QueryEditor.vue';
|
||||
import WorkspaceTabQueryEmptyState from '@/components/WorkspaceTabQueryEmptyState.vue';
|
||||
import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable.vue';
|
||||
import { useResultTables } from '@/composables/useResultTables';
|
||||
import Application from '@/ipc-api/Application';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import { useConsoleStore } from '@/stores/console';
|
||||
@ -302,13 +327,16 @@ const {
|
||||
getWorkspace,
|
||||
changeBreadcrumbs,
|
||||
updateTabContent,
|
||||
setUnsavedChanges
|
||||
setUnsavedChanges,
|
||||
newTab
|
||||
} = workspacesStore;
|
||||
|
||||
const queryEditor: Ref<Component & { editor: Ace.Editor; $el: HTMLElement }> = ref(null);
|
||||
const queryAreaFooter: Ref<HTMLDivElement> = ref(null);
|
||||
const resizer: Ref<HTMLDivElement> = ref(null);
|
||||
const queryName = ref('');
|
||||
const query = ref('');
|
||||
const filePath = ref('');
|
||||
const lastQuery = ref('');
|
||||
const isCancelling = ref(false);
|
||||
const showCancel = ref(false);
|
||||
@ -339,11 +367,32 @@ watch(query, (val) => {
|
||||
|
||||
debounceTimeout.value = setTimeout(() => {
|
||||
updateTabContent({
|
||||
elementName: queryName.value,
|
||||
filePath: filePath.value,
|
||||
uid: props.connection.uid,
|
||||
tab: props.tab.uid,
|
||||
type: 'query',
|
||||
schema: selectedSchema.value,
|
||||
content: val
|
||||
|
||||
});
|
||||
|
||||
isQuerySaved.value = false;
|
||||
}, 200);
|
||||
});
|
||||
|
||||
watch(queryName, (val) => {
|
||||
clearTimeout(debounceTimeout.value);
|
||||
|
||||
debounceTimeout.value = setTimeout(() => {
|
||||
updateTabContent({
|
||||
elementName: val,
|
||||
filePath: filePath.value,
|
||||
uid: props.connection.uid,
|
||||
tab: props.tab.uid,
|
||||
type: 'query',
|
||||
schema: selectedSchema.value,
|
||||
content: query.value
|
||||
});
|
||||
|
||||
isQuerySaved.value = false;
|
||||
@ -529,7 +578,8 @@ const saveQuery = () => {
|
||||
type: 'query',
|
||||
date: new Date(),
|
||||
note: query.value,
|
||||
isArchived: false
|
||||
isArchived: false,
|
||||
title: queryName.value
|
||||
});
|
||||
isQuerySaved.value = true;
|
||||
};
|
||||
@ -596,6 +646,8 @@ const rollbackTab = async () => {
|
||||
defineExpose({ resizeResults });
|
||||
|
||||
query.value = props.tab.content as string;
|
||||
queryName.value = props.tab.elementName as string;
|
||||
filePath.value = props.tab.filePath as string;
|
||||
selectedSchema.value = props.tab.schema || breadcrumbsSchema.value;
|
||||
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
@ -630,6 +682,68 @@ const historyListener = () => {
|
||||
openHistoryModal();
|
||||
};
|
||||
|
||||
const openFileListener = () => {
|
||||
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
|
||||
if (props.isSelected && !hasModalOpen)
|
||||
openFile();
|
||||
};
|
||||
|
||||
const saveFileAsListener = () => {
|
||||
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
|
||||
if (props.isSelected && !hasModalOpen)
|
||||
saveFileAs();
|
||||
};
|
||||
|
||||
const saveContentListener = () => {
|
||||
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
|
||||
if (props.isSelected && !hasModalOpen && filePath)
|
||||
saveFile();
|
||||
};
|
||||
|
||||
const openFile = async () => {
|
||||
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql', 'txt'] }] });
|
||||
if (result && !result.canceled) {
|
||||
const file = result.filePaths[0];
|
||||
const content = await Application.readFile(file);
|
||||
const fileName = file.split('/').pop().split('\\').pop();
|
||||
if (props.tab.filePath && props.tab.filePath !== file) {
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
type: 'query',
|
||||
filePath: file,
|
||||
content: '',
|
||||
schema: selectedSchema.value,
|
||||
elementName: fileName
|
||||
});
|
||||
}
|
||||
else {
|
||||
filePath.value = file;
|
||||
queryName.value = fileName;
|
||||
query.value = content;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const saveFileAs = async () => {
|
||||
const result = await Application.showSaveDialog({ filters: [{ name: 'SQL', extensions: ['sql'] }], defaultPath: `${queryName.value || 'query'}.sql` });
|
||||
if (result && !result.canceled) {
|
||||
await Application.writeFile(result.filePath, query.value);
|
||||
addNotification({ status: 'success', message: t('general.actionSuccessful', { action: 'SAVE FILE' }) });
|
||||
queryName.value = result.filePath.split('/').pop().split('\\').pop();
|
||||
filePath.value = result.filePath;
|
||||
}
|
||||
};
|
||||
|
||||
const saveFile = async () => {
|
||||
await Application.writeFile(filePath.value, query.value);
|
||||
addNotification({ status: 'success', message: t('general.actionSuccessful', { action: 'SAVE FILE' }) });
|
||||
};
|
||||
|
||||
const loadFileContent = async (file: string) => {
|
||||
const content = await Application.readFile(file);
|
||||
query.value = content;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const localResizer = resizer.value;
|
||||
|
||||
@ -638,6 +752,9 @@ onMounted(() => {
|
||||
ipcRenderer.on('kill-query', killQueryListener);
|
||||
ipcRenderer.on('clear-query', clearQueryListener);
|
||||
ipcRenderer.on('query-history', historyListener);
|
||||
ipcRenderer.on('open-file', openFileListener);
|
||||
ipcRenderer.on('save-file-as', saveFileAsListener);
|
||||
ipcRenderer.on('save-content', saveContentListener);
|
||||
|
||||
localResizer.addEventListener('mousedown', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
@ -648,6 +765,9 @@ onMounted(() => {
|
||||
|
||||
if (props.tab.autorun)
|
||||
runQuery(query.value);
|
||||
|
||||
if (props.tab.filePath)
|
||||
loadFileContent(props.tab.filePath);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@ -663,6 +783,9 @@ onBeforeUnmount(() => {
|
||||
ipcRenderer.removeListener('kill-query', killQueryListener);
|
||||
ipcRenderer.removeListener('clear-query', clearQueryListener);
|
||||
ipcRenderer.removeListener('query-history', historyListener);
|
||||
ipcRenderer.removeListener('open-file', openFileListener);
|
||||
ipcRenderer.removeListener('save-file-as', saveFileAsListener);
|
||||
ipcRenderer.removeListener('save-content', saveContentListener);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -8,6 +8,10 @@ export default class {
|
||||
return ipcRenderer.invoke('show-open-dialog', unproxify(options));
|
||||
}
|
||||
|
||||
static showSaveDialog (options: OpenDialogOptions): Promise<OpenDialogReturnValue> {
|
||||
return ipcRenderer.invoke('show-save-dialog', unproxify(options));
|
||||
}
|
||||
|
||||
static getDownloadPathDirectory (): Promise<string> {
|
||||
return ipcRenderer.invoke('get-download-dir-path');
|
||||
}
|
||||
@ -27,4 +31,12 @@ export default class {
|
||||
static unregisterShortcuts () {
|
||||
return ipcRenderer.invoke('unregister-shortcuts');
|
||||
}
|
||||
|
||||
static readFile (path: string): Promise<string> {
|
||||
return ipcRenderer.invoke('read-file', path);
|
||||
}
|
||||
|
||||
static writeFile (path: string, content:any) {
|
||||
return ipcRenderer.invoke('write-file', path, content);
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ export interface WorkspaceTab {
|
||||
isChanged?: boolean;
|
||||
content?: string;
|
||||
autorun?: boolean;
|
||||
filePath?: string;
|
||||
}
|
||||
|
||||
export interface WorkspaceStructure {
|
||||
@ -492,7 +493,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
_addTab ({ uid, tab, content, type, autorun, schema, database, elementName, elementType }: WorkspaceTab) {
|
||||
_addTab ({ uid, tab, content, type, autorun, schema, database, elementName, elementType, filePath }: WorkspaceTab) {
|
||||
if (type === 'query')
|
||||
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
|
||||
|
||||
@ -506,7 +507,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
elementName,
|
||||
elementType,
|
||||
content: content || '',
|
||||
autorun: !!autorun
|
||||
autorun: !!autorun,
|
||||
filePath: filePath || ''
|
||||
};
|
||||
|
||||
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
|
||||
@ -522,14 +524,14 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
|
||||
persistentStore.set(uid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid).tabs);
|
||||
},
|
||||
_replaceTab ({ uid, tab: tUid, type, schema, content, elementName, elementType }: WorkspaceTab) {
|
||||
_replaceTab ({ uid, tab: tUid, type, schema, content, elementName, elementType, filePath }: WorkspaceTab) {
|
||||
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
|
||||
if (workspace.uid === uid) {
|
||||
return {
|
||||
...workspace,
|
||||
tabs: workspace.tabs.map(tab => {
|
||||
if (tab.uid === tUid)
|
||||
return { ...tab, type, schema, content, elementName, elementType };
|
||||
return { ...tab, type, schema, content, elementName, elementType, filePath };
|
||||
|
||||
return tab;
|
||||
})
|
||||
@ -541,7 +543,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
|
||||
persistentStore.set(uid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid).tabs);
|
||||
},
|
||||
newTab ({ uid, content, type, autorun, schema, elementName, elementType }: WorkspaceTab) {
|
||||
newTab ({ uid, content, type, autorun, schema, elementName, elementType, filePath }: WorkspaceTab) {
|
||||
let tabUid;
|
||||
const workspaceTabs = (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid);
|
||||
|
||||
@ -562,7 +564,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
database: workspaceTabs.database,
|
||||
schema,
|
||||
elementName,
|
||||
elementType
|
||||
elementType,
|
||||
filePath
|
||||
});
|
||||
break;
|
||||
case 'temp-data':
|
||||
@ -594,21 +597,22 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
type: tab.type.replace('temp-', ''),
|
||||
schema: tab.schema,
|
||||
elementName: tab.elementName,
|
||||
elementType: tab.elementType
|
||||
elementType: tab.elementType,
|
||||
filePath: tab.filePath
|
||||
});
|
||||
|
||||
tabUid = uidGen('T');
|
||||
this._addTab({ uid, tab: tabUid, content, type, autorun, database: workspaceTabs.database, schema, elementName, elementType });
|
||||
this._addTab({ uid, tab: tabUid, content, type, autorun, database: workspaceTabs.database, schema, elementName, elementType, filePath });
|
||||
}
|
||||
else {
|
||||
this._replaceTab({ uid, tab: tab.uid, type, schema, elementName, elementType });
|
||||
this._replaceTab({ uid, tab: tab.uid, type, schema, elementName, elementType, filePath });
|
||||
tabUid = tab.uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
tabUid = uidGen('T');
|
||||
this._addTab({ uid, tab: tabUid, content, type, autorun, database: workspaceTabs.database, schema, elementName, elementType });
|
||||
this._addTab({ uid, tab: tabUid, content, type, autorun, database: workspaceTabs.database, schema, elementName, elementType, filePath });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -635,7 +639,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
database: workspaceTabs.database,
|
||||
schema,
|
||||
elementName,
|
||||
elementType
|
||||
elementType,
|
||||
filePath
|
||||
});
|
||||
tabUid = existentTab.uid;
|
||||
}
|
||||
@ -649,7 +654,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
database: workspaceTabs.database,
|
||||
schema,
|
||||
elementName,
|
||||
elementType
|
||||
elementType,
|
||||
filePath
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -664,7 +670,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
database: workspaceTabs.database,
|
||||
schema,
|
||||
elementName,
|
||||
elementType
|
||||
elementType,
|
||||
filePath
|
||||
});
|
||||
break;
|
||||
}
|
||||
@ -687,8 +694,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
||||
this.selectTab({ uid, tab: workspace.tabs[workspace.tabs.length - 1].uid });
|
||||
}
|
||||
},
|
||||
updateTabContent ({ uid, tab, type, schema, content }: WorkspaceTab) {
|
||||
this._replaceTab({ uid, tab, type, schema, content });
|
||||
updateTabContent ({ uid, tab, type, schema, content, elementName, filePath }: WorkspaceTab) {
|
||||
this._replaceTab({ uid, tab, type, schema, content, elementName, filePath });
|
||||
},
|
||||
renameTabs ({ uid, schema, elementName, elementNewName }: WorkspaceTab) {
|
||||
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user