diff --git a/desktop/ELECTRON_EVENTS.ts b/desktop/ELECTRON_EVENTS.ts index b06794567c7d..99f275ab996b 100644 --- a/desktop/ELECTRON_EVENTS.ts +++ b/desktop/ELECTRON_EVENTS.ts @@ -14,6 +14,7 @@ const ELECTRON_EVENTS = { DOWNLOAD_FAILED: 'download-started', DOWNLOAD_CANCELED: 'download-canceled', SILENT_UPDATE: 'silent-update', + OPEN_LOCATION_SETTING: 'open-location-setting', } as const; export default ELECTRON_EVENTS; diff --git a/desktop/contextBridge.ts b/desktop/contextBridge.ts index 74b91c4634a1..13a752e0d6aa 100644 --- a/desktop/contextBridge.ts +++ b/desktop/contextBridge.ts @@ -18,6 +18,7 @@ const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [ ELECTRON_EVENTS.LOCALE_UPDATED, ELECTRON_EVENTS.DOWNLOAD, ELECTRON_EVENTS.SILENT_UPDATE, + ELECTRON_EVENTS.OPEN_LOCATION_SETTING, ] as const; const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ diff --git a/desktop/main.ts b/desktop/main.ts index 4f642d90da51..2b296d84f6a3 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -1,3 +1,4 @@ +import {exec} from 'child_process'; import {app, BrowserWindow, clipboard, dialog, ipcMain, Menu, shell} from 'electron'; import type {BaseWindow, BrowserView, MenuItem, MenuItemConstructorOptions, WebContents, WebviewTag} from 'electron'; import contextMenu from 'electron-context-menu'; @@ -6,7 +7,7 @@ import type {ElectronLog} from 'electron-log'; import {autoUpdater} from 'electron-updater'; import {machineId} from 'node-machine-id'; import checkForUpdates from '@libs/checkForUpdates'; -import * as Localize from '@libs/Localize'; +import {translate} from '@libs/Localize'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -71,9 +72,9 @@ function pasteAsPlainText(browserWindow: BrowserWindow | BrowserView | WebviewTa function createContextMenu(preferredLocale: Locale = LOCALES.DEFAULT): () => void { return contextMenu({ labels: { - cut: Localize.translate(preferredLocale, 'desktopApplicationMenu.cut'), - paste: Localize.translate(preferredLocale, 'desktopApplicationMenu.paste'), - copy: Localize.translate(preferredLocale, 'desktopApplicationMenu.copy'), + cut: translate(preferredLocale, 'desktopApplicationMenu.cut'), + paste: translate(preferredLocale, 'desktopApplicationMenu.paste'), + copy: translate(preferredLocale, 'desktopApplicationMenu.copy'), }, append: (defaultActions, parameters, browserWindow) => [ { @@ -81,10 +82,10 @@ function createContextMenu(preferredLocale: Locale = LOCALES.DEFAULT): () => voi visible: parameters.isEditable && parameters.editFlags.canPaste, role: 'pasteAndMatchStyle', accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AND_MATCH_STYLE, - label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'), + label: translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'), }, { - label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'), + label: translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'), visible: parameters.isEditable && parameters.editFlags.canPaste && clipboard.readText().length > 0, accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AS_PLAIN_TEXT, click: () => pasteAsPlainText(browserWindow), @@ -126,7 +127,7 @@ let hasUpdate = false; let downloadedVersion: string; let isSilentUpdating = false; -// Note that we have to subscribe to this separately and cannot use Localize.translateLocal, +// Note that we have to subscribe to this separately and cannot use translateLocal, // because the only way code can be shared between the main and renderer processes at runtime is via the context bridge // So we track preferredLocale separately via ELECTRON_EVENTS.LOCALE_UPDATED const preferredLocale: Locale = CONST.LOCALES.DEFAULT; @@ -165,23 +166,23 @@ const manuallyCheckForUpdates = (menuItem?: MenuItem, browserWindow?: BaseWindow if (downloadPromise) { dialog.showMessageBox(browserWindow, { type: 'info', - message: Localize.translate(preferredLocale, 'checkForUpdatesModal.available.title'), - detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.available.message', {isSilentUpdating}), - buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.available.soundsGood')], + message: translate(preferredLocale, 'checkForUpdatesModal.available.title'), + detail: translate(preferredLocale, 'checkForUpdatesModal.available.message', {isSilentUpdating}), + buttons: [translate(preferredLocale, 'checkForUpdatesModal.available.soundsGood')], }); } else if (result && 'error' in result && result.error) { dialog.showMessageBox(browserWindow, { type: 'error', - message: Localize.translate(preferredLocale, 'checkForUpdatesModal.error.title'), - detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.error.message'), - buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')], + message: translate(preferredLocale, 'checkForUpdatesModal.error.title'), + detail: translate(preferredLocale, 'checkForUpdatesModal.error.message'), + buttons: [translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')], }); } else { dialog.showMessageBox(browserWindow, { type: 'info', - message: Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.title'), - detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.message'), - buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')], + message: translate(preferredLocale, 'checkForUpdatesModal.notAvailable.title'), + detail: translate(preferredLocale, 'checkForUpdatesModal.notAvailable.message'), + buttons: [translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')], cancelId: 2, }); } @@ -242,7 +243,7 @@ const localizeMenuItems = (submenu: MenuItemConstructorOptions[], updatedLocale: submenu.map((menu) => { const newMenu: MenuItemConstructorOptions = {...menu}; if (menu.id) { - const labelTranslation = Localize.translate(updatedLocale, `desktopApplicationMenu.${menu.id}` as TranslationPaths); + const labelTranslation = translate(updatedLocale, `desktopApplicationMenu.${menu.id}` as TranslationPaths); if (labelTranslation) { newMenu.label = labelTranslation; } @@ -310,7 +311,25 @@ const mainWindow = (): Promise => { }); ipcMain.handle(ELECTRON_EVENTS.REQUEST_DEVICE_ID, () => machineId()); + ipcMain.handle(ELECTRON_EVENTS.OPEN_LOCATION_SETTING, () => { + if (process.platform !== 'darwin') { + // Platform not supported for location settings + return Promise.resolve(undefined); + } + return new Promise((resolve, reject) => { + const command = 'open x-apple.systempreferences:com.apple.preference.security?Privacy_Location'; + + exec(command, (error) => { + if (error) { + console.error('Error opening location settings:', error); + reject(error); + return; + } + resolve(undefined); + }); + }); + }); /* * The default origin of our Electron app is app://- instead of https://new.expensify.com or https://staging.new.expensify.com * This causes CORS errors because the referer and origin headers are wrong and the API responds with an Access-Control-Allow-Origin that doesn't match app://- @@ -353,14 +372,14 @@ const mainWindow = (): Promise => { const initialMenuTemplate: MenuItemConstructorOptions[] = [ { id: 'mainMenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.mainMenu`), + label: translate(preferredLocale, `desktopApplicationMenu.mainMenu`), submenu: [ {id: 'about', role: 'about'}, - {id: 'update', label: Localize.translate(preferredLocale, `desktopApplicationMenu.update`), click: quitAndInstallWithUpdate, visible: false}, - {id: 'checkForUpdates', label: Localize.translate(preferredLocale, `desktopApplicationMenu.checkForUpdates`), click: manuallyCheckForUpdates}, + {id: 'update', label: translate(preferredLocale, `desktopApplicationMenu.update`), click: quitAndInstallWithUpdate, visible: false}, + {id: 'checkForUpdates', label: translate(preferredLocale, `desktopApplicationMenu.checkForUpdates`), click: manuallyCheckForUpdates}, { id: 'viewShortcuts', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.viewShortcuts`), + label: translate(preferredLocale, `desktopApplicationMenu.viewShortcuts`), accelerator: 'CmdOrCtrl+J', click: () => { showKeyboardShortcutsPage(browserWindow); @@ -378,12 +397,12 @@ const mainWindow = (): Promise => { }, { id: 'fileMenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.fileMenu`), + label: translate(preferredLocale, `desktopApplicationMenu.fileMenu`), submenu: [{id: 'closeWindow', role: 'close', accelerator: 'Cmd+w'}], }, { id: 'editMenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.editMenu`), + label: translate(preferredLocale, `desktopApplicationMenu.editMenu`), submenu: [ {id: 'undo', role: 'undo'}, {id: 'redo', role: 'redo'}, @@ -406,7 +425,7 @@ const mainWindow = (): Promise => { {type: 'separator'}, { id: 'speechSubmenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.speechSubmenu`), + label: translate(preferredLocale, `desktopApplicationMenu.speechSubmenu`), submenu: [ {id: 'startSpeaking', role: 'startSpeaking'}, {id: 'stopSpeaking', role: 'stopSpeaking'}, @@ -416,7 +435,7 @@ const mainWindow = (): Promise => { }, { id: 'viewMenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.viewMenu`), + label: translate(preferredLocale, `desktopApplicationMenu.viewMenu`), submenu: [ {id: 'reload', role: 'reload'}, {id: 'forceReload', role: 'forceReload'}, @@ -431,7 +450,7 @@ const mainWindow = (): Promise => { }, { id: 'historyMenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.historyMenu`), + label: translate(preferredLocale, `desktopApplicationMenu.historyMenu`), submenu: [ { id: 'back', @@ -472,33 +491,33 @@ const mainWindow = (): Promise => { }, { id: 'helpMenu', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.helpMenu`), + label: translate(preferredLocale, `desktopApplicationMenu.helpMenu`), role: 'help', submenu: [ { id: 'learnMore', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.learnMore`), + label: translate(preferredLocale, `desktopApplicationMenu.learnMore`), click: () => { shell.openExternal(CONST.MENU_HELP_URLS.LEARN_MORE); }, }, { id: 'documentation', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.documentation`), + label: translate(preferredLocale, `desktopApplicationMenu.documentation`), click: () => { shell.openExternal(CONST.MENU_HELP_URLS.DOCUMENTATION); }, }, { id: 'communityDiscussions', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.communityDiscussions`), + label: translate(preferredLocale, `desktopApplicationMenu.communityDiscussions`), click: () => { shell.openExternal(CONST.MENU_HELP_URLS.COMMUNITY_DISCUSSIONS); }, }, { id: 'searchIssues', - label: Localize.translate(preferredLocale, `desktopApplicationMenu.searchIssues`), + label: translate(preferredLocale, `desktopApplicationMenu.searchIssues`), click: () => { shell.openExternal(CONST.MENU_HELP_URLS.SEARCH_ISSUES); }, diff --git a/src/CONST.ts b/src/CONST.ts index 3dfa6b4b078d..e49cf3e845fb 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1463,6 +1463,7 @@ const CONST = { USE_DEBOUNCED_STATE_DELAY: 300, LIST_SCROLLING_DEBOUNCE_TIME: 200, PUSHER_PING_PONG: 'pusher_ping_pong', + LOCATION_UPDATE_INTERVAL: 5000, }, PRIORITY_MODE: { GSD: 'gsd', diff --git a/src/components/ConfirmContent.tsx b/src/components/ConfirmContent.tsx index 3bfb5a146d05..ec2c6d57c21f 100644 --- a/src/components/ConfirmContent.tsx +++ b/src/components/ConfirmContent.tsx @@ -98,6 +98,9 @@ type ConfirmContentProps = { /** Whether the modal is visibile */ isVisible: boolean; + + /** Whether the confirm button is loading */ + isConfirmLoading?: boolean; }; function ConfirmContent({ @@ -127,6 +130,7 @@ function ConfirmContent({ titleContainerStyles, shouldReverseStackedButtons = false, isVisible, + isConfirmLoading, }: ConfirmContentProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -209,6 +213,7 @@ function ConfirmContent({ text={confirmText || translate('common.yes')} accessibilityLabel={confirmText || translate('common.yes')} isDisabled={isOffline && shouldDisableConfirmButtonWhenOffline} + isLoading={isConfirmLoading} /> {shouldShowCancelButton && !shouldReverseStackedButtons && (