Skip to content

Commit 7af0bc5

Browse files
Added support for automatically opening a file after it is downloaded in desktop mode. #4194
1 parent 4e52a86 commit 7af0bc5

File tree

13 files changed

+88
-61
lines changed

13 files changed

+88
-61
lines changed

runtime/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"type": "module",
88
"main": "src/js/pgadmin.js",
99
"scripts": {
10-
"start": "electron .",
10+
"start": "electron --trace-warnings .",
1111
"linter": "yarn run eslint -c .eslintrc.js ."
1212
},
1313
"packageManager": "[email protected]",
@@ -18,6 +18,7 @@
1818
"dependencies": {
1919
"axios": "^1.8.1",
2020
"electron-context-menu": "^4.0.5",
21+
"electron-dl": "^4.0.0",
2122
"electron-store": "^10.0.0"
2223
}
2324
}

runtime/src/js/pgadmin.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { spawn } from 'child_process';
1616
import { fileURLToPath } from 'url';
1717
import { setupMenu } from './menu.js';
1818
import contextMenu from 'electron-context-menu';
19+
import { CancelError, download } from 'electron-dl';
1920

2021
const configStore = new Store({
2122
defaults: {
@@ -126,6 +127,28 @@ function reloadApp() {
126127
currWin.webContents.reload();
127128
}
128129

130+
async function downloadFile(payload) {
131+
const currWin = BrowserWindow.getFocusedWindow();
132+
try {
133+
await download(currWin, payload.downloadUrl, {
134+
filename: payload.fileName,
135+
saveAs: payload.show_save_prompt,
136+
onProgress: (progress) => {
137+
currWin.webContents.send('download-progress', progress);
138+
},
139+
onCompleted: (item) => {
140+
currWin.webContents.send('download-complete', item);
141+
if (payload.open_file_after_download)
142+
shell.openPath(item.path);
143+
},
144+
});
145+
} catch (error) {
146+
if (!(error instanceof CancelError)) {
147+
misc.writeServerLog(error);
148+
}
149+
}
150+
}
151+
129152
// This functions is used to start the pgAdmin4 server by spawning a
130153
// separate process.
131154
function startDesktopMode() {
@@ -369,6 +392,7 @@ ipcMain.on('log', (text) => ()=>{
369392
misc.writeServerLog(text);
370393
});
371394
ipcMain.on('reloadApp', reloadApp);
395+
ipcMain.on('downloadFile', (_, payload) => downloadFile(payload));
372396
ipcMain.handle('checkPortAvailable', async (_e, fixedPort)=>{
373397
try {
374398
await misc.getAvailablePort(fixedPort);

runtime/src/js/pgadmin_preload.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ contextBridge.exposeInMainWorld('electronUI', {
2424
showSaveDialog: (options) => ipcRenderer.invoke('showSaveDialog', options),
2525
log: (text)=> ipcRenderer.send('log', text),
2626
reloadApp: ()=>{ipcRenderer.send('reloadApp');},
27+
downloadFile: (payload) => ipcRenderer.send('downloadFile', payload),
2728
});

runtime/yarn.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,6 +1550,7 @@ __metadata:
15501550
axios: ^1.8.1
15511551
electron: 34.3.0
15521552
electron-context-menu: ^4.0.5
1553+
electron-dl: ^4.0.0
15531554
electron-store: ^10.0.0
15541555
eslint: ^9.21.0
15551556
languageName: unknown

web/pgadmin/browser/server_groups/servers/databases/schemas/tables/schema_diff_table_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@ def table_constraint_comp(source_table, target_table):
219219
'unique_constraint': ['col_count',
220220
'condeferrable',
221221
'condeffered',
222-
'columns', 'indnullsnotdistinct'],
222+
'columns',
223+
'indnullsnotdistinct'],
223224
'check_constraint': ['consrc'],
224225
'exclude_constraint': ['amname',
225226
'indconstraint',

web/pgadmin/dashboard/static/js/Dashboard.jsx

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import Replication from './Replication';
4040
import { getExpandCell } from '../../../static/js/components/PgReactTableStyled';
4141
import CodeMirror from '../../../static/js/components/ReactCodeMirror';
4242
import GetAppRoundedIcon from '@mui/icons-material/GetAppRounded';
43-
import { getBrowser } from '../../../static/js/utils';
43+
import { downloadFile } from '../../../static/js/utils';
4444
import RefreshButton from './components/RefreshButtons';
4545

4646
function parseData(data) {
@@ -453,22 +453,7 @@ function Dashboard({
453453
let fileName = 'data-' + new Date().getTime() + extension;
454454

455455
try {
456-
let respBlob = new Blob([respData], {type : 'text/'+type}),
457-
urlCreator = window.URL || window.webkitURL,
458-
download_url = urlCreator.createObjectURL(respBlob),
459-
link = document.createElement('a');
460-
461-
document.body.appendChild(link);
462-
463-
if (getBrowser() == 'IE' && window.navigator.msSaveBlob) {
464-
// IE10: (has Blob, but not a[download] or URL)
465-
window.navigator.msSaveBlob(respBlob, fileName);
466-
} else {
467-
link.setAttribute('href', download_url);
468-
link.setAttribute('download', fileName);
469-
link.click();
470-
}
471-
document.body.removeChild(link);
456+
downloadFile(respData, fileName, `text/${type}`);
472457
} catch {
473458
setSsMsg(gettext('Failed to download the logs.'));
474459
}

web/pgadmin/misc/__init__.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
from pgadmin.user_login_check import pga_login_required
1616
from pathlib import Path
1717
from pgadmin.utils import PgAdminModule, get_binary_path_versions
18-
from pgadmin.utils.constants import PREF_LABEL_USER_INTERFACE
18+
from pgadmin.utils.constants import PREF_LABEL_USER_INTERFACE, \
19+
PREF_LABEL_DOWNLOADS
1920
from pgadmin.utils.csrf import pgCSRFProtect
2021
from pgadmin.utils.session import cleanup_session_files
2122
from pgadmin.misc.themes import get_all_themes
@@ -119,6 +120,28 @@ def register_preferences(self):
119120
'default or the workspace selected at the time of opening)'
120121
)
121122
)
123+
self.preference.register(
124+
'file_downloads', 'open_file_after_download',
125+
gettext("Automatically open downloaded files?"),
126+
'boolean', False,
127+
category_label=PREF_LABEL_DOWNLOADS,
128+
help_str=gettext(
129+
'''This setting applies only in desktop mode.
130+
When set to True, the downloaded file will open
131+
in the system's default application for that file type.'''
132+
)
133+
)
134+
self.preference.register(
135+
'file_downloads', 'prompt_for_download_location',
136+
gettext("Prompt for the download location?"),
137+
'boolean', True,
138+
category_label=PREF_LABEL_DOWNLOADS,
139+
help_str=gettext(
140+
'This setting applies only in desktop mode. '
141+
'When set to True, a save file prompt will '
142+
'be shown after downloading a file.'
143+
)
144+
)
122145

123146
def get_exposed_url_endpoints(self):
124147
"""

web/pgadmin/static/js/Explain/svg_download.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88
//////////////////////////////////////////////////////////////
99
import getApiInstance from '../api_instance';
10+
import { downloadFile } from '../utils';
1011

1112
function convertImageURLtoDataURI(api, image) {
1213
return new Promise(function(resolve, reject) {
@@ -42,13 +43,6 @@ export function downloadSvg(svg, svgName) {
4243
}
4344

4445
Promise.all(image_promises).then(function() {
45-
let blob = new Blob([svgElement.outerHTML], {type: 'image/svg+xml'});
46-
let svgURL = (window.URL || window.webkitURL).createObjectURL(blob);
47-
let newElement = document.createElement('a');
48-
newElement.href = svgURL;
49-
newElement.setAttribute('download', svgName);
50-
document.body.appendChild(newElement);
51-
newElement.click();
52-
document.body.removeChild(newElement);
46+
downloadFile(svgElement.outerHTML, svgName, 'image/svg+xml');
5347
});
5448
}

web/pgadmin/static/js/utils.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,14 +369,16 @@ export function checkTrojanSource(content, isPasteEvent) {
369369
}
370370
}
371371

372-
export function downloadBlob(blob, fileName) {
373-
if (getBrowser() == 'IE' && window.navigator.msSaveBlob) {
372+
export async function downloadBlob(blob, fileName) {
373+
const {open_file_after_download, show_save_prompt} = usePreferences.getState().getPreferencesForModule('misc');
374+
const urlCreator = window.URL || window.webkitURL;
375+
const downloadUrl = urlCreator.createObjectURL(blob);
376+
if (getBrowser().name == 'IE' && window.navigator.msSaveBlob) {
374377
// IE10+ : (has Blob, but not a[download] or URL)
375378
window.navigator.msSaveBlob(blob, fileName);
379+
} else if (getBrowser().name == 'Electron') {
380+
await window.electronUI.downloadFile({downloadUrl, fileName, open_file_after_download, show_save_prompt});
376381
} else {
377-
const urlCreator = window.URL || window.webkitURL;
378-
const downloadUrl = urlCreator.createObjectURL(blob);
379-
380382
const link = document.createElement('a');
381383
link.setAttribute('href', downloadUrl);
382384
link.setAttribute('download', fileName);
@@ -388,6 +390,19 @@ export function downloadBlob(blob, fileName) {
388390
}
389391
}
390392

393+
export async function downloadUrlData(downloadUrl, fileName) {
394+
const {open_file_after_download, show_save_prompt} = usePreferences.getState().getPreferencesForModule('misc');
395+
if (getBrowser().name == 'Electron') {
396+
window.electronUI.downloadFile({downloadUrl, fileName, open_file_after_download, show_save_prompt});
397+
} else {
398+
let link = document.createElement('a');
399+
link.setAttribute('href', downloadUrl);
400+
link.setAttribute('download', fileName);
401+
link.click();
402+
link.remove();
403+
}
404+
}
405+
391406
export function downloadFile(textData, fileName, fileType) {
392407
const respBlob = new Blob([textData], {type : fileType});
393408
downloadBlob(respBlob, fileName);

web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import pgAdmin from 'sources/pgadmin';
3737
import { styled } from '@mui/material/styles';
3838
import BeforeUnload from './BeforeUnload';
3939
import { isMac } from '../../../../../../static/js/keyboard_shortcuts';
40+
import { downloadUrlData } from '../../../../../../static/js/utils';
4041

4142
/* Custom react-diagram action for keyboard events */
4243
export class KeyboardShortcutAction extends Action {
@@ -760,11 +761,7 @@ export default class ERDTool extends React.Component {
760761
}
761762
toPng(this.canvasEle, {width, height})
762763
.then((dataUrl)=>{
763-
let link = document.createElement('a');
764-
link.setAttribute('href', dataUrl);
765-
link.setAttribute('download', this.getCurrentProjectName() + '.png');
766-
link.click();
767-
link.remove();
764+
downloadUrlData(dataUrl, `${this.getCurrentProjectName()}.png`);
768765
}).catch((err)=>{
769766
console.error(err);
770767
let msg = gettext('Unknown error. Check console logs');

0 commit comments

Comments
 (0)