import { app, BaseWindow } from 'electron/main'; import type { OpenDialogOptions, OpenDialogReturnValue, MessageBoxOptions, SaveDialogOptions, SaveDialogReturnValue, MessageBoxReturnValue, CertificateTrustDialogOptions } from 'electron/main'; const dialogBinding = process._linkedBinding('electron_browser_dialog'); enum SaveFileDialogProperties { createDirectory = 1 << 0, showHiddenFiles = 1 << 1, treatPackageAsDirectory = 1 << 2, showOverwriteConfirmation = 1 << 3, dontAddToRecent = 1 << 4 } enum OpenFileDialogProperties { openFile = 1 << 0, openDirectory = 1 << 1, multiSelections = 1 << 2, createDirectory = 1 << 3, // macOS showHiddenFiles = 1 << 4, promptToCreate = 1 << 5, // Windows noResolveAliases = 1 << 6, // macOS treatPackageAsDirectory = 1 << 7, // macOS dontAddToRecent = 1 << 8 // Windows } let nextId = 0; const getNextId = function () { return ++nextId; }; const normalizeAccessKey = (text: string) => { if (typeof text !== 'string') return text; // macOS does not have access keys so remove single ampersands // and replace double ampersands with a single ampersand if (process.platform === 'darwin') { return text.replaceAll(/&(&?)/g, '$1'); } // Linux uses a single underscore as an access key prefix so escape // existing single underscores with a second underscore, replace double // ampersands with a single ampersand, and replace a single ampersand with // a single underscore if (process.platform === 'linux') { return text.replaceAll('_', '__').replaceAll(/&(.?)/g, (match, after) => { if (after === '&') return after; return `_${after}`; }); } return text; }; const checkAppInitialized = function () { if (!app.isReady()) { throw new Error('dialog module can only be used after app is ready'); } }; const setupOpenDialogProperties = (properties: (keyof typeof OpenFileDialogProperties)[]): number => { let dialogProperties = 0; for (const property of properties) { if (Object.hasOwn(OpenFileDialogProperties, property)) { dialogProperties |= OpenFileDialogProperties[property]; } } return dialogProperties; }; const setupSaveDialogProperties = (properties: (keyof typeof SaveFileDialogProperties)[]): number => { let dialogProperties = 0; for (const property of properties) { if (Object.hasOwn(SaveFileDialogProperties, property)) { dialogProperties |= SaveFileDialogProperties[property]; } } return dialogProperties; }; const saveDialog = (sync: boolean, window: BaseWindow | null, options?: SaveDialogOptions) => { checkAppInitialized(); if (options == null) options = { title: 'Save' }; const { buttonLabel = '', defaultPath = '', filters = [], properties = [], title = '', message = '', securityScopedBookmarks = false, nameFieldLabel = '', showsTagField = true } = options; if (typeof title !== 'string') throw new TypeError('Title must be a string'); if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string'); if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string'); if (typeof message !== 'string') throw new TypeError('Message must be a string'); if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string'); const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window, properties: setupSaveDialogProperties(properties) }; return sync ? dialogBinding.showSaveDialogSync(settings) : dialogBinding.showSaveDialog(settings); }; const openDialog = (sync: boolean, window: BaseWindow | null, options?: OpenDialogOptions) => { checkAppInitialized(); if (options == null) { options = { title: 'Open', properties: ['openFile'] }; } const { buttonLabel = '', defaultPath = '', filters = [], properties = ['openFile'], title = '', message = '', securityScopedBookmarks = false } = options; if (!Array.isArray(properties)) throw new TypeError('Properties must be an array'); if (typeof title !== 'string') throw new TypeError('Title must be a string'); if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string'); if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string'); if (typeof message !== 'string') throw new TypeError('Message must be a string'); const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window, properties: setupOpenDialogProperties(properties) }; return (sync) ? dialogBinding.showOpenDialogSync(settings) : dialogBinding.showOpenDialog(settings); }; const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageBoxOptions) => { checkAppInitialized(); if (options == null) options = { type: 'none', message: '' }; const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question']; let { buttons = [], cancelId, signal, checkboxLabel = '', checkboxChecked, defaultId = -1, detail = '', icon = null, textWidth = 0, noLink = false, message = '', title = '', type = 'none' } = options; const messageBoxType = messageBoxTypes.indexOf(type); if (messageBoxType === -1) throw new TypeError('Invalid message box type'); if (!Array.isArray(buttons)) throw new TypeError('Buttons must be an array'); if (options.normalizeAccessKeys) buttons = buttons.map(normalizeAccessKey); if (typeof title !== 'string') throw new TypeError('Title must be a string'); if (typeof noLink !== 'boolean') throw new TypeError('noLink must be a boolean'); if (typeof message !== 'string') throw new TypeError('Message must be a string'); if (typeof detail !== 'string') throw new TypeError('Detail must be a string'); if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string'); checkboxChecked = !!checkboxChecked; if (checkboxChecked && !checkboxLabel) { throw new Error('checkboxChecked requires that checkboxLabel also be passed'); } // Choose a default button to get selected when dialog is cancelled. if (cancelId == null) { // If the defaultId is set to 0, ensure the cancel button is a different index (1) cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0; for (const [i, button] of buttons.entries()) { const text = button.toLowerCase(); if (text === 'cancel' || text === 'no') { cancelId = i; break; } } } // AbortSignal processing. let id: number | undefined; if (signal) { // Generate an ID used for closing the message box. id = getNextId(); // Close the message box when signal is aborted. if (signal.aborted) { return Promise.resolve({ cancelId, checkboxChecked }); } signal.addEventListener('abort', () => dialogBinding._closeMessageBox(id)); } const settings = { window, messageBoxType, buttons, id, defaultId, cancelId, noLink, title, message, detail, checkboxLabel, checkboxChecked, icon, textWidth }; if (sync) { return dialogBinding.showMessageBoxSync(settings); } else { return dialogBinding.showMessageBox(settings); } }; export function showOpenDialog(window: BaseWindow, options: OpenDialogOptions): OpenDialogReturnValue; export function showOpenDialog(options: OpenDialogOptions): OpenDialogReturnValue; export function showOpenDialog (windowOrOptions: BaseWindow | OpenDialogOptions, maybeOptions?: OpenDialogOptions): OpenDialogReturnValue { const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions); const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions); return openDialog(false, window, options); } export function showOpenDialogSync(window: BaseWindow, options: OpenDialogOptions): OpenDialogReturnValue; export function showOpenDialogSync(options: OpenDialogOptions): OpenDialogReturnValue; export function showOpenDialogSync (windowOrOptions: BaseWindow | OpenDialogOptions, maybeOptions?: OpenDialogOptions): OpenDialogReturnValue { const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions); const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions); return openDialog(true, window, options); } export function showSaveDialog(window: BaseWindow, options: SaveDialogOptions): SaveDialogReturnValue; export function showSaveDialog(options: SaveDialogOptions): SaveDialogReturnValue; export function showSaveDialog (windowOrOptions: BaseWindow | SaveDialogOptions, maybeOptions?: SaveDialogOptions): SaveDialogReturnValue { const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions); const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions); return saveDialog(false, window, options); } export function showSaveDialogSync(window: BaseWindow, options: SaveDialogOptions): SaveDialogReturnValue; export function showSaveDialogSync(options: SaveDialogOptions): SaveDialogReturnValue; export function showSaveDialogSync (windowOrOptions: BaseWindow | SaveDialogOptions, maybeOptions?: SaveDialogOptions): SaveDialogReturnValue { const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions); const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions); return saveDialog(true, window, options); } export function showMessageBox(window: BaseWindow, options: MessageBoxOptions): MessageBoxReturnValue; export function showMessageBox(options: MessageBoxOptions): MessageBoxReturnValue; export function showMessageBox (windowOrOptions: BaseWindow | MessageBoxOptions, maybeOptions?: MessageBoxOptions): MessageBoxReturnValue { const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions); const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions); return messageBox(false, window, options); } export function showMessageBoxSync(window: BaseWindow, options: MessageBoxOptions): MessageBoxReturnValue; export function showMessageBoxSync(options: MessageBoxOptions): MessageBoxReturnValue; export function showMessageBoxSync (windowOrOptions: BaseWindow | MessageBoxOptions, maybeOptions?: MessageBoxOptions): MessageBoxReturnValue { const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions); const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions); return messageBox(true, window, options); } export function showErrorBox (...args: any[]) { return dialogBinding.showErrorBox(...args); } export function showCertificateTrustDialog (windowOrOptions: BaseWindow | CertificateTrustDialogOptions, maybeOptions?: CertificateTrustDialogOptions) { const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions); const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions); if (options == null || typeof options !== 'object') { throw new TypeError('options must be an object'); } const { certificate, message = '' } = options; if (certificate == null || typeof certificate !== 'object') { throw new TypeError('certificate must be an object'); } if (typeof message !== 'string') throw new TypeError('message must be a string'); return dialogBinding.showCertificateTrustDialog(window, certificate, message); }