mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-08-17 16:34:44 -05:00
Added support to automatically open a file after it is downloaded in the desktop mode. #4194
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
@@ -273,6 +273,24 @@ The Miscellaneous Node
|
||||
|
||||
Expand the *Miscellaneous* node to specify miscellaneous display preferences.
|
||||
|
||||
.. image:: images/preferences_misc_file_downloads.png
|
||||
:alt: Preferences dialog file downloads section
|
||||
:align: center
|
||||
|
||||
Use the fields on the *File Downloads* panel to manage file downloads related preferences.
|
||||
|
||||
* When the *Automatically open downloaded files?* switch is set to *True*
|
||||
the downloaded file will automatically open in the system's default
|
||||
application associated with that file type.
|
||||
|
||||
* When the *Prompt for the download location?* switch is set to *True*
|
||||
a prompt will appear after clicking the download button, allowing you
|
||||
to choose the download location.
|
||||
|
||||
**Note:** File Downloads related settings are applicable and visible only in desktop mode.
|
||||
|
||||
Use the fields on the *User Interface* panel to set the user interface related preferences.
|
||||
|
||||
.. image:: images/preferences_misc_user_interface.png
|
||||
:alt: Preferences dialog user interface section
|
||||
:align: center
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.8.1",
|
||||
"electron-context-menu": "^4.0.5",
|
||||
"electron-dl": "^4.0.0",
|
||||
"electron-store": "^10.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { spawn } from 'child_process';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { setupMenu } from './menu.js';
|
||||
import contextMenu from 'electron-context-menu';
|
||||
import { CancelError, download } from 'electron-dl';
|
||||
|
||||
const configStore = new Store({
|
||||
defaults: {
|
||||
@@ -126,6 +127,28 @@ function reloadApp() {
|
||||
currWin.webContents.reload();
|
||||
}
|
||||
|
||||
async function desktopFileDownload(payload) {
|
||||
const currWin = BrowserWindow.getFocusedWindow();
|
||||
try {
|
||||
await download(currWin, payload.downloadUrl, {
|
||||
filename: payload.fileName,
|
||||
saveAs: payload.prompt_for_download_location,
|
||||
onProgress: (progress) => {
|
||||
currWin.webContents.send('download-progress', progress);
|
||||
},
|
||||
onCompleted: (item) => {
|
||||
currWin.webContents.send('download-complete', item);
|
||||
if (payload.automatically_open_downloaded_file)
|
||||
shell.openPath(item.path);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (!(error instanceof CancelError)) {
|
||||
misc.writeServerLog(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This functions is used to start the pgAdmin4 server by spawning a
|
||||
// separate process.
|
||||
function startDesktopMode() {
|
||||
@@ -369,6 +392,7 @@ ipcMain.on('log', (text) => ()=>{
|
||||
misc.writeServerLog(text);
|
||||
});
|
||||
ipcMain.on('reloadApp', reloadApp);
|
||||
ipcMain.on('onFileDownload', (_, payload) => desktopFileDownload(payload));
|
||||
ipcMain.handle('checkPortAvailable', async (_e, fixedPort)=>{
|
||||
try {
|
||||
await misc.getAvailablePort(fixedPort);
|
||||
|
||||
@@ -24,4 +24,5 @@ contextBridge.exposeInMainWorld('electronUI', {
|
||||
showSaveDialog: (options) => ipcRenderer.invoke('showSaveDialog', options),
|
||||
log: (text)=> ipcRenderer.send('log', text),
|
||||
reloadApp: ()=>{ipcRenderer.send('reloadApp');},
|
||||
onFileDownload: (payload) => ipcRenderer.send('onFileDownload', payload),
|
||||
});
|
||||
@@ -1550,6 +1550,7 @@ __metadata:
|
||||
axios: ^1.8.1
|
||||
electron: 34.3.0
|
||||
electron-context-menu: ^4.0.5
|
||||
electron-dl: ^4.0.0
|
||||
electron-store: ^10.0.0
|
||||
eslint: ^9.21.0
|
||||
languageName: unknown
|
||||
|
||||
@@ -40,7 +40,7 @@ import Replication from './Replication';
|
||||
import { getExpandCell } from '../../../static/js/components/PgReactTableStyled';
|
||||
import CodeMirror from '../../../static/js/components/ReactCodeMirror';
|
||||
import GetAppRoundedIcon from '@mui/icons-material/GetAppRounded';
|
||||
import { getBrowser } from '../../../static/js/utils';
|
||||
import { downloadFile } from '../../../static/js/utils';
|
||||
import RefreshButton from './components/RefreshButtons';
|
||||
|
||||
function parseData(data) {
|
||||
@@ -453,22 +453,7 @@ function Dashboard({
|
||||
let fileName = 'data-' + new Date().getTime() + extension;
|
||||
|
||||
try {
|
||||
let respBlob = new Blob([respData], {type : 'text/'+type}),
|
||||
urlCreator = window.URL || window.webkitURL,
|
||||
download_url = urlCreator.createObjectURL(respBlob),
|
||||
link = document.createElement('a');
|
||||
|
||||
document.body.appendChild(link);
|
||||
|
||||
if (getBrowser() == 'IE' && window.navigator.msSaveBlob) {
|
||||
// IE10: (has Blob, but not a[download] or URL)
|
||||
window.navigator.msSaveBlob(respBlob, fileName);
|
||||
} else {
|
||||
link.setAttribute('href', download_url);
|
||||
link.setAttribute('download', fileName);
|
||||
link.click();
|
||||
}
|
||||
document.body.removeChild(link);
|
||||
downloadFile(respData, fileName, `text/${type}`);
|
||||
} catch {
|
||||
setSsMsg(gettext('Failed to download the logs.'));
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ from flask_babel import gettext
|
||||
from pgadmin.user_login_check import pga_login_required
|
||||
from pathlib import Path
|
||||
from pgadmin.utils import PgAdminModule, get_binary_path_versions
|
||||
from pgadmin.utils.constants import PREF_LABEL_USER_INTERFACE
|
||||
from pgadmin.utils.constants import PREF_LABEL_USER_INTERFACE, \
|
||||
PREF_LABEL_FILE_DOWNLOADS
|
||||
from pgadmin.utils.csrf import pgCSRFProtect
|
||||
from pgadmin.utils.session import cleanup_session_files
|
||||
from pgadmin.misc.themes import get_all_themes
|
||||
@@ -120,6 +121,32 @@ class MiscModule(PgAdminModule):
|
||||
)
|
||||
)
|
||||
|
||||
if not config.SERVER_MODE:
|
||||
self.preference.register(
|
||||
'file_downloads', 'automatically_open_downloaded_file',
|
||||
gettext("Automatically open downloaded file?"),
|
||||
'boolean', False,
|
||||
category_label=PREF_LABEL_FILE_DOWNLOADS,
|
||||
help_str=gettext(
|
||||
'''This setting is applicable and visible only in
|
||||
desktop mode. When set to True, the downloaded file
|
||||
will automatically open in the system's default
|
||||
application associated with that file type.'''
|
||||
)
|
||||
)
|
||||
self.preference.register(
|
||||
'file_downloads', 'prompt_for_download_location',
|
||||
gettext("Prompt for the download location?"),
|
||||
'boolean', True,
|
||||
category_label=PREF_LABEL_FILE_DOWNLOADS,
|
||||
help_str=gettext(
|
||||
'This setting is applicable and visible only '
|
||||
'in desktop mode. When set to True, a prompt '
|
||||
'will appear after clicking the download button, '
|
||||
'allowing you to choose the download location'
|
||||
)
|
||||
)
|
||||
|
||||
def get_exposed_url_endpoints(self):
|
||||
"""
|
||||
Returns:
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import getApiInstance from '../api_instance';
|
||||
import { downloadFile } from '../utils';
|
||||
|
||||
function convertImageURLtoDataURI(api, image) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
@@ -42,13 +43,6 @@ export function downloadSvg(svg, svgName) {
|
||||
}
|
||||
|
||||
Promise.all(image_promises).then(function() {
|
||||
let blob = new Blob([svgElement.outerHTML], {type: 'image/svg+xml'});
|
||||
let svgURL = (window.URL || window.webkitURL).createObjectURL(blob);
|
||||
let newElement = document.createElement('a');
|
||||
newElement.href = svgURL;
|
||||
newElement.setAttribute('download', svgName);
|
||||
document.body.appendChild(newElement);
|
||||
newElement.click();
|
||||
document.body.removeChild(newElement);
|
||||
downloadFile(svgElement.outerHTML, svgName, 'image/svg+xml');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -369,14 +369,16 @@ export function checkTrojanSource(content, isPasteEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
export function downloadBlob(blob, fileName) {
|
||||
if (getBrowser() == 'IE' && window.navigator.msSaveBlob) {
|
||||
export async function downloadBlob(blob, fileName) {
|
||||
const {automatically_open_downloaded_file, prompt_for_download_location} = usePreferences.getState().getPreferencesForModule('misc');
|
||||
const urlCreator = window.URL || window.webkitURL;
|
||||
const downloadUrl = urlCreator.createObjectURL(blob);
|
||||
if (getBrowser().name == 'IE' && window.navigator.msSaveBlob) {
|
||||
// IE10+ : (has Blob, but not a[download] or URL)
|
||||
window.navigator.msSaveBlob(blob, fileName);
|
||||
} else if (getBrowser().name == 'Electron') {
|
||||
await window.electronUI.onFileDownload({downloadUrl, fileName, automatically_open_downloaded_file, prompt_for_download_location});
|
||||
} else {
|
||||
const urlCreator = window.URL || window.webkitURL;
|
||||
const downloadUrl = urlCreator.createObjectURL(blob);
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('href', downloadUrl);
|
||||
link.setAttribute('download', fileName);
|
||||
@@ -388,6 +390,19 @@ export function downloadBlob(blob, fileName) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadUrlData(downloadUrl, fileName) {
|
||||
const {automatically_open_downloaded_file, prompt_for_download_location} = usePreferences.getState().getPreferencesForModule('misc');
|
||||
if (getBrowser().name == 'Electron') {
|
||||
window.electronUI.onFileDownload({downloadUrl, fileName, automatically_open_downloaded_file, prompt_for_download_location});
|
||||
} else {
|
||||
let link = document.createElement('a');
|
||||
link.setAttribute('href', downloadUrl);
|
||||
link.setAttribute('download', fileName);
|
||||
link.click();
|
||||
link.remove();
|
||||
}
|
||||
}
|
||||
|
||||
export function downloadFile(textData, fileName, fileType) {
|
||||
const respBlob = new Blob([textData], {type : fileType});
|
||||
downloadBlob(respBlob, fileName);
|
||||
|
||||
@@ -37,6 +37,7 @@ import pgAdmin from 'sources/pgadmin';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import BeforeUnload from './BeforeUnload';
|
||||
import { isMac } from '../../../../../../static/js/keyboard_shortcuts';
|
||||
import { downloadUrlData } from '../../../../../../static/js/utils';
|
||||
|
||||
/* Custom react-diagram action for keyboard events */
|
||||
export class KeyboardShortcutAction extends Action {
|
||||
@@ -760,11 +761,7 @@ export default class ERDTool extends React.Component {
|
||||
}
|
||||
toPng(this.canvasEle, {width, height})
|
||||
.then((dataUrl)=>{
|
||||
let link = document.createElement('a');
|
||||
link.setAttribute('href', dataUrl);
|
||||
link.setAttribute('download', this.getCurrentProjectName() + '.png');
|
||||
link.click();
|
||||
link.remove();
|
||||
downloadUrlData(dataUrl, `${this.getCurrentProjectName()}.png`);
|
||||
}).catch((err)=>{
|
||||
console.error(err);
|
||||
let msg = gettext('Unknown error. Check console logs');
|
||||
|
||||
@@ -26,7 +26,7 @@ import { LineChart, BarChart, PieChart, DATA_POINT_STYLE, DATA_POINT_SIZE,
|
||||
LightenDarkenColor} from 'sources/chartjs';
|
||||
import { QueryToolEventsContext, QueryToolContext } from '../QueryToolComponent';
|
||||
import { QUERY_TOOL_EVENTS, PANELS } from '../QueryToolConstants';
|
||||
import { getChartColor } from '../../../../../../static/js/utils';
|
||||
import { downloadUrlData, getChartColor } from '../../../../../../static/js/utils';
|
||||
|
||||
const StyledBox = styled(Box)(({theme}) => ({
|
||||
width: '100%',
|
||||
@@ -377,11 +377,10 @@ export function GraphVisualiser({initColumns}) {
|
||||
};
|
||||
|
||||
// Download button callback
|
||||
const onDownloadGraph = ()=> {
|
||||
let a = document.createElement('a');
|
||||
a.href = chartObjRef.current.toBase64Image();
|
||||
a.download = 'graph_visualiser-' + new Date().getTime() + '.png';
|
||||
a.click();
|
||||
const onDownloadGraph = async ()=> {
|
||||
let downloadUrl = chartObjRef.current.toBase64Image(),
|
||||
fileName = 'graph_visualiser-' + new Date().getTime() + '.png';
|
||||
downloadUrlData(downloadUrl, fileName);
|
||||
};
|
||||
|
||||
// This plugin is used to set the background color of the canvas. Very useful
|
||||
|
||||
@@ -22,7 +22,7 @@ import { LayoutDockerContext } from '../../../../../../static/js/helpers/Layout'
|
||||
import { GeometryViewer } from './GeometryViewer';
|
||||
import Explain from '../../../../../../static/js/Explain';
|
||||
import { QuerySources } from './QueryHistory';
|
||||
import { getBrowser } from '../../../../../../static/js/utils';
|
||||
import { downloadFile } from '../../../../../../static/js/utils';
|
||||
import CopyData from '../QueryToolDataGrid/CopyData';
|
||||
import moment from 'moment';
|
||||
import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveContent';
|
||||
@@ -478,22 +478,7 @@ export class ResultSetUtils {
|
||||
}
|
||||
} else {
|
||||
this.hasQueryCommitted = false;
|
||||
let respBlob = new Blob([respData], {type : 'text/csv'}),
|
||||
urlCreator = window.URL || window.webkitURL,
|
||||
download_url = urlCreator.createObjectURL(respBlob),
|
||||
link = document.createElement('a');
|
||||
|
||||
document.body.appendChild(link);
|
||||
|
||||
if (getBrowser() == 'IE' && window.navigator.msSaveBlob) {
|
||||
// IE10+ : (has Blob, but not a[download] or URL)
|
||||
window.navigator.msSaveBlob(respBlob, fileName);
|
||||
} else {
|
||||
link.setAttribute('href', download_url);
|
||||
link.setAttribute('download', fileName);
|
||||
link.click();
|
||||
}
|
||||
document.body.removeChild(link);
|
||||
downloadFile(respData, fileName, 'text/csv');
|
||||
}
|
||||
this.eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_SAVE_RESULTS_END);
|
||||
} catch (error) {
|
||||
|
||||
@@ -29,6 +29,7 @@ PREF_LABEL_TABS_SETTINGS = gettext('Tab settings')
|
||||
PREF_LABEL_REFRESH_RATES = gettext('Refresh rates')
|
||||
PREF_LABEL_GRAPH_VISUALISER = gettext('Graph Visualiser')
|
||||
PREF_LABEL_USER_INTERFACE = gettext('User Interface')
|
||||
PREF_LABEL_FILE_DOWNLOADS = gettext('File Downloads')
|
||||
|
||||
PGADMIN_STRING_SEPARATOR = '_$PGADMIN$_'
|
||||
PGADMIN_NODE = 'pgadmin.node.%s'
|
||||
|
||||
Reference in New Issue
Block a user