Use patched frameless-titlebar on Windows
This commit is contained in:
parent
79c52847cd
commit
5634601554
54 changed files with 1343 additions and 323 deletions
|
@ -3,6 +3,11 @@
|
||||||
|
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<link rel="stylesheet" href="../stylesheets/manifest.css" />
|
<link rel="stylesheet" href="../stylesheets/manifest.css" />
|
||||||
|
<link
|
||||||
|
href="../node_modules/@indutny/frameless-titlebar/dist/styles.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
/>
|
||||||
<script>
|
<script>
|
||||||
window.SignalWindow = window.SignalWindow || {};
|
window.SignalWindow = window.SignalWindow || {};
|
||||||
window.SignalWindow.log = {
|
window.SignalWindow.log = {
|
||||||
|
@ -13,4 +18,17 @@
|
||||||
debug: console.debug.bind(console),
|
debug: console.debug.bind(console),
|
||||||
trace: console.trace.bind(console),
|
trace: console.trace.bind(console),
|
||||||
};
|
};
|
||||||
|
window.SignalContext = {
|
||||||
|
nativeThemeListener: {
|
||||||
|
getSystemValue: async () => 'light',
|
||||||
|
subscribe: () => {},
|
||||||
|
unsubscribe: () => {},
|
||||||
|
},
|
||||||
|
Settings: {
|
||||||
|
themeSetting: {
|
||||||
|
getValue: async () => 'light',
|
||||||
|
},
|
||||||
|
waitForChange: () => {},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,6 +5,30 @@
|
||||||
|
|
||||||
Signal Desktop makes use of the following open source projects.
|
Signal Desktop makes use of the following open source projects.
|
||||||
|
|
||||||
|
## @indutny/frameless-titlebar
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Cristian Ponce
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
## @popperjs/core
|
## @popperjs/core
|
||||||
|
|
||||||
License: MIT
|
License: MIT
|
||||||
|
|
|
@ -20,6 +20,11 @@
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
/>
|
/>
|
||||||
|
<link
|
||||||
|
href="node_modules/@indutny/frameless-titlebar/dist/styles.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
/>
|
||||||
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
298
app/main.ts
298
app/main.ts
|
@ -27,6 +27,10 @@ import {
|
||||||
shell,
|
shell,
|
||||||
systemPreferences,
|
systemPreferences,
|
||||||
} from 'electron';
|
} from 'electron';
|
||||||
|
import type {
|
||||||
|
MenuItemConstructorOptions,
|
||||||
|
TitleBarOverlayOptions,
|
||||||
|
} from 'electron';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import packageJson from '../package.json';
|
import packageJson from '../package.json';
|
||||||
|
@ -75,7 +79,8 @@ import * as logging from '../ts/logging/main_process_logging';
|
||||||
import { MainSQL } from '../ts/sql/main';
|
import { MainSQL } from '../ts/sql/main';
|
||||||
import * as sqlChannels from './sql_channel';
|
import * as sqlChannels from './sql_channel';
|
||||||
import * as windowState from './window_state';
|
import * as windowState from './window_state';
|
||||||
import type { MenuOptionsType } from './menu';
|
import type { CreateTemplateOptionsType } from './menu';
|
||||||
|
import type { MenuActionType } from '../ts/types/menu';
|
||||||
import { createTemplate } from './menu';
|
import { createTemplate } from './menu';
|
||||||
import { installFileHandler, installWebHandler } from './protocol_filter';
|
import { installFileHandler, installWebHandler } from './protocol_filter';
|
||||||
import * as OS from '../ts/OS';
|
import * as OS from '../ts/OS';
|
||||||
|
@ -91,10 +96,6 @@ import {
|
||||||
} from '../ts/util/sgnlHref';
|
} from '../ts/util/sgnlHref';
|
||||||
import { clearTimeoutIfNecessary } from '../ts/util/clearTimeoutIfNecessary';
|
import { clearTimeoutIfNecessary } from '../ts/util/clearTimeoutIfNecessary';
|
||||||
import { toggleMaximizedBrowserWindow } from '../ts/util/toggleMaximizedBrowserWindow';
|
import { toggleMaximizedBrowserWindow } from '../ts/util/toggleMaximizedBrowserWindow';
|
||||||
import {
|
|
||||||
getTitleBarVisibility,
|
|
||||||
TitleBarVisibility,
|
|
||||||
} from '../ts/types/Settings';
|
|
||||||
import { ChallengeMainHandler } from '../ts/main/challengeMain';
|
import { ChallengeMainHandler } from '../ts/main/challengeMain';
|
||||||
import { NativeThemeNotifier } from '../ts/main/NativeThemeNotifier';
|
import { NativeThemeNotifier } from '../ts/main/NativeThemeNotifier';
|
||||||
import { PowerChannel } from '../ts/main/powerChannel';
|
import { PowerChannel } from '../ts/main/powerChannel';
|
||||||
|
@ -324,6 +325,8 @@ if (windowFromUserConfig) {
|
||||||
ephemeralConfig.set('window', windowConfig);
|
ephemeralConfig.set('window', windowConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let menuOptions: CreateTemplateOptionsType | undefined;
|
||||||
|
|
||||||
// These will be set after app fires the 'ready' event
|
// These will be set after app fires the 'ready' event
|
||||||
let logger: LoggerType | undefined;
|
let logger: LoggerType | undefined;
|
||||||
let locale: LocaleType | undefined;
|
let locale: LocaleType | undefined;
|
||||||
|
@ -429,7 +432,10 @@ async function handleUrl(event: Electron.Event, rawTarget: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCommonWindowEvents(window: BrowserWindow) {
|
function handleCommonWindowEvents(
|
||||||
|
window: BrowserWindow,
|
||||||
|
titleBarOverlay: TitleBarOverlayOptions | false = false
|
||||||
|
) {
|
||||||
window.webContents.on('will-navigate', handleUrl);
|
window.webContents.on('will-navigate', handleUrl);
|
||||||
window.webContents.on('new-window', handleUrl);
|
window.webContents.on('new-window', handleUrl);
|
||||||
window.webContents.on(
|
window.webContents.on(
|
||||||
|
@ -467,6 +473,23 @@ function handleCommonWindowEvents(window: BrowserWindow) {
|
||||||
window.webContents.on('preferred-size-changed', onZoomChanged);
|
window.webContents.on('preferred-size-changed', onZoomChanged);
|
||||||
|
|
||||||
nativeThemeNotifier.addWindow(window);
|
nativeThemeNotifier.addWindow(window);
|
||||||
|
|
||||||
|
if (titleBarOverlay) {
|
||||||
|
const onThemeChange = async () => {
|
||||||
|
try {
|
||||||
|
const newOverlay = await getTitleBarOverlay();
|
||||||
|
if (!newOverlay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.setTitleBarOverlay(newOverlay);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('onThemeChange error', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeTheme.on('updated', onThemeChange);
|
||||||
|
settingsChannel?.on('change:themeSetting', onThemeChange);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_WIDTH = 800;
|
const DEFAULT_WIDTH = 800;
|
||||||
|
@ -521,10 +544,50 @@ if (OS.isWindows()) {
|
||||||
windowIcon = join(__dirname, '../build/icons/png/512x512.png');
|
windowIcon = join(__dirname, '../build/icons/png/512x512.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mainTitleBarStyle =
|
||||||
|
OS.isLinux() || isTestEnvironment(getEnvironment())
|
||||||
|
? ('default' as const)
|
||||||
|
: ('hidden' as const);
|
||||||
|
|
||||||
|
const nonMainTitleBarStyle = OS.isWindows()
|
||||||
|
? ('hidden' as const)
|
||||||
|
: ('default' as const);
|
||||||
|
|
||||||
|
async function getTitleBarOverlay(): Promise<TitleBarOverlayOptions | false> {
|
||||||
|
if (!OS.isWindows()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const theme = await getResolvedThemeSetting();
|
||||||
|
|
||||||
|
let color: string;
|
||||||
|
let symbolColor: string;
|
||||||
|
if (theme === 'light') {
|
||||||
|
color = '#e8e8e8';
|
||||||
|
symbolColor = '#1b1b1b';
|
||||||
|
} else if (theme === 'dark') {
|
||||||
|
color = '#24292e';
|
||||||
|
symbolColor = '#fff';
|
||||||
|
} else {
|
||||||
|
throw missingCaseError(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
color,
|
||||||
|
symbolColor,
|
||||||
|
|
||||||
|
// Should match stylesheets/components/TitleBarContainer.scss minus the
|
||||||
|
// border
|
||||||
|
height: 28 - 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function createWindow() {
|
async function createWindow() {
|
||||||
const usePreloadBundle =
|
const usePreloadBundle =
|
||||||
!isTestEnvironment(getEnvironment()) || forcePreloadBundle;
|
!isTestEnvironment(getEnvironment()) || forcePreloadBundle;
|
||||||
|
|
||||||
|
const titleBarOverlay = await getTitleBarOverlay();
|
||||||
|
|
||||||
const windowOptions: Electron.BrowserWindowConstructorOptions = {
|
const windowOptions: Electron.BrowserWindowConstructorOptions = {
|
||||||
show: false,
|
show: false,
|
||||||
width: DEFAULT_WIDTH,
|
width: DEFAULT_WIDTH,
|
||||||
|
@ -532,11 +595,8 @@ async function createWindow() {
|
||||||
minWidth: MIN_WIDTH,
|
minWidth: MIN_WIDTH,
|
||||||
minHeight: MIN_HEIGHT,
|
minHeight: MIN_HEIGHT,
|
||||||
autoHideMenuBar: false,
|
autoHideMenuBar: false,
|
||||||
titleBarStyle:
|
titleBarStyle: mainTitleBarStyle,
|
||||||
getTitleBarVisibility() === TitleBarVisibility.Hidden &&
|
titleBarOverlay,
|
||||||
!isTestEnvironment(getEnvironment())
|
|
||||||
? 'hidden'
|
|
||||||
: 'default',
|
|
||||||
backgroundColor: isTestEnvironment(getEnvironment())
|
backgroundColor: isTestEnvironment(getEnvironment())
|
||||||
? '#ffffff' // Tests should always be rendered on a white background
|
? '#ffffff' // Tests should always be rendered on a white background
|
||||||
: await getBackgroundColor(),
|
: await getBackgroundColor(),
|
||||||
|
@ -616,7 +676,20 @@ async function createWindow() {
|
||||||
systemTrayService.setMainWindow(mainWindow);
|
systemTrayService.setMainWindow(mainWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
function captureAndSaveWindowStats() {
|
function saveWindowStats() {
|
||||||
|
if (!windowConfig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogger().info(
|
||||||
|
'Updating BrowserWindow config: %s',
|
||||||
|
JSON.stringify(windowConfig)
|
||||||
|
);
|
||||||
|
ephemeralConfig.set('window', windowConfig);
|
||||||
|
}
|
||||||
|
const debouncedSaveStats = debounce(saveWindowStats, 500);
|
||||||
|
|
||||||
|
function captureWindowStats() {
|
||||||
if (!mainWindow) {
|
if (!mainWindow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -624,8 +697,7 @@ async function createWindow() {
|
||||||
const size = mainWindow.getSize();
|
const size = mainWindow.getSize();
|
||||||
const position = mainWindow.getPosition();
|
const position = mainWindow.getPosition();
|
||||||
|
|
||||||
// so if we need to recreate the window, we have the most recent settings
|
const newWindowConfig = {
|
||||||
windowConfig = {
|
|
||||||
maximized: mainWindow.isMaximized(),
|
maximized: mainWindow.isMaximized(),
|
||||||
autoHideMenuBar: mainWindow.autoHideMenuBar,
|
autoHideMenuBar: mainWindow.autoHideMenuBar,
|
||||||
fullscreen: mainWindow.isFullScreen(),
|
fullscreen: mainWindow.isFullScreen(),
|
||||||
|
@ -635,16 +707,24 @@ async function createWindow() {
|
||||||
y: position[1],
|
y: position[1],
|
||||||
};
|
};
|
||||||
|
|
||||||
getLogger().info(
|
if (
|
||||||
'Updating BrowserWindow config: %s',
|
newWindowConfig.fullscreen !== windowConfig?.fullscreen ||
|
||||||
JSON.stringify(windowConfig)
|
newWindowConfig.maximized !== windowConfig?.maximized
|
||||||
);
|
) {
|
||||||
ephemeralConfig.set('window', windowConfig);
|
mainWindow.webContents.send('window:set-window-stats', {
|
||||||
|
isMaximized: newWindowConfig.maximized,
|
||||||
|
isFullScreen: newWindowConfig.fullscreen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// so if we need to recreate the window, we have the most recent settings
|
||||||
|
windowConfig = newWindowConfig;
|
||||||
|
|
||||||
|
debouncedSaveStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
const debouncedCaptureStats = debounce(captureAndSaveWindowStats, 500);
|
mainWindow.on('resize', captureWindowStats);
|
||||||
mainWindow.on('resize', debouncedCaptureStats);
|
mainWindow.on('move', captureWindowStats);
|
||||||
mainWindow.on('move', debouncedCaptureStats);
|
|
||||||
|
|
||||||
const setWindowFocus = () => {
|
const setWindowFocus = () => {
|
||||||
if (!mainWindow) {
|
if (!mainWindow) {
|
||||||
|
@ -681,7 +761,7 @@ async function createWindow() {
|
||||||
mainWindow.webContents.openDevTools();
|
mainWindow.webContents.openDevTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCommonWindowEvents(mainWindow);
|
handleCommonWindowEvents(mainWindow, titleBarOverlay);
|
||||||
|
|
||||||
// App dock icon bounce
|
// App dock icon bounce
|
||||||
bounce.init(mainWindow);
|
bounce.init(mainWindow);
|
||||||
|
@ -981,6 +1061,7 @@ function showScreenShareWindow(sourceName: string) {
|
||||||
resizable: false,
|
resizable: false,
|
||||||
show: false,
|
show: false,
|
||||||
title: getLocale().i18n('screenShareWindow'),
|
title: getLocale().i18n('screenShareWindow'),
|
||||||
|
titleBarStyle: nonMainTitleBarStyle,
|
||||||
width,
|
width,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
...defaultWebPrefs,
|
...defaultWebPrefs,
|
||||||
|
@ -1021,11 +1102,15 @@ async function showAbout() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const titleBarOverlay = await getTitleBarOverlay();
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
width: 500,
|
width: 500,
|
||||||
height: 500,
|
height: 500,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
title: getLocale().i18n('aboutSignalDesktop'),
|
title: getLocale().i18n('aboutSignalDesktop'),
|
||||||
|
titleBarStyle: nonMainTitleBarStyle,
|
||||||
|
titleBarOverlay,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
backgroundColor: await getBackgroundColor(),
|
backgroundColor: await getBackgroundColor(),
|
||||||
show: false,
|
show: false,
|
||||||
|
@ -1041,7 +1126,7 @@ async function showAbout() {
|
||||||
|
|
||||||
aboutWindow = new BrowserWindow(options);
|
aboutWindow = new BrowserWindow(options);
|
||||||
|
|
||||||
handleCommonWindowEvents(aboutWindow);
|
handleCommonWindowEvents(aboutWindow, titleBarOverlay);
|
||||||
|
|
||||||
aboutWindow.loadURL(prepareFileUrl([__dirname, '../about.html']));
|
aboutWindow.loadURL(prepareFileUrl([__dirname, '../about.html']));
|
||||||
|
|
||||||
|
@ -1063,12 +1148,16 @@ async function showSettingsWindow() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const titleBarOverlay = await getTitleBarOverlay();
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
width: 700,
|
width: 700,
|
||||||
height: 700,
|
height: 700,
|
||||||
frame: true,
|
frame: true,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
title: getLocale().i18n('signalDesktopPreferences'),
|
title: getLocale().i18n('signalDesktopPreferences'),
|
||||||
|
titleBarStyle: nonMainTitleBarStyle,
|
||||||
|
titleBarOverlay,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
backgroundColor: await getBackgroundColor(),
|
backgroundColor: await getBackgroundColor(),
|
||||||
show: false,
|
show: false,
|
||||||
|
@ -1084,7 +1173,7 @@ async function showSettingsWindow() {
|
||||||
|
|
||||||
settingsWindow = new BrowserWindow(options);
|
settingsWindow = new BrowserWindow(options);
|
||||||
|
|
||||||
handleCommonWindowEvents(settingsWindow);
|
handleCommonWindowEvents(settingsWindow, titleBarOverlay);
|
||||||
|
|
||||||
settingsWindow.loadURL(prepareFileUrl([__dirname, '../settings.html']));
|
settingsWindow.loadURL(prepareFileUrl([__dirname, '../settings.html']));
|
||||||
|
|
||||||
|
@ -1132,6 +1221,7 @@ async function showStickerCreator() {
|
||||||
|
|
||||||
const { x = 0, y = 0 } = windowConfig || {};
|
const { x = 0, y = 0 } = windowConfig || {};
|
||||||
|
|
||||||
|
// TODO: DESKTOP-3670
|
||||||
const options = {
|
const options = {
|
||||||
x: x + 100,
|
x: x + 100,
|
||||||
y: y + 100,
|
y: y + 100,
|
||||||
|
@ -1191,12 +1281,16 @@ async function showDebugLogWindow() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const titleBarOverlay = await getTitleBarOverlay();
|
||||||
|
|
||||||
const theme = await getThemeSetting();
|
const theme = await getThemeSetting();
|
||||||
const options = {
|
const options = {
|
||||||
width: 700,
|
width: 700,
|
||||||
height: 500,
|
height: 500,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
title: getLocale().i18n('debugLog'),
|
title: getLocale().i18n('debugLog'),
|
||||||
|
titleBarStyle: nonMainTitleBarStyle,
|
||||||
|
titleBarOverlay,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
backgroundColor: await getBackgroundColor(),
|
backgroundColor: await getBackgroundColor(),
|
||||||
show: false,
|
show: false,
|
||||||
|
@ -1218,7 +1312,7 @@ async function showDebugLogWindow() {
|
||||||
|
|
||||||
debugLogWindow = new BrowserWindow(options);
|
debugLogWindow = new BrowserWindow(options);
|
||||||
|
|
||||||
handleCommonWindowEvents(debugLogWindow);
|
handleCommonWindowEvents(debugLogWindow, titleBarOverlay);
|
||||||
|
|
||||||
debugLogWindow.loadURL(
|
debugLogWindow.loadURL(
|
||||||
prepareFileUrl([__dirname, '../debug_log.html'], { theme })
|
prepareFileUrl([__dirname, '../debug_log.html'], { theme })
|
||||||
|
@ -1259,6 +1353,7 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) {
|
||||||
height: Math.min(150, size[1]),
|
height: Math.min(150, size[1]),
|
||||||
resizable: false,
|
resizable: false,
|
||||||
title: getLocale().i18n('allowAccess'),
|
title: getLocale().i18n('allowAccess'),
|
||||||
|
titleBarStyle: nonMainTitleBarStyle,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
backgroundColor: await getBackgroundColor(),
|
backgroundColor: await getBackgroundColor(),
|
||||||
show: false,
|
show: false,
|
||||||
|
@ -1681,9 +1776,9 @@ app.on('ready', async () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
function setupMenu(options?: Partial<MenuOptionsType>) {
|
function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
|
||||||
const { platform } = process;
|
const { platform } = process;
|
||||||
const menuOptions = {
|
menuOptions = {
|
||||||
// options
|
// options
|
||||||
development,
|
development,
|
||||||
devTools: defaultWebPrefs.devTools,
|
devTools: defaultWebPrefs.devTools,
|
||||||
|
@ -1713,6 +1808,14 @@ function setupMenu(options?: Partial<MenuOptionsType>) {
|
||||||
const template = createTemplate(menuOptions, getLocale().messages);
|
const template = createTemplate(menuOptions, getLocale().messages);
|
||||||
const menu = Menu.buildFromTemplate(template);
|
const menu = Menu.buildFromTemplate(template);
|
||||||
Menu.setApplicationMenu(menu);
|
Menu.setApplicationMenu(menu);
|
||||||
|
|
||||||
|
mainWindow?.webContents.send('window:set-menu-options', {
|
||||||
|
development: menuOptions.development,
|
||||||
|
devTools: menuOptions.devTools,
|
||||||
|
includeSetup: menuOptions.includeSetup,
|
||||||
|
isProduction: menuOptions.isProduction,
|
||||||
|
platform: menuOptions.platform,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestShutdown() {
|
async function requestShutdown() {
|
||||||
|
@ -1910,12 +2013,6 @@ ipc.on(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
ipc.on('close-about', () => {
|
|
||||||
if (aboutWindow) {
|
|
||||||
aboutWindow.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.on('close-screen-share-controller', () => {
|
ipc.on('close-screen-share-controller', () => {
|
||||||
if (screenShareWindow) {
|
if (screenShareWindow) {
|
||||||
screenShareWindow.close();
|
screenShareWindow.close();
|
||||||
|
@ -1941,11 +2038,6 @@ ipc.on('update-tray-icon', (_event: Electron.Event, unreadCount: number) => {
|
||||||
// Debug Log-related IPC calls
|
// Debug Log-related IPC calls
|
||||||
|
|
||||||
ipc.on('show-debug-log', showDebugLogWindow);
|
ipc.on('show-debug-log', showDebugLogWindow);
|
||||||
ipc.on('close-debug-log', () => {
|
|
||||||
if (debugLogWindow) {
|
|
||||||
debugLogWindow.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ipc.on(
|
ipc.on(
|
||||||
'show-debug-log-save-dialog',
|
'show-debug-log-save-dialog',
|
||||||
async (_event: Electron.Event, logText: string) => {
|
async (_event: Electron.Event, logText: string) => {
|
||||||
|
@ -1973,11 +2065,6 @@ ipc.handle(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
ipc.on('close-permissions-popup', () => {
|
|
||||||
if (permissionsPopupWindow) {
|
|
||||||
permissionsPopupWindow.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Settings-related IPC calls
|
// Settings-related IPC calls
|
||||||
|
|
||||||
|
@ -1993,11 +2080,6 @@ function removeDarkOverlay() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ipc.on('show-settings', showSettingsWindow);
|
ipc.on('show-settings', showSettingsWindow);
|
||||||
ipc.on('close-settings', () => {
|
|
||||||
if (settingsWindow) {
|
|
||||||
settingsWindow.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.on('delete-all-data', () => {
|
ipc.on('delete-all-data', () => {
|
||||||
if (settingsWindow) {
|
if (settingsWindow) {
|
||||||
|
@ -2188,6 +2270,124 @@ ipc.handle('getScreenCaptureSources', async () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipc.handle('executeMenuRole', async ({ sender }, untypedRole) => {
|
||||||
|
const role = untypedRole as MenuItemConstructorOptions['role'];
|
||||||
|
|
||||||
|
const senderWindow = BrowserWindow.fromWebContents(sender);
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case 'undo':
|
||||||
|
sender.undo();
|
||||||
|
break;
|
||||||
|
case 'redo':
|
||||||
|
sender.redo();
|
||||||
|
break;
|
||||||
|
case 'cut':
|
||||||
|
sender.cut();
|
||||||
|
break;
|
||||||
|
case 'copy':
|
||||||
|
sender.copy();
|
||||||
|
break;
|
||||||
|
case 'paste':
|
||||||
|
sender.paste();
|
||||||
|
break;
|
||||||
|
case 'pasteAndMatchStyle':
|
||||||
|
sender.pasteAndMatchStyle();
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
sender.delete();
|
||||||
|
break;
|
||||||
|
case 'selectAll':
|
||||||
|
sender.selectAll();
|
||||||
|
break;
|
||||||
|
case 'reload':
|
||||||
|
sender.reload();
|
||||||
|
break;
|
||||||
|
case 'toggleDevTools':
|
||||||
|
sender.toggleDevTools();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'resetZoom':
|
||||||
|
sender.setZoomLevel(0);
|
||||||
|
break;
|
||||||
|
case 'zoomIn':
|
||||||
|
sender.setZoomLevel(sender.getZoomLevel() + 1);
|
||||||
|
break;
|
||||||
|
case 'zoomOut':
|
||||||
|
sender.setZoomLevel(sender.getZoomLevel() - 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'togglefullscreen':
|
||||||
|
senderWindow?.setFullScreen(!senderWindow?.isFullScreen());
|
||||||
|
break;
|
||||||
|
case 'minimize':
|
||||||
|
senderWindow?.minimize();
|
||||||
|
break;
|
||||||
|
case 'close':
|
||||||
|
senderWindow?.close();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'quit':
|
||||||
|
app.quit();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// ignored
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.handle('getMainWindowStats', async () => {
|
||||||
|
return {
|
||||||
|
isMaximized: windowConfig?.maximized ?? false,
|
||||||
|
isFullScreen: windowConfig?.fullscreen ?? false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.handle('getMenuOptions', async () => {
|
||||||
|
return {
|
||||||
|
development: menuOptions?.development ?? false,
|
||||||
|
devTools: menuOptions?.devTools ?? false,
|
||||||
|
includeSetup: menuOptions?.includeSetup ?? false,
|
||||||
|
isProduction: menuOptions?.isProduction ?? true,
|
||||||
|
platform: menuOptions?.platform ?? 'unknown',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.handle('executeMenuAction', async (_event, action: MenuActionType) => {
|
||||||
|
if (action === 'forceUpdate') {
|
||||||
|
forceUpdate();
|
||||||
|
} else if (action === 'openContactUs') {
|
||||||
|
openContactUs();
|
||||||
|
} else if (action === 'openForums') {
|
||||||
|
openForums();
|
||||||
|
} else if (action === 'openJoinTheBeta') {
|
||||||
|
openJoinTheBeta();
|
||||||
|
} else if (action === 'openReleaseNotes') {
|
||||||
|
openReleaseNotes();
|
||||||
|
} else if (action === 'openSupportPage') {
|
||||||
|
openSupportPage();
|
||||||
|
} else if (action === 'setupAsNewDevice') {
|
||||||
|
setupAsNewDevice();
|
||||||
|
} else if (action === 'setupAsStandalone') {
|
||||||
|
setupAsStandalone();
|
||||||
|
} else if (action === 'showAbout') {
|
||||||
|
showAbout();
|
||||||
|
} else if (action === 'showDebugLog') {
|
||||||
|
showDebugLogWindow();
|
||||||
|
} else if (action === 'showKeyboardShortcuts') {
|
||||||
|
showKeyboardShortcuts();
|
||||||
|
} else if (action === 'showSettings') {
|
||||||
|
showSettingsWindow();
|
||||||
|
} else if (action === 'showStickerCreator') {
|
||||||
|
showStickerCreator();
|
||||||
|
} else if (action === 'showWindow') {
|
||||||
|
showWindow();
|
||||||
|
} else {
|
||||||
|
throw missingCaseError(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (isTestEnvironment(getEnvironment())) {
|
if (isTestEnvironment(getEnvironment())) {
|
||||||
ipc.handle('ci:test-electron:done', async (_event, info) => {
|
ipc.handle('ci:test-electron:done', async (_event, info) => {
|
||||||
if (!process.env.TEST_QUIT_ON_COMPLETE) {
|
if (!process.env.TEST_QUIT_ON_COMPLETE) {
|
||||||
|
|
41
app/menu.ts
41
app/menu.ts
|
@ -1,40 +1,19 @@
|
||||||
// Copyright 2017-2020 Signal Messenger, LLC
|
// Copyright 2017-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { isString } from 'lodash';
|
import { isString } from 'lodash';
|
||||||
import type { MenuItemConstructorOptions } from 'electron';
|
|
||||||
|
|
||||||
import type { LocaleMessagesType } from '../ts/types/I18N';
|
import type { LocaleMessagesType } from '../ts/types/I18N';
|
||||||
|
import type {
|
||||||
|
MenuListType,
|
||||||
|
MenuOptionsType,
|
||||||
|
MenuActionsType,
|
||||||
|
} from '../ts/types/menu';
|
||||||
|
|
||||||
export type MenuListType = Array<MenuItemConstructorOptions>;
|
export type CreateTemplateOptionsType = MenuOptionsType & MenuActionsType;
|
||||||
|
|
||||||
export type MenuOptionsType = {
|
|
||||||
// options
|
|
||||||
development: boolean;
|
|
||||||
devTools: boolean;
|
|
||||||
includeSetup: boolean;
|
|
||||||
isProduction: boolean;
|
|
||||||
platform: string;
|
|
||||||
|
|
||||||
// actions
|
|
||||||
forceUpdate: () => unknown;
|
|
||||||
openContactUs: () => unknown;
|
|
||||||
openForums: () => unknown;
|
|
||||||
openJoinTheBeta: () => unknown;
|
|
||||||
openReleaseNotes: () => unknown;
|
|
||||||
openSupportPage: () => unknown;
|
|
||||||
setupAsNewDevice: () => unknown;
|
|
||||||
setupAsStandalone: () => unknown;
|
|
||||||
showAbout: () => unknown;
|
|
||||||
showDebugLog: () => unknown;
|
|
||||||
showKeyboardShortcuts: () => unknown;
|
|
||||||
showSettings: () => unknown;
|
|
||||||
showStickerCreator: () => unknown;
|
|
||||||
showWindow: () => unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createTemplate = (
|
export const createTemplate = (
|
||||||
options: MenuOptionsType,
|
options: CreateTemplateOptionsType,
|
||||||
messages: LocaleMessagesType
|
messages: LocaleMessagesType
|
||||||
): MenuListType => {
|
): MenuListType => {
|
||||||
if (!isString(options.platform)) {
|
if (!isString(options.platform)) {
|
||||||
|
@ -131,7 +110,7 @@ export const createTemplate = (
|
||||||
label: messages.viewMenuResetZoom.message,
|
label: messages.viewMenuResetZoom.message,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accelerator: platform === 'darwin' ? 'Command+=' : 'Control+=',
|
accelerator: 'CmdOrCtrl+=',
|
||||||
role: 'zoomIn',
|
role: 'zoomIn',
|
||||||
label: messages.viewMenuZoomIn.message,
|
label: messages.viewMenuZoomIn.message,
|
||||||
},
|
},
|
||||||
|
@ -265,7 +244,7 @@ export const createTemplate = (
|
||||||
function updateForMac(
|
function updateForMac(
|
||||||
template: MenuListType,
|
template: MenuListType,
|
||||||
messages: LocaleMessagesType,
|
messages: LocaleMessagesType,
|
||||||
options: MenuOptionsType
|
options: CreateTemplateOptionsType
|
||||||
): MenuListType {
|
): MenuListType {
|
||||||
const { showAbout, showSettings, showWindow } = options;
|
const { showAbout, showSettings, showWindow } = options;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { fileURLToPath } from 'url';
|
||||||
import { maybeParseUrl } from '../ts/util/url';
|
import { maybeParseUrl } from '../ts/util/url';
|
||||||
import type { LocaleType } from './locale';
|
import type { LocaleType } from './locale';
|
||||||
|
|
||||||
import type { MenuListType } from './menu';
|
import type { MenuListType } from '../ts/types/menu';
|
||||||
|
|
||||||
export function getLanguages(
|
export function getLanguages(
|
||||||
userLocale: string,
|
userLocale: string,
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
img-src 'self' blob: data:;
|
img-src 'self' blob: data:;
|
||||||
media-src 'self' blob:;
|
media-src 'self' blob:;
|
||||||
object-src 'none';
|
object-src 'none';
|
||||||
script-src 'self' 'sha256-eLeGwSfPmXJ+EUiLfIeXABvLiUqDbiKgNLpHITaabgQ=';
|
script-src 'self' 'sha256-Qu05oqDmBO5fZacm7tr/oerJcqsW0G/XqP4PRCziovc=' 'sha256-eLeGwSfPmXJ+EUiLfIeXABvLiUqDbiKgNLpHITaabgQ=';
|
||||||
style-src 'self' 'unsafe-inline';"
|
style-src 'self' 'unsafe-inline';"
|
||||||
/>
|
/>
|
||||||
<title>Signal</title>
|
<title>Signal</title>
|
||||||
|
@ -81,6 +81,11 @@
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
/>
|
/>
|
||||||
|
<link
|
||||||
|
href="node_modules/@indutny/frameless-titlebar/dist/styles.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
/>
|
||||||
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
@ -152,6 +157,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
document
|
||||||
|
.querySelector('.app-loading-screen')
|
||||||
|
.addEventListener('dblclick', () => window.showDebugLog());
|
||||||
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript" src="js/components.js"></script>
|
<script type="text/javascript" src="js/components.js"></script>
|
||||||
<script type="text/javascript" src="ts/set_os_class.js"></script>
|
<script type="text/javascript" src="ts/set_os_class.js"></script>
|
||||||
<script
|
<script
|
||||||
|
@ -164,13 +175,6 @@
|
||||||
src="ts/backbone/reliable_trigger.js"
|
src="ts/backbone/reliable_trigger.js"
|
||||||
></script>
|
></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="js/libphonenumber-util.js"></script>
|
|
||||||
<script type="text/javascript" src="js/expiring_messages.js"></script>
|
|
||||||
<script
|
|
||||||
type="text/javascript"
|
|
||||||
src="js/expiring_tap_to_view_messages.js"
|
|
||||||
></script>
|
|
||||||
|
|
||||||
<script
|
<script
|
||||||
type="text/javascript"
|
type="text/javascript"
|
||||||
src="js/views/react_wrapper_view.js"
|
src="js/views/react_wrapper_view.js"
|
||||||
|
|
|
@ -20,6 +20,11 @@
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
/>
|
/>
|
||||||
|
<link
|
||||||
|
href="node_modules/@indutny/frameless-titlebar/dist/styles.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
/>
|
||||||
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -76,6 +76,7 @@
|
||||||
"fs-xattr": "0.3.0"
|
"fs-xattr": "0.3.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@indutny/frameless-titlebar": "2.1.4-rc.8",
|
||||||
"@popperjs/core": "2.9.2",
|
"@popperjs/core": "2.9.2",
|
||||||
"@react-spring/web": "9.4.5",
|
"@react-spring/web": "9.4.5",
|
||||||
"@signalapp/libsignal-client": "0.16.0",
|
"@signalapp/libsignal-client": "0.16.0",
|
||||||
|
|
14
preload.js
14
preload.js
|
@ -233,6 +233,20 @@ try {
|
||||||
Whisper.events.trigger('powerMonitorLockScreen');
|
Whisper.events.trigger('powerMonitorLockScreen');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipc.on('window:set-window-stats', (_event, stats) => {
|
||||||
|
if (!Whisper.events) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Whisper.events.trigger('setWindowStats', stats);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.on('window:set-menu-options', (_event, options) => {
|
||||||
|
if (!Whisper.events) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Whisper.events.trigger('setMenuOptions', options);
|
||||||
|
});
|
||||||
|
|
||||||
window.sendChallengeRequest = request =>
|
window.sendChallengeRequest = request =>
|
||||||
ipc.send('challenge:request', request);
|
ipc.send('challenge:request', request);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,11 @@
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
/>
|
/>
|
||||||
|
<link
|
||||||
|
href="node_modules/@indutny/frameless-titlebar/dist/styles.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
/>
|
||||||
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
.facade {
|
.facade {
|
||||||
background: rgba(0, 0, 0, 0.33);
|
background: rgba(0, 0, 0, 0.33);
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -4,6 +4,11 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<link
|
||||||
|
href="../../node_modules/@indutny/frameless-titlebar/src/title-bar/style.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
/>
|
||||||
<link rel="stylesheet" href="../../stylesheets/manifest_bridge.css" />
|
<link rel="stylesheet" href="../../stylesheets/manifest_bridge.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -254,7 +254,7 @@ const getThemeSetting = createSetting('themeSetting');
|
||||||
|
|
||||||
async function resolveTheme() {
|
async function resolveTheme() {
|
||||||
const theme = (await getThemeSetting.getValue()) || 'system';
|
const theme = (await getThemeSetting.getValue()) || 'system';
|
||||||
if (process.platform === 'darwin' && theme === 'system') {
|
if (theme === 'system') {
|
||||||
return SignalContext.nativeThemeListener.getSystemTheme();
|
return SignalContext.nativeThemeListener.getSystemTheme();
|
||||||
}
|
}
|
||||||
return theme;
|
return theme;
|
||||||
|
|
|
@ -27,6 +27,14 @@ body {
|
||||||
--draggable-app-region: drag;
|
--draggable-app-region: drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--window-height: 100vh;
|
||||||
|
--titlebar-height: 0px;
|
||||||
|
|
||||||
|
&.os-windows:not(.full-screen) {
|
||||||
|
--titlebar-height: 28px;
|
||||||
|
--window-height: calc(100vh - var(--titlebar-height));
|
||||||
|
}
|
||||||
|
|
||||||
&.light-theme {
|
&.light-theme {
|
||||||
background-color: $color-white;
|
background-color: $color-white;
|
||||||
color: $color-gray-90;
|
color: $color-gray-90;
|
||||||
|
@ -236,6 +244,9 @@ $loading-height: 16px;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
||||||
|
/* There is no titlebar during loading screen on Windows */
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
|
||||||
/* Note: background-color is intentionally transparent until body has the
|
/* Note: background-color is intentionally transparent until body has the
|
||||||
* theme class.
|
* theme class.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -672,7 +672,7 @@
|
||||||
@mixin install-screen {
|
@mixin install-screen {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
|
@ -3741,7 +3741,7 @@ button.module-image__border-overlay:focus {
|
||||||
background-color: $calling-background-color;
|
background-color: $calling-background-color;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -3855,7 +3855,7 @@ button.module-image__border-overlay:focus {
|
||||||
|
|
||||||
&__remote-video-disabled {
|
&__remote-video-disabled {
|
||||||
background-color: $color-gray-95;
|
background-color: $color-gray-95;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -4292,7 +4292,7 @@ button.module-image__border-overlay:focus {
|
||||||
|
|
||||||
&__overlay {
|
&__overlay {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
left: 0;
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -4415,7 +4415,7 @@ button.module-image__border-overlay:focus {
|
||||||
color: $color-gray-05;
|
color: $color-gray-05;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -5983,7 +5983,7 @@ button.module-image__border-overlay:focus {
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -7281,7 +7281,7 @@ button.module-image__border-overlay:focus {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|
||||||
max-height: calc(100vh - 40px);
|
max-height: calc(var(--window-height) - 40px);
|
||||||
max-width: 1150px;
|
max-width: 1150px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
@ -7626,7 +7626,7 @@ button.module-image__border-overlay:focus {
|
||||||
|
|
||||||
.module-modal-host__overlay {
|
.module-modal-host__overlay {
|
||||||
background: $color-black-alpha-40;
|
background: $color-black-alpha-40;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
left: 0;
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -7637,7 +7637,7 @@ button.module-image__border-overlay:focus {
|
||||||
.module-modal-host__overlay-container {
|
.module-modal-host__overlay-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
left: 0;
|
left: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -7784,7 +7784,7 @@ button.module-image__border-overlay:focus {
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -257,6 +257,7 @@ $z-index-context-menu: 125;
|
||||||
$z-index-tooltip: 150;
|
$z-index-tooltip: 150;
|
||||||
$z-index-toast: 200;
|
$z-index-toast: 200;
|
||||||
$z-index-on-top-of-everything: 9000;
|
$z-index-on-top-of-everything: 9000;
|
||||||
|
$z-index-window-controls: 10000;
|
||||||
|
|
||||||
// Component specific
|
// Component specific
|
||||||
// The scroll down button should be above everything in the timeline but
|
// The scroll down button should be above everything in the timeline but
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
// TitleBar support
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
&.light-theme {
|
&.light-theme {
|
||||||
background-color: $color-white;
|
background-color: $color-white;
|
||||||
color: $color-gray-90;
|
color: $color-gray-90;
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: var(--titlebar-height);
|
||||||
z-index: $z-index-popup;
|
z-index: $z-index-popup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
background: $color-gray-95;
|
background: $color-gray-95;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
left: 0;
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: var(--titlebar-height);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
z-index: $z-index-popup-overlay;
|
z-index: $z-index-popup-overlay;
|
||||||
|
|
|
@ -120,7 +120,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__settings-pane {
|
&__settings-pane {
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
overflow: overlay;
|
overflow: overlay;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
.Stories {
|
.Stories {
|
||||||
background: $color-gray-95;
|
background: $color-gray-95;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
left: 0;
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: var(--titlebar-height);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: $z-index-stories;
|
z-index: $z-index-stories;
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
.StoryViewer {
|
.StoryViewer {
|
||||||
&__overlay {
|
&__overlay {
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
left: 0;
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: var(--titlebar-height);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: $z-index-popup-overlay;
|
z-index: $z-index-popup-overlay;
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,11 @@
|
||||||
background: $color-black-alpha-20;
|
background: $color-black-alpha-20;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
left: 0;
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: var(--titlebar-height);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: $z-index-popup-overlay;
|
z-index: $z-index-popup-overlay;
|
||||||
}
|
}
|
||||||
|
@ -238,7 +238,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__animated-emojis {
|
&__animated-emojis {
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: $z-index-above-base;
|
z-index: $z-index-above-base;
|
||||||
|
@ -248,7 +248,7 @@
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100vh;
|
height: var(--window-height);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 25%;
|
width: 25%;
|
||||||
z-index: $z-index-above-above-base;
|
z-index: $z-index-above-above-base;
|
||||||
|
|
31
stylesheets/components/TitleBarContainer.scss
Normal file
31
stylesheets/components/TitleBarContainer.scss
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
.TitleBarContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: $z-index-window-controls;
|
||||||
|
|
||||||
|
// This matches the inline styles of frameless-titlebar
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||||
|
Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', Arial,
|
||||||
|
sans-serif;
|
||||||
|
|
||||||
|
& button {
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
margin-top: var(--titlebar-height);
|
||||||
|
height: var(--window-height);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
|
@ -114,5 +114,6 @@
|
||||||
@import './components/TimelineFloatingHeader.scss';
|
@import './components/TimelineFloatingHeader.scss';
|
||||||
@import './components/TimelineWarning.scss';
|
@import './components/TimelineWarning.scss';
|
||||||
@import './components/TimelineWarnings.scss';
|
@import './components/TimelineWarnings.scss';
|
||||||
|
@import './components/TitleBarContainer.scss';
|
||||||
@import './components/Toast.scss';
|
@import './components/Toast.scss';
|
||||||
@import './components/WhatsNew.scss';
|
@import './components/WhatsNew.scss';
|
||||||
|
|
|
@ -26,8 +26,8 @@ import * as Bytes from './Bytes';
|
||||||
import * as Timers from './Timers';
|
import * as Timers from './Timers';
|
||||||
import * as indexedDb from './indexeddb';
|
import * as indexedDb from './indexeddb';
|
||||||
import type { WhatIsThis } from './window.d';
|
import type { WhatIsThis } from './window.d';
|
||||||
|
import type { MenuOptionsType } from './types/menu';
|
||||||
import type { Receipt } from './types/Receipt';
|
import type { Receipt } from './types/Receipt';
|
||||||
import { getTitleBarVisibility, TitleBarVisibility } from './types/Settings';
|
|
||||||
import { SocketStatus } from './types/SocketStatus';
|
import { SocketStatus } from './types/SocketStatus';
|
||||||
import { DEFAULT_CONVERSATION_COLOR } from './types/Colors';
|
import { DEFAULT_CONVERSATION_COLOR } from './types/Colors';
|
||||||
import { ThemeType } from './types/Util';
|
import { ThemeType } from './types/Util';
|
||||||
|
@ -141,6 +141,7 @@ import { ToastConversationArchived } from './components/ToastConversationArchive
|
||||||
import { ToastConversationUnarchived } from './components/ToastConversationUnarchived';
|
import { ToastConversationUnarchived } from './components/ToastConversationUnarchived';
|
||||||
import { showToast } from './util/showToast';
|
import { showToast } from './util/showToast';
|
||||||
import { startInteractionMode } from './windows/startInteractionMode';
|
import { startInteractionMode } from './windows/startInteractionMode';
|
||||||
|
import type { MainWindowStatsType } from './windows/context';
|
||||||
import { deliveryReceiptsJobQueue } from './jobs/deliveryReceiptsJobQueue';
|
import { deliveryReceiptsJobQueue } from './jobs/deliveryReceiptsJobQueue';
|
||||||
import { updateOurUsername } from './util/updateOurUsername';
|
import { updateOurUsername } from './util/updateOurUsername';
|
||||||
import { ReactionSource } from './reactions/ReactionSource';
|
import { ReactionSource } from './reactions/ReactionSource';
|
||||||
|
@ -452,7 +453,7 @@ export async function startApp(): Promise<void> {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (getTitleBarVisibility() === TitleBarVisibility.Hidden) {
|
if (window.platform === 'darwin') {
|
||||||
window.addEventListener('dblclick', (event: Event) => {
|
window.addEventListener('dblclick', (event: Event) => {
|
||||||
const target = event.target as HTMLElement;
|
const target = event.target as HTMLElement;
|
||||||
if (isWindowDragElement(target)) {
|
if (isWindowDragElement(target)) {
|
||||||
|
@ -930,6 +931,19 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
}, FIVE_MINUTES);
|
}, FIVE_MINUTES);
|
||||||
|
|
||||||
|
let mainWindowStats = {
|
||||||
|
isMaximized: false,
|
||||||
|
isFullScreen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let menuOptions = {
|
||||||
|
development: false,
|
||||||
|
devTools: false,
|
||||||
|
includeSetup: false,
|
||||||
|
isProduction: true,
|
||||||
|
platform: 'unknown',
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
window.ConversationController.load(),
|
window.ConversationController.load(),
|
||||||
|
@ -938,6 +952,12 @@ export async function startApp(): Promise<void> {
|
||||||
loadInitialBadgesState(),
|
loadInitialBadgesState(),
|
||||||
loadStories(),
|
loadStories(),
|
||||||
window.textsecure.storage.protocol.hydrateCaches(),
|
window.textsecure.storage.protocol.hydrateCaches(),
|
||||||
|
(async () => {
|
||||||
|
mainWindowStats = await window.SignalContext.getMainWindowStats();
|
||||||
|
})(),
|
||||||
|
(async () => {
|
||||||
|
menuOptions = await window.SignalContext.getMenuOptions();
|
||||||
|
})(),
|
||||||
]);
|
]);
|
||||||
await window.ConversationController.checkForConflicts();
|
await window.ConversationController.checkForConflicts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -946,7 +966,7 @@ export async function startApp(): Promise<void> {
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
initializeRedux();
|
initializeRedux({ mainWindowStats, menuOptions });
|
||||||
start();
|
start();
|
||||||
window.Signal.Services.initializeNetworkObserver(
|
window.Signal.Services.initializeNetworkObserver(
|
||||||
window.reduxActions.network
|
window.reduxActions.network
|
||||||
|
@ -964,12 +984,20 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function initializeRedux() {
|
function initializeRedux({
|
||||||
|
mainWindowStats,
|
||||||
|
menuOptions,
|
||||||
|
}: {
|
||||||
|
mainWindowStats: MainWindowStatsType;
|
||||||
|
menuOptions: MenuOptionsType;
|
||||||
|
}) {
|
||||||
// Here we set up a full redux store with initial state for our LeftPane Root
|
// Here we set up a full redux store with initial state for our LeftPane Root
|
||||||
const convoCollection = window.getConversations();
|
const convoCollection = window.getConversations();
|
||||||
const initialState = getInitialState({
|
const initialState = getInitialState({
|
||||||
badges: initialBadgesState,
|
badges: initialBadgesState,
|
||||||
stories: getStoriesForRedux(),
|
stories: getStoriesForRedux(),
|
||||||
|
mainWindowStats,
|
||||||
|
menuOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = window.Signal.State.createStore(initialState);
|
const store = window.Signal.State.createStore(initialState);
|
||||||
|
@ -1110,6 +1138,26 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.Whisper.events.on(
|
||||||
|
'setWindowStats',
|
||||||
|
({
|
||||||
|
isFullScreen,
|
||||||
|
isMaximized,
|
||||||
|
}: {
|
||||||
|
isFullScreen: boolean;
|
||||||
|
isMaximized: boolean;
|
||||||
|
}) => {
|
||||||
|
window.reduxActions.user.userChanged({
|
||||||
|
isMainWindowMaximized: isMaximized,
|
||||||
|
isMainWindowFullScreen: isFullScreen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
window.Whisper.events.on('setMenuOptions', (options: MenuOptionsType) => {
|
||||||
|
window.reduxActions.user.userChanged({ menuOptions: options });
|
||||||
|
});
|
||||||
|
|
||||||
let shortcutGuideView: WhatIsThis | null = null;
|
let shortcutGuideView: WhatIsThis | null = null;
|
||||||
|
|
||||||
window.showKeyboardShortcuts = () => {
|
window.showKeyboardShortcuts = () => {
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||||
|
import { useTheme } from '../hooks/useTheme';
|
||||||
|
import { TitleBarContainer } from './TitleBarContainer';
|
||||||
|
import type { ExecuteMenuRoleType } from './TitleBarContainer';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
closeAbout: () => unknown;
|
closeAbout: () => unknown;
|
||||||
environment: string;
|
environment: string;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
version: string;
|
version: string;
|
||||||
|
platform: string;
|
||||||
|
executeMenuRole: ExecuteMenuRoleType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const About = ({
|
export const About = ({
|
||||||
|
@ -17,34 +23,45 @@ export const About = ({
|
||||||
i18n,
|
i18n,
|
||||||
environment,
|
environment,
|
||||||
version,
|
version,
|
||||||
|
platform,
|
||||||
|
executeMenuRole,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element => {
|
||||||
useEscapeHandling(closeAbout);
|
useEscapeHandling(closeAbout);
|
||||||
|
|
||||||
return (
|
const theme = useTheme();
|
||||||
<div className="About">
|
|
||||||
<div className="module-splash-screen">
|
|
||||||
<div className="module-splash-screen__logo module-img--150" />
|
|
||||||
|
|
||||||
<div className="version">{version}</div>
|
return (
|
||||||
<div className="environment">{environment}</div>
|
<TitleBarContainer
|
||||||
<div>
|
platform={platform}
|
||||||
<a href="https://signal.org">signal.org</a>
|
theme={theme}
|
||||||
</div>
|
executeMenuRole={executeMenuRole}
|
||||||
<br />
|
title={i18n('aboutSignalDesktop')}
|
||||||
<div>
|
>
|
||||||
<a
|
<div className="About">
|
||||||
className="acknowledgments"
|
<div className="module-splash-screen">
|
||||||
href="https://github.com/signalapp/Signal-Desktop/blob/main/ACKNOWLEDGMENTS.md"
|
<div className="module-splash-screen__logo module-img--150" />
|
||||||
>
|
|
||||||
{i18n('softwareAcknowledgments')}
|
<div className="version">{version}</div>
|
||||||
</a>
|
<div className="environment">{environment}</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
<a href="https://signal.org">signal.org</a>
|
||||||
<a className="privacy" href="https://signal.org/legal">
|
</div>
|
||||||
{i18n('privacyPolicy')}
|
<br />
|
||||||
</a>
|
<div>
|
||||||
|
<a
|
||||||
|
className="acknowledgments"
|
||||||
|
href="https://github.com/signalapp/Signal-Desktop/blob/main/ACKNOWLEDGMENTS.md"
|
||||||
|
>
|
||||||
|
{i18n('softwareAcknowledgments')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a className="privacy" href="https://signal.org/legal">
|
||||||
|
{i18n('privacyPolicy')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</TitleBarContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { ComponentProps } from 'react';
|
import type { ComponentProps } from 'react';
|
||||||
|
@ -11,11 +11,16 @@ import { Inbox } from './Inbox';
|
||||||
import { SmartInstallScreen } from '../state/smart/InstallScreen';
|
import { SmartInstallScreen } from '../state/smart/InstallScreen';
|
||||||
import { StandaloneRegistration } from './StandaloneRegistration';
|
import { StandaloneRegistration } from './StandaloneRegistration';
|
||||||
import { ThemeType } from '../types/Util';
|
import { ThemeType } from '../types/Util';
|
||||||
|
import type { LocaleMessagesType } from '../types/I18N';
|
||||||
import { usePageVisibility } from '../hooks/usePageVisibility';
|
import { usePageVisibility } from '../hooks/usePageVisibility';
|
||||||
import { useReducedMotion } from '../hooks/useReducedMotion';
|
import { useReducedMotion } from '../hooks/useReducedMotion';
|
||||||
|
import type { MenuOptionsType, MenuActionType } from '../types/menu';
|
||||||
|
import { TitleBarContainer } from './TitleBarContainer';
|
||||||
|
import type { ExecuteMenuRoleType } from './TitleBarContainer';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
appView: AppViewType;
|
appView: AppViewType;
|
||||||
|
localeMessages: LocaleMessagesType;
|
||||||
openInbox: () => void;
|
openInbox: () => void;
|
||||||
registerSingleDevice: (number: string, code: string) => Promise<void>;
|
registerSingleDevice: (number: string, code: string) => Promise<void>;
|
||||||
renderCallManager: () => JSX.Element;
|
renderCallManager: () => JSX.Element;
|
||||||
|
@ -28,6 +33,14 @@ type PropsType = {
|
||||||
token: string
|
token: string
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
|
isMaximized: boolean;
|
||||||
|
isFullScreen: boolean;
|
||||||
|
menuOptions: MenuOptionsType;
|
||||||
|
platform: string;
|
||||||
|
|
||||||
|
executeMenuRole: ExecuteMenuRoleType;
|
||||||
|
executeMenuAction: (action: MenuActionType) => void;
|
||||||
|
titleBarDoubleClick: () => void;
|
||||||
} & ComponentProps<typeof Inbox>;
|
} & ComponentProps<typeof Inbox>;
|
||||||
|
|
||||||
export const App = ({
|
export const App = ({
|
||||||
|
@ -39,6 +52,11 @@ export const App = ({
|
||||||
i18n,
|
i18n,
|
||||||
isCustomizingPreferredReactions,
|
isCustomizingPreferredReactions,
|
||||||
isShowingStoriesView,
|
isShowingStoriesView,
|
||||||
|
isMaximized,
|
||||||
|
isFullScreen,
|
||||||
|
menuOptions,
|
||||||
|
platform,
|
||||||
|
localeMessages,
|
||||||
renderCallManager,
|
renderCallManager,
|
||||||
renderCustomizingPreferredReactionsModal,
|
renderCustomizingPreferredReactionsModal,
|
||||||
renderGlobalModalContainer,
|
renderGlobalModalContainer,
|
||||||
|
@ -49,6 +67,9 @@ export const App = ({
|
||||||
registerSingleDevice,
|
registerSingleDevice,
|
||||||
theme,
|
theme,
|
||||||
verifyConversationsStoppingSend,
|
verifyConversationsStoppingSend,
|
||||||
|
executeMenuAction,
|
||||||
|
executeMenuRole,
|
||||||
|
titleBarDoubleClick,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element => {
|
||||||
let contents;
|
let contents;
|
||||||
|
|
||||||
|
@ -113,17 +134,31 @@ export const App = ({
|
||||||
}, [prefersReducedMotion]);
|
}, [prefersReducedMotion]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<TitleBarContainer
|
||||||
className={classNames({
|
title="Signal"
|
||||||
App: true,
|
theme={theme}
|
||||||
'light-theme': theme === ThemeType.light,
|
isMaximized={isMaximized}
|
||||||
'dark-theme': theme === ThemeType.dark,
|
isFullScreen={isFullScreen}
|
||||||
})}
|
platform={platform}
|
||||||
|
hasMenu
|
||||||
|
localeMessages={localeMessages}
|
||||||
|
menuOptions={menuOptions}
|
||||||
|
executeMenuRole={executeMenuRole}
|
||||||
|
executeMenuAction={executeMenuAction}
|
||||||
|
titleBarDoubleClick={titleBarDoubleClick}
|
||||||
>
|
>
|
||||||
{renderGlobalModalContainer()}
|
<div
|
||||||
{renderCallManager()}
|
className={classNames({
|
||||||
{isShowingStoriesView && renderStories()}
|
App: true,
|
||||||
{contents}
|
'light-theme': theme === ThemeType.light,
|
||||||
</div>
|
'dark-theme': theme === ThemeType.dark,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{renderGlobalModalContainer()}
|
||||||
|
{renderCallManager()}
|
||||||
|
{isShowingStoriesView && renderStories()}
|
||||||
|
{contents}
|
||||||
|
</div>
|
||||||
|
</TitleBarContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,8 @@ const createProps = (): PropsType => ({
|
||||||
await sleep(5000);
|
await sleep(5000);
|
||||||
return 'https://picsum.photos/1800/900';
|
return 'https://picsum.photos/1800/900';
|
||||||
},
|
},
|
||||||
|
executeMenuRole: action('executeMenuRole'),
|
||||||
|
platform: 'win32',
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -10,10 +10,13 @@ import type { LocalizerType } from '../types/Util';
|
||||||
import { Spinner } from './Spinner';
|
import { Spinner } from './Spinner';
|
||||||
import { ToastDebugLogError } from './ToastDebugLogError';
|
import { ToastDebugLogError } from './ToastDebugLogError';
|
||||||
import { ToastLinkCopied } from './ToastLinkCopied';
|
import { ToastLinkCopied } from './ToastLinkCopied';
|
||||||
|
import { TitleBarContainer } from './TitleBarContainer';
|
||||||
|
import type { ExecuteMenuRoleType } from './TitleBarContainer';
|
||||||
import { ToastLoadingFullLogs } from './ToastLoadingFullLogs';
|
import { ToastLoadingFullLogs } from './ToastLoadingFullLogs';
|
||||||
import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
|
import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
|
||||||
import { createSupportUrl } from '../util/createSupportUrl';
|
import { createSupportUrl } from '../util/createSupportUrl';
|
||||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||||
|
import { useTheme } from '../hooks/useTheme';
|
||||||
|
|
||||||
enum LoadState {
|
enum LoadState {
|
||||||
NotStarted,
|
NotStarted,
|
||||||
|
@ -28,6 +31,8 @@ export type PropsType = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
fetchLogs: () => Promise<string>;
|
fetchLogs: () => Promise<string>;
|
||||||
uploadLogs: (logs: string) => Promise<string>;
|
uploadLogs: (logs: string) => Promise<string>;
|
||||||
|
platform: string;
|
||||||
|
executeMenuRole: ExecuteMenuRoleType;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ToastType {
|
enum ToastType {
|
||||||
|
@ -42,6 +47,8 @@ export const DebugLogWindow = ({
|
||||||
i18n,
|
i18n,
|
||||||
fetchLogs,
|
fetchLogs,
|
||||||
uploadLogs,
|
uploadLogs,
|
||||||
|
platform,
|
||||||
|
executeMenuRole,
|
||||||
}: PropsType): JSX.Element => {
|
}: PropsType): JSX.Element => {
|
||||||
const [loadState, setLoadState] = useState<LoadState>(LoadState.NotStarted);
|
const [loadState, setLoadState] = useState<LoadState>(LoadState.NotStarted);
|
||||||
const [logText, setLogText] = useState<string | undefined>();
|
const [logText, setLogText] = useState<string | undefined>();
|
||||||
|
@ -49,6 +56,8 @@ export const DebugLogWindow = ({
|
||||||
const [textAreaValue, setTextAreaValue] = useState<string>(i18n('loading'));
|
const [textAreaValue, setTextAreaValue] = useState<string>(i18n('loading'));
|
||||||
const [toastType, setToastType] = useState<ToastType | undefined>();
|
const [toastType, setToastType] = useState<ToastType | undefined>();
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
useEscapeHandling(closeWindow);
|
useEscapeHandling(closeWindow);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -135,32 +144,41 @@ export const DebugLogWindow = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="DebugLogWindow">
|
<TitleBarContainer
|
||||||
<div>
|
platform={platform}
|
||||||
<div className="DebugLogWindow__title">{i18n('debugLogSuccess')}</div>
|
theme={theme}
|
||||||
<p className="DebugLogWindow__subtitle">
|
executeMenuRole={executeMenuRole}
|
||||||
{i18n('debugLogSuccessNextSteps')}
|
title={i18n('debugLog')}
|
||||||
</p>
|
>
|
||||||
|
<div className="DebugLogWindow">
|
||||||
|
<div>
|
||||||
|
<div className="DebugLogWindow__title">
|
||||||
|
{i18n('debugLogSuccess')}
|
||||||
|
</div>
|
||||||
|
<p className="DebugLogWindow__subtitle">
|
||||||
|
{i18n('debugLogSuccessNextSteps')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="DebugLogWindow__container">
|
||||||
|
<input
|
||||||
|
className="DebugLogWindow__link"
|
||||||
|
readOnly
|
||||||
|
type="text"
|
||||||
|
value={publicLogURL}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="DebugLogWindow__footer">
|
||||||
|
<Button
|
||||||
|
onClick={() => openLinkInWebBrowser(supportURL)}
|
||||||
|
variant={ButtonVariant.Secondary}
|
||||||
|
>
|
||||||
|
{i18n('reportIssue')}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={copyLog}>{i18n('debugLogCopy')}</Button>
|
||||||
|
</div>
|
||||||
|
{toastElement}
|
||||||
</div>
|
</div>
|
||||||
<div className="DebugLogWindow__container">
|
</TitleBarContainer>
|
||||||
<input
|
|
||||||
className="DebugLogWindow__link"
|
|
||||||
readOnly
|
|
||||||
type="text"
|
|
||||||
value={publicLogURL}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="DebugLogWindow__footer">
|
|
||||||
<Button
|
|
||||||
onClick={() => openLinkInWebBrowser(supportURL)}
|
|
||||||
variant={ButtonVariant.Secondary}
|
|
||||||
>
|
|
||||||
{i18n('reportIssue')}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={copyLog}>{i18n('debugLogCopy')}</Button>
|
|
||||||
</div>
|
|
||||||
{toastElement}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,43 +188,50 @@ export const DebugLogWindow = ({
|
||||||
loadState === LoadState.Started || loadState === LoadState.Submitting;
|
loadState === LoadState.Started || loadState === LoadState.Submitting;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="DebugLogWindow">
|
<TitleBarContainer
|
||||||
<div>
|
platform={platform}
|
||||||
<div className="DebugLogWindow__title">{i18n('submitDebugLog')}</div>
|
theme={theme}
|
||||||
<p className="DebugLogWindow__subtitle">
|
executeMenuRole={executeMenuRole}
|
||||||
{i18n('debugLogExplanation')}
|
title={i18n('debugLog')}
|
||||||
</p>
|
>
|
||||||
|
<div className="DebugLogWindow">
|
||||||
|
<div>
|
||||||
|
<div className="DebugLogWindow__title">{i18n('submitDebugLog')}</div>
|
||||||
|
<p className="DebugLogWindow__subtitle">
|
||||||
|
{i18n('debugLogExplanation')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="DebugLogWindow__container">
|
||||||
|
{isLoading ? (
|
||||||
|
<Spinner svgSize="normal" />
|
||||||
|
) : (
|
||||||
|
<textarea
|
||||||
|
className="DebugLogWindow__textarea"
|
||||||
|
readOnly
|
||||||
|
rows={5}
|
||||||
|
spellCheck={false}
|
||||||
|
value={textAreaValue}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="DebugLogWindow__footer">
|
||||||
|
<Button
|
||||||
|
disabled={!canSave}
|
||||||
|
onClick={() => {
|
||||||
|
if (logText) {
|
||||||
|
downloadLog(logText);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
variant={ButtonVariant.Secondary}
|
||||||
|
>
|
||||||
|
{i18n('debugLogSave')}
|
||||||
|
</Button>
|
||||||
|
<Button disabled={!canSubmit} onClick={handleSubmit}>
|
||||||
|
{i18n('submit')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{toastElement}
|
||||||
</div>
|
</div>
|
||||||
<div className="DebugLogWindow__container">
|
</TitleBarContainer>
|
||||||
{isLoading ? (
|
|
||||||
<Spinner svgSize="normal" />
|
|
||||||
) : (
|
|
||||||
<textarea
|
|
||||||
className="DebugLogWindow__textarea"
|
|
||||||
readOnly
|
|
||||||
rows={5}
|
|
||||||
spellCheck={false}
|
|
||||||
value={textAreaValue}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="DebugLogWindow__footer">
|
|
||||||
<Button
|
|
||||||
disabled={!canSave}
|
|
||||||
onClick={() => {
|
|
||||||
if (logText) {
|
|
||||||
downloadLog(logText);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
variant={ButtonVariant.Secondary}
|
|
||||||
>
|
|
||||||
{i18n('debugLogSave')}
|
|
||||||
</Button>
|
|
||||||
<Button disabled={!canSubmit} onClick={handleSubmit}>
|
|
||||||
{i18n('submit')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{toastElement}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -103,8 +103,19 @@ export const ModalHost = React.memo(
|
||||||
useFocusTrap ? (
|
useFocusTrap ? (
|
||||||
<FocusTrap
|
<FocusTrap
|
||||||
focusTrapOptions={{
|
focusTrapOptions={{
|
||||||
// This is alright because the overlay covers the entire screen
|
allowOutsideClick: ({ target }) => {
|
||||||
allowOutsideClick: false,
|
if (!target || !(target instanceof HTMLElement)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleBar = document.querySelector(
|
||||||
|
'.TitleBarContainer__title'
|
||||||
|
);
|
||||||
|
if (titleBar?.contains(target)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{modalContent}
|
{modalContent}
|
||||||
|
|
|
@ -156,6 +156,9 @@ const createProps = (): PropsType => ({
|
||||||
onZoomFactorChange: action('onZoomFactorChange'),
|
onZoomFactorChange: action('onZoomFactorChange'),
|
||||||
|
|
||||||
i18n,
|
i18n,
|
||||||
|
|
||||||
|
executeMenuRole: action('executeMenuRole'),
|
||||||
|
platform: 'win32',
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -29,6 +29,8 @@ import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability';
|
||||||
import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode';
|
import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode';
|
||||||
import { Select } from './Select';
|
import { Select } from './Select';
|
||||||
import { Spinner } from './Spinner';
|
import { Spinner } from './Spinner';
|
||||||
|
import { TitleBarContainer } from './TitleBarContainer';
|
||||||
|
import type { ExecuteMenuRoleType } from './TitleBarContainer';
|
||||||
import { getCustomColorStyle } from '../util/getCustomColorStyle';
|
import { getCustomColorStyle } from '../util/getCustomColorStyle';
|
||||||
import {
|
import {
|
||||||
DEFAULT_DURATIONS_IN_SECONDS,
|
DEFAULT_DURATIONS_IN_SECONDS,
|
||||||
|
@ -37,6 +39,7 @@ import {
|
||||||
} from '../util/expirationTimer';
|
} from '../util/expirationTimer';
|
||||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||||
import { useUniqueId } from '../hooks/useUniqueId';
|
import { useUniqueId } from '../hooks/useUniqueId';
|
||||||
|
import { useTheme } from '../hooks/useTheme';
|
||||||
|
|
||||||
type CheckboxChangeHandlerType = (value: boolean) => unknown;
|
type CheckboxChangeHandlerType = (value: boolean) => unknown;
|
||||||
type SelectChangeHandlerType<T = string | number> = (value: T) => unknown;
|
type SelectChangeHandlerType<T = string | number> = (value: T) => unknown;
|
||||||
|
@ -99,6 +102,8 @@ export type PropsType = {
|
||||||
value: CustomColorType;
|
value: CustomColorType;
|
||||||
}
|
}
|
||||||
) => unknown;
|
) => unknown;
|
||||||
|
platform: string;
|
||||||
|
executeMenuRole: ExecuteMenuRoleType;
|
||||||
|
|
||||||
// Limited support features
|
// Limited support features
|
||||||
isAudioNotificationsSupported: boolean;
|
isAudioNotificationsSupported: boolean;
|
||||||
|
@ -193,6 +198,7 @@ export const Preferences = ({
|
||||||
doDeleteAllData,
|
doDeleteAllData,
|
||||||
doneRendering,
|
doneRendering,
|
||||||
editCustomColor,
|
editCustomColor,
|
||||||
|
executeMenuRole,
|
||||||
getConversationsWithCustomColor,
|
getConversationsWithCustomColor,
|
||||||
hasAudioNotifications,
|
hasAudioNotifications,
|
||||||
hasAutoDownloadUpdate,
|
hasAutoDownloadUpdate,
|
||||||
|
@ -250,6 +256,7 @@ export const Preferences = ({
|
||||||
onThemeChange,
|
onThemeChange,
|
||||||
onUniversalExpireTimerChange,
|
onUniversalExpireTimerChange,
|
||||||
onZoomFactorChange,
|
onZoomFactorChange,
|
||||||
|
platform,
|
||||||
removeCustomColor,
|
removeCustomColor,
|
||||||
removeCustomColorOnConversations,
|
removeCustomColorOnConversations,
|
||||||
resetAllChatColors,
|
resetAllChatColors,
|
||||||
|
@ -273,6 +280,7 @@ export const Preferences = ({
|
||||||
const [nowSyncing, setNowSyncing] = useState(false);
|
const [nowSyncing, setNowSyncing] = useState(false);
|
||||||
const [showDisappearingTimerDialog, setShowDisappearingTimerDialog] =
|
const [showDisappearingTimerDialog, setShowDisappearingTimerDialog] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
doneRendering();
|
doneRendering();
|
||||||
|
@ -1017,78 +1025,85 @@ export const Preferences = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Preferences">
|
<TitleBarContainer
|
||||||
<div className="Preferences__page-selector">
|
platform={platform}
|
||||||
<button
|
theme={theme}
|
||||||
type="button"
|
executeMenuRole={executeMenuRole}
|
||||||
className={classNames({
|
title={i18n('signalDesktopPreferences')}
|
||||||
Preferences__button: true,
|
>
|
||||||
'Preferences__button--general': true,
|
<div className="Preferences">
|
||||||
'Preferences__button--selected': page === Page.General,
|
<div className="Preferences__page-selector">
|
||||||
})}
|
<button
|
||||||
onClick={() => setPage(Page.General)}
|
type="button"
|
||||||
>
|
className={classNames({
|
||||||
{i18n('Preferences__button--general')}
|
Preferences__button: true,
|
||||||
</button>
|
'Preferences__button--general': true,
|
||||||
<button
|
'Preferences__button--selected': page === Page.General,
|
||||||
type="button"
|
})}
|
||||||
className={classNames({
|
onClick={() => setPage(Page.General)}
|
||||||
Preferences__button: true,
|
>
|
||||||
'Preferences__button--appearance': true,
|
{i18n('Preferences__button--general')}
|
||||||
'Preferences__button--selected':
|
</button>
|
||||||
page === Page.Appearance || page === Page.ChatColor,
|
<button
|
||||||
})}
|
type="button"
|
||||||
onClick={() => setPage(Page.Appearance)}
|
className={classNames({
|
||||||
>
|
Preferences__button: true,
|
||||||
{i18n('Preferences__button--appearance')}
|
'Preferences__button--appearance': true,
|
||||||
</button>
|
'Preferences__button--selected':
|
||||||
<button
|
page === Page.Appearance || page === Page.ChatColor,
|
||||||
type="button"
|
})}
|
||||||
className={classNames({
|
onClick={() => setPage(Page.Appearance)}
|
||||||
Preferences__button: true,
|
>
|
||||||
'Preferences__button--chats': true,
|
{i18n('Preferences__button--appearance')}
|
||||||
'Preferences__button--selected': page === Page.Chats,
|
</button>
|
||||||
})}
|
<button
|
||||||
onClick={() => setPage(Page.Chats)}
|
type="button"
|
||||||
>
|
className={classNames({
|
||||||
{i18n('Preferences__button--chats')}
|
Preferences__button: true,
|
||||||
</button>
|
'Preferences__button--chats': true,
|
||||||
<button
|
'Preferences__button--selected': page === Page.Chats,
|
||||||
type="button"
|
})}
|
||||||
className={classNames({
|
onClick={() => setPage(Page.Chats)}
|
||||||
Preferences__button: true,
|
>
|
||||||
'Preferences__button--calls': true,
|
{i18n('Preferences__button--chats')}
|
||||||
'Preferences__button--selected': page === Page.Calls,
|
</button>
|
||||||
})}
|
<button
|
||||||
onClick={() => setPage(Page.Calls)}
|
type="button"
|
||||||
>
|
className={classNames({
|
||||||
{i18n('Preferences__button--calls')}
|
Preferences__button: true,
|
||||||
</button>
|
'Preferences__button--calls': true,
|
||||||
<button
|
'Preferences__button--selected': page === Page.Calls,
|
||||||
type="button"
|
})}
|
||||||
className={classNames({
|
onClick={() => setPage(Page.Calls)}
|
||||||
Preferences__button: true,
|
>
|
||||||
'Preferences__button--notifications': true,
|
{i18n('Preferences__button--calls')}
|
||||||
'Preferences__button--selected': page === Page.Notifications,
|
</button>
|
||||||
})}
|
<button
|
||||||
onClick={() => setPage(Page.Notifications)}
|
type="button"
|
||||||
>
|
className={classNames({
|
||||||
{i18n('Preferences__button--notifications')}
|
Preferences__button: true,
|
||||||
</button>
|
'Preferences__button--notifications': true,
|
||||||
<button
|
'Preferences__button--selected': page === Page.Notifications,
|
||||||
type="button"
|
})}
|
||||||
className={classNames({
|
onClick={() => setPage(Page.Notifications)}
|
||||||
Preferences__button: true,
|
>
|
||||||
'Preferences__button--privacy': true,
|
{i18n('Preferences__button--notifications')}
|
||||||
'Preferences__button--selected': page === Page.Privacy,
|
</button>
|
||||||
})}
|
<button
|
||||||
onClick={() => setPage(Page.Privacy)}
|
type="button"
|
||||||
>
|
className={classNames({
|
||||||
{i18n('Preferences__button--privacy')}
|
Preferences__button: true,
|
||||||
</button>
|
'Preferences__button--privacy': true,
|
||||||
|
'Preferences__button--selected': page === Page.Privacy,
|
||||||
|
})}
|
||||||
|
onClick={() => setPage(Page.Privacy)}
|
||||||
|
>
|
||||||
|
{i18n('Preferences__button--privacy')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="Preferences__settings-pane">{settings}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="Preferences__settings-pane">{settings}</div>
|
</TitleBarContainer>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
185
ts/components/TitleBarContainer.tsx
Normal file
185
ts/components/TitleBarContainer.tsx
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import TitleBar from '@indutny/frameless-titlebar';
|
||||||
|
import type { MenuItem } from '@indutny/frameless-titlebar';
|
||||||
|
import type { MenuItemConstructorOptions } from 'electron';
|
||||||
|
|
||||||
|
import { createTemplate } from '../../app/menu';
|
||||||
|
import { ThemeType } from '../types/Util';
|
||||||
|
import type { LocaleMessagesType } from '../types/I18N';
|
||||||
|
import type { MenuOptionsType, MenuActionType } from '../types/menu';
|
||||||
|
|
||||||
|
export type MenuPropsType = Readonly<{
|
||||||
|
hasMenu: true;
|
||||||
|
localeMessages: LocaleMessagesType;
|
||||||
|
menuOptions: MenuOptionsType;
|
||||||
|
executeMenuAction: (action: MenuActionType) => void;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type ExecuteMenuRoleType = (
|
||||||
|
role: MenuItemConstructorOptions['role']
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export type PropsType = Readonly<{
|
||||||
|
title: string;
|
||||||
|
theme: ThemeType;
|
||||||
|
isMaximized?: boolean;
|
||||||
|
isFullScreen?: boolean;
|
||||||
|
platform: string;
|
||||||
|
executeMenuRole: ExecuteMenuRoleType;
|
||||||
|
titleBarDoubleClick?: () => void;
|
||||||
|
children: ReactNode;
|
||||||
|
}> &
|
||||||
|
(MenuPropsType | { hasMenu?: false });
|
||||||
|
|
||||||
|
// Windows only
|
||||||
|
const ROLE_TO_ACCELERATOR = new Map<
|
||||||
|
MenuItemConstructorOptions['role'],
|
||||||
|
string
|
||||||
|
>();
|
||||||
|
ROLE_TO_ACCELERATOR.set('undo', 'CmdOrCtrl+Z');
|
||||||
|
ROLE_TO_ACCELERATOR.set('redo', 'CmdOrCtrl+Y');
|
||||||
|
ROLE_TO_ACCELERATOR.set('cut', 'CmdOrCtrl+X');
|
||||||
|
ROLE_TO_ACCELERATOR.set('copy', 'CmdOrCtrl+C');
|
||||||
|
ROLE_TO_ACCELERATOR.set('paste', 'CmdOrCtrl+V');
|
||||||
|
ROLE_TO_ACCELERATOR.set('pasteAndMatchStyle', 'CmdOrCtrl+Shift+V');
|
||||||
|
ROLE_TO_ACCELERATOR.set('selectAll', 'CmdOrCtrl+A');
|
||||||
|
ROLE_TO_ACCELERATOR.set('resetZoom', 'CmdOrCtrl+0');
|
||||||
|
ROLE_TO_ACCELERATOR.set('zoomIn', 'CmdOrCtrl+=');
|
||||||
|
ROLE_TO_ACCELERATOR.set('zoomOut', 'CmdOrCtrl+-');
|
||||||
|
ROLE_TO_ACCELERATOR.set('togglefullscreen', 'F11');
|
||||||
|
ROLE_TO_ACCELERATOR.set('toggleDevTools', 'CmdOrCtrl+Shift+I');
|
||||||
|
ROLE_TO_ACCELERATOR.set('minimize', 'CmdOrCtrl+M');
|
||||||
|
|
||||||
|
function convertMenu(
|
||||||
|
menuList: ReadonlyArray<MenuItemConstructorOptions>,
|
||||||
|
executeMenuRole: (role: MenuItemConstructorOptions['role']) => void
|
||||||
|
): Array<MenuItem> {
|
||||||
|
return menuList.map(item => {
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
label,
|
||||||
|
accelerator: originalAccelerator,
|
||||||
|
click: originalClick,
|
||||||
|
submenu: originalSubmenu,
|
||||||
|
role,
|
||||||
|
} = item;
|
||||||
|
let submenu: Array<MenuItem> | undefined;
|
||||||
|
|
||||||
|
if (Array.isArray(originalSubmenu)) {
|
||||||
|
submenu = convertMenu(originalSubmenu, executeMenuRole);
|
||||||
|
} else if (originalSubmenu) {
|
||||||
|
throw new Error('Non-array submenu is not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
let click: (() => unknown) | undefined;
|
||||||
|
if (originalClick) {
|
||||||
|
if (role) {
|
||||||
|
throw new Error(`Menu item: ${label} has both click and role`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't use arguments in app/menu.ts
|
||||||
|
click = originalClick as () => unknown;
|
||||||
|
} else if (role) {
|
||||||
|
click = () => executeMenuRole(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
let accelerator: string | undefined;
|
||||||
|
if (originalAccelerator) {
|
||||||
|
accelerator = originalAccelerator.toString();
|
||||||
|
} else if (role) {
|
||||||
|
accelerator = ROLE_TO_ACCELERATOR.get(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
label,
|
||||||
|
accelerator,
|
||||||
|
click,
|
||||||
|
submenu,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TitleBarContainer = (props: PropsType): JSX.Element => {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
theme,
|
||||||
|
isMaximized,
|
||||||
|
isFullScreen,
|
||||||
|
executeMenuRole,
|
||||||
|
titleBarDoubleClick,
|
||||||
|
children,
|
||||||
|
platform,
|
||||||
|
hasMenu,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
if (platform !== 'win32' || isFullScreen) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let maybeMenu: Array<MenuItem> | undefined;
|
||||||
|
if (hasMenu) {
|
||||||
|
const { localeMessages, menuOptions, executeMenuAction } = props;
|
||||||
|
|
||||||
|
const menuTemplate = createTemplate(
|
||||||
|
{
|
||||||
|
...menuOptions,
|
||||||
|
|
||||||
|
// actions
|
||||||
|
forceUpdate: () => executeMenuAction('forceUpdate'),
|
||||||
|
openContactUs: () => executeMenuAction('openContactUs'),
|
||||||
|
openForums: () => executeMenuAction('openForums'),
|
||||||
|
openJoinTheBeta: () => executeMenuAction('openJoinTheBeta'),
|
||||||
|
openReleaseNotes: () => executeMenuAction('openReleaseNotes'),
|
||||||
|
openSupportPage: () => executeMenuAction('openSupportPage'),
|
||||||
|
setupAsNewDevice: () => executeMenuAction('setupAsNewDevice'),
|
||||||
|
setupAsStandalone: () => executeMenuAction('setupAsStandalone'),
|
||||||
|
showAbout: () => executeMenuAction('showAbout'),
|
||||||
|
showDebugLog: () => executeMenuAction('showDebugLog'),
|
||||||
|
showKeyboardShortcuts: () => executeMenuAction('showKeyboardShortcuts'),
|
||||||
|
showSettings: () => executeMenuAction('showSettings'),
|
||||||
|
showStickerCreator: () => executeMenuAction('showStickerCreator'),
|
||||||
|
showWindow: () => executeMenuAction('showWindow'),
|
||||||
|
},
|
||||||
|
localeMessages
|
||||||
|
);
|
||||||
|
|
||||||
|
maybeMenu = convertMenu(menuTemplate, executeMenuRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleBarTheme = {
|
||||||
|
bar: {
|
||||||
|
palette:
|
||||||
|
theme === ThemeType.light ? ('light' as const) : ('dark' as const),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hide overlay
|
||||||
|
menu: {
|
||||||
|
overlay: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="TitleBarContainer">
|
||||||
|
<TitleBar
|
||||||
|
className="TitleBarContainer__title"
|
||||||
|
platform={platform}
|
||||||
|
title={title}
|
||||||
|
iconSrc="images/icon_32.png"
|
||||||
|
theme={titleBarTheme}
|
||||||
|
maximized={isMaximized}
|
||||||
|
menu={maybeMenu}
|
||||||
|
onDoubleClick={titleBarDoubleClick}
|
||||||
|
hideControls
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="TitleBarContainer__content">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -27,6 +27,7 @@ export type SystemThemeHolder = { systemTheme: SystemThemeType };
|
||||||
export type NativeThemeType = {
|
export type NativeThemeType = {
|
||||||
getSystemTheme: () => SystemThemeType;
|
getSystemTheme: () => SystemThemeType;
|
||||||
subscribe: (fn: Callback) => void;
|
subscribe: (fn: Callback) => void;
|
||||||
|
unsubscribe: (fn: Callback) => void;
|
||||||
update: () => SystemThemeType;
|
update: () => SystemThemeType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,6 +51,14 @@ export function createNativeThemeListener(
|
||||||
subscribers.push(fn);
|
subscribers.push(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unsubscribe(fn: Callback): void {
|
||||||
|
const index = subscribers.indexOf(fn);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
subscribers.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ipc.on(
|
ipc.on(
|
||||||
'native-theme:changed',
|
'native-theme:changed',
|
||||||
(_event: unknown, change: NativeThemeState) => {
|
(_event: unknown, change: NativeThemeState) => {
|
||||||
|
@ -67,6 +76,7 @@ export function createNativeThemeListener(
|
||||||
return {
|
return {
|
||||||
getSystemTheme: () => systemTheme,
|
getSystemTheme: () => systemTheme,
|
||||||
subscribe,
|
subscribe,
|
||||||
|
unsubscribe,
|
||||||
update,
|
update,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
58
ts/hooks/useTheme.ts
Normal file
58
ts/hooks/useTheme.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { ThemeType } from '../types/Util';
|
||||||
|
|
||||||
|
// Note that this hook is used in non-main windows (e.g. "About" and
|
||||||
|
// "Debug Log" windows), and thus can't access redux state.
|
||||||
|
export const useTheme = (): ThemeType => {
|
||||||
|
const [theme, updateTheme] = useState(ThemeType.light);
|
||||||
|
|
||||||
|
// Storybook support
|
||||||
|
const { SignalContext } = window;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
|
const { signal } = abortController;
|
||||||
|
|
||||||
|
async function applyTheme() {
|
||||||
|
let newTheme = await SignalContext.Settings.themeSetting.getValue();
|
||||||
|
if (newTheme === 'system') {
|
||||||
|
newTheme = SignalContext.nativeThemeListener.getSystemTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signal.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newTheme === 'dark') {
|
||||||
|
updateTheme(ThemeType.dark);
|
||||||
|
} else {
|
||||||
|
updateTheme(ThemeType.light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loop() {
|
||||||
|
while (!signal.aborted) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await applyTheme();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await SignalContext.Settings.waitForChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalContext.nativeThemeListener.subscribe(applyTheme);
|
||||||
|
loop();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
abortController.abort();
|
||||||
|
SignalContext.nativeThemeListener.unsubscribe(applyTheme);
|
||||||
|
};
|
||||||
|
}, [updateTheme, SignalContext.Settings, SignalContext.nativeThemeListener]);
|
||||||
|
|
||||||
|
return theme;
|
||||||
|
};
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import type { BrowserWindow } from 'electron';
|
import type { BrowserWindow } from 'electron';
|
||||||
import { ipcMain as ipc, session } from 'electron';
|
import { ipcMain as ipc, session } from 'electron';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
import { userConfig } from '../../app/user_config';
|
import { userConfig } from '../../app/user_config';
|
||||||
import { ephemeralConfig } from '../../app/ephemeral_config';
|
import { ephemeralConfig } from '../../app/ephemeral_config';
|
||||||
|
@ -25,7 +26,7 @@ type ResponseQueueEntry = Readonly<{
|
||||||
reject(error: Error): void;
|
reject(error: Error): void;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export class SettingsChannel {
|
export class SettingsChannel extends EventEmitter {
|
||||||
private mainWindow?: BrowserWindow;
|
private mainWindow?: BrowserWindow;
|
||||||
|
|
||||||
private readonly responseQueue = new Map<number, ResponseQueueEntry>();
|
private readonly responseQueue = new Map<number, ResponseQueueEntry>();
|
||||||
|
@ -229,7 +230,7 @@ export class SettingsChannel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ipc.handle(`settings:set:${name}`, (_event, value) => {
|
ipc.handle(`settings:set:${name}`, async (_event, value) => {
|
||||||
if (isEphemeral) {
|
if (isEphemeral) {
|
||||||
const ephemeralName = EPHEMERAL_NAME_MAP.get(name);
|
const ephemeralName = EPHEMERAL_NAME_MAP.get(name);
|
||||||
strictAssert(
|
strictAssert(
|
||||||
|
@ -239,7 +240,9 @@ export class SettingsChannel {
|
||||||
ephemeralConfig.set(ephemeralName, value);
|
ephemeralConfig.set(ephemeralName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.setSettingInMainWindow(name, value);
|
await this.setSettingInMainWindow(name, value);
|
||||||
|
|
||||||
|
this.emit(`change:${name}`, value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,10 @@ import { trigger } from '../../shims/events';
|
||||||
|
|
||||||
import type { NoopActionType } from './noop';
|
import type { NoopActionType } from './noop';
|
||||||
import type { LocalizerType } from '../../types/Util';
|
import type { LocalizerType } from '../../types/Util';
|
||||||
|
import type { LocaleMessagesType } from '../../types/I18N';
|
||||||
import { ThemeType } from '../../types/Util';
|
import { ThemeType } from '../../types/Util';
|
||||||
import type { UUIDStringType } from '../../types/UUID';
|
import type { UUIDStringType } from '../../types/UUID';
|
||||||
|
import type { MenuOptionsType } from '../../types/menu';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -21,7 +23,11 @@ export type UserStateType = {
|
||||||
platform: string;
|
platform: string;
|
||||||
regionCode: string | undefined;
|
regionCode: string | undefined;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
localeMessages: LocaleMessagesType;
|
||||||
interactionMode: 'mouse' | 'keyboard';
|
interactionMode: 'mouse' | 'keyboard';
|
||||||
|
isMainWindowMaximized: boolean;
|
||||||
|
isMainWindowFullScreen: boolean;
|
||||||
|
menuOptions: MenuOptionsType;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
version: string;
|
version: string;
|
||||||
};
|
};
|
||||||
|
@ -38,6 +44,9 @@ type UserChangedActionType = {
|
||||||
regionCode?: string;
|
regionCode?: string;
|
||||||
interactionMode?: 'mouse' | 'keyboard';
|
interactionMode?: 'mouse' | 'keyboard';
|
||||||
theme?: ThemeType;
|
theme?: ThemeType;
|
||||||
|
isMainWindowMaximized?: boolean;
|
||||||
|
isMainWindowFullScreen?: boolean;
|
||||||
|
menuOptions?: MenuOptionsType;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,6 +67,9 @@ function userChanged(attributes: {
|
||||||
ourUuid?: UUIDStringType;
|
ourUuid?: UUIDStringType;
|
||||||
regionCode?: string;
|
regionCode?: string;
|
||||||
theme?: ThemeType;
|
theme?: ThemeType;
|
||||||
|
isMainWindowMaximized?: boolean;
|
||||||
|
isMainWindowFullScreen?: boolean;
|
||||||
|
menuOptions?: MenuOptionsType;
|
||||||
}): UserChangedActionType {
|
}): UserChangedActionType {
|
||||||
return {
|
return {
|
||||||
type: 'USER_CHANGED',
|
type: 'USER_CHANGED',
|
||||||
|
@ -88,6 +100,15 @@ export function getEmptyState(): UserStateType {
|
||||||
regionCode: 'missing',
|
regionCode: 'missing',
|
||||||
platform: 'missing',
|
platform: 'missing',
|
||||||
interactionMode: 'mouse',
|
interactionMode: 'mouse',
|
||||||
|
isMainWindowMaximized: false,
|
||||||
|
isMainWindowFullScreen: false,
|
||||||
|
menuOptions: {
|
||||||
|
development: false,
|
||||||
|
devTools: false,
|
||||||
|
includeSetup: false,
|
||||||
|
isProduction: true,
|
||||||
|
platform: 'unknown',
|
||||||
|
},
|
||||||
theme: ThemeType.light,
|
theme: ThemeType.light,
|
||||||
i18n: Object.assign(
|
i18n: Object.assign(
|
||||||
() => {
|
() => {
|
||||||
|
@ -99,6 +120,7 @@ export function getEmptyState(): UserStateType {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
localeMessages: {},
|
||||||
version: '0.0.0',
|
version: '0.0.0',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,14 +25,20 @@ import type { StateType } from './reducer';
|
||||||
import type { BadgesStateType } from './ducks/badges';
|
import type { BadgesStateType } from './ducks/badges';
|
||||||
import type { StoryDataType } from './ducks/stories';
|
import type { StoryDataType } from './ducks/stories';
|
||||||
import { getInitialState as stickers } from '../types/Stickers';
|
import { getInitialState as stickers } from '../types/Stickers';
|
||||||
|
import type { MenuOptionsType } from '../types/menu';
|
||||||
import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis';
|
import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis';
|
||||||
|
import type { MainWindowStatsType } from '../windows/context';
|
||||||
|
|
||||||
export function getInitialState({
|
export function getInitialState({
|
||||||
badges,
|
badges,
|
||||||
stories,
|
stories,
|
||||||
|
mainWindowStats,
|
||||||
|
menuOptions,
|
||||||
}: {
|
}: {
|
||||||
badges: BadgesStateType;
|
badges: BadgesStateType;
|
||||||
stories: Array<StoryDataType>;
|
stories: Array<StoryDataType>;
|
||||||
|
mainWindowStats: MainWindowStatsType;
|
||||||
|
menuOptions: MenuOptionsType;
|
||||||
}): StateType {
|
}): StateType {
|
||||||
const items = window.storage.getItemsState();
|
const items = window.storage.getItemsState();
|
||||||
|
|
||||||
|
@ -108,9 +114,13 @@ export function getInitialState({
|
||||||
ourUuid,
|
ourUuid,
|
||||||
platform: window.platform,
|
platform: window.platform,
|
||||||
i18n: window.i18n,
|
i18n: window.i18n,
|
||||||
|
localeMessages: window.SignalContext.localeMessages,
|
||||||
interactionMode: window.getInteractionMode(),
|
interactionMode: window.getInteractionMode(),
|
||||||
theme,
|
theme,
|
||||||
version: window.getVersion(),
|
version: window.getVersion(),
|
||||||
|
isMainWindowMaximized: mainWindowStats.isMaximized,
|
||||||
|
isMainWindowFullScreen: mainWindowStats.isFullScreen,
|
||||||
|
menuOptions,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||||
import type { UUIDStringType } from '../../types/UUID';
|
import type { UUIDStringType } from '../../types/UUID';
|
||||||
|
import type { LocaleMessagesType } from '../../types/I18N';
|
||||||
|
import type { MenuOptionsType } from '../../types/menu';
|
||||||
|
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import type { UserStateType } from '../ducks/user';
|
import type { UserStateType } from '../ducks/user';
|
||||||
|
@ -43,6 +45,11 @@ export const getIntl = createSelector(
|
||||||
(state: UserStateType): LocalizerType => state.i18n
|
(state: UserStateType): LocalizerType => state.i18n
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getLocaleMessages = createSelector(
|
||||||
|
getUser,
|
||||||
|
(state: UserStateType): LocaleMessagesType => state.localeMessages
|
||||||
|
);
|
||||||
|
|
||||||
export const getInteractionMode = createSelector(
|
export const getInteractionMode = createSelector(
|
||||||
getUser,
|
getUser,
|
||||||
(state: UserStateType) => state.interactionMode
|
(state: UserStateType) => state.interactionMode
|
||||||
|
@ -81,3 +88,18 @@ const getVersion = createSelector(
|
||||||
export const getIsAlpha = createSelector(getVersion, isAlpha);
|
export const getIsAlpha = createSelector(getVersion, isAlpha);
|
||||||
|
|
||||||
export const getIsBeta = createSelector(getVersion, isBeta);
|
export const getIsBeta = createSelector(getVersion, isBeta);
|
||||||
|
|
||||||
|
export const getIsMainWindowMaximized = createSelector(
|
||||||
|
getUser,
|
||||||
|
(state: UserStateType): boolean => state.isMainWindowMaximized
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getIsMainWindowFullScreen = createSelector(
|
||||||
|
getUser,
|
||||||
|
(state: UserStateType): boolean => state.isMainWindowFullScreen
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getMenuOptions = createSelector(
|
||||||
|
getUser,
|
||||||
|
(state: UserStateType): MenuOptionsType => state.menuOptions
|
||||||
|
);
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import type { MenuItemConstructorOptions } from 'electron';
|
||||||
|
|
||||||
|
import type { MenuActionType } from '../../types/menu';
|
||||||
import { App } from '../../components/App';
|
import { App } from '../../components/App';
|
||||||
import { SmartCallManager } from './CallManager';
|
import { SmartCallManager } from './CallManager';
|
||||||
import { SmartCustomizingPreferredReactionsModal } from './CustomizingPreferredReactionsModal';
|
import { SmartCustomizingPreferredReactionsModal } from './CustomizingPreferredReactionsModal';
|
||||||
|
@ -12,7 +14,15 @@ import { SmartSafetyNumberViewer } from './SafetyNumberViewer';
|
||||||
import { SmartStories } from './Stories';
|
import { SmartStories } from './Stories';
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
import {
|
||||||
|
getIntl,
|
||||||
|
getLocaleMessages,
|
||||||
|
getTheme,
|
||||||
|
getIsMainWindowMaximized,
|
||||||
|
getIsMainWindowFullScreen,
|
||||||
|
getMenuOptions,
|
||||||
|
getPlatform,
|
||||||
|
} from '../selectors/user';
|
||||||
import { shouldShowStoriesView } from '../selectors/stories';
|
import { shouldShowStoriesView } from '../selectors/stories';
|
||||||
import { getConversationsStoppingSend } from '../selectors/conversations';
|
import { getConversationsStoppingSend } from '../selectors/conversations';
|
||||||
import { getIsCustomizingPreferredReactions } from '../selectors/preferredReactions';
|
import { getIsCustomizingPreferredReactions } from '../selectors/preferredReactions';
|
||||||
|
@ -25,7 +35,12 @@ const mapStateToProps = (state: StateType) => {
|
||||||
conversationsStoppingSend: getConversationsStoppingSend(state),
|
conversationsStoppingSend: getConversationsStoppingSend(state),
|
||||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
|
localeMessages: getLocaleMessages(state),
|
||||||
isCustomizingPreferredReactions: getIsCustomizingPreferredReactions(state),
|
isCustomizingPreferredReactions: getIsCustomizingPreferredReactions(state),
|
||||||
|
isMaximized: getIsMainWindowMaximized(state),
|
||||||
|
isFullScreen: getIsMainWindowFullScreen(state),
|
||||||
|
menuOptions: getMenuOptions(state),
|
||||||
|
platform: getPlatform(state),
|
||||||
renderCallManager: () => <SmartCallManager />,
|
renderCallManager: () => <SmartCallManager />,
|
||||||
renderCustomizingPreferredReactionsModal: () => (
|
renderCustomizingPreferredReactionsModal: () => (
|
||||||
<SmartCustomizingPreferredReactionsModal />
|
<SmartCustomizingPreferredReactionsModal />
|
||||||
|
@ -53,6 +68,16 @@ const mapStateToProps = (state: StateType) => {
|
||||||
return window.getAccountManager().registerSingleDevice(number, code);
|
return window.getAccountManager().registerSingleDevice(number, code);
|
||||||
},
|
},
|
||||||
theme: getTheme(state),
|
theme: getTheme(state),
|
||||||
|
|
||||||
|
executeMenuRole: (role: MenuItemConstructorOptions['role']): void => {
|
||||||
|
window.SignalContext.executeMenuRole(role);
|
||||||
|
},
|
||||||
|
executeMenuAction: (action: MenuActionType): void => {
|
||||||
|
window.SignalContext.executeMenuAction(action);
|
||||||
|
},
|
||||||
|
titleBarDoubleClick: (): void => {
|
||||||
|
window.titleBarDoubleClick();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,10 @@ import { assert } from 'chai';
|
||||||
import { stub } from 'sinon';
|
import { stub } from 'sinon';
|
||||||
import type { MenuItemConstructorOptions } from 'electron';
|
import type { MenuItemConstructorOptions } from 'electron';
|
||||||
|
|
||||||
import type { MenuListType, MenuOptionsType } from '../../../app/menu';
|
import type { CreateTemplateOptionsType } from '../../../app/menu';
|
||||||
import { createTemplate } from '../../../app/menu';
|
import { createTemplate } from '../../../app/menu';
|
||||||
import { load as loadLocale } from '../../../app/locale';
|
import { load as loadLocale } from '../../../app/locale';
|
||||||
|
import type { MenuListType } from '../../types/menu';
|
||||||
|
|
||||||
const forceUpdate = stub();
|
const forceUpdate = stub();
|
||||||
const openContactUs = stub();
|
const openContactUs = stub();
|
||||||
|
@ -53,13 +54,11 @@ const getExpectedEditMenu = (
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const getExpectedViewMenu = (
|
const getExpectedViewMenu = (): MenuItemConstructorOptions => ({
|
||||||
zoomModifier: 'Command' | 'Control'
|
|
||||||
): MenuItemConstructorOptions => ({
|
|
||||||
label: '&View',
|
label: '&View',
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Actual Size', role: 'resetZoom' },
|
{ label: 'Actual Size', role: 'resetZoom' },
|
||||||
{ accelerator: `${zoomModifier}+=`, label: 'Zoom In', role: 'zoomIn' },
|
{ accelerator: 'CmdOrCtrl+=', label: 'Zoom In', role: 'zoomIn' },
|
||||||
{ label: 'Zoom Out', role: 'zoomOut' },
|
{ label: 'Zoom Out', role: 'zoomOut' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'Toggle Full Screen', role: 'togglefullscreen' },
|
{ label: 'Toggle Full Screen', role: 'togglefullscreen' },
|
||||||
|
@ -127,7 +126,7 @@ const EXPECTED_MACOS: MenuListType = [
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
getExpectedEditMenu(true),
|
getExpectedEditMenu(true),
|
||||||
getExpectedViewMenu('Command'),
|
getExpectedViewMenu(),
|
||||||
{
|
{
|
||||||
label: '&Window',
|
label: '&Window',
|
||||||
role: 'window',
|
role: 'window',
|
||||||
|
@ -157,7 +156,7 @@ const EXPECTED_WINDOWS: MenuListType = [
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
getExpectedEditMenu(false),
|
getExpectedEditMenu(false),
|
||||||
getExpectedViewMenu('Control'),
|
getExpectedViewMenu(),
|
||||||
{
|
{
|
||||||
label: '&Window',
|
label: '&Window',
|
||||||
role: 'window',
|
role: 'window',
|
||||||
|
@ -226,7 +225,7 @@ describe('createTemplate', () => {
|
||||||
PLATFORMS.forEach(({ label, platform, expectedDefault }) => {
|
PLATFORMS.forEach(({ label, platform, expectedDefault }) => {
|
||||||
describe(label, () => {
|
describe(label, () => {
|
||||||
it('should return the correct template without setup options', () => {
|
it('should return the correct template without setup options', () => {
|
||||||
const options: MenuOptionsType = {
|
const options: CreateTemplateOptionsType = {
|
||||||
development: false,
|
development: false,
|
||||||
devTools: true,
|
devTools: true,
|
||||||
includeSetup: false,
|
includeSetup: false,
|
||||||
|
@ -240,7 +239,7 @@ describe('createTemplate', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return correct template with setup options', () => {
|
it('should return correct template with setup options', () => {
|
||||||
const options: MenuOptionsType = {
|
const options: CreateTemplateOptionsType = {
|
||||||
development: false,
|
development: false,
|
||||||
devTools: true,
|
devTools: true,
|
||||||
includeSetup: true,
|
includeSetup: true,
|
||||||
|
|
|
@ -45,15 +45,6 @@ export const isHideMenuBarSupported = (): boolean => !OS.isMacOS();
|
||||||
// the "draw attention on notification" option is specific to Windows and Linux
|
// the "draw attention on notification" option is specific to Windows and Linux
|
||||||
export const isDrawAttentionSupported = (): boolean => !OS.isMacOS();
|
export const isDrawAttentionSupported = (): boolean => !OS.isMacOS();
|
||||||
|
|
||||||
export enum TitleBarVisibility {
|
|
||||||
Visible,
|
|
||||||
Hidden,
|
|
||||||
}
|
|
||||||
|
|
||||||
// This should match the "logic" in `stylesheets/_global.scss`.
|
|
||||||
export const getTitleBarVisibility = (): TitleBarVisibility =>
|
|
||||||
OS.isMacOS() ? TitleBarVisibility.Hidden : TitleBarVisibility.Visible;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns `true` if you can minimize the app to the system tray. Users can override this
|
* Returns `true` if you can minimize the app to the system tray. Users can override this
|
||||||
* option with a command line flag, but that is not officially supported.
|
* option with a command line flag, but that is not officially supported.
|
||||||
|
|
33
ts/types/menu.ts
Normal file
33
ts/types/menu.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2017-2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { MenuItemConstructorOptions } from 'electron';
|
||||||
|
|
||||||
|
export type MenuListType = Array<MenuItemConstructorOptions>;
|
||||||
|
|
||||||
|
export type MenuOptionsType = Readonly<{
|
||||||
|
development: boolean;
|
||||||
|
devTools: boolean;
|
||||||
|
includeSetup: boolean;
|
||||||
|
isProduction: boolean;
|
||||||
|
platform: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type MenuActionsType = Readonly<{
|
||||||
|
forceUpdate: () => unknown;
|
||||||
|
openContactUs: () => unknown;
|
||||||
|
openForums: () => unknown;
|
||||||
|
openJoinTheBeta: () => unknown;
|
||||||
|
openReleaseNotes: () => unknown;
|
||||||
|
openSupportPage: () => unknown;
|
||||||
|
setupAsNewDevice: () => unknown;
|
||||||
|
setupAsStandalone: () => unknown;
|
||||||
|
showAbout: () => unknown;
|
||||||
|
showDebugLog: () => unknown;
|
||||||
|
showKeyboardShortcuts: () => unknown;
|
||||||
|
showSettings: () => unknown;
|
||||||
|
showStickerCreator: () => unknown;
|
||||||
|
showWindow: () => unknown;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type MenuActionType = keyof MenuActionsType;
|
|
@ -132,6 +132,160 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2021-04-05T20:48:36.065Z"
|
"updated": "2021-04-05T20:48:36.065Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-createRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.es.js",
|
||||||
|
"line": " .map(() => createRef());",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.es.js",
|
||||||
|
"line": " const ref = useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.es.js",
|
||||||
|
"line": " const ref = useRef();",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.es.js",
|
||||||
|
"line": " const activeMenus = useRef((_a = menu === null || menu === void 0 ? void 0 : menu.length) !== null && _a !== void 0 ? _a : 0);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.es.js",
|
||||||
|
"line": " const savedCallback = useRef(onClickAway);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.es.js",
|
||||||
|
"line": " const menuRef = useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.es.js",
|
||||||
|
"line": " const scrollRef = useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.es.js",
|
||||||
|
"line": " myRef = myRef !== null && myRef !== void 0 ? myRef : useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.es.js",
|
||||||
|
"line": " const overflowRef = useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.es.js",
|
||||||
|
"line": " const menuBar = useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.es.js",
|
||||||
|
"line": " const ref = useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-createRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.js",
|
||||||
|
"line": " .map(() => React.createRef());",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.js",
|
||||||
|
"line": " const ref = React.useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.js",
|
||||||
|
"line": " const ref = React.useRef();",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.js",
|
||||||
|
"line": " const activeMenus = React.useRef((_a = menu === null || menu === void 0 ? void 0 : menu.length) !== null && _a !== void 0 ? _a : 0);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.js",
|
||||||
|
"line": " const savedCallback = React.useRef(onClickAway);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.js",
|
||||||
|
"line": " const menuRef = React.useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.js",
|
||||||
|
"line": " const scrollRef = React.useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.js",
|
||||||
|
"line": " myRef = myRef !== null && myRef !== void 0 ? myRef : React.useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.js",
|
||||||
|
"line": " const overflowRef = React.useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.js",
|
||||||
|
"line": " const menuBar = React.useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "node_modules/@indutny/frameless-titlebar/dist/index.js",
|
||||||
|
"line": " const ref = React.useRef(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-06-06T22:58:37.359Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-append(",
|
"rule": "jQuery-append(",
|
||||||
"path": "node_modules/@jridgewell/source-map/dist/source-map.umd.js",
|
"path": "node_modules/@jridgewell/source-map/dist/source-map.umd.js",
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { contextBridge, ipcRenderer } from 'electron';
|
import { contextBridge } from 'electron';
|
||||||
|
|
||||||
import { SignalContext } from '../context';
|
import { SignalContext } from '../context';
|
||||||
import { About } from '../../components/About';
|
import { About } from '../../components/About';
|
||||||
|
@ -29,10 +29,12 @@ contextBridge.exposeInMainWorld('SignalContext', {
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
React.createElement(About, {
|
React.createElement(About, {
|
||||||
closeAbout: () => ipcRenderer.send('close-about'),
|
closeAbout: () => SignalContext.executeMenuRole('close'),
|
||||||
environment: `${environmentText.join(' - ')}${platform}`,
|
environment: `${environmentText.join(' - ')}${platform}`,
|
||||||
i18n: SignalContext.i18n,
|
i18n: SignalContext.i18n,
|
||||||
version: SignalContext.getVersion(),
|
version: SignalContext.getVersion(),
|
||||||
|
platform: process.platform,
|
||||||
|
executeMenuRole: SignalContext.executeMenuRole,
|
||||||
}),
|
}),
|
||||||
document.getElementById('app')
|
document.getElementById('app')
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,11 +2,14 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
|
import type { MenuItemConstructorOptions } from 'electron';
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
import type { ParsedUrlQuery } from 'querystring';
|
import type { ParsedUrlQuery } from 'querystring';
|
||||||
|
import type { MenuOptionsType, MenuActionType } from '../types/menu';
|
||||||
import type { IPCEventsValuesType } from '../util/createIPCEvents';
|
import type { IPCEventsValuesType } from '../util/createIPCEvents';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import type { LoggerType } from '../types/Logging';
|
import type { LoggerType } from '../types/Logging';
|
||||||
|
import type { LocaleMessagesType } from '../types/I18N';
|
||||||
import type { NativeThemeType } from '../context/createNativeThemeListener';
|
import type { NativeThemeType } from '../context/createNativeThemeListener';
|
||||||
import type { SettingType } from '../util/preload';
|
import type { SettingType } from '../util/preload';
|
||||||
import { Bytes } from '../context/Bytes';
|
import { Bytes } from '../context/Bytes';
|
||||||
|
@ -37,6 +40,11 @@ strictAssert(Boolean(window.SignalContext), 'context must be defined');
|
||||||
|
|
||||||
initializeLogging();
|
initializeLogging();
|
||||||
|
|
||||||
|
export type MainWindowStatsType = Readonly<{
|
||||||
|
isMaximized: boolean;
|
||||||
|
isFullScreen: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type SignalContextType = {
|
export type SignalContextType = {
|
||||||
bytes: Bytes;
|
bytes: Bytes;
|
||||||
crypto: Crypto;
|
crypto: Crypto;
|
||||||
|
@ -55,8 +63,13 @@ export type SignalContextType = {
|
||||||
getVersion: () => string;
|
getVersion: () => string;
|
||||||
getPath: (name: 'userData' | 'home') => string;
|
getPath: (name: 'userData' | 'home') => string;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
localeMessages: LocaleMessagesType;
|
||||||
log: LoggerType;
|
log: LoggerType;
|
||||||
renderWindow?: () => void;
|
renderWindow?: () => void;
|
||||||
|
executeMenuRole: (role: MenuItemConstructorOptions['role']) => Promise<void>;
|
||||||
|
getMainWindowStats: () => Promise<MainWindowStatsType>;
|
||||||
|
getMenuOptions: () => Promise<MenuOptionsType>;
|
||||||
|
executeMenuAction: (action: MenuActionType) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SignalContext: SignalContextType = {
|
export const SignalContext: SignalContextType = {
|
||||||
|
@ -76,12 +89,27 @@ export const SignalContext: SignalContextType = {
|
||||||
return String(config[`${name}Path`]);
|
return String(config[`${name}Path`]);
|
||||||
},
|
},
|
||||||
i18n: setupI18n(locale, localeMessages),
|
i18n: setupI18n(locale, localeMessages),
|
||||||
|
localeMessages,
|
||||||
log: window.SignalContext.log,
|
log: window.SignalContext.log,
|
||||||
nativeThemeListener: createNativeThemeListener(ipcRenderer, window),
|
nativeThemeListener: createNativeThemeListener(ipcRenderer, window),
|
||||||
setIsCallActive(isCallActive: boolean): void {
|
setIsCallActive(isCallActive: boolean): void {
|
||||||
ipcRenderer.send('set-is-call-active', isCallActive);
|
ipcRenderer.send('set-is-call-active', isCallActive);
|
||||||
},
|
},
|
||||||
timers: new Timers(),
|
timers: new Timers(),
|
||||||
|
async executeMenuRole(
|
||||||
|
role: MenuItemConstructorOptions['role']
|
||||||
|
): Promise<void> {
|
||||||
|
await ipcRenderer.invoke('executeMenuRole', role);
|
||||||
|
},
|
||||||
|
async getMainWindowStats(): Promise<MainWindowStatsType> {
|
||||||
|
return ipcRenderer.invoke('getMainWindowStats');
|
||||||
|
},
|
||||||
|
async getMenuOptions(): Promise<MenuOptionsType> {
|
||||||
|
return ipcRenderer.invoke('getMenuOptions');
|
||||||
|
},
|
||||||
|
async executeMenuAction(action: MenuActionType): Promise<void> {
|
||||||
|
return ipcRenderer.invoke('executeMenuAction', action);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
window.SignalContext = SignalContext;
|
window.SignalContext = SignalContext;
|
||||||
|
|
|
@ -23,7 +23,9 @@ contextBridge.exposeInMainWorld('SignalContext', {
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
React.createElement(DebugLogWindow, {
|
React.createElement(DebugLogWindow, {
|
||||||
closeWindow: () => ipcRenderer.send('close-debug-log'),
|
platform: process.platform,
|
||||||
|
executeMenuRole: SignalContext.executeMenuRole,
|
||||||
|
closeWindow: () => SignalContext.executeMenuRole('close'),
|
||||||
downloadLog: (logText: string) =>
|
downloadLog: (logText: string) =>
|
||||||
ipcRenderer.send('show-debug-log-save-dialog', logText),
|
ipcRenderer.send('show-debug-log-save-dialog', logText),
|
||||||
i18n: SignalContext.i18n,
|
i18n: SignalContext.i18n,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { contextBridge, ipcRenderer } from 'electron';
|
import { contextBridge } from 'electron';
|
||||||
|
|
||||||
import { SignalContext } from '../context';
|
import { SignalContext } from '../context';
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ contextBridge.exposeInMainWorld('SignalContext', {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClose() {
|
function onClose() {
|
||||||
ipcRenderer.send('close-permissions-popup');
|
SignalContext.executeMenuRole('close');
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
|
|
|
@ -13,9 +13,10 @@ contextBridge.exposeInMainWorld('SignalContext', SignalContext);
|
||||||
function renderScreenSharingController(presentedSourceName: string): void {
|
function renderScreenSharingController(presentedSourceName: string): void {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
React.createElement(CallingScreenSharingController, {
|
React.createElement(CallingScreenSharingController, {
|
||||||
|
platform: process.platform,
|
||||||
|
executeMenuRole: SignalContext.executeMenuRole,
|
||||||
i18n: SignalContext.i18n,
|
i18n: SignalContext.i18n,
|
||||||
onCloseController: () =>
|
onCloseController: () => SignalContext.executeMenuRole('close'),
|
||||||
ipcRenderer.send('close-screen-share-controller'),
|
|
||||||
onStopSharing: () => ipcRenderer.send('stop-screen-share'),
|
onStopSharing: () => ipcRenderer.send('stop-screen-share'),
|
||||||
presentedSourceName,
|
presentedSourceName,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -247,7 +247,7 @@ const renderPreferences = async () => {
|
||||||
|
|
||||||
// Actions and other props
|
// Actions and other props
|
||||||
addCustomColor: ipcAddCustomColor,
|
addCustomColor: ipcAddCustomColor,
|
||||||
closeSettings: () => ipcRenderer.send('close-settings'),
|
closeSettings: () => SignalContext.executeMenuRole('close'),
|
||||||
doDeleteAllData: () => ipcRenderer.send('delete-all-data'),
|
doDeleteAllData: () => ipcRenderer.send('delete-all-data'),
|
||||||
doneRendering,
|
doneRendering,
|
||||||
editCustomColor: ipcEditCustomColor,
|
editCustomColor: ipcEditCustomColor,
|
||||||
|
@ -337,6 +337,9 @@ const renderPreferences = async () => {
|
||||||
onZoomFactorChange: settingZoomFactor.setValue,
|
onZoomFactorChange: settingZoomFactor.setValue,
|
||||||
|
|
||||||
i18n: SignalContext.i18n,
|
i18n: SignalContext.i18n,
|
||||||
|
|
||||||
|
platform: process.platform,
|
||||||
|
executeMenuRole: SignalContext.executeMenuRole,
|
||||||
};
|
};
|
||||||
|
|
||||||
function reRender<Value>(f: (value: Value) => Promise<Value>) {
|
function reRender<Value>(f: (value: Value) => Promise<Value>) {
|
||||||
|
|
16
yarn.lock
16
yarn.lock
|
@ -1378,6 +1378,14 @@
|
||||||
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
||||||
integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==
|
integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==
|
||||||
|
|
||||||
|
"@indutny/frameless-titlebar@2.1.4-rc.8":
|
||||||
|
version "2.1.4-rc.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@indutny/frameless-titlebar/-/frameless-titlebar-2.1.4-rc.8.tgz#e315d9c0199e769f8d7811d67d9b821658b66afb"
|
||||||
|
integrity sha512-R9gXCfe4LA6K0urEiCKT3h9hxcg4Z/BZZxbMp607sQ7z2bfLaCAVCm7WdAkl2piOPMNorYCWd7Vo2oG6krGmVg==
|
||||||
|
dependencies:
|
||||||
|
classnames "^2.2.6"
|
||||||
|
deepmerge "^4.2.2"
|
||||||
|
|
||||||
"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3":
|
"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3":
|
||||||
version "0.1.3"
|
version "0.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
|
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
|
||||||
|
@ -5545,10 +5553,10 @@ classnames@2.2.5:
|
||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
|
||||||
integrity sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0=
|
integrity sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0=
|
||||||
|
|
||||||
classnames@^2.2.5:
|
classnames@^2.2.5, classnames@^2.2.6:
|
||||||
version "2.2.6"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
|
||||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
||||||
|
|
||||||
clean-css@^4.2.3:
|
clean-css@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
|
|
Loading…
Reference in a new issue