Skip to content

Commit

Permalink
Merge pull request #785 from bagusindrayana/feat-open-edit-save-file
Browse files Browse the repository at this point in the history
Feat open, edit, and save file in query tab
  • Loading branch information
Fabio286 authored Apr 8, 2024
2 parents 1875e89 + e7efb9c commit 099a71a
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 21 deletions.
18 changes: 18 additions & 0 deletions src/common/shortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export const shortcutEvents: Record<string, { l18n: string; l18nParam?: string |
'kill-query': { l18n: 'database.killQuery', context: 'tab' },
'query-history': { l18n: 'database.queryHistory', context: 'tab' },
'clear-query': { l18n: 'database.clearQuery', context: 'tab' },
// 'save-file': { l18n: 'application.saveFile', context: 'tab' },
'open-file': { l18n: 'application.openFile', context: 'tab' },
'save-file-as': { l18n: 'application.saveFileAs', context: 'tab' },
'next-tab': { l18n: 'application.nextTab' },
'prev-tab': { l18n: 'application.previousTab' },
'open-all-connections': { l18n: 'application.openAllConnections' },
Expand Down Expand Up @@ -119,6 +122,21 @@ const shortcuts: ShortcutRecord[] = [
event: 'toggle-console',
keys: ['CommandOrControl+`'],
os: ['darwin', 'linux', 'win32']
},
// {
// event: 'save-file',
// keys: ['CommandOrControl+S'],
// os: ['darwin', 'linux', 'win32']
// },
{
event: 'open-file',
keys: ['CommandOrControl+O'],
os: ['darwin', 'linux', 'win32']
},
{
event: 'save-file-as',
keys: ['Shift+CommandOrControl+S'],
os: ['darwin', 'linux', 'win32']
}
];

Expand Down
28 changes: 28 additions & 0 deletions src/main/ipc-handlers/application.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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() };
}
});
};
2 changes: 1 addition & 1 deletion src/renderer/components/ScratchpadNote.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<div class="tile-icon">
<BaseIcon
:icon-name="note.type === 'query'
? 'mdiStarOutline'
? 'mdiHeartOutline'
: note.type === 'todo'
? note.isArchived
? 'mdiCheckboxMarkedOutline'
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/components/Workspace.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@
>
<BaseIcon
class="mt-1 mr-1"
icon-name="mdiCodeTags"
:icon-name="element.filePath ? 'mdiFileCodeOutline' : 'mdiCodeTags'"
: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')"
Expand Down
127 changes: 125 additions & 2 deletions src/renderer/components/WorkspaceTabQuery.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -529,7 +578,8 @@ const saveQuery = () => {
type: 'query',
date: new Date(),
note: query.value,
isArchived: false
isArchived: false,
title: queryName.value
});
isQuerySaved.value = true;
};
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -648,6 +765,9 @@ onMounted(() => {
if (props.tab.autorun)
runQuery(query.value);
if (props.tab.filePath)
loadFileContent(props.tab.filePath);
});
onBeforeUnmount(() => {
Expand All @@ -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>

Expand Down
6 changes: 5 additions & 1 deletion src/renderer/i18n/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,11 @@ export const enUS = {
editNote: 'Edit note',
showArchivedNotes: 'Show archived notes',
hideArchivedNotes: 'Hide archived notes',
tag: 'Tag' // Note tag
tag: 'Tag', // Note tag,
saveFile: 'Save file',
saveFileAs: 'Save file as',
openFile: 'Open file'

},
faker: { // Faker.js methods, used in random generated content
address: 'Address',
Expand Down
12 changes: 12 additions & 0 deletions src/renderer/ipc-api/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand All @@ -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);
}
}
Loading

0 comments on commit 099a71a

Please sign in to comment.