Electron: Various improvements (#3310)
This commit is contained in:
parent
7daee68c0e
commit
7dd9c2e677
10
README.md
10
README.md
@ -43,7 +43,7 @@ Electron allows building a native application that can be installed on Windows,
|
|||||||
|
|
||||||
#### NPM scripts
|
#### NPM scripts
|
||||||
|
|
||||||
- `npm run electron:dev`
|
- `npm run dev:electron`
|
||||||
|
|
||||||
Run Electron in development mode, concurrently starts 3 processes with watch for changes: main (main Electron process), renderer (FE code) and Webpack for Electron (compiles main Electron process from TypeScript).
|
Run Electron in development mode, concurrently starts 3 processes with watch for changes: main (main Electron process), renderer (FE code) and Webpack for Electron (compiles main Electron process from TypeScript).
|
||||||
|
|
||||||
@ -55,15 +55,15 @@ The main process code for Electron, which includes preload functionality, is wri
|
|||||||
|
|
||||||
Prepare renderer (FE code) build, compile Electron main process code, install and build native dependencies, is used before packaging or publishing.
|
Prepare renderer (FE code) build, compile Electron main process code, install and build native dependencies, is used before packaging or publishing.
|
||||||
|
|
||||||
- `npm run electron:package:staging`
|
- `npm run electron:staging`
|
||||||
|
|
||||||
Create packages for macOS, Windows and Linux in `dist-electron` folders with `APP_ENV` as `staging` (allows to open DevTools, includes sourcemaps and does not minify built JavaScript code), can be used for manual distribution and testing packaged application.
|
Create packages for macOS, Windows and Linux in `dist-electron` folders with `APP_ENV` as `staging` (allows to open DevTools, includes sourcemaps and does not minify built JavaScript code), can be used for manual distribution and testing packaged application.
|
||||||
|
|
||||||
- `npm run electron:package:production`
|
- `npm run electron:production`
|
||||||
|
|
||||||
Create packages for macOS, Windows and Linux in `dist-electron` folders with `APP_ENV` as `production` (disabled DevTools, minified built JavaScript code), can be used for manual distribution and testing packaged application.
|
Create packages for macOS, Windows and Linux in `dist-electron` folders with `APP_ENV` as `production` (disabled DevTools, minified built JavaScript code), can be used for manual distribution and testing packaged application.
|
||||||
|
|
||||||
- `npm run electron:publish`
|
- `npm run deploy:electron`
|
||||||
|
|
||||||
Create packages for macOS, Windows and Linux in `dist-electron` folder and publish release to GitHub, which allows supporting autoupdates. See [GitHub release workflow](#github-release) for more info.
|
Create packages for macOS, Windows and Linux in `dist-electron` folder and publish release to GitHub, which allows supporting autoupdates. See [GitHub release workflow](#github-release) for more info.
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ More info in the [official documentation](https://www.electronjs.org/docs/latest
|
|||||||
|
|
||||||
#### Notarize on MacOS
|
#### Notarize on MacOS
|
||||||
|
|
||||||
Application notarization is done with [electron-builder-notarize](https://github.com/karaggeorge/electron-builder-notarize) module, which requires `APPLE_ID` and `APPLE_ID_PASSWORD` environment variables to be passed.
|
Application notarization is done automatically in [electron-builder](https://github.com/electron-userland/electron-builder/) module, which requires `APPLE_ID` and `APPLE_APP_SPECIFIC_PASSWORD` environment variables to be passed.
|
||||||
|
|
||||||
How to obtain app-specific password:
|
How to obtain app-specific password:
|
||||||
|
|
||||||
|
|||||||
1127
package-lock.json
generated
1127
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -60,7 +60,6 @@
|
|||||||
"@babel/preset-react": "^7.18.6",
|
"@babel/preset-react": "^7.18.6",
|
||||||
"@babel/preset-typescript": "^7.21.0",
|
"@babel/preset-typescript": "^7.21.0",
|
||||||
"@babel/register": "^7.21.0",
|
"@babel/register": "^7.21.0",
|
||||||
"@electron/rebuild": "^3.2.10",
|
|
||||||
"@glen/jest-raw-loader": "^2.0.0",
|
"@glen/jest-raw-loader": "^2.0.0",
|
||||||
"@playwright/test": "^1.31.2",
|
"@playwright/test": "^1.31.2",
|
||||||
"@statoscope/cli": "^5.26.1",
|
"@statoscope/cli": "^5.26.1",
|
||||||
@ -86,10 +85,10 @@
|
|||||||
"css-loader": "^6.7.3",
|
"css-loader": "^6.7.3",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"electron": "^22.0.0",
|
"electron": "^22.0.0",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-builder": "^24.5.2",
|
||||||
"electron-builder-notarize": "^1.5.1",
|
"electron-context-menu": "^3.6.1",
|
||||||
|
"electron-store": "^8.1.0",
|
||||||
"electron-updater": "^5.3.0",
|
"electron-updater": "^5.3.0",
|
||||||
"electron-window-state": "^5.0.3",
|
|
||||||
"electronmon": "^2.0.2",
|
"electronmon": "^2.0.2",
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^8.36.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
|
|||||||
@ -2,11 +2,17 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.cs.allow-jit</key>
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.cs.disable-library-validation</key>
|
<key>com.apple.security.device.camera</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.microphone</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.audio-input</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.print</key>
|
||||||
|
<true />
|
||||||
|
<key>com.apple.security.personal-information.location</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 117 KiB |
@ -31,6 +31,12 @@
|
|||||||
height: 10rem;
|
height: 10rem;
|
||||||
margin-bottom: 2.5rem;
|
margin-bottom: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.is-electron & {
|
||||||
|
width: 6rem;
|
||||||
|
height: 6rem;
|
||||||
|
margin-bottom: 1.75rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#logo {
|
#logo {
|
||||||
|
|||||||
@ -28,9 +28,11 @@ const ActiveCallHeader: FC<StateProps> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.body.classList.toggle('has-call-header', Boolean(isCallPanelVisible));
|
document.body.classList.toggle('has-call-header', Boolean(isCallPanelVisible));
|
||||||
|
window.electron?.setTrafficLightPosition(isCallPanelVisible ? 'lowered' : 'standard');
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.body.classList.toggle('has-call-header', false);
|
document.body.classList.toggle('has-call-header', false);
|
||||||
|
window.electron?.setTrafficLightPosition('standard');
|
||||||
};
|
};
|
||||||
}, [isCallPanelVisible]);
|
}, [isCallPanelVisible]);
|
||||||
|
|
||||||
|
|||||||
@ -81,6 +81,10 @@
|
|||||||
&.scrolled {
|
&.scrolled {
|
||||||
border-bottom-color: var(--group-call-panel-header-border-color);
|
border-bottom-color: var(--group-call-panel-header-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(body.is-electron) .root.fullscreen:not(.landscape) & {
|
||||||
|
padding-left: 5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.headerButton {
|
.headerButton {
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import React, {
|
|||||||
useEffect, useRef, memo, useState,
|
useEffect, useRef, memo, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import { IS_ELECTRON } from '../../config';
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import buildStyle from '../../util/buildStyle';
|
import buildStyle from '../../util/buildStyle';
|
||||||
import generateUniqueId from '../../util/generateUniqueId';
|
import generateUniqueId from '../../util/generateUniqueId';
|
||||||
@ -255,7 +257,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
className={buildClassName('AnimatedSticker', className)}
|
className={buildClassName('AnimatedSticker', className)}
|
||||||
style={buildStyle(
|
style={buildStyle(
|
||||||
size !== undefined && `width: ${size}px; height: ${size}px;`,
|
size !== undefined && `width: ${size}px; height: ${size}px;`,
|
||||||
onClick && 'cursor: pointer',
|
onClick && !IS_ELECTRON && 'cursor: pointer',
|
||||||
colorFilter,
|
colorFilter,
|
||||||
style,
|
style,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { getActions } from '../../../global';
|
|||||||
|
|
||||||
import type { ActiveEmojiInteraction } from '../../../global/types';
|
import type { ActiveEmojiInteraction } from '../../../global/types';
|
||||||
|
|
||||||
|
import { IS_ELECTRON } from '../../../config';
|
||||||
|
|
||||||
import safePlay from '../../../util/safePlay';
|
import safePlay from '../../../util/safePlay';
|
||||||
import buildStyle from '../../../util/buildStyle';
|
import buildStyle from '../../../util/buildStyle';
|
||||||
import { REM } from '../helpers/mediaDimensions';
|
import { REM } from '../helpers/mediaDimensions';
|
||||||
@ -37,7 +39,7 @@ export default function useAnimatedEmoji(
|
|||||||
const soundMediaData = useMedia(soundId ? `document${soundId}` : undefined, !soundId);
|
const soundMediaData = useMedia(soundId ? `document${soundId}` : undefined, !soundId);
|
||||||
|
|
||||||
const size = preferredSize || SIZE;
|
const size = preferredSize || SIZE;
|
||||||
const style = buildStyle(`width: ${size}px`, `height: ${size}px`, emoji && 'cursor: pointer');
|
const style = buildStyle(`width: ${size}px`, `height: ${size}px`, emoji && !IS_ELECTRON && 'cursor: pointer');
|
||||||
|
|
||||||
const interactions = useRef<number[] | undefined>(undefined);
|
const interactions = useRef<number[] | undefined>(undefined);
|
||||||
const startedInteractions = useRef<number | undefined>(undefined);
|
const startedInteractions = useRef<number | undefined>(undefined);
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import {
|
|||||||
IS_TEST,
|
IS_TEST,
|
||||||
PRODUCTION_HOSTNAME,
|
PRODUCTION_HOSTNAME,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { IS_APP } from '../../../util/windowEnvironment';
|
import { IS_APP, IS_MAC_OS } from '../../../util/windowEnvironment';
|
||||||
import {
|
import {
|
||||||
INITIAL_PERFORMANCE_STATE_MAX,
|
INITIAL_PERFORMANCE_STATE_MAX,
|
||||||
INITIAL_PERFORMANCE_STATE_MID,
|
INITIAL_PERFORMANCE_STATE_MID,
|
||||||
@ -421,7 +421,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
shouldDisableDropdownMenuTransitionRef.current && lang.isRtl && 'disable-transition',
|
shouldDisableDropdownMenuTransitionRef.current && lang.isRtl && 'disable-transition',
|
||||||
)}
|
)}
|
||||||
positionX={shouldHideSearch && lang.isRtl ? 'right' : 'left'}
|
positionX={shouldHideSearch && lang.isRtl ? 'right' : 'left'}
|
||||||
transformOriginX={IS_ELECTRON && !isFullscreen ? 90 : undefined}
|
transformOriginX={IS_ELECTRON && IS_MAC_OS && !isFullscreen ? 90 : undefined}
|
||||||
onTransitionEnd={lang.isRtl ? handleDropdownMenuTransitionEnd : undefined}
|
onTransitionEnd={lang.isRtl ? handleDropdownMenuTransitionEnd : undefined}
|
||||||
>
|
>
|
||||||
{menuItems}
|
{menuItems}
|
||||||
|
|||||||
@ -282,11 +282,11 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeUpdateDownloadedListener = window.electron?.on(ElectronEvent.UPDATE_DOWNLOADED, () => {
|
const removeUpdateDownloadedListener = window.electron!.on(ElectronEvent.UPDATE_DOWNLOADED, () => {
|
||||||
setIsAppUpdateAvailable(true);
|
setIsAppUpdateAvailable(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
const removeUpdateErrorListener = window.electron?.on(ElectronEvent.UPDATE_ERROR, () => {
|
const removeUpdateErrorListener = window.electron!.on(ElectronEvent.UPDATE_ERROR, () => {
|
||||||
setIsAppUpdateAvailable(false);
|
setIsAppUpdateAvailable(false);
|
||||||
removeUpdateDownloadedListener?.();
|
removeUpdateDownloadedListener?.();
|
||||||
});
|
});
|
||||||
@ -501,7 +501,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Online status and browser tab indicators
|
// Online status and browser tab indicators
|
||||||
useBackgroundMode(handleBlur, handleFocus);
|
useBackgroundMode(handleBlur, handleFocus, !!IS_ELECTRON);
|
||||||
useBeforeUnload(handleBlur);
|
useBeforeUnload(handleBlur);
|
||||||
usePreventPinchZoomGesture(isMediaViewerOpen);
|
usePreventPinchZoomGesture(isMediaViewerOpen);
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,11 @@
|
|||||||
top: 1rem;
|
top: 1rem;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
cursor: pointer;
|
cursor: var(--custom-cursor, pointer);
|
||||||
|
|
||||||
|
:global(body.is-electron) & {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
|
|||||||
@ -47,6 +47,14 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.is-electron.is-macos & {
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
|
||||||
|
.SearchInput {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body.is-electron.is-macos #Main:not(.is-fullscreen) & {
|
body.is-electron.is-macos #Main:not(.is-fullscreen) & {
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
padding-left: 5rem;
|
padding-left: 5rem;
|
||||||
|
|||||||
42
src/electron/autoUpdates.ts
Normal file
42
src/electron/autoUpdates.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import type { BrowserWindow } from 'electron';
|
||||||
|
import type { UpdateInfo } from 'electron-updater';
|
||||||
|
import { autoUpdater } from 'electron-updater';
|
||||||
|
|
||||||
|
import { ElectronAction, ElectronEvent } from '../types/electron';
|
||||||
|
import { IS_MAC_OS, forceQuit, windows } from './utils';
|
||||||
|
import type { WindowState } from './windowState';
|
||||||
|
|
||||||
|
let interval: NodeJS.Timer;
|
||||||
|
const CHECK_UPDATE_INTERVAL = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
export default function setupAutoUpdates(window: BrowserWindow, state: WindowState) {
|
||||||
|
if (!interval) {
|
||||||
|
autoUpdater.autoDownload = true;
|
||||||
|
autoUpdater.autoInstallOnAppQuit = true;
|
||||||
|
autoUpdater.checkForUpdates();
|
||||||
|
|
||||||
|
interval = setInterval(() => autoUpdater.checkForUpdates(), CHECK_UPDATE_INTERVAL);
|
||||||
|
|
||||||
|
ipcMain.handle(ElectronAction.INSTALL_UPDATE, () => {
|
||||||
|
state.saveLastUrlHash();
|
||||||
|
|
||||||
|
if (IS_MAC_OS) {
|
||||||
|
forceQuit.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return autoUpdater.quitAndInstall();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
autoUpdater.on('error', (error: Error) => {
|
||||||
|
if (windows.has(window)) {
|
||||||
|
window.webContents.send(ElectronEvent.UPDATE_ERROR, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
|
||||||
|
if (windows.has(window)) {
|
||||||
|
window.webContents.send(ElectronEvent.UPDATE_DOWNLOADED, info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,9 +1,9 @@
|
|||||||
productName: "Telegram A"
|
productName: "Telegram A"
|
||||||
artifactName: "${productName}-${arch}-${version}.${ext}"
|
artifactName: "${productName}-${arch}.${ext}"
|
||||||
appId: "org.telegram.TelegramA"
|
appId: "org.telegram.TelegramA"
|
||||||
afterSign: "electron-builder-notarize"
|
|
||||||
extraMetadata:
|
extraMetadata:
|
||||||
main: "./dist/electron.js"
|
main: "./dist/electron.js"
|
||||||
|
productName: "Telegram A"
|
||||||
files:
|
files:
|
||||||
- "dist"
|
- "dist"
|
||||||
- "package.json"
|
- "package.json"
|
||||||
@ -20,15 +20,22 @@ publish:
|
|||||||
provider: "github"
|
provider: "github"
|
||||||
owner: "Ajaxy"
|
owner: "Ajaxy"
|
||||||
repo: "telegram-tt"
|
repo: "telegram-tt"
|
||||||
|
releaseType: "release"
|
||||||
win:
|
win:
|
||||||
target: "nsis"
|
target: "nsis"
|
||||||
icon: "public/icon-electron-windows.ico"
|
icon: "public/icon-electron-windows.ico"
|
||||||
|
nsis:
|
||||||
|
oneClick: false
|
||||||
|
createDesktopShortcut: true
|
||||||
|
createStartMenuShortcut: true
|
||||||
mac:
|
mac:
|
||||||
target:
|
target:
|
||||||
target: "default"
|
target: "default"
|
||||||
arch: ["x64", "arm64"]
|
arch: ["x64", "arm64"]
|
||||||
entitlements: "public/electron-entitlements.mac.plist"
|
entitlements: "public/electron-entitlements.mac.plist"
|
||||||
icon: "public/icon-electron-macos.icns"
|
icon: "public/icon-electron-macos.icns"
|
||||||
|
notarize:
|
||||||
|
teamId: "Y54Z4K69Z9"
|
||||||
linux:
|
linux:
|
||||||
category: "Community"
|
category: "Community"
|
||||||
target: ["AppImage"]
|
target: ["AppImage"]
|
||||||
|
|||||||
@ -1,11 +1,20 @@
|
|||||||
import 'v8-compile-cache';
|
import 'v8-compile-cache';
|
||||||
|
|
||||||
import { app, nativeImage } from 'electron';
|
import { app, nativeImage } from 'electron';
|
||||||
|
import contextMenu from 'electron-context-menu';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { createWindow, setupCloseHandlers, setupElectronActionHandlers } from './window';
|
import { createWindow, setupCloseHandlers, setupElectronActionHandlers } from './window';
|
||||||
import { IS_MAC_OS, IS_WINDOWS } from './utils';
|
import { IS_MAC_OS, IS_WINDOWS } from './utils';
|
||||||
|
|
||||||
|
contextMenu({
|
||||||
|
showLearnSpelling: false,
|
||||||
|
showLookUpSelection: false,
|
||||||
|
showSearchWithGoogle: false,
|
||||||
|
showCopyImage: false,
|
||||||
|
showSelectAll: true,
|
||||||
|
});
|
||||||
|
|
||||||
app.on('ready', () => {
|
app.on('ready', () => {
|
||||||
if (IS_MAC_OS) {
|
if (IS_MAC_OS) {
|
||||||
app.dock.setIcon(nativeImage.createFromPath(path.resolve(__dirname, '../public/icon-electron-macos.png')));
|
app.dock.setIcon(nativeImage.createFromPath(path.resolve(__dirname, '../public/icon-electron-macos.png')));
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { contextBridge, ipcRenderer } from 'electron';
|
import { contextBridge, ipcRenderer } from 'electron';
|
||||||
|
|
||||||
import type { IpcRendererEvent } from 'electron';
|
import type { IpcRendererEvent } from 'electron';
|
||||||
import type { ElectronApi, ElectronEvent } from '../types/electron';
|
import type { ElectronApi, ElectronEvent, TrafficLightPosition } from '../types/electron';
|
||||||
import { ElectronAction } from '../types/electron';
|
import { ElectronAction } from '../types/electron';
|
||||||
|
|
||||||
const electronApi: ElectronApi = {
|
const electronApi: ElectronApi = {
|
||||||
@ -9,6 +9,9 @@ const electronApi: ElectronApi = {
|
|||||||
installUpdate: () => ipcRenderer.invoke(ElectronAction.INSTALL_UPDATE),
|
installUpdate: () => ipcRenderer.invoke(ElectronAction.INSTALL_UPDATE),
|
||||||
handleDoubleClick: () => ipcRenderer.invoke(ElectronAction.HANDLE_DOUBLE_CLICK),
|
handleDoubleClick: () => ipcRenderer.invoke(ElectronAction.HANDLE_DOUBLE_CLICK),
|
||||||
openNewWindow: (url: string) => ipcRenderer.invoke(ElectronAction.OPEN_NEW_WINDOW, url),
|
openNewWindow: (url: string) => ipcRenderer.invoke(ElectronAction.OPEN_NEW_WINDOW, url),
|
||||||
|
setWindowTitle: (title?: string) => ipcRenderer.invoke(ElectronAction.SET_WINDOW_TITLE, title),
|
||||||
|
setTrafficLightPosition:
|
||||||
|
(position: TrafficLightPosition) => ipcRenderer.invoke(ElectronAction.SET_TRAFFIC_LIGHT_POSITION, position),
|
||||||
|
|
||||||
on: (eventName: ElectronEvent, callback) => {
|
on: (eventName: ElectronEvent, callback) => {
|
||||||
const subscription = (event: IpcRendererEvent, ...args: any) => callback(...args);
|
const subscription = (event: IpcRendererEvent, ...args: any) => callback(...args);
|
||||||
|
|||||||
@ -1,3 +1,41 @@
|
|||||||
|
import { app } from 'electron';
|
||||||
|
import type { BrowserWindow, Point } from 'electron';
|
||||||
|
|
||||||
|
import type { TrafficLightPosition } from '../types/electron';
|
||||||
|
|
||||||
|
export const windows = new Set<BrowserWindow>();
|
||||||
|
|
||||||
export const IS_MAC_OS = process.platform === 'darwin';
|
export const IS_MAC_OS = process.platform === 'darwin';
|
||||||
export const IS_WINDOWS = process.platform === 'win32';
|
export const IS_WINDOWS = process.platform === 'win32';
|
||||||
export const IS_LINUX = process.platform === 'linux';
|
export const IS_LINUX = process.platform === 'linux';
|
||||||
|
|
||||||
|
export function getAppTitle(chatTitle?: string): string {
|
||||||
|
const appName = app.getName();
|
||||||
|
|
||||||
|
if (!chatTitle) {
|
||||||
|
return appName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${chatTitle} · ${appName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TRAFFIC_LIGHT_POSITION: Record<TrafficLightPosition, Point> = {
|
||||||
|
standard: { x: 10, y: 20 },
|
||||||
|
lowered: { x: 10, y: 52 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const forceQuit = {
|
||||||
|
value: false,
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
this.value = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
this.value = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
get isEnabled(): boolean {
|
||||||
|
return this.value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@ -2,24 +2,22 @@ import {
|
|||||||
app, BrowserWindow, ipcMain, shell, systemPreferences,
|
app, BrowserWindow, ipcMain, shell, systemPreferences,
|
||||||
} from 'electron';
|
} from 'electron';
|
||||||
import type { HandlerDetails } from 'electron';
|
import type { HandlerDetails } from 'electron';
|
||||||
import type { UpdateInfo } from 'electron-updater';
|
|
||||||
import { autoUpdater } from 'electron-updater';
|
|
||||||
import windowStateKeeper from 'electron-window-state';
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { ElectronAction, ElectronEvent } from '../types/electron';
|
import { ElectronAction, ElectronEvent } from '../types/electron';
|
||||||
import { IS_MAC_OS } from './utils';
|
import type { TrafficLightPosition } from '../types/electron';
|
||||||
|
import setupAutoUpdates from './autoUpdates';
|
||||||
|
import {
|
||||||
|
forceQuit, getAppTitle, IS_MAC_OS, TRAFFIC_LIGHT_POSITION, windows,
|
||||||
|
} from './utils';
|
||||||
|
import windowStateKeeper from './windowState';
|
||||||
|
|
||||||
let forceQuit = false;
|
const ALLOWED_DEVICE_ORIGINS = ['http://localhost:1234', 'file://'];
|
||||||
let interval: NodeJS.Timer;
|
|
||||||
|
|
||||||
const windows = new Set<BrowserWindow>();
|
|
||||||
const CHECK_UPDATE_INTERVAL = 10 * 60 * 1000;
|
|
||||||
|
|
||||||
export function createWindow(url?: string) {
|
export function createWindow(url?: string) {
|
||||||
const windowState = windowStateKeeper({
|
const windowState = windowStateKeeper({
|
||||||
defaultWidth: 1088,
|
defaultWidth: 1088,
|
||||||
defaultHeight: IS_MAC_OS ? 700 : 750,
|
defaultHeight: 700,
|
||||||
});
|
});
|
||||||
|
|
||||||
let x;
|
let x;
|
||||||
@ -55,14 +53,14 @@ export function createWindow(url?: string) {
|
|||||||
minWidth: 360,
|
minWidth: 360,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
title: 'Telegram A',
|
title: getAppTitle(),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
devTools: process.env.APP_ENV !== 'production',
|
devTools: process.env.APP_ENV !== 'production',
|
||||||
},
|
},
|
||||||
...(IS_MAC_OS && {
|
...(IS_MAC_OS && {
|
||||||
titleBarStyle: 'hidden',
|
titleBarStyle: 'hidden',
|
||||||
trafficLightPosition: { x: 10, y: 20 },
|
trafficLightPosition: TRAFFIC_LIGHT_POSITION.standard,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -77,6 +75,10 @@ export function createWindow(url?: string) {
|
|||||||
return { action: 'deny' };
|
return { action: 'deny' };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.webContents.session.setDevicePermissionHandler(({ deviceType, origin }) => {
|
||||||
|
return deviceType === 'hid' && ALLOWED_DEVICE_ORIGINS.includes(origin);
|
||||||
|
});
|
||||||
|
|
||||||
window.on('enter-full-screen', () => {
|
window.on('enter-full-screen', () => {
|
||||||
window.webContents.send(ElectronEvent.FULLSCREEN_CHANGE, true);
|
window.webContents.send(ElectronEvent.FULLSCREEN_CHANGE, true);
|
||||||
});
|
});
|
||||||
@ -87,14 +89,15 @@ export function createWindow(url?: string) {
|
|||||||
|
|
||||||
window.on('close', (event) => {
|
window.on('close', (event) => {
|
||||||
if (IS_MAC_OS) {
|
if (IS_MAC_OS) {
|
||||||
if (forceQuit) {
|
if (forceQuit.isEnabled) {
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
forceQuit = false;
|
forceQuit.disable();
|
||||||
} else {
|
} else {
|
||||||
const hasExtraWindows = BrowserWindow.getAllWindows().length > 1;
|
const hasExtraWindows = BrowserWindow.getAllWindows().length > 1;
|
||||||
|
|
||||||
if (hasExtraWindows) {
|
if (hasExtraWindows) {
|
||||||
windows.delete(window);
|
windows.delete(window);
|
||||||
|
windowState.unmanage();
|
||||||
} else {
|
} else {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
window.hide();
|
window.hide();
|
||||||
@ -106,12 +109,14 @@ export function createWindow(url?: string) {
|
|||||||
if (url) {
|
if (url) {
|
||||||
window.loadURL(url);
|
window.loadURL(url);
|
||||||
} else if (app.isPackaged) {
|
} else if (app.isPackaged) {
|
||||||
window.loadURL(`file://${__dirname}/index.html`);
|
window.loadURL(`file://${__dirname}/index.html${windowState.urlHash}`);
|
||||||
} else {
|
} else {
|
||||||
window.loadURL('http://localhost:1234');
|
window.loadURL(`http://localhost:1234${windowState.urlHash}`);
|
||||||
window.webContents.openDevTools();
|
window.webContents.openDevTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
windowState.clearLastUrlHash();
|
||||||
|
|
||||||
if (!IS_MAC_OS) {
|
if (!IS_MAC_OS) {
|
||||||
window.removeMenu();
|
window.removeMenu();
|
||||||
}
|
}
|
||||||
@ -120,45 +125,21 @@ export function createWindow(url?: string) {
|
|||||||
window.show();
|
window.show();
|
||||||
|
|
||||||
if (process.env.APP_ENV === 'production') {
|
if (process.env.APP_ENV === 'production') {
|
||||||
setupAutoUpdates(window);
|
setupAutoUpdates(window, windowState);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
windows.add(window);
|
windows.add(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupAutoUpdates(window: BrowserWindow) {
|
|
||||||
if (!interval) {
|
|
||||||
autoUpdater.autoDownload = true;
|
|
||||||
autoUpdater.autoInstallOnAppQuit = true;
|
|
||||||
autoUpdater.checkForUpdates();
|
|
||||||
|
|
||||||
interval = setInterval(() => autoUpdater.checkForUpdates(), CHECK_UPDATE_INTERVAL);
|
|
||||||
|
|
||||||
ipcMain.handle(ElectronAction.INSTALL_UPDATE, () => {
|
|
||||||
if (IS_MAC_OS) {
|
|
||||||
forceQuit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return autoUpdater.quitAndInstall();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
autoUpdater.on('error', (error: Error) => {
|
|
||||||
if (windows.has(window)) {
|
|
||||||
window.webContents.send(ElectronEvent.UPDATE_ERROR, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
|
|
||||||
if (windows.has(window)) {
|
|
||||||
window.webContents.send(ElectronEvent.UPDATE_DOWNLOADED, info);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setupElectronActionHandlers() {
|
export function setupElectronActionHandlers() {
|
||||||
ipcMain.handle(ElectronAction.OPEN_NEW_WINDOW, (_, newWindowUrl: string) => {
|
ipcMain.handle(ElectronAction.OPEN_NEW_WINDOW, (_, url: string) => {
|
||||||
createWindow(newWindowUrl);
|
createWindow(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle(ElectronAction.SET_WINDOW_TITLE, (_, newTitle?: string) => {
|
||||||
|
const currentWindow = BrowserWindow.getFocusedWindow();
|
||||||
|
currentWindow?.setTitle(getAppTitle(newTitle));
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(ElectronAction.GET_IS_FULLSCREEN, () => {
|
ipcMain.handle(ElectronAction.GET_IS_FULLSCREEN, () => {
|
||||||
@ -180,6 +161,15 @@ export function setupElectronActionHandlers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle(ElectronAction.SET_TRAFFIC_LIGHT_POSITION, (_, position: TrafficLightPosition) => {
|
||||||
|
if (!IS_MAC_OS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentWindow = BrowserWindow.getFocusedWindow();
|
||||||
|
currentWindow?.setTrafficLightPosition(TRAFFIC_LIGHT_POSITION[position]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupCloseHandlers() {
|
export function setupCloseHandlers() {
|
||||||
@ -190,9 +180,9 @@ export function setupCloseHandlers() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.on('before-quit', (event) => {
|
app.on('before-quit', (event) => {
|
||||||
if (IS_MAC_OS && !forceQuit) {
|
if (IS_MAC_OS && !forceQuit.isEnabled) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
forceQuit = true;
|
forceQuit.enable();
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -203,7 +193,7 @@ export function setupCloseHandlers() {
|
|||||||
if (!hasActiveWindow) {
|
if (!hasActiveWindow) {
|
||||||
createWindow();
|
createWindow();
|
||||||
} else if (IS_MAC_OS) {
|
} else if (IS_MAC_OS) {
|
||||||
forceQuit = false;
|
forceQuit.disable();
|
||||||
|
|
||||||
const currentWindow = Array.from(windows).pop();
|
const currentWindow = Array.from(windows).pop();
|
||||||
currentWindow?.show();
|
currentWindow?.show();
|
||||||
|
|||||||
205
src/electron/windowState.ts
Normal file
205
src/electron/windowState.ts
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
import type { BrowserWindow, Rectangle } from 'electron';
|
||||||
|
|
||||||
|
import { screen } from 'electron';
|
||||||
|
import Store from 'electron-store';
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
defaultHeight?: number;
|
||||||
|
defaultWidth?: number;
|
||||||
|
fullScreen?: boolean;
|
||||||
|
maximize?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
displayBounds: {
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
};
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
isFullScreen: boolean;
|
||||||
|
isMaximized: boolean;
|
||||||
|
urlHash: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WindowState = State & {
|
||||||
|
manage: (window: Electron.BrowserWindow) => void;
|
||||||
|
unmanage: () => void;
|
||||||
|
resetStateToDefault: () => void;
|
||||||
|
saveLastUrlHash: () => void;
|
||||||
|
clearLastUrlHash: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EVENT_HANDLING_DELAY = 100;
|
||||||
|
const STORE_KEY = 'window-state';
|
||||||
|
const DEFAULT_OPTIONS = {
|
||||||
|
defaultHeight: 600,
|
||||||
|
defaultWidth: 800,
|
||||||
|
maximize: true,
|
||||||
|
fullScreen: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const store: Store = new Store();
|
||||||
|
|
||||||
|
function windowStateKeeper(options: Options): WindowState {
|
||||||
|
let state: State;
|
||||||
|
let winRef: BrowserWindow | undefined;
|
||||||
|
let stateChangeTimer: NodeJS.Timer | number;
|
||||||
|
|
||||||
|
options = {
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
function isNormal(win: BrowserWindow): boolean {
|
||||||
|
return !win.isMaximized() && !win.isMinimized() && !win.isFullScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasBounds(): boolean {
|
||||||
|
return state
|
||||||
|
&& Number.isInteger(state.x)
|
||||||
|
&& Number.isInteger(state.y)
|
||||||
|
&& Number.isInteger(state.width) && state.width > 0
|
||||||
|
&& Number.isInteger(state.height) && state.height > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetStateToDefault() {
|
||||||
|
const displayBounds = screen.getPrimaryDisplay().bounds;
|
||||||
|
|
||||||
|
state = {
|
||||||
|
width: options.defaultWidth!,
|
||||||
|
height: options.defaultHeight!,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
displayBounds,
|
||||||
|
isMaximized: false,
|
||||||
|
isFullScreen: false,
|
||||||
|
urlHash: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function windowWithinBounds(bounds: Rectangle) {
|
||||||
|
return state.x >= bounds.x
|
||||||
|
&& state.y >= bounds.y
|
||||||
|
&& state.x + state.width <= bounds.x + bounds.width
|
||||||
|
&& state.y + state.height <= bounds.y + bounds.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureWindowVisibleOnSomeDisplay() {
|
||||||
|
const visible = screen.getAllDisplays().some((display) => windowWithinBounds(display.bounds));
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
resetStateToDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateState() {
|
||||||
|
const isValid = state && (hasBounds() || state.isMaximized || state.isFullScreen);
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
resetStateToDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasBounds() && state.displayBounds) {
|
||||||
|
ensureWindowVisibleOnSomeDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateState() {
|
||||||
|
if (!winRef) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't throw an error when window was closed
|
||||||
|
try {
|
||||||
|
const winBounds = winRef.getBounds();
|
||||||
|
if (isNormal(winRef)) {
|
||||||
|
state.x = winBounds.x;
|
||||||
|
state.y = winBounds.y;
|
||||||
|
state.width = winBounds.width;
|
||||||
|
state.height = winBounds.height;
|
||||||
|
}
|
||||||
|
state.isMaximized = winRef.isMaximized();
|
||||||
|
state.isFullScreen = winRef.isFullScreen();
|
||||||
|
state.displayBounds = screen.getDisplayMatching(winBounds).bounds;
|
||||||
|
} catch (err) {
|
||||||
|
// Handler not supported, ignoring
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleStateChange() {
|
||||||
|
clearTimeout(stateChangeTimer);
|
||||||
|
stateChangeTimer = setTimeout(updateState, EVENT_HANDLING_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClosed() {
|
||||||
|
unmanage();
|
||||||
|
store.set(STORE_KEY, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function manage(win: BrowserWindow) {
|
||||||
|
if (options.maximize && state.isMaximized) {
|
||||||
|
win.maximize();
|
||||||
|
}
|
||||||
|
if (options.fullScreen && state.isFullScreen) {
|
||||||
|
win.setFullScreen(true);
|
||||||
|
}
|
||||||
|
win.on('resize', handleStateChange);
|
||||||
|
win.on('move', handleStateChange);
|
||||||
|
win.on('close', handleClose);
|
||||||
|
win.on('closed', handleClosed);
|
||||||
|
winRef = win;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unmanage() {
|
||||||
|
if (winRef) {
|
||||||
|
winRef.removeListener('resize', handleStateChange);
|
||||||
|
winRef.removeListener('move', handleStateChange);
|
||||||
|
clearTimeout(stateChangeTimer);
|
||||||
|
winRef.removeListener('close', handleClose);
|
||||||
|
winRef.removeListener('closed', handleClosed);
|
||||||
|
winRef = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveLastUrlHash() {
|
||||||
|
if (winRef) {
|
||||||
|
const { hash } = new URL(winRef.webContents.getURL());
|
||||||
|
|
||||||
|
state.urlHash = hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLastUrlHash() {
|
||||||
|
state.urlHash = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
state = store.get(STORE_KEY) as State;
|
||||||
|
|
||||||
|
validateState();
|
||||||
|
|
||||||
|
return {
|
||||||
|
get x() { return state.x; },
|
||||||
|
get y() { return state.y; },
|
||||||
|
get width() { return state.width; },
|
||||||
|
get height() { return state.height; },
|
||||||
|
get displayBounds() { return state.displayBounds; },
|
||||||
|
get isMaximized() { return state.isMaximized; },
|
||||||
|
get isFullScreen() { return state.isFullScreen; },
|
||||||
|
get urlHash() { return state.urlHash || ''; },
|
||||||
|
unmanage,
|
||||||
|
manage,
|
||||||
|
resetStateToDefault,
|
||||||
|
saveLastUrlHash,
|
||||||
|
clearLastUrlHash,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default windowStateKeeper;
|
||||||
@ -82,6 +82,7 @@ addActionHandler('openChatInNewTab', (global, actions, payload): ActionReturnTyp
|
|||||||
const { chatId, threadId = MAIN_THREAD_ID } = payload;
|
const { chatId, threadId = MAIN_THREAD_ID } = payload;
|
||||||
|
|
||||||
const hashUrl = createMessageHashUrl(chatId, 'thread', threadId);
|
const hashUrl = createMessageHashUrl(chatId, 'thread', threadId);
|
||||||
|
|
||||||
if (IS_ELECTRON) {
|
if (IS_ELECTRON) {
|
||||||
window.electron!.openNewWindow(hashUrl);
|
window.electron!.openNewWindow(hashUrl);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -694,7 +694,7 @@ addActionHandler('updatePageTitle', (global, actions, payload): ActionReturnType
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setPageTitleInstant(PAGE_TITLE);
|
setPageTitleInstant(IS_ELECTRON ? '' : PAGE_TITLE);
|
||||||
});
|
});
|
||||||
|
|
||||||
let prevIsScreenLocked: boolean | undefined;
|
let prevIsScreenLocked: boolean | undefined;
|
||||||
|
|||||||
@ -71,6 +71,10 @@ body.is-ios {
|
|||||||
--border-radius-messages-small: 0.5rem;
|
--border-radius-messages-small: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.is-electron {
|
||||||
|
--custom-cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
body.cursor-grabbing {
|
body.cursor-grabbing {
|
||||||
--custom-cursor: grabbing;
|
--custom-cursor: grabbing;
|
||||||
cursor: grabbing !important;
|
cursor: grabbing !important;
|
||||||
|
|||||||
@ -9,13 +9,19 @@ export enum ElectronAction {
|
|||||||
INSTALL_UPDATE = 'install-update',
|
INSTALL_UPDATE = 'install-update',
|
||||||
HANDLE_DOUBLE_CLICK = 'handle-double-click',
|
HANDLE_DOUBLE_CLICK = 'handle-double-click',
|
||||||
OPEN_NEW_WINDOW = 'open-new-window',
|
OPEN_NEW_WINDOW = 'open-new-window',
|
||||||
|
SET_WINDOW_TITLE = 'set-window-title',
|
||||||
|
SET_TRAFFIC_LIGHT_POSITION = 'set-traffic-light-position',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TrafficLightPosition = 'standard' | 'lowered';
|
||||||
|
|
||||||
export interface ElectronApi {
|
export interface ElectronApi {
|
||||||
isFullscreen: () => Promise<boolean>;
|
isFullscreen: () => Promise<boolean>;
|
||||||
installUpdate: () => Promise<void>;
|
installUpdate: () => Promise<void>;
|
||||||
handleDoubleClick: () => Promise<void>;
|
handleDoubleClick: () => Promise<void>;
|
||||||
openNewWindow: (url: string) => Promise<void>;
|
openNewWindow: (url: string, title?: string) => Promise<void>;
|
||||||
|
setWindowTitle: (title?: string) => Promise<void>;
|
||||||
|
setTrafficLightPosition: (position: TrafficLightPosition) => Promise<void>;
|
||||||
on: (eventName: ElectronEvent, callback: any) => VoidFunction;
|
on: (eventName: ElectronEvent, callback: any) => VoidFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -346,9 +346,9 @@ function getNotificationContent(chat: ApiChat, message: ApiMessage, reaction?: A
|
|||||||
!isScreenLocked
|
!isScreenLocked
|
||||||
&& selectShouldShowMessagePreview(chat, selectNotifySettings(global), selectNotifyExceptions(global))
|
&& selectShouldShowMessagePreview(chat, selectNotifySettings(global), selectNotifyExceptions(global))
|
||||||
) {
|
) {
|
||||||
if (isActionMessage(message)) {
|
const isChat = chat && (isChatChannel(chat) || message.senderId === message.chatId);
|
||||||
const isChat = chat && (isChatChannel(chat) || message.senderId === message.chatId);
|
|
||||||
|
|
||||||
|
if (isActionMessage(message)) {
|
||||||
body = renderActionMessageText(
|
body = renderActionMessageText(
|
||||||
translate,
|
translate,
|
||||||
message,
|
message,
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { IS_ELECTRON } from '../config';
|
||||||
import { debounce } from './schedulers';
|
import { debounce } from './schedulers';
|
||||||
|
|
||||||
const UPDATE_DEBOUNCE_MS = 200;
|
const UPDATE_DEBOUNCE_MS = 200;
|
||||||
@ -5,6 +6,12 @@ const UPDATE_DEBOUNCE_MS = 200;
|
|||||||
// For some reason setting `document.title` to the same value
|
// For some reason setting `document.title` to the same value
|
||||||
// causes increment of Chrome Dev Tools > Performance Monitor > DOM Nodes counter
|
// causes increment of Chrome Dev Tools > Performance Monitor > DOM Nodes counter
|
||||||
export function setPageTitleInstant(nextTitle: string) {
|
export function setPageTitleInstant(nextTitle: string) {
|
||||||
|
if (IS_ELECTRON) {
|
||||||
|
window.electron!.setWindowTitle(nextTitle);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (document.title !== nextTitle) {
|
if (document.title !== nextTitle) {
|
||||||
document.title = nextTitle;
|
document.title = nextTitle;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user