diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 34f25ed9691..c0c33be1cb2 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1682,6 +1682,10 @@ "messageformat": "Need help?", "description": "Shown on the install screen. Link takes users to a support page" }, + "icu:Preferences--header": { + "messageformat": "Settings", + "description": "Shown at the top of the settings tab when open" + }, "icu:Preferences--phone-number": { "messageformat": "Phone Number", "description": "The label in settings panel shown for the phone number associated with user's account" diff --git a/app/main.ts b/app/main.ts index 13e59d963e7..818f533ef85 100644 --- a/app/main.ts +++ b/app/main.ts @@ -1173,9 +1173,6 @@ async function readyForUpdates() { 'SettingsChannel must be initialized' ); await updater.start({ - settingsChannel, - logger: getLogger(), - getMainWindow, canRunSilently: () => { return ( systemTrayService?.isVisible() === true && @@ -1183,6 +1180,9 @@ async function readyForUpdates() { mainWindow?.webContents?.getBackgroundThrottling() !== false ); }, + getMainWindow, + logger: getLogger(), + sql, }); } catch (error) { getLogger().error( @@ -1424,57 +1424,6 @@ async function showAbout() { ); } -let settingsWindow: BrowserWindow | undefined; -async function showSettingsWindow() { - if (settingsWindow) { - settingsWindow.show(); - return; - } - - const options = { - width: 700, - height: 700, - frame: true, - resizable: false, - title: getResolvedMessagesLocale().i18n('icu:signalDesktopPreferences'), - titleBarStyle: mainTitleBarStyle, - autoHideMenuBar: true, - backgroundColor: await getBackgroundColor(), - show: false, - webPreferences: { - ...defaultWebPrefs, - nodeIntegration: false, - nodeIntegrationInWorker: false, - sandbox: true, - contextIsolation: true, - preload: join(__dirname, '../bundles/settings/preload.js'), - nativeWindowOpen: true, - }, - }; - - settingsWindow = new BrowserWindow(options); - - await handleCommonWindowEvents(settingsWindow); - - settingsWindow.on('closed', () => { - settingsWindow = undefined; - }); - - ipc.once('settings-done-rendering', () => { - if (!settingsWindow) { - getLogger().warn('settings-done-rendering: no settingsWindow available!'); - return; - } - - settingsWindow.show(); - }); - - await safeLoadURL( - settingsWindow, - await prepareFileUrl([__dirname, '../settings.html']) - ); -} - async function getIsLinked() { try { const number = await sql.sqlRead('getItemById', 'number_id'); @@ -2310,6 +2259,8 @@ app.on('ready', async () => { }, }); + appStartInitialSpellcheckSetting = await getSpellCheckSetting(); + // Run window preloading in parallel with database initialization. await createWindow(); @@ -2322,8 +2273,6 @@ app.on('ready', async () => { return; } - appStartInitialSpellcheckSetting = await getSpellCheckSetting(); - try { const IDB_KEY = 'indexeddb-delete-needed'; const item = await sql.sqlRead('getItemById', IDB_KEY); @@ -2382,7 +2331,15 @@ function setupMenu(options?: Partial) { showDebugLog: showDebugLogWindow, showCallingDevTools: showCallingDevToolsWindow, showKeyboardShortcuts, - showSettings: showSettingsWindow, + showSettings: () => { + if (!settingsChannel) { + getLogger().warn( + 'showSettings: No settings channel; cannot open settings tab.' + ); + return; + } + settingsChannel.openSettingsTab(); + }, showWindow, zoomIn, zoomOut, @@ -2750,17 +2707,6 @@ function removeDarkOverlay() { } } -ipc.on('show-settings', showSettingsWindow); - -ipc.on('delete-all-data', () => { - if (settingsWindow) { - settingsWindow.close(); - } - if (mainWindow && mainWindow.webContents) { - mainWindow.webContents.send('delete-all-data'); - } -}); - ipc.on('get-config', async event => { const theme = await getResolvedThemeSetting(); diff --git a/images/icons/v3/settings/settings-fill.svg b/images/icons/v3/settings/settings-fill.svg new file mode 100644 index 00000000000..28f95692546 --- /dev/null +++ b/images/icons/v3/settings/settings-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/esbuild.js b/scripts/esbuild.js index 220d36235aa..42f712dedff 100644 --- a/scripts/esbuild.js +++ b/scripts/esbuild.js @@ -134,7 +134,6 @@ async function sandboxedEnv() { path.join(ROOT_DIR, 'ts', 'windows', 'loading', 'start.ts'), path.join(ROOT_DIR, 'ts', 'windows', 'permissions', 'app.tsx'), path.join(ROOT_DIR, 'ts', 'windows', 'screenShare', 'app.tsx'), - path.join(ROOT_DIR, 'ts', 'windows', 'settings', 'app.tsx'), path.join( ROOT_DIR, 'ts', @@ -154,7 +153,6 @@ async function sandboxedEnv() { path.join(ROOT_DIR, 'ts', 'windows', 'permissions', 'preload.ts'), path.join(ROOT_DIR, 'ts', 'windows', 'calling-tools', 'preload.ts'), path.join(ROOT_DIR, 'ts', 'windows', 'screenShare', 'preload.ts'), - path.join(ROOT_DIR, 'ts', 'windows', 'settings', 'preload.ts'), ], format: 'cjs', outdir: 'bundles', diff --git a/stylesheets/components/NavTabs.scss b/stylesheets/components/NavTabs.scss index 56d848a7236..489ad19c5b9 100644 --- a/stylesheets/components/NavTabs.scss +++ b/stylesheets/components/NavTabs.scss @@ -170,6 +170,10 @@ $NavTabs__ProfileAvatar__size: 28px; .NavTabs__ItemIcon--Settings { @include NavTabs__Icon('../images/icons/v3/settings/settings.svg'); + .NavTabs__Item:active &, + .NavTabs__Item[aria-selected='true'] & { + @include NavTabs__Icon('../images/icons/v3/settings/settings-fill.svg'); + } } .NavTabs__ItemIcon--Chats { @@ -201,13 +205,31 @@ $NavTabs__ProfileAvatar__size: 28px; } .NavTabs__TabList { - display: flex; - flex-direction: column; + display: grid; + grid-template-rows: + [Chats] auto + [Calls] auto + [Stories] auto + 1fr + [Settings] auto; align-items: center; width: 100%; flex: 1; } +.NavTabs__Item--Chats { + grid-row: Chats; +} +.NavTabs__Item--Calls { + grid-row: Calls; +} +.NavTabs__Item--Stories { + grid-row: Stories; +} +.NavTabs__Item--Settings { + grid-row: Settings; +} + .NavTabs__Misc { width: 100%; display: flex; diff --git a/stylesheets/components/Preferences.scss b/stylesheets/components/Preferences.scss index f665c9d49af..a1908ccc462 100644 --- a/stylesheets/components/Preferences.scss +++ b/stylesheets/components/Preferences.scss @@ -24,6 +24,8 @@ $secondary-text-color: light-dark( display: flex; overflow: hidden; user-select: none; + width: 100vw; + @include mixins.light-theme { background: variables.$color-white; } @@ -31,14 +33,43 @@ $secondary-text-color: light-dark( background: variables.$color-gray-95; } + &__header { + margin-inline-start: 24px; + @include mixins.font-title-medium; + line-height: 20px; + margin-top: 12px; + margin-bottom: 8px; + } + &__page-selector { - padding-top: calc(24px + var(--title-bar-drag-area-height)); - min-width: min(34%, 240px); + padding-top: var(--title-bar-drag-area-height); + width: 279px; + flex-grow: 0; + flex-shrink: 0; @include mixins.light-theme { - background: variables.$color-gray-02; + background: variables.$color-gray-04; + border-inline-end: 0.5px solid variables.$color-black-alpha-16; } @include mixins.dark-theme { background: variables.$color-gray-80; + border-inline-end: 0.5px solid variables.$color-white-alpha-16; + } + } + + &__scroll-area { + margin-top: 8px; + overflow-y: scroll; + max-height: calc(100% - var(--title-bar-drag-area-height) - 20px); + + &::-webkit-scrollbar-thumb { + @include mixins.light-theme { + background: variables.$color-gray-25; + border-color: variables.$color-gray-04; + } + @include mixins.dark-theme { + background: variables.$color-gray-45; + border-color: variables.$color-gray-80; + } } } @@ -58,9 +89,12 @@ $secondary-text-color: light-dark( align-items: center; display: flex; height: 48px; - width: 100%; + width: calc(100% - 20px); padding-block: 14px; padding-inline: 0; + margin-inline-start: 10px; + margin-inline-end: 10px; + border-radius: 10px; } &--selected { @@ -130,12 +164,19 @@ $secondary-text-color: light-dark( height: 100vh; overflow: overlay; width: 100%; + flex-grow: 1; + max-width: 825px; &::-webkit-scrollbar-corner { background: transparent; } } + &__settings-pane-spacer { + flex-grow: 1; + min-width: 0; + } + &__title { @include mixins.font-body-1-bold; align-items: center; diff --git a/ts/background.ts b/ts/background.ts index 64a16490a12..d74e114907a 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -215,6 +215,7 @@ import { waitForEvent } from './shims/events'; import { sendSyncRequests } from './textsecure/syncRequests'; import { handleServerAlerts } from './util/handleServerAlerts'; import { isLocalBackupsEnabled } from './util/isLocalBackupsEnabled'; +import { NavTab } from './state/ducks/nav'; export function isOverHourIntoPast(timestamp: number): boolean { return isNumber(timestamp) && isOlderThan(timestamp, HOUR); @@ -1356,6 +1357,10 @@ export async function startApp(): Promise { window.reduxActions.app.openStandalone(); }); + window.Whisper.events.on('openSettingsTab', () => { + window.reduxActions.nav.changeNavTab(NavTab.Settings); + }); + window.Whisper.events.on('powerMonitorSuspend', () => { log.info('powerMonitor: suspend'); server?.cancelInflightRequests('powerMonitorSuspend'); diff --git a/ts/components/ChatColorPicker.stories.tsx b/ts/components/ChatColorPicker.stories.tsx index f629df4e150..0c6bbcb6713 100644 --- a/ts/components/ChatColorPicker.stories.tsx +++ b/ts/components/ChatColorPicker.stories.tsx @@ -24,7 +24,7 @@ export default { addCustomColor: action('addCustomColor'), colorSelected: action('colorSelected'), editCustomColor: action('editCustomColor'), - getConversationsWithCustomColor: (_: string) => Promise.resolve([]), + getConversationsWithCustomColor: (_: string) => [], i18n, removeCustomColor: action('removeCustomColor'), removeCustomColorOnConversations: action( diff --git a/ts/components/ChatColorPicker.tsx b/ts/components/ChatColorPicker.tsx index ffa90c4c196..eb559d9295c 100644 --- a/ts/components/ChatColorPicker.tsx +++ b/ts/components/ChatColorPicker.tsx @@ -26,9 +26,7 @@ type CustomColorDataType = { export type PropsDataType = { conversationId?: string; customColors?: Record; - getConversationsWithCustomColor: ( - colorId: string - ) => Promise>; + getConversationsWithCustomColor: (colorId: string) => Array; i18n: LocalizerType; isGlobal?: boolean; selectedColor?: ConversationColorType; @@ -270,9 +268,7 @@ export function ChatColorPicker({ type CustomColorBubblePropsType = { color: CustomColorType; colorId: string; - getConversationsWithCustomColor: ( - colorId: string - ) => Promise>; + getConversationsWithCustomColor: (colorId: string) => Array; i18n: LocalizerType; isSelected: boolean; onDelete: () => unknown; @@ -393,12 +389,11 @@ function CustomColorBubble({ attributes={{ className: 'ChatColorPicker__context--delete', }} - onClick={async (event: MouseEvent) => { + onClick={(event: MouseEvent) => { event.stopPropagation(); event.preventDefault(); - const conversations = - await getConversationsWithCustomColor(colorId); + const conversations = getConversationsWithCustomColor(colorId); if (!conversations.length) { onDelete(); } else { diff --git a/ts/components/DialogUpdate.tsx b/ts/components/DialogUpdate.tsx index ad50a7e25c8..d4677789693 100644 --- a/ts/components/DialogUpdate.tsx +++ b/ts/components/DialogUpdate.tsx @@ -3,16 +3,19 @@ import type { ReactNode } from 'react'; import React, { useCallback } from 'react'; + import { isBeta } from '../util/version'; import { DialogType } from '../types/Dialogs'; -import type { LocalizerType } from '../types/Util'; import { PRODUCTION_DOWNLOAD_URL, BETA_DOWNLOAD_URL } from '../types/support'; import { I18n } from './I18n'; import { LeftPaneDialog } from './LeftPaneDialog'; -import type { WidthBreakpoint } from './_util'; import { formatFileSize } from '../util/formatFileSize'; import { getLocalizedUrl } from '../util/getLocalizedUrl'; +import type { LocalizerType } from '../types/Util'; +import type { DismissOptions } from './LeftPaneDialog'; +import type { WidthBreakpoint } from './_util'; + function contactSupportLink(parts: ReactNode): JSX.Element { const localizedSupportLink = getLocalizedUrl( 'https://support.signal.org/hc/LOCALE/requests/new?desktop' @@ -32,6 +35,7 @@ function contactSupportLink(parts: ReactNode): JSX.Element { export type PropsType = { containerWidthBreakpoint: WidthBreakpoint; dialogType: DialogType; + disableDismiss?: boolean; dismissDialog: () => void; downloadSize?: number; downloadedSize?: number; @@ -45,6 +49,7 @@ export type PropsType = { export function DialogUpdate({ containerWidthBreakpoint, dialogType, + disableDismiss, dismissDialog, downloadSize, downloadedSize, @@ -215,6 +220,19 @@ export function DialogUpdate({ title = i18n('icu:DialogUpdate__downloaded'); } + let dismissOptions: DismissOptions = { + hasXButton: true, + onClose: snoozeUpdate, + closeLabel: i18n('icu:autoUpdateIgnoreButtonLabel'), + }; + if (disableDismiss) { + dismissOptions = { + hasXButton: false, + onClose: undefined, + closeLabel: undefined, + }; + } + return ( ); } diff --git a/ts/components/Inbox.tsx b/ts/components/Inbox.tsx index d12d1565a2e..ee0a6db0ab8 100644 --- a/ts/components/Inbox.tsx +++ b/ts/components/Inbox.tsx @@ -23,6 +23,7 @@ export type PropsType = { renderCustomizingPreferredReactionsModal: () => JSX.Element; renderNavTabs: (props: SmartNavTabsProps) => JSX.Element; renderStoriesTab: () => JSX.Element; + renderSettingsTab: () => JSX.Element; }; const PART_COUNT = 16; @@ -41,6 +42,7 @@ export function Inbox({ renderCustomizingPreferredReactionsModal, renderNavTabs, renderStoriesTab, + renderSettingsTab, }: PropsType): JSX.Element { const [internalHasInitialLoadCompleted, setInternalHasInitialLoadCompleted] = useState(hasInitialLoadCompleted); @@ -200,6 +202,7 @@ export function Inbox({ renderChatsTab, renderCallsTab, renderStoriesTab, + renderSettingsTab, })} {activeModal} diff --git a/ts/components/LeftPaneDialog.tsx b/ts/components/LeftPaneDialog.tsx index 9389ae5a233..0e08fefef92 100644 --- a/ts/components/LeftPaneDialog.tsx +++ b/ts/components/LeftPaneDialog.tsx @@ -9,6 +9,17 @@ import { WidthBreakpoint } from './_util'; const BASE_CLASS_NAME = 'LeftPaneDialog'; const TOOLTIP_CLASS_NAME = `${BASE_CLASS_NAME}__tooltip`; +export type DismissOptions = + | { + onClose?: undefined; + closeLabel?: undefined; + hasXButton?: false; + } + | { + onClose: () => void; + closeLabel: string; + hasXButton: true; + }; export type PropsType = { type?: 'warning' | 'error' | 'info'; @@ -30,18 +41,7 @@ export type PropsType = { hasAction: boolean; } ) & - ( - | { - onClose?: undefined; - closeLabel?: undefined; - hasXButton?: false; - } - | { - onClose: () => void; - closeLabel: string; - hasXButton: true; - } - ); + DismissOptions; export function LeftPaneDialog({ icon = 'warning', diff --git a/ts/components/NavTabs.tsx b/ts/components/NavTabs.tsx index fdf94f14767..6dcc152b20f 100644 --- a/ts/components/NavTabs.tsx +++ b/ts/components/NavTabs.tsx @@ -13,7 +13,6 @@ import { NavTab } from '../state/ducks/nav'; import { Tooltip, TooltipPlacement } from './Tooltip'; import { Theme } from '../util/theme'; import type { UnreadStats } from '../util/countUnreadStats'; -import { ContextMenu } from './ContextMenu'; type NavTabsItemBadgesProps = Readonly<{ i18n: LocalizerType; @@ -72,25 +71,33 @@ function NavTabsItemBadges({ } type NavTabProps = Readonly<{ + hasError?: boolean; i18n: LocalizerType; iconClassName: string; id: NavTab; - hasError?: boolean; label: string; + navTabClassName: string; unreadStats: UnreadStats | null; + hasPendingUpdate?: boolean; }>; function NavTabsItem({ + hasError, i18n, iconClassName, id, label, + navTabClassName, unreadStats, - hasError, + hasPendingUpdate, }: NavTabProps) { const isRTL = i18n.getLocaleDirection() === 'rtl'; return ( - + {label} @@ -187,14 +195,13 @@ export type NavTabsProps = Readonly<{ i18n: LocalizerType; me: ConversationType; navTabsCollapsed: boolean; - onShowSettings: () => void; - onStartUpdate: () => unknown; onNavTabSelected: (tab: NavTab) => void; onToggleNavTabsCollapse: (collapsed: boolean) => void; onToggleProfileEditor: () => void; renderCallsTab: () => ReactNode; renderChatsTab: () => ReactNode; renderStoriesTab: () => ReactNode; + renderSettingsTab: () => ReactNode; selectedNavTab: NavTab; storiesEnabled: boolean; theme: ThemeType; @@ -210,14 +217,13 @@ export function NavTabs({ i18n, me, navTabsCollapsed, - onShowSettings, - onStartUpdate, onNavTabSelected, onToggleNavTabsCollapse, onToggleProfileEditor, renderCallsTab, renderChatsTab, renderStoriesTab, + renderSettingsTab, selectedNavTab, storiesEnabled, theme, @@ -259,6 +265,7 @@ export function NavTabs({ id={NavTab.Chats} label={i18n('icu:NavTabs__ItemLabel--Chats')} iconClassName="NavTabs__ItemIcon--Chats" + navTabClassName="NavTabs__Item--Chats" unreadStats={unreadConversationsStats} /> )} +
- - {({ onClick, onKeyDown, ref }) => { - return ( - - ); - }} - -