App Inactive: Support disconnecting when opening in another tab
This commit is contained in:
parent
baffa925d8
commit
987dfacaf5
24
src/App.tsx
24
src/App.tsx
@ -1,25 +1,42 @@
|
||||
import { FC, useEffect } from './lib/teact/teact';
|
||||
import React, { withGlobal } from './lib/teact/teactn';
|
||||
|
||||
import { GlobalState } from './global/types';
|
||||
import { GlobalActions, GlobalState } from './global/types';
|
||||
|
||||
import { INACTIVE_MARKER, PAGE_TITLE } from './config';
|
||||
import { pick } from './util/iteratees';
|
||||
import { updateSizes } from './util/windowSize';
|
||||
import { addActiveTabChangeListener } from './util/activeTabMonitor';
|
||||
import useFlag from './hooks/useFlag';
|
||||
|
||||
import Auth from './components/auth/Auth';
|
||||
import UiLoader from './components/common/UiLoader';
|
||||
import Main from './components/main/Main.async';
|
||||
import AppInactive from './components/main/AppInactive';
|
||||
// import Test from './components/test/TestNoRedundancy';
|
||||
|
||||
type StateProps = Pick<GlobalState, 'authState' | 'authIsSessionRemembered'>;
|
||||
type DispatchProps = Pick<GlobalActions, 'disconnect'>;
|
||||
|
||||
const App: FC<StateProps & DispatchProps> = ({ authState, authIsSessionRemembered, disconnect }) => {
|
||||
const [isInactive, markInactive] = useFlag(false);
|
||||
|
||||
const App: FC<StateProps> = ({ authState, authIsSessionRemembered }) => {
|
||||
useEffect(() => {
|
||||
updateSizes();
|
||||
}, []);
|
||||
addActiveTabChangeListener(() => {
|
||||
disconnect();
|
||||
document.title = `${PAGE_TITLE}${INACTIVE_MARKER}`;
|
||||
|
||||
markInactive();
|
||||
});
|
||||
}, [disconnect, markInactive]);
|
||||
|
||||
// return <Test />;
|
||||
|
||||
if (isInactive) {
|
||||
return <AppInactive />;
|
||||
}
|
||||
|
||||
if (authState) {
|
||||
switch (authState) {
|
||||
case 'authorizationStateWaitPhoneNumber':
|
||||
@ -49,4 +66,5 @@ function renderMain() {
|
||||
|
||||
export default withGlobal(
|
||||
(global): StateProps => pick(global, ['authState', 'authIsSessionRemembered']),
|
||||
(setGlobal, actions): DispatchProps => pick(actions, ['disconnect']),
|
||||
)(App);
|
||||
|
||||
@ -95,6 +95,10 @@ export async function destroy() {
|
||||
await client.destroy();
|
||||
}
|
||||
|
||||
export async function disconnect() {
|
||||
await client.disconnect();
|
||||
}
|
||||
|
||||
export function getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export { destroy, downloadMedia, fetchCurrentUser } from './client';
|
||||
export {
|
||||
destroy, disconnect, downloadMedia, fetchCurrentUser,
|
||||
} from './client';
|
||||
|
||||
export {
|
||||
provideAuthPhoneNumber, provideAuthCode, provideAuthPassword, provideAuthRegistration, restartAuth, restartAuthWithQr,
|
||||
|
||||
BIN
src/assets/app-inactive.png
Normal file
BIN
src/assets/app-inactive.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
32
src/components/main/AppInactive.scss
Normal file
32
src/components/main/AppInactive.scss
Normal file
@ -0,0 +1,32 @@
|
||||
#AppInactive {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.content {
|
||||
max-width: 28rem;
|
||||
margin: auto;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.Button {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
32
src/components/main/AppInactive.tsx
Normal file
32
src/components/main/AppInactive.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React, { FC } from '../../lib/teact/teact';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
|
||||
import appInactivePath from '../../assets/app-inactive.png';
|
||||
import './AppInactive.scss';
|
||||
|
||||
|
||||
const AppInactive: FC = () => {
|
||||
const handleReload = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="AppInactive">
|
||||
<div className="content">
|
||||
<img src={appInactivePath} alt="" />
|
||||
<h3 className="title">Such error, many tabs</h3>
|
||||
<div className="description">
|
||||
Telegram supports only one active tab with the app.
|
||||
<br />
|
||||
Please reload this page to continue using this tab or close it.
|
||||
</div>
|
||||
<div className="actions">
|
||||
<Button isText ripple onClick={handleReload}>Reload app</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppInactive;
|
||||
@ -5,7 +5,9 @@ import { GlobalActions } from '../../global/types';
|
||||
import { ApiMessage } from '../../api/types';
|
||||
|
||||
import '../../modules/actions/all';
|
||||
import { ANIMATION_END_DELAY, DEBUG } from '../../config';
|
||||
import {
|
||||
ANIMATION_END_DELAY, DEBUG, INACTIVE_MARKER, PAGE_TITLE,
|
||||
} from '../../config';
|
||||
import { pick } from '../../util/iteratees';
|
||||
import {
|
||||
selectChatMessage,
|
||||
@ -44,7 +46,6 @@ type StateProps = {
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, 'loadAnimatedEmojis'>;
|
||||
|
||||
const APP_NAME = 'Telegram';
|
||||
const ANIMATION_DURATION = 350;
|
||||
const NOTIFICATION_INTERVAL = 1000;
|
||||
|
||||
@ -122,6 +123,11 @@ const Main: FC<StateProps & DispatchProps> = ({
|
||||
|
||||
clearInterval(notificationInterval);
|
||||
notificationInterval = window.setInterval(() => {
|
||||
if (document.title.includes(INACTIVE_MARKER)) {
|
||||
updateIcon(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (index % 2 === 0) {
|
||||
const newUnread = selectCountNotMutedUnread(getGlobal()) - initialUnread;
|
||||
if (newUnread > 0) {
|
||||
@ -129,7 +135,7 @@ const Main: FC<StateProps & DispatchProps> = ({
|
||||
updateIcon(true);
|
||||
}
|
||||
} else {
|
||||
document.title = APP_NAME;
|
||||
document.title = PAGE_TITLE;
|
||||
updateIcon(false);
|
||||
}
|
||||
|
||||
@ -138,7 +144,11 @@ const Main: FC<StateProps & DispatchProps> = ({
|
||||
}, () => {
|
||||
clearInterval(notificationInterval);
|
||||
notificationInterval = undefined;
|
||||
document.title = APP_NAME;
|
||||
|
||||
if (!document.title.includes(INACTIVE_MARKER)) {
|
||||
document.title = PAGE_TITLE;
|
||||
}
|
||||
|
||||
updateIcon(false);
|
||||
});
|
||||
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
export const PAGE_TITLE = 'Telegram';
|
||||
export const INACTIVE_MARKER = ' [Inactive]';
|
||||
|
||||
export const APP_INFO = process.env.APP_INFO || 'Telegram T';
|
||||
|
||||
export const DEBUG = (
|
||||
|
||||
@ -383,7 +383,7 @@ export type GlobalState = {
|
||||
|
||||
export type ActionTypes = (
|
||||
// system
|
||||
'init' | 'reset' | 'initApi' | 'apiUpdate' | 'sync' | 'saveSession' | 'afterSync' |
|
||||
'init' | 'reset' | 'disconnect' | 'initApi' | 'apiUpdate' | 'sync' | 'saveSession' | 'afterSync' |
|
||||
'showNotification' | 'dismissNotification' | 'showError' | 'dismissError' |
|
||||
// ui
|
||||
'toggleChatInfo' | 'setIsUiReady' | 'addRecentEmoji' | 'addRecentSticker' | 'toggleLeftColumn' |
|
||||
|
||||
@ -126,6 +126,12 @@ addReducer('reset', () => {
|
||||
getDispatch().init();
|
||||
});
|
||||
|
||||
addReducer('disconnect', () => {
|
||||
(async () => {
|
||||
await callApi('disconnect');
|
||||
})();
|
||||
});
|
||||
|
||||
addReducer('loadNearestCountry', (global) => {
|
||||
if (global.connectionState !== 'connectionStateReady') {
|
||||
return;
|
||||
|
||||
19
src/util/activeTabMonitor.ts
Normal file
19
src/util/activeTabMonitor.ts
Normal file
@ -0,0 +1,19 @@
|
||||
const STORAGE_KEY = 'tt-active-tab';
|
||||
const INTERVAL = 2000;
|
||||
|
||||
const tabKey = String(Date.now() + Math.random());
|
||||
|
||||
localStorage.setItem(STORAGE_KEY, tabKey);
|
||||
|
||||
let callback: NoneToVoidFunction;
|
||||
|
||||
const interval = window.setInterval(() => {
|
||||
if (callback && localStorage.getItem(STORAGE_KEY) !== tabKey) {
|
||||
callback();
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, INTERVAL);
|
||||
|
||||
export function addActiveTabChangeListener(_callback: NoneToVoidFunction) {
|
||||
callback = _callback;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user