Multiple Tabs: Introduce Multiple Tabs Support (#2221)
This commit is contained in:
parent
58c71fd908
commit
2a7c78c12a
@ -9,9 +9,15 @@
|
|||||||
"jsx-expressions",
|
"jsx-expressions",
|
||||||
"no-async-without-await",
|
"no-async-without-await",
|
||||||
"teactn",
|
"teactn",
|
||||||
"no-null"
|
"no-null",
|
||||||
|
"eslint-multitab-tt"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"eslint-multitab-tt/no-immediate-global": "error",
|
||||||
|
"eslint-multitab-tt/must-update-global-after-await": "off",
|
||||||
|
"eslint-multitab-tt/set-global-only-variable": "error",
|
||||||
|
"eslint-multitab-tt/no-getactions-in-actions": "error",
|
||||||
|
"eslint-multitab-tt/must-specify-action-handler-return-type": "error",
|
||||||
"indent": [
|
"indent": [
|
||||||
"error",
|
"error",
|
||||||
2,
|
2,
|
||||||
|
|||||||
19
dev/eslint-multitab/.eslintrc.js
Normal file
19
dev/eslint-multitab/.eslintrc.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:eslint-plugin/recommended",
|
||||||
|
"plugin:node/recommended",
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ["tests/**/*.js"],
|
||||||
|
env: { mocha: true },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
22
dev/eslint-multitab/lib/index.js
Normal file
22
dev/eslint-multitab/lib/index.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview eslint-multitab-tt
|
||||||
|
* @author undrfined
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Requirements
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const requireIndex = require("requireindex");
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Plugin Definition
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
// import all rules in lib/rules
|
||||||
|
module.exports.rules = requireIndex(__dirname + "/rules");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Must specify action handler return type
|
||||||
|
* @author undrfined
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Rule Definition
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** @type {import('eslint').Rule.RuleModule} */
|
||||||
|
module.exports = {
|
||||||
|
meta: {
|
||||||
|
type: "problem",
|
||||||
|
docs: {
|
||||||
|
description: "Must specify action handler return type",
|
||||||
|
recommended: false,
|
||||||
|
url: null,
|
||||||
|
},
|
||||||
|
fixable: null,
|
||||||
|
schema: [],
|
||||||
|
messages: {
|
||||||
|
mustSpecifyActionHandlerReturnType: "Must specify action handler return type",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
create(context) {
|
||||||
|
return {
|
||||||
|
ArrowFunctionExpression: (node) => {
|
||||||
|
if(node.parent.type === "CallExpression" && node.parent.callee.name === 'addActionHandler' && !node.returnType) {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
messageId: "mustSpecifyActionHandlerReturnType",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
108
dev/eslint-multitab/lib/rules/must-update-global-after-await.js
Normal file
108
dev/eslint-multitab/lib/rules/must-update-global-after-await.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Must update global after await
|
||||||
|
* @author undrfined
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Rule Definition
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** @type {import('eslint').Rule.RuleModule} */
|
||||||
|
// TODO This rule is not working properly
|
||||||
|
module.exports = {
|
||||||
|
meta: {
|
||||||
|
type: "problem",
|
||||||
|
docs: {
|
||||||
|
description: "Must update global after await",
|
||||||
|
recommended: false,
|
||||||
|
url: null,
|
||||||
|
},
|
||||||
|
fixable: null,
|
||||||
|
schema: [],
|
||||||
|
messages: {
|
||||||
|
mustUpdateGlobalAfterAwait: "Global is outdated because of await here -> {{before}}, use global = getGlobal() to update",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
create(context) {
|
||||||
|
let hasAssignmentOnBlockLevel;
|
||||||
|
let blocks = 0;
|
||||||
|
let d;
|
||||||
|
let hasAwait = false;
|
||||||
|
let hasAwaitOnBlockLevel;
|
||||||
|
let assigned;
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
function endFunction() {
|
||||||
|
hasAwait = false;
|
||||||
|
assigned = undefined;
|
||||||
|
d = undefined;
|
||||||
|
hasAssignmentOnBlockLevel = undefined;
|
||||||
|
hasAwaitOnBlockLevel = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Public
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
|
return {
|
||||||
|
'FunctionDeclaration:exit': endFunction,
|
||||||
|
'FunctionExpression:exit': endFunction,
|
||||||
|
'ArrowFunctionExpression:exit': endFunction,
|
||||||
|
'AwaitExpression:exit': (node) => {
|
||||||
|
if(!node) return;
|
||||||
|
hasAwait = true;
|
||||||
|
hasAwaitOnBlockLevel = blocks;
|
||||||
|
d = node;
|
||||||
|
},
|
||||||
|
'BlockStatement': () => {
|
||||||
|
blocks += 1;
|
||||||
|
},
|
||||||
|
'BlockStatement:exit': () => {
|
||||||
|
blocks -= 1;
|
||||||
|
if(hasAwaitOnBlockLevel && blocks === hasAwaitOnBlockLevel) {
|
||||||
|
hasAwaitOnBlockLevel = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'ReturnStatement:exit': (node) => {
|
||||||
|
if(hasAwait && hasAwaitOnBlockLevel && blocks === hasAwaitOnBlockLevel && node.parent.type === 'BlockExpression') {
|
||||||
|
endFunction();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'AssignmentExpression': (node) => {
|
||||||
|
if(node.left.type !== "Identifier" || node.left.name !== "global") return;
|
||||||
|
if(node.right.type !== "CallExpression" || node.right.callee.name !== "getGlobal") return;
|
||||||
|
|
||||||
|
if(hasAwaitOnBlockLevel && blocks === hasAwaitOnBlockLevel) {
|
||||||
|
hasAwait = false;
|
||||||
|
hasAwaitOnBlockLevel = undefined;
|
||||||
|
d = undefined;
|
||||||
|
} else {
|
||||||
|
hasAssignmentOnBlockLevel = blocks;
|
||||||
|
assigned = node;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Identifier: (node) => {
|
||||||
|
if(node.name !== "global") return;
|
||||||
|
if(node.parent === assigned) return;
|
||||||
|
if(hasAwait) {
|
||||||
|
if(hasAssignmentOnBlockLevel !== undefined && hasAssignmentOnBlockLevel <= blocks) {
|
||||||
|
endFunction();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
messageId: "mustUpdateGlobalAfterAwait",
|
||||||
|
data: {
|
||||||
|
before: d ? d.loc.start.line + ':' + d.loc.start.column : 'unknown'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Program:exit": endFunction,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
40
dev/eslint-multitab/lib/rules/no-getactions-in-actions.js
Normal file
40
dev/eslint-multitab/lib/rules/no-getactions-in-actions.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Forbid usage of getActions in actions
|
||||||
|
* @author undrfined
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Rule Definition
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** @type {import('eslint').Rule.RuleModule} */
|
||||||
|
module.exports = {
|
||||||
|
meta: {
|
||||||
|
type: "problem",
|
||||||
|
docs: {
|
||||||
|
description: "Forbid usage of getActions in action handlers",
|
||||||
|
recommended: false,
|
||||||
|
url: null,
|
||||||
|
},
|
||||||
|
fixable: null,
|
||||||
|
schema: [],
|
||||||
|
messages: {
|
||||||
|
noGetActionsInActions: "Do not use getActions inside action handlers, instead use the second argument of the action handler",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
create(context) {
|
||||||
|
return {
|
||||||
|
CallExpression: (node) => {
|
||||||
|
if(!context.getPhysicalFilename().substring(context.getCwd().length).startsWith('/src/global')) return;
|
||||||
|
if(node.callee.name === 'getActions') {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
messageId: 'noGetActionsInActions',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
42
dev/eslint-multitab/lib/rules/no-immediate-global.js
Normal file
42
dev/eslint-multitab/lib/rules/no-immediate-global.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview No immediate global
|
||||||
|
* @author undrfined
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Rule Definition
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** @type {import('eslint').Rule.RuleModule} */
|
||||||
|
module.exports = {
|
||||||
|
meta: {
|
||||||
|
type: "problem",
|
||||||
|
docs: {
|
||||||
|
description: "No immediate global",
|
||||||
|
recommended: false,
|
||||||
|
url: null,
|
||||||
|
},
|
||||||
|
fixable: null,
|
||||||
|
schema: [],
|
||||||
|
messages: {
|
||||||
|
noImmediateGlobal: "Only use getGlobal() to assign to global variable",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
create(context) {
|
||||||
|
return {
|
||||||
|
CallExpression: (node) => {
|
||||||
|
if(!context.getPhysicalFilename().substring(context.getCwd().length).startsWith('/src/global')) return;
|
||||||
|
if(node.callee.name === 'getGlobal'
|
||||||
|
&& node.parent.type !== 'AssignmentExpression'
|
||||||
|
) {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
messageId: "noImmediateGlobal",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
53
dev/eslint-multitab/lib/rules/set-global-only-variable.js
Normal file
53
dev/eslint-multitab/lib/rules/set-global-only-variable.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview setGlobal must only be used with 'global' variable
|
||||||
|
* @author undrfined
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// Rule Definition
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** @type {import('eslint').Rule.RuleModule} */
|
||||||
|
module.exports = {
|
||||||
|
meta: {
|
||||||
|
type: "problem",
|
||||||
|
docs: {
|
||||||
|
description: "setGlobal must only be used with 'global' variable",
|
||||||
|
recommended: false,
|
||||||
|
url: null,
|
||||||
|
},
|
||||||
|
fixable: null,
|
||||||
|
schema: [],
|
||||||
|
hasSuggestions: true,
|
||||||
|
messages: {
|
||||||
|
setGlobalOnlyVariable: "setGlobal must only be used with 'global' variable",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
create(context) {
|
||||||
|
return {
|
||||||
|
CallExpression: (node) => {
|
||||||
|
if(node.callee.name === 'setGlobal') {
|
||||||
|
if(node.arguments[0] && node.arguments[0].type !== 'Identifier' || node.arguments[0].name !== 'global') {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
messageId: 'setGlobalOnlyVariable',
|
||||||
|
...(node.parent.type === 'ExpressionStatement' && {
|
||||||
|
suggest: [{
|
||||||
|
desc: "Move the global assignment before the setGlobal call",
|
||||||
|
*fix(fixer) {
|
||||||
|
const sc = context.getSourceCode();
|
||||||
|
const parent = node.parent;
|
||||||
|
yield fixer.insertTextBefore(parent, 'global = ' + sc.getText(node.arguments[0]) + ';\n');
|
||||||
|
yield fixer.replaceText(node.arguments[0], 'global');
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
3647
dev/eslint-multitab/package-lock.json
generated
Normal file
3647
dev/eslint-multitab/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
dev/eslint-multitab/package.json
Normal file
39
dev/eslint-multitab/package.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "eslint-plugin-eslint-multitab-tt",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "eslint-multitab-tt",
|
||||||
|
"keywords": [
|
||||||
|
"eslint",
|
||||||
|
"eslintplugin",
|
||||||
|
"eslint-plugin"
|
||||||
|
],
|
||||||
|
"author": "undrfined",
|
||||||
|
"main": "./lib/index.js",
|
||||||
|
"exports": "./lib/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"lint": "npm-run-all \"lint:*\"",
|
||||||
|
"lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"",
|
||||||
|
"lint:js": "eslint .",
|
||||||
|
"test": "mocha tests --recursive",
|
||||||
|
"update:eslint-docs": "eslint-doc-generator"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"requireindex": "^1.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"chalk": "^5.2.0",
|
||||||
|
"eslint": "^8.19.0",
|
||||||
|
"eslint-doc-generator": "^1.0.0",
|
||||||
|
"eslint-plugin-eslint-plugin": "^5.0.0",
|
||||||
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
"mocha": "^10.0.0",
|
||||||
|
"npm-run-all": "^4.1.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.17.0 || ^16.0.0 || >= 18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": ">=7"
|
||||||
|
},
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
||||||
924
package-lock.json
generated
924
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,8 @@
|
|||||||
"test:record": "playwright codegen localhost:1235",
|
"test:record": "playwright codegen localhost:1235",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"statoscope:validate": "statoscope validate --input public/build-stats.json",
|
"statoscope:validate": "statoscope validate --input public/build-stats.json",
|
||||||
"statoscope:validate-diff": "statoscope validate --input input.json --reference reference.json"
|
"statoscope:validate-diff": "statoscope validate --input input.json --reference reference.json",
|
||||||
|
"postinstall": "(cd dev/eslint-multitab && npm i)"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18",
|
"node": "^18",
|
||||||
@ -76,6 +77,7 @@
|
|||||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-import-resolver-webpack": "^0.13.2",
|
"eslint-import-resolver-webpack": "^0.13.2",
|
||||||
|
"eslint-plugin-eslint-multitab-tt": "file:dev/eslint-multitab",
|
||||||
"eslint-plugin-flowtype": "^8.0.3",
|
"eslint-plugin-flowtype": "^8.0.3",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||||
|
|||||||
48
src/App.tsx
48
src/App.tsx
@ -6,13 +6,16 @@ import type { GlobalState } from './global/types';
|
|||||||
import type { UiLoaderPage } from './components/common/UiLoader';
|
import type { UiLoaderPage } from './components/common/UiLoader';
|
||||||
|
|
||||||
import { INACTIVE_MARKER, PAGE_TITLE } from './config';
|
import { INACTIVE_MARKER, PAGE_TITLE } from './config';
|
||||||
import { PLATFORM_ENV } from './util/environment';
|
import { IS_MULTITAB_SUPPORTED, PLATFORM_ENV } from './util/environment';
|
||||||
|
import { selectTabState } from './global/selectors';
|
||||||
import { updateSizes } from './util/windowSize';
|
import { updateSizes } from './util/windowSize';
|
||||||
import { addActiveTabChangeListener } from './util/activeTabMonitor';
|
import { addActiveTabChangeListener } from './util/activeTabMonitor';
|
||||||
import { hasStoredSession } from './util/sessions';
|
import { hasStoredSession } from './util/sessions';
|
||||||
import buildClassName from './util/buildClassName';
|
import buildClassName from './util/buildClassName';
|
||||||
|
import { parseInitialLocationHash } from './util/routing';
|
||||||
import useFlag from './hooks/useFlag';
|
import useFlag from './hooks/useFlag';
|
||||||
import usePrevious from './hooks/usePrevious';
|
import usePrevious from './hooks/usePrevious';
|
||||||
|
import useAppLayout from './hooks/useAppLayout';
|
||||||
|
|
||||||
import Auth from './components/auth/Auth';
|
import Auth from './components/auth/Auth';
|
||||||
import Main from './components/main/Main.async';
|
import Main from './components/main/Main.async';
|
||||||
@ -20,14 +23,13 @@ import LockScreen from './components/main/LockScreen.async';
|
|||||||
import AppInactive from './components/main/AppInactive';
|
import AppInactive from './components/main/AppInactive';
|
||||||
import Transition from './components/ui/Transition';
|
import Transition from './components/ui/Transition';
|
||||||
import UiLoader from './components/common/UiLoader';
|
import UiLoader from './components/common/UiLoader';
|
||||||
import { parseInitialLocationHash } from './util/routing';
|
|
||||||
import useAppLayout from './hooks/useAppLayout';
|
|
||||||
// import Test from './components/test/TestNoRedundancy';
|
// import Test from './components/test/TestNoRedundancy';
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
authState: GlobalState['authState'];
|
authState: GlobalState['authState'];
|
||||||
isScreenLocked?: boolean;
|
isScreenLocked?: boolean;
|
||||||
hasPasscode?: boolean;
|
hasPasscode?: boolean;
|
||||||
|
isInactiveAuth?: boolean;
|
||||||
hasWebAuthTokenFailed?: boolean;
|
hasWebAuthTokenFailed?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,23 +45,14 @@ const App: FC<StateProps> = ({
|
|||||||
isScreenLocked,
|
isScreenLocked,
|
||||||
hasPasscode,
|
hasPasscode,
|
||||||
hasWebAuthTokenFailed,
|
hasWebAuthTokenFailed,
|
||||||
|
isInactiveAuth,
|
||||||
}) => {
|
}) => {
|
||||||
const { disconnect } = getActions();
|
const { disconnect } = getActions();
|
||||||
|
|
||||||
const [isInactive, markInactive] = useFlag(false);
|
const [isInactive, markInactive, unmarkInactive] = useFlag(false);
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
const isMobileOs = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android';
|
const isMobileOs = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android';
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateSizes();
|
|
||||||
addActiveTabChangeListener(() => {
|
|
||||||
disconnect();
|
|
||||||
document.title = `${PAGE_TITLE}${INACTIVE_MARKER}`;
|
|
||||||
|
|
||||||
markInactive();
|
|
||||||
});
|
|
||||||
}, [disconnect, markInactive]);
|
|
||||||
|
|
||||||
// Prevent drop on elements that do not accept it
|
// Prevent drop on elements that do not accept it
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
@ -144,13 +137,35 @@ const App: FC<StateProps> = ({
|
|||||||
activeKey = AppScreens.main;
|
activeKey = AppScreens.main;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateSizes();
|
||||||
|
|
||||||
|
if (IS_MULTITAB_SUPPORTED) return;
|
||||||
|
|
||||||
|
addActiveTabChangeListener(() => {
|
||||||
|
disconnect();
|
||||||
|
document.title = `${PAGE_TITLE}${INACTIVE_MARKER}`;
|
||||||
|
|
||||||
|
markInactive();
|
||||||
|
});
|
||||||
|
}, [activeKey, disconnect, markInactive]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isInactiveAuth) {
|
||||||
|
document.title = `${PAGE_TITLE}${INACTIVE_MARKER}`;
|
||||||
|
markInactive();
|
||||||
|
} else {
|
||||||
|
unmarkInactive();
|
||||||
|
}
|
||||||
|
}, [isInactiveAuth, markInactive, unmarkInactive]);
|
||||||
|
|
||||||
const prevActiveKey = usePrevious(activeKey);
|
const prevActiveKey = usePrevious(activeKey);
|
||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
function renderContent(isActive: boolean) {
|
function renderContent() {
|
||||||
switch (activeKey) {
|
switch (activeKey) {
|
||||||
case AppScreens.auth:
|
case AppScreens.auth:
|
||||||
return <Auth isActive={isActive} />;
|
return <Auth />;
|
||||||
case AppScreens.main:
|
case AppScreens.main:
|
||||||
return <Main isMobile={isMobile} />;
|
return <Main isMobile={isMobile} />;
|
||||||
case AppScreens.lock:
|
case AppScreens.lock:
|
||||||
@ -183,6 +198,7 @@ export default withGlobal(
|
|||||||
authState: global.authState,
|
authState: global.authState,
|
||||||
isScreenLocked: global.passcode?.isScreenLocked,
|
isScreenLocked: global.passcode?.isScreenLocked,
|
||||||
hasPasscode: global.passcode?.hasPasscode,
|
hasPasscode: global.passcode?.hasPasscode,
|
||||||
|
isInactiveAuth: selectTabState(global).isInactive,
|
||||||
hasWebAuthTokenFailed: global.hasWebAuthTokenFailed || global.hasWebAuthTokenPasswordRequired,
|
hasWebAuthTokenFailed: global.hasWebAuthTokenFailed || global.hasWebAuthTokenPasswordRequired,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,2 +1,8 @@
|
|||||||
// export { initApi, callApi, cancelApiProgress } from './provider';
|
// export { initApi, callApi, cancelApiProgress } from './provider';
|
||||||
export { initApi, callApi, cancelApiProgress } from './worker/provider';
|
export {
|
||||||
|
initApi, callApi, cancelApiProgress, cancelApiProgressMaster, callApiLocal,
|
||||||
|
handleMethodCallback,
|
||||||
|
handleMethodResponse,
|
||||||
|
updateFullLocalDb,
|
||||||
|
updateLocalDb,
|
||||||
|
} from './worker/provider';
|
||||||
|
|||||||
@ -1,7 +1,15 @@
|
|||||||
|
import BigInt from 'big-integer';
|
||||||
import type { Api as GramJs } from '../../lib/gramjs';
|
import type { Api as GramJs } from '../../lib/gramjs';
|
||||||
import type { ApiMessage } from '../types';
|
import type { ApiMessage } from '../types';
|
||||||
|
import { omitVirtualClassFields } from './apiBuilders/helpers';
|
||||||
|
import { DATA_BROADCAST_CHANNEL_NAME } from '../../config';
|
||||||
|
import { constructors } from '../../lib/gramjs/tl';
|
||||||
|
import { throttle } from '../../util/schedulers';
|
||||||
|
|
||||||
interface LocalDb {
|
// eslint-disable-next-line no-restricted-globals
|
||||||
|
const IS_MULTITAB_SUPPORTED = 'BroadcastChannel' in self;
|
||||||
|
|
||||||
|
export interface LocalDb {
|
||||||
localMessages: Record<string, ApiMessage>;
|
localMessages: Record<string, ApiMessage>;
|
||||||
// Used for loading avatars and media through in-memory Gram JS instances.
|
// Used for loading avatars and media through in-memory Gram JS instances.
|
||||||
chats: Record<string, GramJs.Chat | GramJs.Channel>;
|
chats: Record<string, GramJs.Chat | GramJs.Channel>;
|
||||||
@ -13,21 +21,99 @@ interface LocalDb {
|
|||||||
webDocuments: Record<string, GramJs.TypeWebDocument>;
|
webDocuments: Record<string, GramJs.TypeWebDocument>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LOCAL_DB_INITIAL = {
|
const channel = IS_MULTITAB_SUPPORTED ? new BroadcastChannel(DATA_BROADCAST_CHANNEL_NAME) : undefined;
|
||||||
localMessages: {},
|
|
||||||
chats: {},
|
|
||||||
users: {},
|
|
||||||
messages: {},
|
|
||||||
documents: {},
|
|
||||||
stickerSets: {},
|
|
||||||
photos: {},
|
|
||||||
webDocuments: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const localDb: LocalDb = LOCAL_DB_INITIAL;
|
let batchedUpdates: {
|
||||||
|
name: string;
|
||||||
|
prop: string;
|
||||||
|
value: any;
|
||||||
|
}[] = [];
|
||||||
|
const throttledLocalDbUpdate = throttle(() => {
|
||||||
|
channel!.postMessage({
|
||||||
|
type: 'localDbUpdate',
|
||||||
|
batchedUpdates,
|
||||||
|
});
|
||||||
|
batchedUpdates = [];
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
function createProxy(name: string, object: any) {
|
||||||
|
return new Proxy(object, {
|
||||||
|
get(target, prop: string, value: any) {
|
||||||
|
return Reflect.get(target, prop, value);
|
||||||
|
},
|
||||||
|
set(target, prop: string, value: any) {
|
||||||
|
batchedUpdates.push({ name, prop, value });
|
||||||
|
throttledLocalDbUpdate();
|
||||||
|
return Reflect.set(target, prop, value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToVirtualClass(value: any): any {
|
||||||
|
if (value instanceof Uint8Array) return Buffer.from(value);
|
||||||
|
if (typeof value === 'object' && Object.keys(value).length === 1 && Object.keys(value)[0] === 'value') {
|
||||||
|
return BigInt(value.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map(convertToVirtualClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== 'object' || !('CONSTRUCTOR_ID' in value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
const path = value.className.split('.');
|
||||||
|
const VirtualClass = path.reduce((acc: any, field: string) => {
|
||||||
|
return acc[field];
|
||||||
|
}, constructors);
|
||||||
|
|
||||||
|
const valueOmited = omitVirtualClassFields(value);
|
||||||
|
const valueConverted = Object.keys(valueOmited).reduce((acc, key) => {
|
||||||
|
acc[key] = convertToVirtualClass(valueOmited[key]);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, any>);
|
||||||
|
|
||||||
|
return new VirtualClass(valueConverted);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLocalDbInitial(initial?: LocalDb): LocalDb {
|
||||||
|
return [
|
||||||
|
'localMessages', 'chats', 'users', 'messages', 'documents', 'stickerSets', 'photos', 'webDocuments',
|
||||||
|
]
|
||||||
|
.reduce((acc: Record<string, any>, key) => {
|
||||||
|
const value = initial?.[key as keyof LocalDb] ?? {};
|
||||||
|
const valueVirtualClass = Object.keys(value).reduce((acc2, key2) => {
|
||||||
|
acc2[key2] = convertToVirtualClass(value[key2]);
|
||||||
|
return acc2;
|
||||||
|
}, {} as Record<string, any>);
|
||||||
|
|
||||||
|
acc[key] = IS_MULTITAB_SUPPORTED
|
||||||
|
? createProxy(key, valueVirtualClass)
|
||||||
|
: valueVirtualClass;
|
||||||
|
return acc;
|
||||||
|
}, {} as LocalDb) as LocalDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localDb: LocalDb = createLocalDbInitial();
|
||||||
|
|
||||||
export default localDb;
|
export default localDb;
|
||||||
|
|
||||||
export function clearLocalDb() {
|
export function broadcastLocalDbUpdateFull() {
|
||||||
Object.assign(localDb, LOCAL_DB_INITIAL);
|
if (!channel) return;
|
||||||
|
|
||||||
|
channel.postMessage({
|
||||||
|
type: 'localDbUpdateFull',
|
||||||
|
localDb: Object.keys(localDb).reduce((acc: Record<string, any>, key) => {
|
||||||
|
acc[key] = { ...localDb[key as keyof LocalDb] };
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, any>),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateFullLocalDb(initial: LocalDb) {
|
||||||
|
Object.assign(localDb, createLocalDbInitial(initial));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearLocalDb() {
|
||||||
|
Object.assign(localDb, createLocalDbInitial());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -992,7 +992,7 @@ export function updateChatMemberBannedRights({
|
|||||||
|
|
||||||
export function updateChatAdmin({
|
export function updateChatAdmin({
|
||||||
chat, user, adminRights, customTitle = '',
|
chat, user, adminRights, customTitle = '',
|
||||||
}: { chat: ApiChat; user: ApiUser; adminRights: ApiChatAdminRights; customTitle: string }) {
|
}: { chat: ApiChat; user: ApiUser; adminRights: ApiChatAdminRights; customTitle?: string }) {
|
||||||
const channel = buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel;
|
const channel = buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel;
|
||||||
const userId = buildInputEntity(user.id, user.accessHash) as GramJs.InputUser;
|
const userId = buildInputEntity(user.id, user.accessHash) as GramJs.InputUser;
|
||||||
|
|
||||||
|
|||||||
@ -145,12 +145,12 @@ export function setIsPremium({ isPremium }: { isPremium: boolean }) {
|
|||||||
client.setIsPremium(isPremium);
|
client.setIsPremium(isPremium);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(noLogOut = false) {
|
export async function destroy(noLogOut = false, noClearLocalDb = false) {
|
||||||
if (!noLogOut) {
|
if (!noLogOut) {
|
||||||
await invokeRequest(new GramJs.auth.LogOut());
|
await invokeRequest(new GramJs.auth.LogOut());
|
||||||
}
|
}
|
||||||
|
|
||||||
clearLocalDb();
|
if (!noClearLocalDb) clearLocalDb();
|
||||||
|
|
||||||
await client.destroy();
|
await client.destroy();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -100,3 +100,7 @@ export {
|
|||||||
acceptPhoneCall, confirmPhoneCall, requestPhoneCall, decodePhoneCallData, createPhoneCallState,
|
acceptPhoneCall, confirmPhoneCall, requestPhoneCall, decodePhoneCallData, createPhoneCallState,
|
||||||
destroyPhoneCallState, encodePhoneCallData,
|
destroyPhoneCallState, encodePhoneCallData,
|
||||||
} from './phoneCallState';
|
} from './phoneCallState';
|
||||||
|
|
||||||
|
export {
|
||||||
|
broadcastLocalDbUpdateFull,
|
||||||
|
} from '../localDb';
|
||||||
|
|||||||
@ -91,7 +91,7 @@ export async function updatePrivateLink({
|
|||||||
|
|
||||||
export async function fetchExportedChatInvites({
|
export async function fetchExportedChatInvites({
|
||||||
peer, admin, limit = 0, isRevoked,
|
peer, admin, limit = 0, isRevoked,
|
||||||
}: { peer: ApiChat; admin: ApiUser; limit: number; isRevoked?: boolean }) {
|
}: { peer: ApiChat; admin: ApiUser; limit?: number; isRevoked?: boolean }) {
|
||||||
const exportedInvites = await invokeRequest(new GramJs.messages.GetExportedChatInvites({
|
const exportedInvites = await invokeRequest(new GramJs.messages.GetExportedChatInvites({
|
||||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
adminId: buildInputEntity(admin.id, admin.accessHash) as GramJs.InputUser,
|
adminId: buildInputEntity(admin.id, admin.accessHash) as GramJs.InputUser,
|
||||||
@ -209,7 +209,7 @@ export async function deleteRevokedExportedChatInvites({
|
|||||||
export async function fetchChatInviteImporters({
|
export async function fetchChatInviteImporters({
|
||||||
peer, link, offsetDate = 0, offsetUser, limit = 0, isRequested,
|
peer, link, offsetDate = 0, offsetUser, limit = 0, isRequested,
|
||||||
}: {
|
}: {
|
||||||
peer: ApiChat; link?: string; offsetDate: number; offsetUser?: ApiUser; limit: number; isRequested?: boolean;
|
peer: ApiChat; link?: string; offsetDate?: number; offsetUser?: ApiUser; limit?: number; isRequested?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.messages.GetChatInviteImporters({
|
const result = await invokeRequest(new GramJs.messages.GetChatInviteImporters({
|
||||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { TelegramClient } from '../../../lib/gramjs';
|
import type { TelegramClient } from '../../../lib/gramjs';
|
||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
import type { ApiOnProgress, ApiParsedMedia, ApiPreparedMedia } from '../../types';
|
import type { ApiOnProgress, ApiParsedMedia } from '../../types';
|
||||||
import {
|
import {
|
||||||
ApiMediaFormat,
|
ApiMediaFormat,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
@ -52,11 +52,11 @@ export default async function downloadMedia(
|
|||||||
void cacheApi.save(cacheName, url, parsed);
|
void cacheApi.save(cacheName, url, parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
const prepared = mediaFormat === ApiMediaFormat.Progressive ? '' : prepareMedia(parsed as string | Blob);
|
const dataBlob = mediaFormat === ApiMediaFormat.Progressive ? '' : parsed as string | Blob;
|
||||||
const arrayBuffer = mediaFormat === ApiMediaFormat.Progressive ? parsed as ArrayBuffer : undefined;
|
const arrayBuffer = mediaFormat === ApiMediaFormat.Progressive ? parsed as ArrayBuffer : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
prepared,
|
dataBlob,
|
||||||
arrayBuffer,
|
arrayBuffer,
|
||||||
mimeType,
|
mimeType,
|
||||||
fullSize,
|
fullSize,
|
||||||
@ -262,14 +262,6 @@ async function parseMedia(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareMedia(mediaData: Exclude<ApiParsedMedia, ArrayBuffer>): ApiPreparedMedia {
|
|
||||||
if (mediaData instanceof Blob) {
|
|
||||||
return URL.createObjectURL(mediaData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mediaData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMimeType(data: Uint8Array, fallbackMimeType = 'image/jpeg') {
|
function getMimeType(data: Uint8Array, fallbackMimeType = 'image/jpeg') {
|
||||||
if (data.length < 4) {
|
if (data.length < 4) {
|
||||||
return fallbackMimeType;
|
return fallbackMimeType;
|
||||||
|
|||||||
@ -637,7 +637,7 @@ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment,
|
|||||||
|
|
||||||
export async function pinMessage({
|
export async function pinMessage({
|
||||||
chat, messageId, isUnpin, isOneSide, isSilent,
|
chat, messageId, isUnpin, isOneSide, isSilent,
|
||||||
}: { chat: ApiChat; messageId: number; isUnpin: boolean; isOneSide: boolean; isSilent: boolean }) {
|
}: { chat: ApiChat; messageId: number; isUnpin: boolean; isOneSide?: boolean; isSilent?: boolean }) {
|
||||||
await invokeRequest(new GramJs.messages.UpdatePinnedMessage({
|
await invokeRequest(new GramJs.messages.UpdatePinnedMessage({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
id: messageId,
|
id: messageId,
|
||||||
|
|||||||
@ -370,8 +370,8 @@ export function updateNotificationSettings(peerType: 'contact' | 'group' | 'broa
|
|||||||
isSilent,
|
isSilent,
|
||||||
shouldShowPreviews,
|
shouldShowPreviews,
|
||||||
}: {
|
}: {
|
||||||
isSilent: boolean;
|
isSilent?: boolean;
|
||||||
shouldShowPreviews: boolean;
|
shouldShowPreviews?: boolean;
|
||||||
}) {
|
}) {
|
||||||
let peer: GramJs.TypeInputNotifyPeer;
|
let peer: GramJs.TypeInputNotifyPeer;
|
||||||
if (peerType === 'contact') {
|
if (peerType === 'contact') {
|
||||||
|
|||||||
@ -5,9 +5,11 @@ import type {
|
|||||||
ApiOnProgress,
|
ApiOnProgress,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import type { Methods, MethodArgs, MethodResponse } from './methods/types';
|
import type { Methods, MethodArgs, MethodResponse } from './methods/types';
|
||||||
|
import type { LocalDb } from './localDb';
|
||||||
|
|
||||||
import { API_THROTTLE_RESET_UPDATES, API_UPDATE_THROTTLE } from '../../config';
|
import { API_THROTTLE_RESET_UPDATES, API_UPDATE_THROTTLE } from '../../config';
|
||||||
import { throttle, throttleWithTickEnd } from '../../util/schedulers';
|
import { throttle, throttleWithTickEnd } from '../../util/schedulers';
|
||||||
|
import { updateFullLocalDb } from './localDb';
|
||||||
import { init as initUpdater } from './updater';
|
import { init as initUpdater } from './updater';
|
||||||
import { init as initAuth } from './methods/auth';
|
import { init as initAuth } from './methods/auth';
|
||||||
import { init as initChats } from './methods/chats';
|
import { init as initChats } from './methods/chats';
|
||||||
@ -24,7 +26,7 @@ import * as methods from './methods';
|
|||||||
|
|
||||||
let onUpdate: OnApiUpdate;
|
let onUpdate: OnApiUpdate;
|
||||||
|
|
||||||
export async function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs) {
|
export async function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs, initialLocalDb?: LocalDb) {
|
||||||
onUpdate = _onUpdate;
|
onUpdate = _onUpdate;
|
||||||
|
|
||||||
initUpdater(handleUpdate);
|
initUpdater(handleUpdate);
|
||||||
@ -39,6 +41,8 @@ export async function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArg
|
|||||||
initCalls(handleUpdate);
|
initCalls(handleUpdate);
|
||||||
initPayments(handleUpdate);
|
initPayments(handleUpdate);
|
||||||
|
|
||||||
|
if (initialLocalDb) updateFullLocalDb(initialLocalDb);
|
||||||
|
|
||||||
await initClient(handleUpdate, initialArgs);
|
await initClient(handleUpdate, initialArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
import type { Api } from '../../../lib/gramjs';
|
import type { Api } from '../../../lib/gramjs';
|
||||||
import type { ApiInitialArgs, ApiOnProgress, OnApiUpdate } from '../../types';
|
import type { ApiInitialArgs, ApiOnProgress, OnApiUpdate } from '../../types';
|
||||||
import type { Methods, MethodArgs, MethodResponse } from '../methods/types';
|
import type { Methods, MethodArgs, MethodResponse } from '../methods/types';
|
||||||
import type { WorkerMessageEvent, OriginRequest } from './types';
|
import type { WorkerMessageEvent, OriginRequest, ThenArg } from './types';
|
||||||
|
import type { LocalDb } from '../localDb';
|
||||||
|
import type { TypedBroadcastChannel } from '../../../util/multitab';
|
||||||
|
|
||||||
import { DEBUG } from '../../../config';
|
import { IS_MULTITAB_SUPPORTED } from '../../../util/environment';
|
||||||
|
import { DATA_BROADCAST_CHANNEL_NAME, DEBUG } from '../../../config';
|
||||||
import generateIdFor from '../../../util/generateIdFor';
|
import generateIdFor from '../../../util/generateIdFor';
|
||||||
import { pause } from '../../../util/schedulers';
|
import { pause } from '../../../util/schedulers';
|
||||||
|
import { getCurrentTabId, subscribeToMasterChange } from '../../../util/establishMultitabRole';
|
||||||
|
|
||||||
type RequestStates = {
|
type RequestStates = {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
@ -20,10 +24,44 @@ const HEALTH_CHECK_MIN_DELAY = 5 * 1000; // 5 sec
|
|||||||
let worker: Worker;
|
let worker: Worker;
|
||||||
const requestStates = new Map<string, RequestStates>();
|
const requestStates = new Map<string, RequestStates>();
|
||||||
const requestStatesByCallback = new Map<AnyToVoidFunction, RequestStates>();
|
const requestStatesByCallback = new Map<AnyToVoidFunction, RequestStates>();
|
||||||
|
const savedLocalDb: LocalDb = {
|
||||||
|
localMessages: {},
|
||||||
|
chats: {},
|
||||||
|
users: {},
|
||||||
|
messages: {},
|
||||||
|
documents: {},
|
||||||
|
stickerSets: {},
|
||||||
|
photos: {},
|
||||||
|
webDocuments: {},
|
||||||
|
};
|
||||||
|
|
||||||
// TODO Re-use `util/WorkerConnector.ts`
|
// TODO Re-use `util/WorkerConnector.ts`
|
||||||
|
|
||||||
|
let isMasterTab = true;
|
||||||
|
subscribeToMasterChange((isMasterTabNew) => {
|
||||||
|
isMasterTab = isMasterTabNew;
|
||||||
|
});
|
||||||
|
|
||||||
|
const channel = IS_MULTITAB_SUPPORTED
|
||||||
|
? new BroadcastChannel(DATA_BROADCAST_CHANNEL_NAME) as TypedBroadcastChannel
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
export function initApiOnMasterTab(initialArgs: ApiInitialArgs) {
|
||||||
|
if (!channel) return;
|
||||||
|
|
||||||
|
channel.postMessage({
|
||||||
|
type: 'initApi',
|
||||||
|
token: getCurrentTabId(),
|
||||||
|
initialArgs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function initApi(onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs) {
|
export function initApi(onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs) {
|
||||||
|
if (!isMasterTab) {
|
||||||
|
initApiOnMasterTab(initialArgs);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
if (!worker) {
|
if (!worker) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@ -40,11 +78,33 @@ export function initApi(onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs) {
|
|||||||
|
|
||||||
return makeRequest({
|
return makeRequest({
|
||||||
type: 'initApi',
|
type: 'initApi',
|
||||||
args: [initialArgs],
|
args: [initialArgs, savedLocalDb],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function callApi<T extends keyof Methods>(fnName: T, ...args: MethodArgs<T>) {
|
export function updateLocalDb(name: keyof LocalDb, prop: string, value: any) {
|
||||||
|
savedLocalDb[name][prop] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateFullLocalDb(initial: LocalDb) {
|
||||||
|
Object.assign(savedLocalDb, initial);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function callApiOnMasterTab(payload: any) {
|
||||||
|
if (!channel) return;
|
||||||
|
|
||||||
|
channel.postMessage({
|
||||||
|
type: 'callApi',
|
||||||
|
token: getCurrentTabId(),
|
||||||
|
...payload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Call a worker method on this tab's worker, without transferring to master tab
|
||||||
|
* Mostly needed to disconnect worker when re-electing master
|
||||||
|
*/
|
||||||
|
export function callApiLocal<T extends keyof Methods>(fnName: T, ...args: MethodArgs<T>) {
|
||||||
if (!worker) {
|
if (!worker) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@ -86,6 +146,51 @@ export function callApi<T extends keyof Methods>(fnName: T, ...args: MethodArgs<
|
|||||||
return promise as MethodResponse<T>;
|
return promise as MethodResponse<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function callApi<T extends keyof Methods>(fnName: T, ...args: MethodArgs<T>) {
|
||||||
|
if (!worker && isMasterTab) {
|
||||||
|
if (DEBUG) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn('API is not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = isMasterTab ? makeRequest({
|
||||||
|
type: 'callMethod',
|
||||||
|
name: fnName,
|
||||||
|
args,
|
||||||
|
}) : makeRequestToMaster({
|
||||||
|
name: fnName,
|
||||||
|
args,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Some TypeScript magic to make sure `VirtualClass` is never returned from any method
|
||||||
|
if (DEBUG) {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
type ForbiddenTypes =
|
||||||
|
Api.VirtualClass<any>
|
||||||
|
| (Api.VirtualClass<any> | undefined)[];
|
||||||
|
type ForbiddenResponses =
|
||||||
|
ForbiddenTypes
|
||||||
|
| (AnyLiteral & { [k: string]: ForbiddenTypes });
|
||||||
|
|
||||||
|
// Unwrap all chained promises
|
||||||
|
const response = await promise;
|
||||||
|
// Make sure responses do not include `VirtualClass` instances
|
||||||
|
const allowedResponse: Exclude<typeof response, ForbiddenResponses> = response;
|
||||||
|
// Suppress "unused variable" constraint
|
||||||
|
void allowedResponse;
|
||||||
|
} catch (err) {
|
||||||
|
// Do noting
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise as MethodResponse<T>;
|
||||||
|
}
|
||||||
|
|
||||||
export function cancelApiProgress(progressCallback: ApiOnProgress) {
|
export function cancelApiProgress(progressCallback: ApiOnProgress) {
|
||||||
progressCallback.isCanceled = true;
|
progressCallback.isCanceled = true;
|
||||||
|
|
||||||
@ -94,6 +199,20 @@ export function cancelApiProgress(progressCallback: ApiOnProgress) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMasterTab) {
|
||||||
|
cancelApiProgressMaster(messageId);
|
||||||
|
} else {
|
||||||
|
if (!channel) return;
|
||||||
|
|
||||||
|
channel.postMessage({
|
||||||
|
type: 'cancelApiProgress',
|
||||||
|
token: getCurrentTabId(),
|
||||||
|
messageId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancelApiProgressMaster(messageId: string) {
|
||||||
worker.postMessage({
|
worker.postMessage({
|
||||||
type: 'cancelProgress',
|
type: 'cancelProgress',
|
||||||
messageId,
|
messageId,
|
||||||
@ -105,22 +224,79 @@ function subscribeToWorker(onUpdate: OnApiUpdate) {
|
|||||||
if (data.type === 'update') {
|
if (data.type === 'update') {
|
||||||
onUpdate(data.update);
|
onUpdate(data.update);
|
||||||
} else if (data.type === 'methodResponse') {
|
} else if (data.type === 'methodResponse') {
|
||||||
const requestState = requestStates.get(data.messageId);
|
handleMethodResponse(data);
|
||||||
if (requestState) {
|
|
||||||
if (data.error) {
|
|
||||||
requestState.reject(data.error);
|
|
||||||
} else {
|
|
||||||
requestState.resolve(data.response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (data.type === 'methodCallback') {
|
} else if (data.type === 'methodCallback') {
|
||||||
requestStates.get(data.messageId)?.callback?.(...data.callbackArgs);
|
handleMethodCallback(data);
|
||||||
} else if (data.type === 'unhandledError') {
|
} else if (data.type === 'unhandledError') {
|
||||||
throw new Error(data.error?.message);
|
throw new Error(data.error?.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function handleMethodResponse(data: { messageId: string;
|
||||||
|
response?: ThenArg<MethodResponse<keyof Methods>>;
|
||||||
|
error?: { message: string };
|
||||||
|
}) {
|
||||||
|
const requestState = requestStates.get(data.messageId);
|
||||||
|
if (requestState) {
|
||||||
|
if (data.error) {
|
||||||
|
requestState.reject(data.error);
|
||||||
|
} else {
|
||||||
|
requestState.resolve(data.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleMethodCallback(data: { messageId: string;
|
||||||
|
callbackArgs: any[];
|
||||||
|
}) {
|
||||||
|
requestStates.get(data.messageId)?.callback?.(...data.callbackArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRequestToMaster(message: {
|
||||||
|
messageId?: string;
|
||||||
|
name: keyof Methods;
|
||||||
|
args: MethodArgs<keyof Methods>;
|
||||||
|
withCallback?: boolean;
|
||||||
|
}) {
|
||||||
|
const messageId = generateIdFor(requestStates);
|
||||||
|
const payload = {
|
||||||
|
messageId,
|
||||||
|
...message,
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestState = { messageId } as RequestStates;
|
||||||
|
|
||||||
|
// Re-wrap type because of `postMessage`
|
||||||
|
const promise: Promise<MethodResponse<keyof Methods>> = new Promise((resolve, reject) => {
|
||||||
|
Object.assign(requestState, { resolve, reject });
|
||||||
|
});
|
||||||
|
|
||||||
|
if ('args' in payload && 'name' in payload && typeof payload.args[1] === 'function') {
|
||||||
|
payload.withCallback = true;
|
||||||
|
|
||||||
|
const callback = payload.args.pop() as AnyToVoidFunction;
|
||||||
|
requestState.callback = callback;
|
||||||
|
requestStatesByCallback.set(callback, requestState);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestStates.set(messageId, requestState);
|
||||||
|
|
||||||
|
promise
|
||||||
|
.catch(() => undefined)
|
||||||
|
.finally(() => {
|
||||||
|
requestStates.delete(messageId);
|
||||||
|
|
||||||
|
if (requestState.callback) {
|
||||||
|
requestStatesByCallback.delete(requestState.callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
callApiOnMasterTab(payload);
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
function makeRequest(message: OriginRequest) {
|
function makeRequest(message: OriginRequest) {
|
||||||
const messageId = generateIdFor(requestStates);
|
const messageId = generateIdFor(requestStates);
|
||||||
const payload: OriginRequest = {
|
const payload: OriginRequest = {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { ApiInitialArgs, ApiUpdate } from '../../types';
|
import type { ApiInitialArgs, ApiUpdate } from '../../types';
|
||||||
import type { Methods, MethodArgs, MethodResponse } from '../methods/types';
|
import type { Methods, MethodArgs, MethodResponse } from '../methods/types';
|
||||||
|
import type { LocalDb } from '../localDb';
|
||||||
|
|
||||||
export type ThenArg<T> = T extends Promise<infer U> ? U : T;
|
export type ThenArg<T> = T extends Promise<infer U> ? U : T;
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ export interface WorkerMessageEvent {
|
|||||||
export type OriginRequest = {
|
export type OriginRequest = {
|
||||||
type: 'initApi';
|
type: 'initApi';
|
||||||
messageId?: string;
|
messageId?: string;
|
||||||
args: [ApiInitialArgs];
|
args: [ApiInitialArgs, LocalDb];
|
||||||
} | {
|
} | {
|
||||||
type: 'callMethod';
|
type: 'callMethod';
|
||||||
messageId?: string;
|
messageId?: string;
|
||||||
|
|||||||
@ -21,7 +21,7 @@ onmessage = async (message: OriginMessageEvent) => {
|
|||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case 'initApi': {
|
case 'initApi': {
|
||||||
await initApi(onUpdate, data.args[0]);
|
await initApi(onUpdate, data.args[0], data.args[1]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'callMethod': {
|
case 'callMethod': {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { ApiDocument, ApiPhoto, ApiReaction } from './messages';
|
import type { ApiDocument, ApiPhoto, ApiReaction } from './messages';
|
||||||
import type { ApiUser } from './users';
|
import type { ApiUser } from './users';
|
||||||
import type { ApiLimitType } from '../../global/types';
|
import type { ApiLimitType, CallbackAction } from '../../global/types';
|
||||||
|
|
||||||
export interface ApiInitialArgs {
|
export interface ApiInitialArgs {
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
@ -107,7 +107,7 @@ export type ApiNotification = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
message: string;
|
message: string;
|
||||||
actionText?: string;
|
actionText?: string;
|
||||||
action: VoidFunction;
|
action?: CallbackAction;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { getActions, getGlobal } from '../global';
|
import { getActions, getGlobal } from '../global';
|
||||||
|
|
||||||
|
import { IS_MULTITAB_SUPPORTED } from '../util/environment';
|
||||||
import { DEBUG } from '../config';
|
import { DEBUG } from '../config';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
@ -11,7 +12,7 @@ if (DEBUG) {
|
|||||||
console.log('>>> FINISH LOAD MAIN BUNDLE');
|
console.log('>>> FINISH LOAD MAIN BUNDLE');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { connectionState, passcode: { isScreenLocked } } = getGlobal();
|
const { passcode: { isScreenLocked }, connectionState } = getGlobal();
|
||||||
if (!connectionState && !isScreenLocked) {
|
if (!connectionState && !isScreenLocked && !IS_MULTITAB_SUPPORTED) {
|
||||||
getActions().initApi();
|
getActions().initApi();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { useEffect, memo } from '../../lib/teact/teact';
|
import React, { memo } from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { GlobalState } from '../../global/types';
|
||||||
|
|
||||||
import '../../global/actions/initial';
|
import '../../global/actions/initial';
|
||||||
import { pick } from '../../util/iteratees';
|
|
||||||
import { PLATFORM_ENV } from '../../util/environment';
|
import { PLATFORM_ENV } from '../../util/environment';
|
||||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||||
@ -19,26 +18,15 @@ import AuthQrCode from './AuthQrCode';
|
|||||||
|
|
||||||
import './Auth.scss';
|
import './Auth.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type StateProps = Pick<GlobalState, 'authState'>;
|
||||||
isActive: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StateProps = Pick<GlobalState, 'authState' | 'hasWebAuthTokenPasswordRequired'>;
|
const Auth: FC<StateProps> = ({
|
||||||
|
authState,
|
||||||
const Auth: FC<OwnProps & StateProps> = ({
|
|
||||||
isActive, authState, hasWebAuthTokenPasswordRequired,
|
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
reset, initApi, returnToAuthPhoneNumber, goToAuthQrCode,
|
returnToAuthPhoneNumber, goToAuthQrCode,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isActive && !hasWebAuthTokenPasswordRequired) {
|
|
||||||
reset();
|
|
||||||
initApi();
|
|
||||||
}
|
|
||||||
}, [isActive, reset, initApi, hasWebAuthTokenPasswordRequired]);
|
|
||||||
|
|
||||||
const isMobile = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android';
|
const isMobile = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android';
|
||||||
|
|
||||||
const handleChangeAuthorizationMethod = () => {
|
const handleChangeAuthorizationMethod = () => {
|
||||||
@ -102,6 +90,10 @@ const Auth: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal(
|
||||||
(global): StateProps => pick(global, ['authState', 'hasWebAuthTokenPasswordRequired']),
|
(global): StateProps => {
|
||||||
|
return {
|
||||||
|
authState: global.authState,
|
||||||
|
};
|
||||||
|
},
|
||||||
)(Auth));
|
)(Auth));
|
||||||
|
|||||||
@ -82,6 +82,10 @@ const AuthCode: FC<StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [authError, clearAuthError, code, isTracking, setAuthCode]);
|
}, [authError, clearAuthError, code, isTracking, setAuthCode]);
|
||||||
|
|
||||||
|
function handleReturnToAuthPhoneNumber() {
|
||||||
|
returnToAuthPhoneNumber();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="auth-code-form" className="custom-scroll">
|
<div id="auth-code-form" className="custom-scroll">
|
||||||
<div className="auth-form">
|
<div className="auth-form">
|
||||||
@ -95,7 +99,7 @@ const AuthCode: FC<StateProps> = ({
|
|||||||
{authPhoneNumber}
|
{authPhoneNumber}
|
||||||
<div
|
<div
|
||||||
className="auth-number-edit"
|
className="auth-number-edit"
|
||||||
onClick={returnToAuthPhoneNumber}
|
onClick={handleReturnToAuthPhoneNumber}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
title={lang('WrongNumber')}
|
title={lang('WrongNumber')}
|
||||||
|
|||||||
@ -203,6 +203,10 @@ const AuthPhoneNumber: FC<StateProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleGoToAuthQrCode = useCallback(() => {
|
||||||
|
goToAuthQrCode();
|
||||||
|
}, [goToAuthQrCode]);
|
||||||
|
|
||||||
const isAuthReady = authState === 'authorizationStateWaitPhoneNumber';
|
const isAuthReady = authState === 'authorizationStateWaitPhoneNumber';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -242,7 +246,7 @@ const AuthPhoneNumber: FC<StateProps> = ({
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
{isAuthReady && (
|
{isAuthReady && (
|
||||||
<Button isText ripple isLoading={authIsLoadingQrCode} onClick={goToAuthQrCode}>
|
<Button isText ripple isLoading={authIsLoadingQrCode} onClick={handleGoToAuthQrCode}>
|
||||||
{lang('Login.QR.Login')}
|
{lang('Login.QR.Login')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -129,6 +129,10 @@ const AuthCode: FC<StateProps> = ({
|
|||||||
});
|
});
|
||||||
}, [markIsLoading, setSettingOption, suggestedLanguage, unmarkIsLoading]);
|
}, [markIsLoading, setSettingOption, suggestedLanguage, unmarkIsLoading]);
|
||||||
|
|
||||||
|
const habdleReturnToAuthPhoneNumber = useCallback(() => {
|
||||||
|
returnToAuthPhoneNumber();
|
||||||
|
}, [returnToAuthPhoneNumber]);
|
||||||
|
|
||||||
const isAuthReady = authState === 'authorizationStateWaitQrCode';
|
const isAuthReady = authState === 'authorizationStateWaitQrCode';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -162,7 +166,7 @@ const AuthCode: FC<StateProps> = ({
|
|||||||
<li><span>{lang('Login.QR.Help3')}</span></li>
|
<li><span>{lang('Login.QR.Help3')}</span></li>
|
||||||
</ol>
|
</ol>
|
||||||
{isAuthReady && (
|
{isAuthReady && (
|
||||||
<Button isText onClick={returnToAuthPhoneNumber}>{lang('Login.QR.Cancel')}</Button>
|
<Button isText onClick={habdleReturnToAuthPhoneNumber}>{lang('Login.QR.Cancel')}</Button>
|
||||||
)}
|
)}
|
||||||
{suggestedLanguage && suggestedLanguage !== language && continueText && (
|
{suggestedLanguage && suggestedLanguage !== language && continueText && (
|
||||||
<Button isText isLoading={isLoading} onClick={handleLangChange}>{continueText}</Button>
|
<Button isText isLoading={isLoading} onClick={handleLangChange}>{continueText}</Button>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { getActions, withGlobal } from '../../global';
|
|||||||
import type { ApiGroupCall, ApiUser } from '../../api/types';
|
import type { ApiGroupCall, ApiUser } from '../../api/types';
|
||||||
|
|
||||||
import { selectActiveGroupCall, selectPhoneCallUser } from '../../global/selectors/calls';
|
import { selectActiveGroupCall, selectPhoneCallUser } from '../../global/selectors/calls';
|
||||||
|
import { selectTabState } from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
@ -33,6 +34,10 @@ const ActiveCallHeader: FC<StateProps> = ({
|
|||||||
};
|
};
|
||||||
}, [isCallPanelVisible]);
|
}, [isCallPanelVisible]);
|
||||||
|
|
||||||
|
function handleToggleGroupCallPanel() {
|
||||||
|
toggleGroupCallPanel();
|
||||||
|
}
|
||||||
|
|
||||||
if (!groupCall && !phoneCallUser) return undefined;
|
if (!groupCall && !phoneCallUser) return undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -41,7 +46,7 @@ const ActiveCallHeader: FC<StateProps> = ({
|
|||||||
'ActiveCallHeader',
|
'ActiveCallHeader',
|
||||||
isCallPanelVisible && 'open',
|
isCallPanelVisible && 'open',
|
||||||
)}
|
)}
|
||||||
onClick={toggleGroupCallPanel}
|
onClick={handleToggleGroupCallPanel}
|
||||||
>
|
>
|
||||||
<span className="title">{phoneCallUser?.firstName || groupCall?.title || lang('VoipGroupVoiceChat')}</span>
|
<span className="title">{phoneCallUser?.firstName || groupCall?.title || lang('VoipGroupVoiceChat')}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -50,10 +55,11 @@ const ActiveCallHeader: FC<StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal(
|
export default memo(withGlobal(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
|
const tabState = selectTabState(global);
|
||||||
return {
|
return {
|
||||||
groupCall: selectActiveGroupCall(global),
|
groupCall: tabState.isMasterTab ? selectActiveGroupCall(global) : undefined,
|
||||||
isCallPanelVisible: global.isCallPanelVisible,
|
isCallPanelVisible: tabState.isCallPanelVisible,
|
||||||
phoneCallUser: selectPhoneCallUser(global),
|
phoneCallUser: tabState.isMasterTab ? selectPhoneCallUser(global) : undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(ActiveCallHeader));
|
)(ActiveCallHeader));
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import {
|
|||||||
selectGroupCallParticipant,
|
selectGroupCallParticipant,
|
||||||
selectIsAdminInActiveGroupCall,
|
selectIsAdminInActiveGroupCall,
|
||||||
} from '../../../global/selectors/calls';
|
} from '../../../global/selectors/calls';
|
||||||
|
import { selectTabState } from '../../../global/selectors';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useAppLayout from '../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
@ -249,6 +250,10 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isLeaving, leaveGroupCall, shouldEndGroupCall]);
|
}, [isLeaving, leaveGroupCall, shouldEndGroupCall]);
|
||||||
|
|
||||||
|
const handleToggleGroupCallPresentation = useCallback(() => {
|
||||||
|
toggleGroupCallPresentation();
|
||||||
|
}, [toggleGroupCallPresentation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={!isCallPanelVisible && !isLeaving}
|
isOpen={!isCallPanelVisible && !isLeaving}
|
||||||
@ -293,7 +298,7 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
|||||||
{IS_SCREENSHARE_SUPPORTED && !shouldRaiseHand && (
|
{IS_SCREENSHARE_SUPPORTED && !shouldRaiseHand && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="share-screen-outlined"
|
icon="share-screen-outlined"
|
||||||
onClick={toggleGroupCallPresentation}
|
onClick={handleToggleGroupCallPresentation}
|
||||||
>
|
>
|
||||||
{lang(hasPresentation ? 'VoipChatStopScreenCapture' : 'VoipChatStartScreenCapture')}
|
{lang(hasPresentation ? 'VoipChatStopScreenCapture' : 'VoipChatStartScreenCapture')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -416,7 +421,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isSpeakerEnabled: !isSpeakerDisabled,
|
isSpeakerEnabled: !isSpeakerDisabled,
|
||||||
participantsCount,
|
participantsCount,
|
||||||
meParticipant: selectGroupCallParticipant(global, groupCallId, global.currentUserId!),
|
meParticipant: selectGroupCallParticipant(global, groupCallId, global.currentUserId!),
|
||||||
isCallPanelVisible: Boolean(global.isCallPanelVisible),
|
isCallPanelVisible: Boolean(selectTabState(global).isCallPanelVisible),
|
||||||
isAdmin: selectIsAdminInActiveGroupCall(global),
|
isAdmin: selectIsAdminInActiveGroupCall(global),
|
||||||
participants,
|
participants,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { GroupCallParticipant as TypeGroupCallParticipant } from '../../../lib/secret-sauce';
|
import type { GroupCallParticipant as TypeGroupCallParticipant } from '../../../lib/secret-sauce';
|
||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -36,15 +36,23 @@ const GroupCallParticipantList: FC<OwnProps & StateProps> = ({
|
|||||||
return Object.keys(participants || {});
|
return Object.keys(participants || {});
|
||||||
}, [participants]);
|
}, [participants]);
|
||||||
|
|
||||||
|
const handleLoadMoreGroupCallParticipants = useCallback(() => {
|
||||||
|
loadMoreGroupCallParticipants();
|
||||||
|
}, [loadMoreGroupCallParticipants]);
|
||||||
|
|
||||||
const [viewportIds, getMore] = useInfiniteScroll(
|
const [viewportIds, getMore] = useInfiniteScroll(
|
||||||
loadMoreGroupCallParticipants,
|
handleLoadMoreGroupCallParticipants,
|
||||||
participantsIds,
|
participantsIds,
|
||||||
participantsIds.length >= participantsCount,
|
participantsIds.length >= participantsCount,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function handleCreateGroupCallInviteLink() {
|
||||||
|
createGroupCallInviteLink();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="participants">
|
<div className="participants">
|
||||||
<div className="invite-btn" onClick={createGroupCallInviteLink}>
|
<div className="invite-btn" onClick={handleCreateGroupCallInviteLink}>
|
||||||
<div className="icon">
|
<div className="icon">
|
||||||
<i className="icon-add-user" />
|
<i className="icon-add-user" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -108,7 +108,7 @@ const GroupCallParticipantMenu: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleGroupCallMute({
|
toggleGroupCallMute({
|
||||||
participantId: id,
|
participantId: id!,
|
||||||
value: isAdmin ? !shouldRaiseHand : !isMutedByMe,
|
value: isAdmin ? !shouldRaiseHand : !isMutedByMe,
|
||||||
});
|
});
|
||||||
}, [closeDropdown, toggleGroupCallMute, id, isAdmin, shouldRaiseHand, isMutedByMe]);
|
}, [closeDropdown, toggleGroupCallMute, id, isAdmin, shouldRaiseHand, isMutedByMe]);
|
||||||
@ -131,12 +131,12 @@ const GroupCallParticipantMenu: FC<OwnProps & StateProps> = ({
|
|||||||
runThrottled(() => {
|
runThrottled(() => {
|
||||||
if (value === VOLUME_ZERO) {
|
if (value === VOLUME_ZERO) {
|
||||||
toggleGroupCallMute({
|
toggleGroupCallMute({
|
||||||
participantId: id,
|
participantId: id!,
|
||||||
value: true,
|
value: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setGroupCallParticipantVolume({
|
setGroupCallParticipantVolume({
|
||||||
participantId: id,
|
participantId: id!,
|
||||||
volume: Math.floor(value * GROUP_CALL_VOLUME_MULTIPLIER),
|
volume: Math.floor(value * GROUP_CALL_VOLUME_MULTIPLIER),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import type { AnimationLevel } from '../../../types';
|
|||||||
|
|
||||||
import { selectChatGroupCall } from '../../../global/selectors/calls';
|
import { selectChatGroupCall } from '../../../global/selectors/calls';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { selectChat } from '../../../global/selectors';
|
import { selectChat, selectTabState } from '../../../global/selectors';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
@ -42,17 +42,17 @@ const GroupCallTopPane: FC<OwnProps & StateProps> = ({
|
|||||||
animationLevel,
|
animationLevel,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
joinGroupCall,
|
requestMasterAndJoinGroupCall,
|
||||||
subscribeToGroupCallUpdates,
|
subscribeToGroupCallUpdates,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const handleJoinGroupCall = useCallback(() => {
|
const handleJoinGroupCall = useCallback(() => {
|
||||||
joinGroupCall({
|
requestMasterAndJoinGroupCall({
|
||||||
chatId,
|
chatId,
|
||||||
});
|
});
|
||||||
}, [joinGroupCall, chatId]);
|
}, [requestMasterAndJoinGroupCall, chatId]);
|
||||||
|
|
||||||
const participants = groupCall?.participants;
|
const participants = groupCall?.participants;
|
||||||
|
|
||||||
@ -128,6 +128,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global, { chatId }) => {
|
(global, { chatId }) => {
|
||||||
const chat = selectChat(global, chatId)!;
|
const chat = selectChat(global, chatId)!;
|
||||||
const groupCall = selectChatGroupCall(global, chatId);
|
const groupCall = selectChatGroupCall(global, chatId);
|
||||||
|
const activeGroupCallId = selectTabState(global).isMasterTab ? global.groupCalls.activeGroupCallId : undefined;
|
||||||
return {
|
return {
|
||||||
groupCall,
|
groupCall,
|
||||||
usersById: global.users.byId,
|
usersById: global.users.byId,
|
||||||
@ -135,7 +136,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
activeGroupCallId: global.groupCalls.activeGroupCallId,
|
activeGroupCallId: global.groupCalls.activeGroupCallId,
|
||||||
isActive: ((!groupCall ? (chat && chat.isCallNotEmpty && chat.isCallActive)
|
isActive: ((!groupCall ? (chat && chat.isCallNotEmpty && chat.isCallActive)
|
||||||
: (groupCall.participantsCount > 0 && groupCall.isLoaded)))
|
: (groupCall.participantsCount > 0 && groupCall.isLoaded)))
|
||||||
&& (global.groupCalls.activeGroupCallId !== groupCall?.id),
|
&& (activeGroupCallId !== groupCall?.id),
|
||||||
animationLevel: global.settings.byKey.animationLevel,
|
animationLevel: global.settings.byKey.animationLevel,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
IS_REQUEST_FULLSCREEN_SUPPORTED,
|
IS_REQUEST_FULLSCREEN_SUPPORTED,
|
||||||
} from '../../../util/environment';
|
} from '../../../util/environment';
|
||||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
|
import { selectTabState } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { selectPhoneCallUser } from '../../../global/selectors/calls';
|
import { selectPhoneCallUser } from '../../../global/selectors/calls';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -52,7 +53,7 @@ const PhoneCall: FC<StateProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const {
|
const {
|
||||||
hangUp, acceptCall, playGroupCallSound, toggleGroupCallPanel, connectToActivePhoneCall,
|
hangUp, requestMasterAndAcceptCall, playGroupCallSound, toggleGroupCallPanel, connectToActivePhoneCall,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
@ -347,7 +348,7 @@ const PhoneCall: FC<StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
{isIncomingRequested && (
|
{isIncomingRequested && (
|
||||||
<PhoneCallButton
|
<PhoneCallButton
|
||||||
onClick={acceptCall}
|
onClick={requestMasterAndAcceptCall}
|
||||||
icon="phone-discard"
|
icon="phone-discard"
|
||||||
isDisabled={isDiscarded}
|
isDisabled={isDiscarded}
|
||||||
label={lang('lng_call_accept')}
|
label={lang('lng_call_accept')}
|
||||||
@ -370,12 +371,13 @@ const PhoneCall: FC<StateProps> = ({
|
|||||||
export default memo(withGlobal(
|
export default memo(withGlobal(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const { phoneCall, currentUserId } = global;
|
const { phoneCall, currentUserId } = global;
|
||||||
|
const { isCallPanelVisible, isMasterTab } = selectTabState(global);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isCallPanelVisible: Boolean(global.isCallPanelVisible),
|
isCallPanelVisible: Boolean(isCallPanelVisible),
|
||||||
user: selectPhoneCallUser(global),
|
user: selectPhoneCallUser(global),
|
||||||
isOutgoing: phoneCall?.adminId === currentUserId,
|
isOutgoing: phoneCall?.adminId === currentUserId,
|
||||||
phoneCall,
|
phoneCall: isMasterTab ? phoneCall : undefined,
|
||||||
animationLevel: global.settings.byKey.animationLevel,
|
animationLevel: global.settings.byKey.animationLevel,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -118,7 +118,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
isMuted: !newAreNotificationsEnabled,
|
isMuted: !newAreNotificationsEnabled,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
updateChatMutedState({ chatId, isMuted: !newAreNotificationsEnabled });
|
updateChatMutedState({ chatId: chatId!, isMuted: !newAreNotificationsEnabled });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -80,7 +80,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const handleDeleteAndStop = useCallback(() => {
|
const handleDeleteAndStop = useCallback(() => {
|
||||||
deleteHistory({ chatId: chat.id, shouldDeleteForAll: true });
|
deleteHistory({ chatId: chat.id, shouldDeleteForAll: true });
|
||||||
blockContact({ contactId: chat.id, accessHash: chat.accessHash });
|
blockContact({ contactId: chat.id, accessHash: chat.accessHash! });
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
}, [deleteHistory, chat.id, chat.accessHash, blockContact, onClose]);
|
}, [deleteHistory, chat.id, chat.accessHash, blockContact, onClose]);
|
||||||
@ -89,7 +89,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
if (isPrivateChat) {
|
if (isPrivateChat) {
|
||||||
deleteHistory({ chatId: chat.id, shouldDeleteForAll: false });
|
deleteHistory({ chatId: chat.id, shouldDeleteForAll: false });
|
||||||
} else if (isBasicGroup) {
|
} else if (isBasicGroup) {
|
||||||
deleteChatUser({ chatId: chat.id, userId: currentUserId });
|
deleteChatUser({ chatId: chat.id, userId: currentUserId! });
|
||||||
deleteHistory({ chatId: chat.id, shouldDeleteForAll: false });
|
deleteHistory({ chatId: chat.id, shouldDeleteForAll: false });
|
||||||
} else if ((isChannel || isSuperGroup) && !chat.isCreator) {
|
} else if ((isChannel || isSuperGroup) && !chat.isCreator) {
|
||||||
leaveChannel({ chatId: chat.id });
|
leaveChannel({ chatId: chat.id });
|
||||||
|
|||||||
@ -17,13 +17,13 @@ type OwnProps = {
|
|||||||
const GroupCallLink: FC<OwnProps> = ({
|
const GroupCallLink: FC<OwnProps> = ({
|
||||||
className, groupCall, children,
|
className, groupCall, children,
|
||||||
}) => {
|
}) => {
|
||||||
const { joinGroupCall } = getActions();
|
const { requestMasterAndJoinGroupCall } = getActions();
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
if (groupCall) {
|
if (groupCall) {
|
||||||
joinGroupCall({ id: groupCall.id, accessHash: groupCall.accessHash });
|
requestMasterAndJoinGroupCall({ id: groupCall.id, accessHash: groupCall.accessHash });
|
||||||
}
|
}
|
||||||
}, [groupCall, joinGroupCall]);
|
}, [groupCall, requestMasterAndJoinGroupCall]);
|
||||||
|
|
||||||
if (!groupCall) {
|
if (!groupCall) {
|
||||||
return children;
|
return children;
|
||||||
|
|||||||
@ -37,7 +37,6 @@ type StateProps = {
|
|||||||
const PinMessageModal: FC<OwnProps & StateProps> = ({
|
const PinMessageModal: FC<OwnProps & StateProps> = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
messageId,
|
messageId,
|
||||||
chatId,
|
|
||||||
isChannel,
|
isChannel,
|
||||||
isGroup,
|
isGroup,
|
||||||
isSuperGroup,
|
isSuperGroup,
|
||||||
@ -49,17 +48,17 @@ const PinMessageModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const handlePinMessageForAll = useCallback(() => {
|
const handlePinMessageForAll = useCallback(() => {
|
||||||
pinMessage({
|
pinMessage({
|
||||||
chatId, messageId, isUnpin: false,
|
messageId, isUnpin: false,
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
}, [pinMessage, chatId, messageId, onClose]);
|
}, [pinMessage, messageId, onClose]);
|
||||||
|
|
||||||
const handlePinMessage = useCallback(() => {
|
const handlePinMessage = useCallback(() => {
|
||||||
pinMessage({
|
pinMessage({
|
||||||
chatId, messageId, isUnpin: false, isOneSide: true, isSilent: true,
|
messageId, isUnpin: false, isOneSide: true, isSilent: true,
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
}, [chatId, messageId, onClose, pinMessage]);
|
}, [messageId, onClose, pinMessage]);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { MediaViewerOrigin } from '../../types';
|
|||||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||||
import {
|
import {
|
||||||
|
selectTabState,
|
||||||
selectChat, selectCurrentMessageList, selectThreadMessagesCount, selectUser, selectUserStatus,
|
selectChat, selectCurrentMessageList, selectThreadMessagesCount, selectUser, selectUserStatus,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { getUserStatus, isChatChannel, isUserOnline } from '../../global/helpers';
|
import { getUserStatus, isChatChannel, isUserOnline } from '../../global/helpers';
|
||||||
@ -336,7 +337,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const chat = selectChat(global, userId);
|
const chat = selectChat(global, userId);
|
||||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||||
const { animationLevel } = global.settings.byKey;
|
const { animationLevel } = global.settings.byKey;
|
||||||
const { mediaId, avatarOwnerId } = global.mediaViewer;
|
const { mediaId, avatarOwnerId } = selectTabState(global).mediaViewer;
|
||||||
const isForum = chat?.isForum;
|
const isForum = chat?.isForum;
|
||||||
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
|
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
|
||||||
const topic = isForum && currentTopicId ? chat?.topics?.[currentTopicId] : undefined;
|
const topic = isForum && currentTopicId ? chat?.topics?.[currentTopicId] : undefined;
|
||||||
|
|||||||
@ -47,7 +47,7 @@ const ReportModal: FC<OwnProps> = ({
|
|||||||
const handleReport = useCallback(() => {
|
const handleReport = useCallback(() => {
|
||||||
switch (subject) {
|
switch (subject) {
|
||||||
case 'messages':
|
case 'messages':
|
||||||
reportMessages({ messageIds, reason: selectedReason, description });
|
reportMessages({ messageIds: messageIds!, reason: selectedReason, description });
|
||||||
exitMessageSelectMode();
|
exitMessageSelectMode();
|
||||||
break;
|
break;
|
||||||
case 'peer':
|
case 'peer':
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React, { useCallback, memo } from '../../lib/teact/teact';
|
|||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import { selectChatMessage } from '../../global/selectors';
|
import { selectChatMessage, selectTabState } from '../../global/selectors';
|
||||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||||
|
|
||||||
import Modal from '../ui/Modal';
|
import Modal from '../ui/Modal';
|
||||||
@ -40,6 +40,10 @@ const SeenByModal: FC<OwnProps & StateProps> = ({
|
|||||||
}, CLOSE_ANIMATION_DURATION);
|
}, CLOSE_ANIMATION_DURATION);
|
||||||
}, [closeSeenByModal, openChat]);
|
}, [closeSeenByModal, openChat]);
|
||||||
|
|
||||||
|
const handleCloseSeenByModal = useCallback(() => {
|
||||||
|
closeSeenByModal();
|
||||||
|
}, [closeSeenByModal]);
|
||||||
|
|
||||||
const renderingMemberIds = useCurrentOrPrev(memberIds, true);
|
const renderingMemberIds = useCurrentOrPrev(memberIds, true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -64,7 +68,7 @@ const SeenByModal: FC<OwnProps & StateProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
className="confirm-dialog-button"
|
className="confirm-dialog-button"
|
||||||
isText
|
isText
|
||||||
onClick={closeSeenByModal}
|
onClick={handleCloseSeenByModal}
|
||||||
>
|
>
|
||||||
{lang('Close')}
|
{lang('Close')}
|
||||||
</Button>
|
</Button>
|
||||||
@ -74,7 +78,7 @@ const SeenByModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const { chatId, messageId } = global.seenByModal || {};
|
const { chatId, messageId } = selectTabState(global).seenByModal || {};
|
||||||
if (!chatId || !messageId) {
|
if (!chatId || !messageId) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,7 +79,7 @@ const StickerSetModal: FC<OwnProps & StateProps> = ({
|
|||||||
const prevStickerSet = usePrevious(stickerSet);
|
const prevStickerSet = usePrevious(stickerSet);
|
||||||
const renderingStickerSet = stickerSet || prevStickerSet;
|
const renderingStickerSet = stickerSet || prevStickerSet;
|
||||||
|
|
||||||
const isAdded = !renderingStickerSet?.isArchived && renderingStickerSet?.installedDate;
|
const isAdded = Boolean(!renderingStickerSet?.isArchived && renderingStickerSet?.installedDate);
|
||||||
const isEmoji = renderingStickerSet?.isEmoji;
|
const isEmoji = renderingStickerSet?.isEmoji;
|
||||||
const isButtonLocked = !isAdded && isSetPremium && !isCurrentUserPremium;
|
const isButtonLocked = !isAdded && isSetPremium && !isCurrentUserPremium;
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,16 @@ import React, { useEffect } from '../../lib/teact/teact';
|
|||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
import { ApiMediaFormat } from '../../api/types';
|
import { ApiMediaFormat } from '../../api/types';
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { TabState } from '../../global/types';
|
||||||
import type { ThemeKey } from '../../types';
|
import type { ThemeKey } from '../../types';
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import { getChatAvatarHash } from '../../global/helpers/chats'; // Direct import for better module splitting
|
import { getChatAvatarHash } from '../../global/helpers/chats'; // Direct import for better module splitting
|
||||||
import { selectIsRightColumnShown, selectTheme } from '../../global/selectors';
|
import {
|
||||||
|
selectIsRightColumnShown,
|
||||||
|
selectTheme,
|
||||||
|
selectTabState,
|
||||||
|
} from '../../global/selectors';
|
||||||
import { DARK_THEME_BG_COLOR, LIGHT_THEME_BG_COLOR } from '../../config';
|
import { DARK_THEME_BG_COLOR, LIGHT_THEME_BG_COLOR } from '../../config';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
@ -41,7 +45,7 @@ type OwnProps = {
|
|||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = Pick<GlobalState, 'uiReadyState' | 'shouldSkipHistoryAnimations'> & {
|
type StateProps = Pick<TabState, 'uiReadyState' | 'shouldSkipHistoryAnimations'> & {
|
||||||
isRightColumnShown?: boolean;
|
isRightColumnShown?: boolean;
|
||||||
leftColumnWidth?: number;
|
leftColumnWidth?: number;
|
||||||
theme: ThemeKey;
|
theme: ThemeKey;
|
||||||
@ -74,7 +78,7 @@ function preloadAvatars() {
|
|||||||
|
|
||||||
const preloadTasks = {
|
const preloadTasks = {
|
||||||
main: () => Promise.all([
|
main: () => Promise.all([
|
||||||
loadModule(Bundles.Main, 'Main')
|
loadModule(Bundles.Main)
|
||||||
.then(preloadFonts),
|
.then(preloadFonts),
|
||||||
preloadAvatars(),
|
preloadAvatars(),
|
||||||
preloadImage(reactionThumbsPath),
|
preloadImage(reactionThumbsPath),
|
||||||
@ -176,12 +180,13 @@ const UiLoader: FC<OwnProps & StateProps> = ({
|
|||||||
export default withGlobal<OwnProps>(
|
export default withGlobal<OwnProps>(
|
||||||
(global, { isMobile }): StateProps => {
|
(global, { isMobile }): StateProps => {
|
||||||
const theme = selectTheme(global);
|
const theme = selectTheme(global);
|
||||||
|
const tabState = selectTabState(global);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations: tabState.shouldSkipHistoryAnimations,
|
||||||
uiReadyState: global.uiReadyState,
|
uiReadyState: tabState.uiReadyState,
|
||||||
isRightColumnShown: selectIsRightColumnShown(global, isMobile),
|
isRightColumnShown: selectIsRightColumnShown(global, isMobile),
|
||||||
leftColumnWidth: global.leftColumnWidth,
|
leftColumnWidth: tabState.leftColumnWidth,
|
||||||
theme,
|
theme,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -52,11 +52,11 @@ export default function useAnimatedEmoji(
|
|||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
sendEmojiInteraction({
|
sendEmojiInteraction({
|
||||||
chatId,
|
chatId: chatId!,
|
||||||
messageId,
|
messageId: messageId!,
|
||||||
localEffect,
|
localEffect,
|
||||||
emoji,
|
emoji: emoji!,
|
||||||
interactions: interactions.current,
|
interactions: interactions.current!,
|
||||||
});
|
});
|
||||||
startedInteractions.current = undefined;
|
startedInteractions.current = undefined;
|
||||||
interactions.current = undefined;
|
interactions.current = undefined;
|
||||||
@ -91,7 +91,7 @@ export default function useAnimatedEmoji(
|
|||||||
|
|
||||||
interactWithAnimatedEmoji({
|
interactWithAnimatedEmoji({
|
||||||
localEffect,
|
localEffect,
|
||||||
emoji,
|
emoji: emoji!,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
startSize: size,
|
startSize: size,
|
||||||
@ -131,8 +131,8 @@ export default function useAnimatedEmoji(
|
|||||||
|
|
||||||
sendWatchingEmojiInteraction({
|
sendWatchingEmojiInteraction({
|
||||||
id,
|
id,
|
||||||
chatId,
|
chatId: chatId!,
|
||||||
emoticon: localEffect ? selectLocalAnimatedEmojiEffectByName(localEffect) : emoji,
|
emoticon: localEffect ? selectLocalAnimatedEmojiEffectByName(localEffect)! : emoji!,
|
||||||
startSize: size,
|
startSize: size,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { LeftColumnContent, SettingsScreens } from '../../types';
|
|||||||
|
|
||||||
import { IS_MAC_OS, IS_PWA, LAYERS_ANIMATION_NAME } from '../../util/environment';
|
import { IS_MAC_OS, IS_PWA, LAYERS_ANIMATION_NAME } from '../../util/environment';
|
||||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
import { selectCurrentChat, selectIsForumPanelOpen } from '../../global/selectors';
|
import { selectTabState, selectCurrentChat, selectIsForumPanelOpen } from '../../global/selectors';
|
||||||
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
||||||
import { useResize } from '../../hooks/useResize';
|
import { useResize } from '../../hooks/useResize';
|
||||||
import { useHotkeys } from '../../hooks/useHotkeys';
|
import { useHotkeys } from '../../hooks/useHotkeys';
|
||||||
@ -113,13 +113,13 @@ const LeftColumn: FC<StateProps> = ({
|
|||||||
function fullReset() {
|
function fullReset() {
|
||||||
setContent(LeftColumnContent.ChatList);
|
setContent(LeftColumnContent.ChatList);
|
||||||
setContactsFilter('');
|
setContactsFilter('');
|
||||||
setGlobalSearchClosing(true);
|
setGlobalSearchClosing({ isClosing: true });
|
||||||
resetChatCreation();
|
resetChatCreation();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setGlobalSearchQuery({ query: '' });
|
setGlobalSearchQuery({ query: '' });
|
||||||
setGlobalSearchDate({ date: undefined });
|
setGlobalSearchDate({ date: undefined });
|
||||||
setGlobalSearchChatId({ id: undefined });
|
setGlobalSearchChatId({ id: undefined });
|
||||||
setGlobalSearchClosing(false);
|
setGlobalSearchClosing({ isClosing: false });
|
||||||
setLastResetTime(Date.now());
|
setLastResetTime(Date.now());
|
||||||
}, RESET_TRANSITION_DELAY_MS);
|
}, RESET_TRANSITION_DELAY_MS);
|
||||||
}
|
}
|
||||||
@ -376,13 +376,15 @@ const LeftColumn: FC<StateProps> = ({
|
|||||||
if (nextSettingsScreen !== undefined) {
|
if (nextSettingsScreen !== undefined) {
|
||||||
setContent(LeftColumnContent.Settings);
|
setContent(LeftColumnContent.Settings);
|
||||||
setSettingsScreen(nextSettingsScreen);
|
setSettingsScreen(nextSettingsScreen);
|
||||||
requestNextSettingsScreen(undefined);
|
requestNextSettingsScreen({ screen: undefined });
|
||||||
}
|
}
|
||||||
}, [nextSettingsScreen, requestNextSettingsScreen]);
|
}, [nextSettingsScreen, requestNextSettingsScreen]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
initResize, resetResize, handleMouseUp,
|
initResize, resetResize, handleMouseUp,
|
||||||
} = useResize(resizeRef, setLeftColumnWidth, resetLeftColumnWidth, leftColumnWidth, '--left-column-width');
|
} = useResize(resizeRef, (n) => setLeftColumnWidth({
|
||||||
|
leftColumnWidth: n,
|
||||||
|
}), resetLeftColumnWidth, leftColumnWidth, '--left-column-width');
|
||||||
|
|
||||||
const handleSettingsScreenSelect = useCallback((screen: SettingsScreens) => {
|
const handleSettingsScreenSelect = useCallback((screen: SettingsScreens) => {
|
||||||
setContent(LeftColumnContent.Settings);
|
setContent(LeftColumnContent.Settings);
|
||||||
@ -479,30 +481,29 @@ const LeftColumn: FC<StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal(
|
export default memo(withGlobal(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
|
const tabState = selectTabState(global);
|
||||||
const {
|
const {
|
||||||
globalSearch: {
|
globalSearch: {
|
||||||
query,
|
query,
|
||||||
date,
|
date,
|
||||||
},
|
},
|
||||||
chatFolders: {
|
|
||||||
activeChatFolder,
|
|
||||||
},
|
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
leftColumnWidth,
|
leftColumnWidth,
|
||||||
|
activeChatFolder,
|
||||||
|
nextSettingsScreen,
|
||||||
|
} = tabState;
|
||||||
|
const {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
passcode: {
|
passcode: {
|
||||||
hasPasscode,
|
hasPasscode,
|
||||||
},
|
},
|
||||||
settings: {
|
|
||||||
nextScreen: nextSettingsScreen,
|
|
||||||
},
|
|
||||||
isUpdateAvailable,
|
isUpdateAvailable,
|
||||||
} = global;
|
} = global;
|
||||||
|
|
||||||
const currentChat = selectCurrentChat(global);
|
const currentChat = selectCurrentChat(global);
|
||||||
const isChatOpen = Boolean(currentChat?.id);
|
const isChatOpen = Boolean(currentChat?.id);
|
||||||
const isForumPanelOpen = selectIsForumPanelOpen(global);
|
const isForumPanelOpen = selectIsForumPanelOpen(global);
|
||||||
const forumPanelChatId = global.forumPanelChatId;
|
const forumPanelChatId = tabState.forumPanelChatId;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
searchQuery: query,
|
searchQuery: query,
|
||||||
@ -517,7 +518,7 @@ export default memo(withGlobal(
|
|||||||
isUpdateAvailable,
|
isUpdateAvailable,
|
||||||
isForumPanelOpen,
|
isForumPanelOpen,
|
||||||
forumPanelChatId,
|
forumPanelChatId,
|
||||||
isClosingSearch: global.globalSearch.isClosing,
|
isClosingSearch: tabState.globalSearch.isClosing,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(LeftColumn));
|
)(LeftColumn));
|
||||||
|
|||||||
@ -34,7 +34,7 @@ import {
|
|||||||
selectNotifyExceptions,
|
selectNotifyExceptions,
|
||||||
selectUserStatus,
|
selectUserStatus,
|
||||||
selectTopicFromMessage,
|
selectTopicFromMessage,
|
||||||
selectThreadParam,
|
selectThreadParam, selectTabState,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
@ -323,7 +323,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
type: messageListType,
|
type: messageListType,
|
||||||
} = selectCurrentMessageList(global) || {};
|
} = selectCurrentMessageList(global) || {};
|
||||||
const isSelected = chatId === currentChatId && currentThreadId === MAIN_THREAD_ID;
|
const isSelected = chatId === currentChatId && currentThreadId === MAIN_THREAD_ID;
|
||||||
const isSelectedForum = chatId === global.forumPanelChatId;
|
const isSelectedForum = chatId === selectTabState(global).forumPanelChatId;
|
||||||
|
|
||||||
const user = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
|
const user = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
|
||||||
const userStatus = privateChatUserId ? selectUserStatus(global, privateChatUserId) : undefined;
|
const userStatus = privateChatUserId ? selectUserStatus(global, privateChatUserId) : undefined;
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
|
|||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
||||||
|
import { selectTabState, selectIsForumPanelOpen } from '../../../global/selectors';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
@ -22,7 +23,6 @@ import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManag
|
|||||||
import Transition from '../../ui/Transition';
|
import Transition from '../../ui/Transition';
|
||||||
import TabList from '../../ui/TabList';
|
import TabList from '../../ui/TabList';
|
||||||
import ChatList from './ChatList';
|
import ChatList from './ChatList';
|
||||||
import { selectIsForumPanelOpen } from '../../../global/selectors';
|
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
onScreenSelect: (screen: SettingsScreens) => void;
|
onScreenSelect: (screen: SettingsScreens) => void;
|
||||||
@ -69,10 +69,10 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastSyncTime) {
|
if (lastSyncTime && !orderedFolderIds) {
|
||||||
loadChatFolders();
|
loadChatFolders();
|
||||||
}
|
}
|
||||||
}, [lastSyncTime, loadChatFolders]);
|
}, [lastSyncTime, loadChatFolders, orderedFolderIds]);
|
||||||
|
|
||||||
const allChatsFolder = useMemo(() => {
|
const allChatsFolder = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@ -117,7 +117,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
}, [displayedFolders, folderCountersById, maxFolders]);
|
}, [displayedFolders, folderCountersById, maxFolders]);
|
||||||
|
|
||||||
const handleSwitchTab = useCallback((index: number) => {
|
const handleSwitchTab = useCallback((index: number) => {
|
||||||
setActiveChatFolder(index, { forceOnHeavyAnimation: true });
|
setActiveChatFolder({ activeChatFolder: index }, { forceOnHeavyAnimation: true });
|
||||||
}, [setActiveChatFolder]);
|
}, [setActiveChatFolder]);
|
||||||
|
|
||||||
// Prevent `activeTab` pointing at non-existing folder after update
|
// Prevent `activeTab` pointing at non-existing folder after update
|
||||||
@ -127,7 +127,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (activeChatFolder >= folderTabs.length) {
|
if (activeChatFolder >= folderTabs.length) {
|
||||||
setActiveChatFolder(FIRST_FOLDER_INDEX);
|
setActiveChatFolder({ activeChatFolder: FIRST_FOLDER_INDEX });
|
||||||
}
|
}
|
||||||
}, [activeChatFolder, folderTabs, setActiveChatFolder]);
|
}, [activeChatFolder, folderTabs, setActiveChatFolder]);
|
||||||
|
|
||||||
@ -140,10 +140,13 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
selectorToPreventScroll: '.chat-list',
|
selectorToPreventScroll: '.chat-list',
|
||||||
onSwipe: ((e, direction) => {
|
onSwipe: ((e, direction) => {
|
||||||
if (direction === SwipeDirection.Left) {
|
if (direction === SwipeDirection.Left) {
|
||||||
setActiveChatFolder(Math.min(activeChatFolder + 1, folderTabs.length - 1), { forceOnHeavyAnimation: true });
|
setActiveChatFolder(
|
||||||
|
{ activeChatFolder: Math.min(activeChatFolder + 1, folderTabs.length - 1) },
|
||||||
|
{ forceOnHeavyAnimation: true },
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
} else if (direction === SwipeDirection.Right) {
|
} else if (direction === SwipeDirection.Right) {
|
||||||
setActiveChatFolder(Math.max(0, activeChatFolder - 1), { forceOnHeavyAnimation: true });
|
setActiveChatFolder({ activeChatFolder: Math.max(0, activeChatFolder - 1) }, { forceOnHeavyAnimation: true });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,13 +159,13 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
isNotInFirstFolderRef.current = !isInFirstFolder;
|
isNotInFirstFolderRef.current = !isInFirstFolder;
|
||||||
useEffect(() => (isNotInFirstFolderRef.current ? captureEscKeyListener(() => {
|
useEffect(() => (isNotInFirstFolderRef.current ? captureEscKeyListener(() => {
|
||||||
if (isNotInFirstFolderRef.current) {
|
if (isNotInFirstFolderRef.current) {
|
||||||
setActiveChatFolder(FIRST_FOLDER_INDEX);
|
setActiveChatFolder({ activeChatFolder: FIRST_FOLDER_INDEX });
|
||||||
}
|
}
|
||||||
}) : undefined), [activeChatFolder, setActiveChatFolder]);
|
}) : undefined), [activeChatFolder, setActiveChatFolder]);
|
||||||
|
|
||||||
useHistoryBack({
|
useHistoryBack({
|
||||||
isActive: !isInFirstFolder,
|
isActive: !isInFirstFolder,
|
||||||
onBack: () => setActiveChatFolder(FIRST_FOLDER_INDEX, { forceOnHeavyAnimation: true }),
|
onBack: () => setActiveChatFolder({ activeChatFolder: FIRST_FOLDER_INDEX }, { forceOnHeavyAnimation: true }),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -179,7 +182,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
|||||||
const folder = Number(digit) - 1;
|
const folder = Number(digit) - 1;
|
||||||
if (folder > folderTabs.length - 1) return;
|
if (folder > folderTabs.length - 1) return;
|
||||||
|
|
||||||
setActiveChatFolder(folder, { forceOnHeavyAnimation: true });
|
setActiveChatFolder({ activeChatFolder: folder }, { forceOnHeavyAnimation: true });
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -245,12 +248,11 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
chatFolders: {
|
chatFolders: {
|
||||||
byId: chatFoldersById,
|
byId: chatFoldersById,
|
||||||
orderedIds: orderedFolderIds,
|
orderedIds: orderedFolderIds,
|
||||||
activeChatFolder,
|
|
||||||
},
|
},
|
||||||
currentUserId,
|
currentUserId,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
shouldSkipHistoryAnimations,
|
|
||||||
} = global;
|
} = global;
|
||||||
|
const { shouldSkipHistoryAnimations, activeChatFolder } = selectTabState(global);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chatFoldersById,
|
chatFoldersById,
|
||||||
|
|||||||
@ -12,7 +12,9 @@ import {
|
|||||||
TOPICS_SLICE, TOPIC_HEIGHT_PX, TOPIC_LIST_SENSITIVE_AREA,
|
TOPICS_SLICE, TOPIC_HEIGHT_PX, TOPIC_LIST_SENSITIVE_AREA,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||||
import { selectChat, selectCurrentMessageList, selectIsForumPanelOpen } from '../../../global/selectors';
|
import {
|
||||||
|
selectChat, selectCurrentMessageList, selectIsForumPanelOpen, selectTabState,
|
||||||
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { getOrderedTopics } from '../../../global/helpers';
|
import { getOrderedTopics } from '../../../global/helpers';
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
@ -264,7 +266,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global, ownProps, detachWhenChanged): StateProps => {
|
(global, ownProps, detachWhenChanged): StateProps => {
|
||||||
detachWhenChanged(selectIsForumPanelOpen(global));
|
detachWhenChanged(selectIsForumPanelOpen(global));
|
||||||
|
|
||||||
const chatId = global.forumPanelChatId;
|
const chatId = selectTabState(global).forumPanelChatId;
|
||||||
const chat = chatId ? selectChat(global, chatId) : undefined;
|
const chat = chatId ? selectChat(global, chatId) : undefined;
|
||||||
const {
|
const {
|
||||||
chatId: currentChatId,
|
chatId: currentChatId,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
import type { AnimationLevel, ISettings } from '../../../types';
|
import type { AnimationLevel, ISettings } from '../../../types';
|
||||||
import { LeftColumnContent, SettingsScreens } from '../../../types';
|
import { LeftColumnContent, SettingsScreens } from '../../../types';
|
||||||
import type { ApiChat } from '../../../api/types';
|
import type { ApiChat } from '../../../api/types';
|
||||||
import type { GlobalState } from '../../../global/types';
|
import type { TabState, GlobalState } from '../../../global/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ANIMATION_LEVEL_MAX,
|
ANIMATION_LEVEL_MAX,
|
||||||
@ -25,7 +25,7 @@ import { formatDateToString } from '../../../util/dateFormat';
|
|||||||
import switchTheme from '../../../util/switchTheme';
|
import switchTheme from '../../../util/switchTheme';
|
||||||
import { setPermanentWebVersion } from '../../../util/permanentWebVersion';
|
import { setPermanentWebVersion } from '../../../util/permanentWebVersion';
|
||||||
import { clearWebsync } from '../../../util/websync';
|
import { clearWebsync } from '../../../util/websync';
|
||||||
import { selectCurrentMessageList, selectTheme } from '../../../global/selectors';
|
import { selectCurrentMessageList, selectTabState, selectTheme } from '../../../global/selectors';
|
||||||
import { isChatArchived } from '../../../global/helpers';
|
import { isChatArchived } from '../../../global/helpers';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useConnectionStatus from '../../../hooks/useConnectionStatus';
|
import useConnectionStatus from '../../../hooks/useConnectionStatus';
|
||||||
@ -74,7 +74,7 @@ type StateProps =
|
|||||||
areChatsLoaded?: boolean;
|
areChatsLoaded?: boolean;
|
||||||
hasPasscode?: boolean;
|
hasPasscode?: boolean;
|
||||||
}
|
}
|
||||||
& Pick<GlobalState, 'connectionState' | 'isSyncing' | 'canInstall'>;
|
& Pick<GlobalState, 'connectionState' | 'isSyncing'> & Pick<TabState, 'canInstall'>;
|
||||||
|
|
||||||
const ANIMATION_LEVEL_OPTIONS = [0, 1, 2];
|
const ANIMATION_LEVEL_OPTIONS = [0, 1, 2];
|
||||||
const LEGACY_VERSION_URL = 'https://web.telegram.org/?legacy=1';
|
const LEGACY_VERSION_URL = 'https://web.telegram.org/?legacy=1';
|
||||||
@ -153,7 +153,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
if (hasPasscode) {
|
if (hasPasscode) {
|
||||||
lockScreen();
|
lockScreen();
|
||||||
} else {
|
} else {
|
||||||
requestNextSettingsScreen(SettingsScreens.PasscodeDisabled);
|
requestNextSettingsScreen({ screen: SettingsScreens.PasscodeDisabled });
|
||||||
}
|
}
|
||||||
}, [hasPasscode, lockScreen, requestNextSettingsScreen]);
|
}, [hasPasscode, lockScreen, requestNextSettingsScreen]);
|
||||||
|
|
||||||
@ -461,9 +461,10 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
|
const tabState = selectTabState(global);
|
||||||
const {
|
const {
|
||||||
query: searchQuery, fetchingStatus, chatId, date,
|
query: searchQuery, fetchingStatus, chatId, date,
|
||||||
} = global.globalSearch;
|
} = tabState.globalSearch;
|
||||||
const { currentUserId, connectionState, isSyncing } = global;
|
const { currentUserId, connectionState, isSyncing } = global;
|
||||||
const { byId: chatsById } = global.chats;
|
const { byId: chatsById } = global.chats;
|
||||||
const { isConnectionStatusMinimized, animationLevel } = global.settings.byKey;
|
const { isConnectionStatusMinimized, animationLevel } = global.settings.byKey;
|
||||||
@ -483,7 +484,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isConnectionStatusMinimized,
|
isConnectionStatusMinimized,
|
||||||
areChatsLoaded: Boolean(global.chats.listIds.active),
|
areChatsLoaded: Boolean(global.chats.listIds.active),
|
||||||
hasPasscode: Boolean(global.passcode.hasPasscode),
|
hasPasscode: Boolean(global.passcode.hasPasscode),
|
||||||
canInstall: Boolean(global.canInstall),
|
canInstall: Boolean(tabState.canInstall),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(LeftMainHeader));
|
)(LeftMainHeader));
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
|
|||||||
|
|
||||||
import type { ApiChat } from '../../../api/types';
|
import type { ApiChat } from '../../../api/types';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../../global/selectors';
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
import { filterUsersByName, isUserBot, sortChatIds } from '../../../global/helpers';
|
import { filterUsersByName, isUserBot, sortChatIds } from '../../../global/helpers';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -139,7 +140,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
fetchingStatus,
|
fetchingStatus,
|
||||||
globalResults,
|
globalResults,
|
||||||
localResults,
|
localResults,
|
||||||
} = global.globalSearch;
|
} = selectTabState(global).globalSearch;
|
||||||
const { userIds: globalUserIds } = globalResults || {};
|
const { userIds: globalUserIds } = globalResults || {};
|
||||||
const { userIds: localUserIds } = localResults || {};
|
const { userIds: localUserIds } = localResults || {};
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
|
|
||||||
import { ChatCreationProgress } from '../../../types';
|
import { ChatCreationProgress } from '../../../types';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../../global/selectors';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
@ -198,7 +199,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const {
|
const {
|
||||||
progress: creationProgress,
|
progress: creationProgress,
|
||||||
error: creationError,
|
error: creationError,
|
||||||
} = global.chatCreation || {};
|
} = selectTabState(global).chatCreation || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
creationProgress,
|
creationProgress,
|
||||||
|
|||||||
@ -30,7 +30,6 @@ const AudioResults: FC<OwnProps & StateProps> = ({
|
|||||||
theme,
|
theme,
|
||||||
isVoice,
|
isVoice,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
searchChatId,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
chatsById,
|
chatsById,
|
||||||
usersById,
|
usersById,
|
||||||
@ -52,12 +51,10 @@ const AudioResults: FC<OwnProps & StateProps> = ({
|
|||||||
runThrottled(() => {
|
runThrottled(() => {
|
||||||
searchMessagesGlobal({
|
searchMessagesGlobal({
|
||||||
type: currentType,
|
type: currentType,
|
||||||
query: searchQuery,
|
|
||||||
chatId: searchChatId,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [currentType, lastSyncTime, searchMessagesGlobal, searchQuery, searchChatId]);
|
}, [currentType, lastSyncTime, searchMessagesGlobal]);
|
||||||
|
|
||||||
const foundMessages = useMemo(() => {
|
const foundMessages = useMemo(() => {
|
||||||
if (!foundIds || !globalMessagesByChatId) {
|
if (!foundIds || !globalMessagesByChatId) {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
import type { ApiChat, ApiMessage } from '../../../api/types';
|
import type { ApiChat, ApiMessage } from '../../../api/types';
|
||||||
import { LoadMoreDirection } from '../../../types';
|
import { LoadMoreDirection } from '../../../types';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../../global/selectors';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
||||||
@ -39,7 +40,6 @@ const runThrottled = throttle((cb) => cb(), 500, true);
|
|||||||
|
|
||||||
const ChatMessageResults: FC<OwnProps & StateProps> = ({
|
const ChatMessageResults: FC<OwnProps & StateProps> = ({
|
||||||
searchQuery,
|
searchQuery,
|
||||||
currentUserId,
|
|
||||||
dateSearchQuery,
|
dateSearchQuery,
|
||||||
foundIds,
|
foundIds,
|
||||||
globalMessagesByChatId,
|
globalMessagesByChatId,
|
||||||
@ -61,12 +61,10 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
|
|||||||
runThrottled(() => {
|
runThrottled(() => {
|
||||||
searchMessagesGlobal({
|
searchMessagesGlobal({
|
||||||
type: 'text',
|
type: 'text',
|
||||||
query: searchQuery,
|
|
||||||
chatId: currentUserId,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [currentUserId, lastSyncTime, searchMessagesGlobal, searchQuery]);
|
}, [lastSyncTime, searchMessagesGlobal]);
|
||||||
|
|
||||||
const handleTopicClick = useCallback(
|
const handleTopicClick = useCallback(
|
||||||
(id: number) => {
|
(id: number) => {
|
||||||
@ -171,7 +169,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { currentUserId, messages: { byChatId: globalMessagesByChatId }, lastSyncTime } = global;
|
const { currentUserId, messages: { byChatId: globalMessagesByChatId }, lastSyncTime } = global;
|
||||||
const {
|
const {
|
||||||
fetchingStatus, resultsByType, foundTopicIds, chatId: searchChatId,
|
fetchingStatus, resultsByType, foundTopicIds, chatId: searchChatId,
|
||||||
} = global.globalSearch;
|
} = selectTabState(global).globalSearch;
|
||||||
|
|
||||||
const { foundIds } = (resultsByType?.text) || {};
|
const { foundIds } = (resultsByType?.text) || {};
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
|
|||||||
import type { ApiChat, ApiMessage } from '../../../api/types';
|
import type { ApiChat, ApiMessage } from '../../../api/types';
|
||||||
import { LoadMoreDirection } from '../../../types';
|
import { LoadMoreDirection } from '../../../types';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../../global/selectors';
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
import {
|
import {
|
||||||
sortChatIds,
|
sortChatIds,
|
||||||
@ -81,11 +82,10 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
runThrottled(() => {
|
runThrottled(() => {
|
||||||
searchMessagesGlobal({
|
searchMessagesGlobal({
|
||||||
type: 'text',
|
type: 'text',
|
||||||
query: searchQuery,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [lastSyncTime, searchMessagesGlobal, searchQuery]);
|
}, [lastSyncTime, searchMessagesGlobal]);
|
||||||
|
|
||||||
const handleChatClick = useCallback(
|
const handleChatClick = useCallback(
|
||||||
(id: string) => {
|
(id: string) => {
|
||||||
@ -303,7 +303,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
} = global;
|
} = global;
|
||||||
const {
|
const {
|
||||||
fetchingStatus, globalResults, localResults, resultsByType,
|
fetchingStatus, globalResults, localResults, resultsByType,
|
||||||
} = global.globalSearch;
|
} = selectTabState(global).globalSearch;
|
||||||
const { chatIds: globalChatIds, userIds: globalUserIds } = globalResults || {};
|
const { chatIds: globalChatIds, userIds: globalUserIds } = globalResults || {};
|
||||||
const { chatIds: localChatIds, userIds: localUserIds } = localResults || {};
|
const { chatIds: localChatIds, userIds: localUserIds } = localResults || {};
|
||||||
const { byChatId: globalMessagesByChatId } = messages;
|
const { byChatId: globalMessagesByChatId } = messages;
|
||||||
|
|||||||
@ -35,7 +35,6 @@ const runThrottled = throttle((cb) => cb(), 500, true);
|
|||||||
|
|
||||||
const FileResults: FC<OwnProps & StateProps> = ({
|
const FileResults: FC<OwnProps & StateProps> = ({
|
||||||
searchQuery,
|
searchQuery,
|
||||||
searchChatId,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
chatsById,
|
chatsById,
|
||||||
usersById,
|
usersById,
|
||||||
@ -64,12 +63,10 @@ const FileResults: FC<OwnProps & StateProps> = ({
|
|||||||
runThrottled(() => {
|
runThrottled(() => {
|
||||||
searchMessagesGlobal({
|
searchMessagesGlobal({
|
||||||
type: CURRENT_TYPE,
|
type: CURRENT_TYPE,
|
||||||
query: searchQuery,
|
|
||||||
chatId: searchChatId,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [lastSyncTime, searchMessagesGlobal, searchQuery, searchChatId]);
|
}, [lastSyncTime, searchMessagesGlobal]);
|
||||||
|
|
||||||
const foundMessages = useMemo(() => {
|
const foundMessages = useMemo(() => {
|
||||||
if (!foundIds || !globalMessagesByChatId) {
|
if (!foundIds || !globalMessagesByChatId) {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
|
|
||||||
import { GlobalSearchContent } from '../../../types';
|
import { GlobalSearchContent } from '../../../types';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../../global/selectors';
|
||||||
import { parseDateString } from '../../../util/dateFormat';
|
import { parseDateString } from '../../../util/dateFormat';
|
||||||
import useKeyboardListNavigation from '../../../hooks/useKeyboardListNavigation';
|
import useKeyboardListNavigation from '../../../hooks/useKeyboardListNavigation';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -148,7 +149,7 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const { currentContent, chatId } = global.globalSearch;
|
const { currentContent, chatId } = selectTabState(global).globalSearch;
|
||||||
|
|
||||||
return { currentContent, chatId };
|
return { currentContent, chatId };
|
||||||
},
|
},
|
||||||
|
|||||||
@ -33,7 +33,6 @@ const runThrottled = throttle((cb) => cb(), 500, true);
|
|||||||
|
|
||||||
const LinkResults: FC<OwnProps & StateProps> = ({
|
const LinkResults: FC<OwnProps & StateProps> = ({
|
||||||
searchQuery,
|
searchQuery,
|
||||||
searchChatId,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
chatsById,
|
chatsById,
|
||||||
usersById,
|
usersById,
|
||||||
@ -62,12 +61,10 @@ const LinkResults: FC<OwnProps & StateProps> = ({
|
|||||||
runThrottled(() => {
|
runThrottled(() => {
|
||||||
searchMessagesGlobal({
|
searchMessagesGlobal({
|
||||||
type: CURRENT_TYPE,
|
type: CURRENT_TYPE,
|
||||||
query: searchQuery,
|
|
||||||
chatId: searchChatId,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [lastSyncTime, searchMessagesGlobal, searchQuery, searchChatId]);
|
}, [lastSyncTime, searchMessagesGlobal]);
|
||||||
|
|
||||||
const foundMessages = useMemo(() => {
|
const foundMessages = useMemo(() => {
|
||||||
if (!foundIds || !globalMessagesByChatId) {
|
if (!foundIds || !globalMessagesByChatId) {
|
||||||
|
|||||||
@ -33,7 +33,6 @@ const runThrottled = throttle((cb) => cb(), 500, true);
|
|||||||
|
|
||||||
const MediaResults: FC<OwnProps & StateProps> = ({
|
const MediaResults: FC<OwnProps & StateProps> = ({
|
||||||
searchQuery,
|
searchQuery,
|
||||||
searchChatId,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
globalMessagesByChatId,
|
globalMessagesByChatId,
|
||||||
foundIds,
|
foundIds,
|
||||||
@ -60,12 +59,10 @@ const MediaResults: FC<OwnProps & StateProps> = ({
|
|||||||
runThrottled(() => {
|
runThrottled(() => {
|
||||||
searchMessagesGlobal({
|
searchMessagesGlobal({
|
||||||
type: CURRENT_TYPE,
|
type: CURRENT_TYPE,
|
||||||
query: searchQuery,
|
|
||||||
chatId: searchChatId,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [lastSyncTime, searchMessagesGlobal, searchQuery, searchChatId]);
|
}, [lastSyncTime, searchMessagesGlobal]);
|
||||||
|
|
||||||
const foundMessages = useMemo(() => {
|
const foundMessages = useMemo(() => {
|
||||||
if (!foundIds || !globalMessagesByChatId) {
|
if (!foundIds || !globalMessagesByChatId) {
|
||||||
|
|||||||
@ -68,6 +68,10 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
|
|||||||
}, SEARCH_CLOSE_TIMEOUT_MS);
|
}, SEARCH_CLOSE_TIMEOUT_MS);
|
||||||
}, [openChat, addRecentlyFoundChatId, onReset]);
|
}, [openChat, addRecentlyFoundChatId, onReset]);
|
||||||
|
|
||||||
|
const handleClearRecentlyFoundChats = useCallback(() => {
|
||||||
|
clearRecentlyFoundChats();
|
||||||
|
}, [clearRecentlyFoundChats]);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -94,7 +98,7 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
|
|||||||
size="smaller"
|
size="smaller"
|
||||||
color="translucent"
|
color="translucent"
|
||||||
ariaLabel="Clear recent chats"
|
ariaLabel="Clear recent chats"
|
||||||
onClick={clearRecentlyFoundChats}
|
onClick={handleClearRecentlyFoundChats}
|
||||||
isRtl={lang.isRtl}
|
isRtl={lang.isRtl}
|
||||||
>
|
>
|
||||||
<i className="icon-close" />
|
<i className="icon-close" />
|
||||||
@ -116,7 +120,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const { userIds: topUserIds } = global.topPeers;
|
const { userIds: topUserIds } = global.topPeers;
|
||||||
const usersById = global.users.byId;
|
const usersById = global.users.byId;
|
||||||
const { recentlyFoundChatIds } = global.globalSearch;
|
const { recentlyFoundChatIds } = global;
|
||||||
const { animationLevel } = global.settings.byKey;
|
const { animationLevel } = global.settings.byKey;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type {
|
|||||||
} from '../../../../api/types';
|
} from '../../../../api/types';
|
||||||
import type { ISettings } from '../../../../types';
|
import type { ISettings } from '../../../../types';
|
||||||
|
|
||||||
import { selectChat, selectTheme } from '../../../../global/selectors';
|
import { selectChat, selectTabState, selectTheme } from '../../../../global/selectors';
|
||||||
|
|
||||||
export type StateProps = {
|
export type StateProps = {
|
||||||
theme: ISettings['theme'];
|
theme: ISettings['theme'];
|
||||||
@ -21,11 +21,12 @@ export type StateProps = {
|
|||||||
|
|
||||||
export function createMapStateToProps(type: ApiGlobalMessageSearchType) {
|
export function createMapStateToProps(type: ApiGlobalMessageSearchType) {
|
||||||
return (global: GlobalState, props: any) => {
|
return (global: GlobalState, props: any) => {
|
||||||
|
const tabState = selectTabState(global);
|
||||||
const { byId: chatsById } = global.chats;
|
const { byId: chatsById } = global.chats;
|
||||||
const { byId: usersById } = global.users;
|
const { byId: usersById } = global.users;
|
||||||
const {
|
const {
|
||||||
fetchingStatus, resultsByType, chatId,
|
fetchingStatus, resultsByType, chatId,
|
||||||
} = global.globalSearch;
|
} = tabState.globalSearch;
|
||||||
|
|
||||||
// One component is used for two different types of results.
|
// One component is used for two different types of results.
|
||||||
// The differences between them are only in the isVoice property.
|
// The differences between them are only in the isVoice property.
|
||||||
@ -35,7 +36,7 @@ export function createMapStateToProps(type: ApiGlobalMessageSearchType) {
|
|||||||
const { byChatId: globalMessagesByChatId } = global.messages;
|
const { byChatId: globalMessagesByChatId } = global.messages;
|
||||||
const foundIds = resultsByType?.[currentType]?.foundIds;
|
const foundIds = resultsByType?.[currentType]?.foundIds;
|
||||||
|
|
||||||
const activeDownloads = global.activeDownloads.byChatId;
|
const activeDownloads = tabState.activeDownloads.byChatId;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
theme: selectTheme(global),
|
theme: selectTheme(global),
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
|
|
||||||
import type { ApiUser } from '../../../api/types';
|
import type { ApiUser } from '../../../api/types';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../../global/selectors';
|
||||||
import { filterUsersByName, getUserFullName } from '../../../global/helpers';
|
import { filterUsersByName, getUserFullName } from '../../../global/helpers';
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -102,7 +103,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
usersById,
|
usersById,
|
||||||
blockedIds,
|
blockedIds,
|
||||||
contactIds: contactList?.userIds,
|
contactIds: contactList?.userIds,
|
||||||
localContactIds: global.userSearch.localUserIds,
|
localContactIds: selectTabState(global).userSearch.localUserIds,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { ProfileEditProgress } from '../../../types';
|
|||||||
|
|
||||||
import { PURCHASE_USERNAME, TME_LINK_PREFIX, USERNAME_PURCHASE_ERROR } from '../../../config';
|
import { PURCHASE_USERNAME, TME_LINK_PREFIX, USERNAME_PURCHASE_ERROR } from '../../../config';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
import { selectUser } from '../../../global/selectors';
|
import { selectTabState, selectUser } from '../../../global/selectors';
|
||||||
import { getChatAvatarHash } from '../../../global/helpers';
|
import { getChatAvatarHash } from '../../../global/helpers';
|
||||||
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
@ -165,6 +165,8 @@ const SettingsEditProfile: FC<OwnProps & StateProps> = ({
|
|||||||
const trimmedLastName = lastName.trim();
|
const trimmedLastName = lastName.trim();
|
||||||
const trimmedBio = bio.trim();
|
const trimmedBio = bio.trim();
|
||||||
|
|
||||||
|
if (!editableUsername) return;
|
||||||
|
|
||||||
if (!trimmedFirstName.length) {
|
if (!trimmedFirstName.length) {
|
||||||
setError(ERROR_FIRST_NAME_MISSING);
|
setError(ERROR_FIRST_NAME_MISSING);
|
||||||
return;
|
return;
|
||||||
@ -292,7 +294,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { currentUserId } = global;
|
const { currentUserId } = global;
|
||||||
const {
|
const {
|
||||||
progress, isUsernameAvailable, checkedUsername, error: editUsernameError,
|
progress, isUsernameAvailable, checkedUsername, error: editUsernameError,
|
||||||
} = global.profileEdit || {};
|
} = selectTabState(global).profileEdit || {};
|
||||||
const currentUser = currentUserId ? selectUser(global, currentUserId) : undefined;
|
const currentUser = currentUserId ? selectUser(global, currentUserId) : undefined;
|
||||||
|
|
||||||
const maxBioLength = selectCurrentLimit(global, 'aboutLength');
|
const maxBioLength = selectCurrentLimit(global, 'aboutLength');
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import React, {
|
|||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ISettings, TimeFormat } from '../../../types';
|
import type { AnimationLevel, ISettings, TimeFormat } from '../../../types';
|
||||||
import { SettingsScreens } from '../../../types';
|
import { SettingsScreens } from '../../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -95,7 +95,7 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
|
|||||||
document.body.classList.toggle(`animation-level-${i}`, newLevel === i);
|
document.body.classList.toggle(`animation-level-${i}`, newLevel === i);
|
||||||
});
|
});
|
||||||
|
|
||||||
setSettingOption({ animationLevel: newLevel });
|
setSettingOption({ animationLevel: newLevel as AnimationLevel });
|
||||||
}, [setSettingOption]);
|
}, [setSettingOption]);
|
||||||
|
|
||||||
const handleMessageTextSizeChange = useCallback((newSize: number) => {
|
const handleMessageTextSizeChange = useCallback((newSize: number) => {
|
||||||
@ -120,14 +120,14 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
|
|||||||
}, [animationLevel, setSettingOption, theme]);
|
}, [animationLevel, setSettingOption, theme]);
|
||||||
|
|
||||||
const handleTimeFormatChange = useCallback((newTimeFormat: string) => {
|
const handleTimeFormatChange = useCallback((newTimeFormat: string) => {
|
||||||
setSettingOption({ timeFormat: newTimeFormat });
|
setSettingOption({ timeFormat: newTimeFormat as TimeFormat });
|
||||||
setSettingOption({ wasTimeFormatSetManually: true });
|
setSettingOption({ wasTimeFormatSetManually: true });
|
||||||
|
|
||||||
setTimeFormat(newTimeFormat as TimeFormat);
|
setTimeFormat(newTimeFormat as TimeFormat);
|
||||||
}, [setSettingOption]);
|
}, [setSettingOption]);
|
||||||
|
|
||||||
const handleMessageSendComboChange = useCallback((newCombo: string) => {
|
const handleMessageSendComboChange = useCallback((newCombo: string) => {
|
||||||
setSettingOption({ messageSendKeyCombo: newCombo });
|
setSettingOption({ messageSendKeyCombo: newCombo as ISettings['messageSendKeyCombo'] });
|
||||||
}, [setSettingOption]);
|
}, [setSettingOption]);
|
||||||
|
|
||||||
useHistoryBack({
|
useHistoryBack({
|
||||||
|
|||||||
@ -56,7 +56,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
|||||||
setThemeSettings,
|
setThemeSettings,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const themeRef = useRef<string>();
|
const themeRef = useRef<ThemeKey>();
|
||||||
themeRef.current = theme;
|
themeRef.current = theme;
|
||||||
// Due to the parent Transition, this component never gets unmounted,
|
// Due to the parent Transition, this component never gets unmounted,
|
||||||
// that's why we use throttled API call on every update.
|
// that's why we use throttled API call on every update.
|
||||||
@ -94,20 +94,20 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
|||||||
}, [setThemeSettings, theme]);
|
}, [setThemeSettings, theme]);
|
||||||
|
|
||||||
const handleWallPaperSelect = useCallback((slug: string) => {
|
const handleWallPaperSelect = useCallback((slug: string) => {
|
||||||
setThemeSettings({ theme: themeRef.current, background: slug });
|
setThemeSettings({ theme: themeRef.current!, background: slug });
|
||||||
const currentWallpaper = loadedWallpapers && loadedWallpapers.find((wallpaper) => wallpaper.slug === slug);
|
const currentWallpaper = loadedWallpapers && loadedWallpapers.find((wallpaper) => wallpaper.slug === slug);
|
||||||
if (currentWallpaper?.document.thumbnail) {
|
if (currentWallpaper?.document.thumbnail) {
|
||||||
getAverageColor(currentWallpaper.document.thumbnail.dataUri)
|
getAverageColor(currentWallpaper.document.thumbnail.dataUri)
|
||||||
.then((color) => {
|
.then((color) => {
|
||||||
const patternColor = getPatternColor(color);
|
const patternColor = getPatternColor(color);
|
||||||
const rgbColor = `#${rgb2hex(color)}`;
|
const rgbColor = `#${rgb2hex(color)}`;
|
||||||
setThemeSettings({ theme: themeRef.current, backgroundColor: rgbColor, patternColor });
|
setThemeSettings({ theme: themeRef.current!, backgroundColor: rgbColor, patternColor });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [loadedWallpapers, setThemeSettings]);
|
}, [loadedWallpapers, setThemeSettings]);
|
||||||
|
|
||||||
const handleWallPaperBlurChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleWallPaperBlurChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setThemeSettings({ theme: themeRef.current, isBlurred: e.target.checked });
|
setThemeSettings({ theme: themeRef.current!, isBlurred: e.target.checked });
|
||||||
}, [setThemeSettings]);
|
}, [setThemeSettings]);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|||||||
@ -59,7 +59,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { setThemeSettings } = getActions();
|
const { setThemeSettings } = getActions();
|
||||||
|
|
||||||
const themeRef = useRef<string>();
|
const themeRef = useRef<ThemeKey>();
|
||||||
themeRef.current = theme;
|
themeRef.current = theme;
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
@ -156,7 +156,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
|||||||
if (!isFirstRunRef.current) {
|
if (!isFirstRunRef.current) {
|
||||||
const patternColor = getPatternColor(rgb);
|
const patternColor = getPatternColor(rgb);
|
||||||
setThemeSettings({
|
setThemeSettings({
|
||||||
theme: themeRef.current,
|
theme: themeRef.current!,
|
||||||
background: undefined,
|
background: undefined,
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
patternColor,
|
patternColor,
|
||||||
|
|||||||
@ -58,7 +58,7 @@ const SettingsHeader: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const handleSignOutMessage = useCallback(() => {
|
const handleSignOutMessage = useCallback(() => {
|
||||||
closeSignOutConfirmation();
|
closeSignOutConfirmation();
|
||||||
signOut();
|
signOut({ forceInitApi: true });
|
||||||
}, [closeSignOutConfirmation, signOut]);
|
}, [closeSignOutConfirmation, signOut]);
|
||||||
|
|
||||||
const SettingsMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
|
const SettingsMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
|
||||||
|
|||||||
@ -48,7 +48,7 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
|
|||||||
void setLanguage(langCode as LangCode, () => {
|
void setLanguage(langCode as LangCode, () => {
|
||||||
unmarkIsLoading();
|
unmarkIsLoading();
|
||||||
|
|
||||||
setSettingOption({ language: langCode });
|
setSettingOption({ language: langCode as LangCode });
|
||||||
|
|
||||||
loadAttachBots(); // Should be refetched every language change
|
loadAttachBots(); // Should be refetched every language change
|
||||||
});
|
});
|
||||||
|
|||||||
@ -108,6 +108,10 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isCurrentUserPremium, lang, onScreenSelect, showNotification]);
|
}, [isCurrentUserPremium, lang, onScreenSelect, showNotification]);
|
||||||
|
|
||||||
|
const handleUpdateContentSettings = useCallback((isChecked: boolean) => {
|
||||||
|
updateContentSettings(isChecked);
|
||||||
|
}, [updateContentSettings]);
|
||||||
|
|
||||||
function getVisibilityValue(setting?: ApiPrivacySettings) {
|
function getVisibilityValue(setting?: ApiPrivacySettings) {
|
||||||
const { visibility } = setting || {};
|
const { visibility } = setting || {};
|
||||||
const blockCount = setting ? setting.blockChatIds.length + setting.blockUserIds.length : 0;
|
const blockCount = setting ? setting.blockChatIds.length + setting.blockUserIds.length : 0;
|
||||||
@ -318,7 +322,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
subLabel={lang('lng_settings_sensitive_about')}
|
subLabel={lang('lng_settings_sensitive_about')}
|
||||||
checked={Boolean(isSensitiveEnabled)}
|
checked={Boolean(isSensitiveEnabled)}
|
||||||
disabled={!canChangeSensitive}
|
disabled={!canChangeSensitive}
|
||||||
onCheck={updateContentSettings}
|
onCheck={handleUpdateContentSettings}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -171,7 +171,7 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const handleVisibilityChange = useCallback((value) => {
|
const handleVisibilityChange = useCallback((value) => {
|
||||||
setPrivacyVisibility({
|
setPrivacyVisibility({
|
||||||
privacyKey,
|
privacyKey: privacyKey!,
|
||||||
visibility: value,
|
visibility: value,
|
||||||
});
|
});
|
||||||
}, [privacyKey, setPrivacyVisibility]);
|
}, [privacyKey, setPrivacyVisibility]);
|
||||||
|
|||||||
@ -86,7 +86,7 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
setPrivacySettings({
|
setPrivacySettings({
|
||||||
privacyKey: getPrivacyKey(screen),
|
privacyKey: getPrivacyKey(screen)!,
|
||||||
isAllowList: Boolean(isAllowList),
|
isAllowList: Boolean(isAllowList),
|
||||||
contactsIds: newSelectedContactIds,
|
contactsIds: newSelectedContactIds,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import React, {
|
|||||||
} from '../../../../lib/teact/teact';
|
} from '../../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../../global';
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
|
import type { ApiChatFolder } from '../../../../api/types';
|
||||||
|
|
||||||
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
||||||
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||||
import { findIntersectionWithSet } from '../../../../util/iteratees';
|
import { findIntersectionWithSet } from '../../../../util/iteratees';
|
||||||
@ -142,9 +144,9 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
dispatch({ type: 'setIsLoading', payload: true });
|
dispatch({ type: 'setIsLoading', payload: true });
|
||||||
if (state.mode === 'edit') {
|
if (state.mode === 'edit') {
|
||||||
editChatFolder({ id: state.folderId, folderUpdate: state.folder });
|
editChatFolder({ id: state.folderId!, folderUpdate: state.folder });
|
||||||
} else {
|
} else {
|
||||||
addChatFolder({ folder: state.folder });
|
addChatFolder({ folder: state.folder as ApiChatFolder });
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@ -142,7 +142,7 @@ const SettingsTwoFa: FC<OwnProps & StateProps> = ({
|
|||||||
dispatch({ type: 'setEmail', payload: value });
|
dispatch({ type: 'setEmail', payload: value });
|
||||||
updateRecoveryEmail({
|
updateRecoveryEmail({
|
||||||
...state,
|
...state,
|
||||||
email: value,
|
email: value!,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
onScreenSelect(SettingsScreens.TwoFaCongratulations);
|
onScreenSelect(SettingsScreens.TwoFaCongratulations);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { memo, useCallback, useEffect } from '../../lib/teact/teact';
|
|||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { TabState } from '../../global/types';
|
||||||
|
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
@ -10,7 +10,7 @@ import useFlag from '../../hooks/useFlag';
|
|||||||
import RecipientPicker from '../common/RecipientPicker';
|
import RecipientPicker from '../common/RecipientPicker';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
requestedAttachBotInChat?: GlobalState['requestedAttachBotInChat'];
|
requestedAttachBotInChat?: TabState['requestedAttachBotInChat'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const AttachBotRecipientPicker: FC<OwnProps> = ({
|
const AttachBotRecipientPicker: FC<OwnProps> = ({
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import React, { memo, useRef } from '../../lib/teact/teact';
|
import React, { memo, useRef } from '../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../global';
|
import { withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { TabState } from '../../global/types';
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import { pick } from '../../util/iteratees';
|
import { pick } from '../../util/iteratees';
|
||||||
import buildStyle from '../../util/buildStyle';
|
import buildStyle from '../../util/buildStyle';
|
||||||
|
import { selectTabState } from '../../global/selectors';
|
||||||
|
|
||||||
import useWindowSize from '../../hooks/useWindowSize';
|
import useWindowSize from '../../hooks/useWindowSize';
|
||||||
import useOnChange from '../../hooks/useOnChange';
|
import useOnChange from '../../hooks/useOnChange';
|
||||||
@ -15,7 +16,7 @@ import useAppLayout from '../../hooks/useAppLayout';
|
|||||||
import styles from './ConfettiContainer.module.scss';
|
import styles from './ConfettiContainer.module.scss';
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
confetti?: GlobalState['confetti'];
|
confetti?: TabState['confetti'];
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Confetti {
|
interface Confetti {
|
||||||
@ -203,5 +204,5 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal(
|
export default memo(withGlobal(
|
||||||
(global): StateProps => pick(global, ['confetti']),
|
(global): StateProps => pick(selectTabState(global), ['confetti']),
|
||||||
)(ConfettiContainer));
|
)(ConfettiContainer));
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const DeleteFolderDialog: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const handleDeleteFolderMessage = useCallback(() => {
|
const handleDeleteFolderMessage = useCallback(() => {
|
||||||
closeDeleteChatFolderModal();
|
closeDeleteChatFolderModal();
|
||||||
deleteChatFolder({ id: deleteFolderDialogId });
|
deleteChatFolder({ id: deleteFolderDialogId! });
|
||||||
}, [closeDeleteChatFolderModal, deleteChatFolder, deleteFolderDialogId]);
|
}, [closeDeleteChatFolderModal, deleteChatFolder, deleteFolderDialogId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import type {
|
|||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { AnimationLevel } from '../../types';
|
import type { AnimationLevel } from '../../types';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../global/selectors';
|
||||||
import getReadableErrorText from '../../util/getReadableErrorText';
|
import getReadableErrorText from '../../util/getReadableErrorText';
|
||||||
import { pick } from '../../util/iteratees';
|
import { pick } from '../../util/iteratees';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
@ -20,7 +21,7 @@ import Avatar from '../common/Avatar';
|
|||||||
import './Dialogs.scss';
|
import './Dialogs.scss';
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
dialogs: (ApiError | ApiInviteInfo)[];
|
dialogs: (ApiError | ApiInviteInfo | ApiContact)[];
|
||||||
animationLevel: AnimationLevel;
|
animationLevel: AnimationLevel;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -198,7 +199,7 @@ function getErrorHeader(error: ApiError) {
|
|||||||
export default memo(withGlobal(
|
export default memo(withGlobal(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
return {
|
return {
|
||||||
dialogs: global.dialogs,
|
dialogs: selectTabState(global).dialogs,
|
||||||
animationLevel: global.settings.byKey.animationLevel,
|
animationLevel: global.settings.byKey.animationLevel,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import type { Thread } from '../../global/types';
|
|||||||
import type { ApiMessage } from '../../api/types';
|
import type { ApiMessage } from '../../api/types';
|
||||||
import { ApiMediaFormat } from '../../api/types';
|
import { ApiMediaFormat } from '../../api/types';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../global/selectors';
|
||||||
import { IS_OPFS_SUPPORTED, IS_SERVICE_WORKER_SUPPORTED, MAX_BUFFER_SIZE } from '../../util/environment';
|
import { IS_OPFS_SUPPORTED, IS_SERVICE_WORKER_SUPPORTED, MAX_BUFFER_SIZE } from '../../util/environment';
|
||||||
import * as mediaLoader from '../../util/mediaLoader';
|
import * as mediaLoader from '../../util/mediaLoader';
|
||||||
import download from '../../util/download';
|
import download from '../../util/download';
|
||||||
@ -113,7 +114,7 @@ const DownloadManager: FC<StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal(
|
export default memo(withGlobal(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const activeDownloads = global.activeDownloads.byChatId;
|
const activeDownloads = selectTabState(global).activeDownloads.byChatId;
|
||||||
const messages = global.messages.byChatId;
|
const messages = global.messages.byChatId;
|
||||||
return {
|
return {
|
||||||
activeDownloads,
|
activeDownloads,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import React, {
|
|||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { TabState } from '../../global/types';
|
||||||
|
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
@ -12,7 +12,7 @@ import useFlag from '../../hooks/useFlag';
|
|||||||
import RecipientPicker from '../common/RecipientPicker';
|
import RecipientPicker from '../common/RecipientPicker';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
requestedDraft?: GlobalState['requestedDraft'];
|
requestedDraft?: TabState['requestedDraft'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const DraftRecipientPicker: FC<OwnProps> = ({
|
const DraftRecipientPicker: FC<OwnProps> = ({
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import React, {
|
|||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../global/selectors';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
|
||||||
@ -74,6 +75,6 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||||
return {
|
return {
|
||||||
currentUserId: global.currentUserId,
|
currentUserId: global.currentUserId,
|
||||||
isManyMessages: (global.forwardMessages.messageIds?.length || 0) > 1,
|
isManyMessages: (selectTabState(global).forwardMessages.messageIds?.length || 0) > 1,
|
||||||
};
|
};
|
||||||
})(ForwardRecipientPicker));
|
})(ForwardRecipientPicker));
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { memo, useCallback, useEffect } from '../../lib/teact/teact';
|
|||||||
import { getActions } from '../../lib/teact/teactn';
|
import { getActions } from '../../lib/teact/teactn';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { TabState } from '../../global/types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
|
||||||
import { withGlobal } from '../../global';
|
import { withGlobal } from '../../global';
|
||||||
@ -22,7 +22,7 @@ type GameEvents = { eventType: 'share_score' | 'share_game' };
|
|||||||
const PLAY_GAME_ACTION_INTERVAL = 5000;
|
const PLAY_GAME_ACTION_INTERVAL = 5000;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
openedGame?: GlobalState['openedGame'];
|
openedGame?: TabState['openedGame'];
|
||||||
gameTitle?: string;
|
gameTitle?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { FC } from '../../lib/teact/teact';
|
|||||||
import React, { memo, useCallback } from '../../lib/teact/teact';
|
import React, { memo, useCallback } from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../global/selectors';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
import CalendarModal from '../common/CalendarModal';
|
import CalendarModal from '../common/CalendarModal';
|
||||||
@ -40,6 +41,6 @@ const HistoryCalendar: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
return { selectedAt: global.historyCalendarSelectedAt };
|
return { selectedAt: selectTabState(global).historyCalendarSelectedAt };
|
||||||
},
|
},
|
||||||
)(HistoryCalendar));
|
)(HistoryCalendar));
|
||||||
|
|||||||
@ -90,7 +90,7 @@ const LockScreen: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const handleSignOutMessage = useCallback(() => {
|
const handleSignOutMessage = useCallback(() => {
|
||||||
closeSignOutConfirmation();
|
closeSignOutConfirmation();
|
||||||
signOut();
|
signOut({ forceInitApi: true });
|
||||||
}, [closeSignOutConfirmation, signOut]);
|
}, [closeSignOutConfirmation, signOut]);
|
||||||
|
|
||||||
if (!shouldRender) {
|
if (!shouldRender) {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import type {
|
|||||||
ApiAttachBot,
|
ApiAttachBot,
|
||||||
ApiChat, ApiMessage, ApiUser,
|
ApiChat, ApiMessage, ApiUser,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { ApiLimitTypeWithModal, GlobalState } from '../../global/types';
|
import type { ApiLimitTypeWithModal, TabState } from '../../global/types';
|
||||||
|
|
||||||
import '../../global/actions/all';
|
import '../../global/actions/all';
|
||||||
import {
|
import {
|
||||||
@ -18,6 +18,7 @@ import {
|
|||||||
import { IS_ANDROID } from '../../util/environment';
|
import { IS_ANDROID } from '../../util/environment';
|
||||||
import {
|
import {
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
|
selectTabState,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectIsForwardModalOpen,
|
selectIsForwardModalOpen,
|
||||||
selectIsMediaViewerOpen,
|
selectIsMediaViewerOpen,
|
||||||
@ -28,7 +29,6 @@ import {
|
|||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners';
|
import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners';
|
||||||
import { processDeepLink } from '../../util/deeplink';
|
import { processDeepLink } from '../../util/deeplink';
|
||||||
import { getAllNotificationsCount } from '../../util/folderManager';
|
|
||||||
import { parseInitialLocationHash, parseLocationHash } from '../../util/routing';
|
import { parseInitialLocationHash, parseLocationHash } from '../../util/routing';
|
||||||
import { fastRaf } from '../../util/schedulers';
|
import { fastRaf } from '../../util/schedulers';
|
||||||
|
|
||||||
@ -42,6 +42,8 @@ import useShowTransition from '../../hooks/useShowTransition';
|
|||||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||||
import useInterval from '../../hooks/useInterval';
|
import useInterval from '../../hooks/useInterval';
|
||||||
import useAppLayout from '../../hooks/useAppLayout';
|
import useAppLayout from '../../hooks/useAppLayout';
|
||||||
|
import updatePageTitle from '../../util/updatePageTitle';
|
||||||
|
import updateIcon from '../../util/updateIcon';
|
||||||
|
|
||||||
import StickerSetModal from '../common/StickerSetModal.async';
|
import StickerSetModal from '../common/StickerSetModal.async';
|
||||||
import UnreadCount from '../common/UnreadCounter';
|
import UnreadCount from '../common/UnreadCounter';
|
||||||
@ -84,6 +86,7 @@ export interface OwnProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
|
isMasterTab?: boolean;
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
lastSyncTime?: number;
|
lastSyncTime?: number;
|
||||||
isLeftColumnOpen: boolean;
|
isLeftColumnOpen: boolean;
|
||||||
@ -109,29 +112,26 @@ type StateProps = {
|
|||||||
addedCustomEmojiIds?: string[];
|
addedCustomEmojiIds?: string[];
|
||||||
newContactUserId?: string;
|
newContactUserId?: string;
|
||||||
newContactByPhoneNumber?: boolean;
|
newContactByPhoneNumber?: boolean;
|
||||||
openedGame?: GlobalState['openedGame'];
|
openedGame?: TabState['openedGame'];
|
||||||
gameTitle?: string;
|
gameTitle?: string;
|
||||||
isRatePhoneCallModalOpen?: boolean;
|
isRatePhoneCallModalOpen?: boolean;
|
||||||
webApp?: GlobalState['webApp'];
|
webApp?: TabState['webApp'];
|
||||||
isPremiumModalOpen?: boolean;
|
isPremiumModalOpen?: boolean;
|
||||||
botTrustRequest?: GlobalState['botTrustRequest'];
|
botTrustRequest?: TabState['botTrustRequest'];
|
||||||
botTrustRequestBot?: ApiUser;
|
botTrustRequestBot?: ApiUser;
|
||||||
attachBotToInstall?: ApiAttachBot;
|
attachBotToInstall?: ApiAttachBot;
|
||||||
requestedAttachBotInChat?: GlobalState['requestedAttachBotInChat'];
|
requestedAttachBotInChat?: TabState['requestedAttachBotInChat'];
|
||||||
requestedDraft?: GlobalState['requestedDraft'];
|
requestedDraft?: TabState['requestedDraft'];
|
||||||
currentUser?: ApiUser;
|
currentUser?: ApiUser;
|
||||||
urlAuth?: GlobalState['urlAuth'];
|
urlAuth?: TabState['urlAuth'];
|
||||||
limitReached?: ApiLimitTypeWithModal;
|
limitReached?: ApiLimitTypeWithModal;
|
||||||
deleteFolderDialogId?: number;
|
deleteFolderDialogId?: number;
|
||||||
isPaymentModalOpen?: boolean;
|
isPaymentModalOpen?: boolean;
|
||||||
isReceiptModalOpen?: boolean;
|
isReceiptModalOpen?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const NOTIFICATION_INTERVAL = 1000;
|
|
||||||
const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
|
const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
|
||||||
|
|
||||||
let notificationInterval: number | undefined;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
let DEBUG_isLogged = false;
|
let DEBUG_isLogged = false;
|
||||||
|
|
||||||
@ -177,12 +177,14 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
isPaymentModalOpen,
|
isPaymentModalOpen,
|
||||||
isReceiptModalOpen,
|
isReceiptModalOpen,
|
||||||
deleteFolderDialogId,
|
deleteFolderDialogId,
|
||||||
|
isMasterTab,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadAnimatedEmojis,
|
loadAnimatedEmojis,
|
||||||
loadNotificationSettings,
|
loadNotificationSettings,
|
||||||
loadNotificationExceptions,
|
loadNotificationExceptions,
|
||||||
updateIsOnline,
|
updateIsOnline,
|
||||||
|
onTabFocusChange,
|
||||||
loadTopInlineBots,
|
loadTopInlineBots,
|
||||||
loadEmojiKeywords,
|
loadEmojiKeywords,
|
||||||
loadCountryList,
|
loadCountryList,
|
||||||
@ -224,11 +226,11 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isDesktop, isLeftColumnOpen, isMiddleColumnOpen, toggleLeftColumn]);
|
}, [isDesktop, isLeftColumnOpen, isMiddleColumnOpen, toggleLeftColumn]);
|
||||||
|
|
||||||
useInterval(checkAppVersion, APP_OUTDATED_TIMEOUT_MS, true);
|
useInterval(checkAppVersion, isMasterTab ? APP_OUTDATED_TIMEOUT_MS : undefined, true);
|
||||||
|
|
||||||
// Initial API calls
|
// Initial API calls
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastSyncTime) {
|
if (lastSyncTime && isMasterTab) {
|
||||||
updateIsOnline(true);
|
updateIsOnline(true);
|
||||||
loadConfig();
|
loadConfig();
|
||||||
loadAppConfig();
|
loadAppConfig();
|
||||||
@ -248,33 +250,33 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
}, [
|
}, [
|
||||||
lastSyncTime, loadAnimatedEmojis, loadEmojiKeywords, loadNotificationExceptions, loadNotificationSettings,
|
lastSyncTime, loadAnimatedEmojis, loadEmojiKeywords, loadNotificationExceptions, loadNotificationSettings,
|
||||||
loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig, loadAttachBots, loadContactList,
|
loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig, loadAttachBots, loadContactList,
|
||||||
loadPremiumGifts, checkAppVersion, loadConfig, loadGenericEmojiEffects, loadDefaultTopicIcons,
|
loadPremiumGifts, checkAppVersion, loadConfig, loadGenericEmojiEffects, loadDefaultTopicIcons, isMasterTab,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Language-based API calls
|
// Language-based API calls
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastSyncTime) {
|
if (lastSyncTime && isMasterTab) {
|
||||||
if (language !== BASE_EMOJI_KEYWORD_LANG) {
|
if (language !== BASE_EMOJI_KEYWORD_LANG) {
|
||||||
loadEmojiKeywords({ language });
|
loadEmojiKeywords({ language: language! });
|
||||||
}
|
}
|
||||||
|
|
||||||
loadCountryList({ langCode: language });
|
loadCountryList({ langCode: language });
|
||||||
}
|
}
|
||||||
}, [language, lastSyncTime, loadCountryList, loadEmojiKeywords]);
|
}, [language, lastSyncTime, loadCountryList, loadEmojiKeywords, isMasterTab]);
|
||||||
|
|
||||||
// Re-fetch cached saved emoji for `localDb`
|
// Re-fetch cached saved emoji for `localDb`
|
||||||
useEffectWithPrevDeps(([prevLastSyncTime]) => {
|
useEffectWithPrevDeps(([prevLastSyncTime]) => {
|
||||||
if (!prevLastSyncTime && lastSyncTime) {
|
if (!prevLastSyncTime && lastSyncTime && isMasterTab) {
|
||||||
loadCustomEmojis({
|
loadCustomEmojis({
|
||||||
ids: Object.keys(getGlobal().customEmojis.byId),
|
ids: Object.keys(getGlobal().customEmojis.byId),
|
||||||
ignoreCache: true,
|
ignoreCache: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [lastSyncTime] as const);
|
}, [lastSyncTime, isMasterTab] as const);
|
||||||
|
|
||||||
// Sticker sets
|
// Sticker sets
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastSyncTime) {
|
if (lastSyncTime && isMasterTab) {
|
||||||
if (!addedSetIds || !addedCustomEmojiIds) {
|
if (!addedSetIds || !addedCustomEmojiIds) {
|
||||||
loadStickerSets();
|
loadStickerSets();
|
||||||
loadFavoriteStickers();
|
loadFavoriteStickers();
|
||||||
@ -284,14 +286,17 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
loadAddedStickers();
|
loadAddedStickers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [lastSyncTime, addedSetIds, loadStickerSets, loadFavoriteStickers, loadAddedStickers, addedCustomEmojiIds]);
|
}, [
|
||||||
|
lastSyncTime, addedSetIds, loadStickerSets, loadFavoriteStickers, loadAddedStickers, addedCustomEmojiIds,
|
||||||
|
isMasterTab,
|
||||||
|
]);
|
||||||
|
|
||||||
// Check version when service chat is ready
|
// Check version when service chat is ready
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastSyncTime && isServiceChatReady) {
|
if (lastSyncTime && isServiceChatReady && isMasterTab) {
|
||||||
checkVersionNotification();
|
checkVersionNotification();
|
||||||
}
|
}
|
||||||
}, [lastSyncTime, isServiceChatReady, checkVersionNotification]);
|
}, [lastSyncTime, isServiceChatReady, checkVersionNotification, isMasterTab]);
|
||||||
|
|
||||||
// Ensure time format
|
// Ensure time format
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -391,45 +396,18 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleBlur = useCallback(() => {
|
const handleBlur = useCallback(() => {
|
||||||
updateIsOnline(false);
|
onTabFocusChange({ isBlurred: true });
|
||||||
|
}, [onTabFocusChange]);
|
||||||
const initialUnread = getAllNotificationsCount();
|
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
clearInterval(notificationInterval);
|
|
||||||
notificationInterval = window.setInterval(() => {
|
|
||||||
if (document.title.includes(INACTIVE_MARKER)) {
|
|
||||||
updateIcon(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index % 2 === 0) {
|
|
||||||
const newUnread = getAllNotificationsCount() - initialUnread;
|
|
||||||
if (newUnread > 0) {
|
|
||||||
updatePageTitle(`${newUnread} notification${newUnread > 1 ? 's' : ''}`);
|
|
||||||
updateIcon(true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
updatePageTitle(PAGE_TITLE);
|
|
||||||
updateIcon(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
}, NOTIFICATION_INTERVAL);
|
|
||||||
}, [updateIsOnline]);
|
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
const handleFocus = useCallback(() => {
|
||||||
updateIsOnline(true);
|
onTabFocusChange({ isBlurred: false });
|
||||||
|
|
||||||
clearInterval(notificationInterval);
|
|
||||||
notificationInterval = undefined;
|
|
||||||
|
|
||||||
if (!document.title.includes(INACTIVE_MARKER)) {
|
if (!document.title.includes(INACTIVE_MARKER)) {
|
||||||
updatePageTitle(PAGE_TITLE);
|
updatePageTitle(PAGE_TITLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateIcon(false);
|
updateIcon(false);
|
||||||
}, [updateIsOnline]);
|
}, [onTabFocusChange]);
|
||||||
|
|
||||||
const handleStickerSetModalClose = useCallback(() => {
|
const handleStickerSetModalClose = useCallback(() => {
|
||||||
closeStickerSetModal();
|
closeStickerSetModal();
|
||||||
@ -494,27 +472,6 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function updateIcon(asUnread: boolean) {
|
|
||||||
document.querySelectorAll<HTMLLinkElement>('link[rel="icon"], link[rel="alternate icon"]')
|
|
||||||
.forEach((link) => {
|
|
||||||
if (asUnread) {
|
|
||||||
if (!link.href.includes('favicon-unread')) {
|
|
||||||
link.href = link.href.replace('favicon', 'favicon-unread');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
link.href = link.href.replace('favicon-unread', 'favicon');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// For some reason setting `document.title` to the same value
|
|
||||||
// causes increment of Chrome Dev Tools > Performance Monitor > DOM Nodes counter
|
|
||||||
function updatePageTitle(nextTitle: string) {
|
|
||||||
if (document.title !== nextTitle) {
|
|
||||||
document.title = nextTitle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { isMobile }): StateProps => {
|
(global, { isMobile }): StateProps => {
|
||||||
const {
|
const {
|
||||||
@ -523,6 +480,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
animationLevel, language, wasTimeFormatSetManually,
|
animationLevel, language, wasTimeFormatSetManually,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
lastSyncTime,
|
||||||
|
} = global;
|
||||||
|
|
||||||
|
const {
|
||||||
botTrustRequest,
|
botTrustRequest,
|
||||||
requestedAttachBotInstall,
|
requestedAttachBotInstall,
|
||||||
requestedAttachBotInChat,
|
requestedAttachBotInChat,
|
||||||
@ -530,16 +491,28 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
urlAuth,
|
urlAuth,
|
||||||
webApp,
|
webApp,
|
||||||
safeLinkModalUrl,
|
safeLinkModalUrl,
|
||||||
lastSyncTime,
|
|
||||||
openedStickerSetShortName,
|
openedStickerSetShortName,
|
||||||
openedCustomEmojiSetIds,
|
openedCustomEmojiSetIds,
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
} = global;
|
openedGame,
|
||||||
const { chatId: audioChatId, messageId: audioMessageId } = global.audioPlayer;
|
audioPlayer,
|
||||||
|
isLeftColumnShown,
|
||||||
|
historyCalendarSelectedAt,
|
||||||
|
notifications,
|
||||||
|
dialogs,
|
||||||
|
newContact,
|
||||||
|
ratingPhoneCall,
|
||||||
|
premiumModal,
|
||||||
|
isMasterTab,
|
||||||
|
payment,
|
||||||
|
limitReachedModal,
|
||||||
|
deleteFolderDialogModal,
|
||||||
|
} = selectTabState(global);
|
||||||
|
|
||||||
|
const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer;
|
||||||
const audioMessage = audioChatId && audioMessageId
|
const audioMessage = audioChatId && audioMessageId
|
||||||
? selectChatMessage(global, audioChatId, audioMessageId)
|
? selectChatMessage(global, audioChatId, audioMessageId)
|
||||||
: undefined;
|
: undefined;
|
||||||
const openedGame = global.openedGame;
|
|
||||||
const gameMessage = openedGame && selectChatMessage(global, openedGame.chatId, openedGame.messageId);
|
const gameMessage = openedGame && selectChatMessage(global, openedGame.chatId, openedGame.messageId);
|
||||||
const gameTitle = gameMessage?.content.game?.title;
|
const gameTitle = gameMessage?.content.game?.title;
|
||||||
const currentUser = global.currentUserId ? selectUser(global, global.currentUserId) : undefined;
|
const currentUser = global.currentUserId ? selectUser(global, global.currentUserId) : undefined;
|
||||||
@ -547,32 +520,32 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
isLeftColumnOpen: global.isLeftColumnShown,
|
isLeftColumnOpen: isLeftColumnShown,
|
||||||
isMiddleColumnOpen: Boolean(chatId),
|
isMiddleColumnOpen: Boolean(chatId),
|
||||||
isRightColumnOpen: selectIsRightColumnShown(global, isMobile),
|
isRightColumnOpen: selectIsRightColumnShown(global, isMobile),
|
||||||
isMediaViewerOpen: selectIsMediaViewerOpen(global),
|
isMediaViewerOpen: selectIsMediaViewerOpen(global),
|
||||||
isForwardModalOpen: selectIsForwardModalOpen(global),
|
isForwardModalOpen: selectIsForwardModalOpen(global),
|
||||||
hasNotifications: Boolean(global.notifications.length),
|
hasNotifications: Boolean(notifications.length),
|
||||||
hasDialogs: Boolean(global.dialogs.length),
|
hasDialogs: Boolean(dialogs.length),
|
||||||
audioMessage,
|
audioMessage,
|
||||||
safeLinkModalUrl,
|
safeLinkModalUrl,
|
||||||
isHistoryCalendarOpen: Boolean(global.historyCalendarSelectedAt),
|
isHistoryCalendarOpen: Boolean(historyCalendarSelectedAt),
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
openedStickerSetShortName,
|
openedStickerSetShortName,
|
||||||
openedCustomEmojiSetIds,
|
openedCustomEmojiSetIds,
|
||||||
isServiceChatReady: selectIsServiceChatReady(global),
|
isServiceChatReady: selectIsServiceChatReady(global),
|
||||||
activeGroupCallId: global.groupCalls.activeGroupCallId,
|
activeGroupCallId: isMasterTab ? global.groupCalls.activeGroupCallId : undefined,
|
||||||
animationLevel,
|
animationLevel,
|
||||||
language,
|
language,
|
||||||
wasTimeFormatSetManually,
|
wasTimeFormatSetManually,
|
||||||
isPhoneCallActive: Boolean(global.phoneCall),
|
isPhoneCallActive: isMasterTab ? Boolean(global.phoneCall) : undefined,
|
||||||
addedSetIds: global.stickers.added.setIds,
|
addedSetIds: global.stickers.added.setIds,
|
||||||
addedCustomEmojiIds: global.customEmojis.added.setIds,
|
addedCustomEmojiIds: global.customEmojis.added.setIds,
|
||||||
newContactUserId: global.newContact?.userId,
|
newContactUserId: newContact?.userId,
|
||||||
newContactByPhoneNumber: global.newContact?.isByPhoneNumber,
|
newContactByPhoneNumber: newContact?.isByPhoneNumber,
|
||||||
openedGame,
|
openedGame,
|
||||||
gameTitle,
|
gameTitle,
|
||||||
isRatePhoneCallModalOpen: Boolean(global.ratingPhoneCall),
|
isRatePhoneCallModalOpen: Boolean(ratingPhoneCall),
|
||||||
botTrustRequest,
|
botTrustRequest,
|
||||||
botTrustRequestBot: botTrustRequest && selectUser(global, botTrustRequest.botId),
|
botTrustRequestBot: botTrustRequest && selectUser(global, botTrustRequest.botId),
|
||||||
attachBotToInstall: requestedAttachBotInstall?.bot,
|
attachBotToInstall: requestedAttachBotInstall?.bot,
|
||||||
@ -580,11 +553,12 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
webApp,
|
webApp,
|
||||||
currentUser,
|
currentUser,
|
||||||
urlAuth,
|
urlAuth,
|
||||||
isPremiumModalOpen: global.premiumModal?.isOpen,
|
isPremiumModalOpen: premiumModal?.isOpen,
|
||||||
limitReached: global.limitReachedModal?.limit,
|
limitReached: limitReachedModal?.limit,
|
||||||
isPaymentModalOpen: global.payment.isPaymentModalOpen,
|
isPaymentModalOpen: payment.isPaymentModalOpen,
|
||||||
isReceiptModalOpen: Boolean(global.payment.receipt),
|
isReceiptModalOpen: Boolean(payment.receipt),
|
||||||
deleteFolderDialogId: global.deleteFolderDialogModal,
|
deleteFolderDialogId: deleteFolderDialogModal,
|
||||||
|
isMasterTab,
|
||||||
requestedDraft,
|
requestedDraft,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { getActions, withGlobal } from '../../global';
|
|||||||
|
|
||||||
import type { ApiNotification } from '../../api/types';
|
import type { ApiNotification } from '../../api/types';
|
||||||
|
|
||||||
|
import { selectTabState } from '../../global/selectors';
|
||||||
import { pick } from '../../util/iteratees';
|
import { pick } from '../../util/iteratees';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
|
|
||||||
@ -40,5 +41,5 @@ const Notifications: FC<StateProps> = ({ notifications }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal(
|
export default memo(withGlobal(
|
||||||
(global): StateProps => pick(global, ['notifications']),
|
(global): StateProps => pick(selectTabState(global), ['notifications']),
|
||||||
)(Notifications));
|
)(Notifications));
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { getActions, getGlobal } from '../../global';
|
|||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import type { ApiUser } from '../../api/types';
|
import type { ApiUser } from '../../api/types';
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { TabState } from '../../global/types';
|
||||||
|
|
||||||
import { ensureProtocol } from '../../util/ensureProtocol';
|
import { ensureProtocol } from '../../util/ensureProtocol';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
@ -20,7 +20,7 @@ import Checkbox from '../ui/Checkbox';
|
|||||||
import styles from './UrlAuthModal.module.scss';
|
import styles from './UrlAuthModal.module.scss';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
urlAuth?: GlobalState['urlAuth'];
|
urlAuth?: TabState['urlAuth'];
|
||||||
currentUser?: ApiUser;
|
currentUser?: ApiUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -5,12 +5,14 @@ import { getActions, withGlobal } from '../../global';
|
|||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import type { ApiAttachBot, ApiChat, ApiUser } from '../../api/types';
|
import type { ApiAttachBot, ApiChat, ApiUser } from '../../api/types';
|
||||||
import type { GlobalState } from '../../global/types';
|
import type { TabState } from '../../global/types';
|
||||||
import type { ThemeKey } from '../../types';
|
import type { ThemeKey } from '../../types';
|
||||||
import type { PopupOptions, WebAppInboundEvent } from './hooks/useWebAppFrame';
|
import type { PopupOptions, WebAppInboundEvent } from './hooks/useWebAppFrame';
|
||||||
|
|
||||||
import { TME_LINK_PREFIX } from '../../config';
|
import { TME_LINK_PREFIX } from '../../config';
|
||||||
import { selectCurrentChat, selectTheme, selectUser } from '../../global/selectors';
|
import {
|
||||||
|
selectCurrentChat, selectTabState, selectTheme, selectUser,
|
||||||
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { extractCurrentThemeParams, validateHexColor } from '../../util/themeStyle';
|
import { extractCurrentThemeParams, validateHexColor } from '../../util/themeStyle';
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ type WebAppButton = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
webApp?: GlobalState['webApp'];
|
webApp?: TabState['webApp'];
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -50,7 +52,7 @@ type StateProps = {
|
|||||||
attachBot?: ApiAttachBot;
|
attachBot?: ApiAttachBot;
|
||||||
theme?: ThemeKey;
|
theme?: ThemeKey;
|
||||||
isPaymentModalOpen?: boolean;
|
isPaymentModalOpen?: boolean;
|
||||||
paymentStatus?: GlobalState['payment']['status'];
|
paymentStatus?: TabState['payment']['status'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const NBSP = '\u00A0';
|
const NBSP = '\u00A0';
|
||||||
@ -504,7 +506,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const bot = botId ? selectUser(global, botId) : undefined;
|
const bot = botId ? selectUser(global, botId) : undefined;
|
||||||
const chat = selectCurrentChat(global);
|
const chat = selectCurrentChat(global);
|
||||||
const theme = selectTheme(global);
|
const theme = selectTheme(global);
|
||||||
const { isPaymentModalOpen, status } = global.payment;
|
const { isPaymentModalOpen, status } = selectTabState(global).payment;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
attachBot,
|
attachBot,
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import type { AnimationLevel } from '../../../types';
|
|||||||
import { formatCurrency } from '../../../util/formatCurrency';
|
import { formatCurrency } from '../../../util/formatCurrency';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
import { getUserFirstOrLastName } from '../../../global/helpers';
|
import { getUserFirstOrLastName } from '../../../global/helpers';
|
||||||
import { selectUser } from '../../../global/selectors';
|
import { selectTabState, selectUser } from '../../../global/selectors';
|
||||||
|
|
||||||
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -160,7 +160,7 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||||
const { forUserId, monthlyCurrency, monthlyAmount } = global.giftPremiumModal || {};
|
const { forUserId, monthlyCurrency, monthlyAmount } = selectTabState(global).giftPremiumModal || {};
|
||||||
const user = forUserId ? selectUser(global, forUserId) : undefined;
|
const user = forUserId ? selectUser(global, forUserId) : undefined;
|
||||||
const gifts = user ? user.fullInfo?.premiumGifts : undefined;
|
const gifts = user ? user.fullInfo?.premiumGifts : undefined;
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import PremiumFeatureModal, {
|
|||||||
import { TME_LINK_PREFIX } from '../../../config';
|
import { TME_LINK_PREFIX } from '../../../config';
|
||||||
import { formatCurrency } from '../../../util/formatCurrency';
|
import { formatCurrency } from '../../../util/formatCurrency';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { selectIsCurrentUserPremium, selectUser } from '../../../global/selectors';
|
import { selectTabState, selectIsCurrentUserPremium, selectUser } from '../../../global/selectors';
|
||||||
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
||||||
import { selectPremiumLimit } from '../../../global/selectors/limits';
|
import { selectPremiumLimit } from '../../../global/selectors/limits';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
@ -309,16 +309,19 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||||
|
const {
|
||||||
|
premiumModal,
|
||||||
|
} = selectTabState(global);
|
||||||
return {
|
return {
|
||||||
currentUserId: global.currentUserId,
|
currentUserId: global.currentUserId,
|
||||||
promo: global.premiumModal?.promo,
|
promo: premiumModal?.promo,
|
||||||
isClosing: global.premiumModal?.isClosing,
|
isClosing: premiumModal?.isClosing,
|
||||||
isSuccess: global.premiumModal?.isSuccess,
|
isSuccess: premiumModal?.isSuccess,
|
||||||
isGift: global.premiumModal?.isGift,
|
isGift: premiumModal?.isGift,
|
||||||
monthsAmount: global.premiumModal?.monthsAmount,
|
monthsAmount: premiumModal?.monthsAmount,
|
||||||
fromUser: global.premiumModal?.fromUserId ? selectUser(global, global.premiumModal.fromUserId) : undefined,
|
fromUser: premiumModal?.fromUserId ? selectUser(global, premiumModal.fromUserId) : undefined,
|
||||||
toUser: global.premiumModal?.toUserId ? selectUser(global, global.premiumModal.toUserId) : undefined,
|
toUser: premiumModal?.toUserId ? selectUser(global, premiumModal.toUserId) : undefined,
|
||||||
initialSection: global.premiumModal?.initialSection,
|
initialSection: premiumModal?.initialSection,
|
||||||
isPremium: selectIsCurrentUserPremium(global),
|
isPremium: selectIsCurrentUserPremium(global),
|
||||||
limitChannels: selectPremiumLimit(global, 'channels'),
|
limitChannels: selectPremiumLimit(global, 'channels'),
|
||||||
limitFolders: selectPremiumLimit(global, 'dialogFilters'),
|
limitFolders: selectPremiumLimit(global, 'dialogFilters'),
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectChatMessages,
|
selectChatMessages,
|
||||||
selectChatScheduledMessages,
|
selectChatScheduledMessages,
|
||||||
selectCurrentMediaSearch,
|
selectCurrentMediaSearch, selectTabState,
|
||||||
selectIsChatWithSelf,
|
selectIsChatWithSelf,
|
||||||
selectListedIds,
|
selectListedIds,
|
||||||
selectOutlyingIds,
|
selectOutlyingIds,
|
||||||
@ -218,13 +218,15 @@ const MediaViewer: FC<StateProps> = ({
|
|||||||
const handleFooterClick = useCallback(() => {
|
const handleFooterClick = useCallback(() => {
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
||||||
|
if (!chatId || !mediaId) return;
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toggleChatInfo(false, { forceSyncOnIOs: true });
|
toggleChatInfo({ force: false }, { forceSyncOnIOs: true });
|
||||||
focusMessage({ chatId, threadId, mediaId });
|
focusMessage({ chatId, threadId, messageId: mediaId });
|
||||||
}, ANIMATION_DURATION);
|
}, ANIMATION_DURATION);
|
||||||
} else {
|
} else {
|
||||||
focusMessage({ chatId, threadId, mediaId });
|
focusMessage({ chatId, threadId, messageId: mediaId });
|
||||||
}
|
}
|
||||||
}, [handleClose, isMobile, chatId, threadId, focusMessage, toggleChatInfo, mediaId]);
|
}, [handleClose, isMobile, chatId, threadId, focusMessage, toggleChatInfo, mediaId]);
|
||||||
|
|
||||||
@ -370,6 +372,7 @@ const MediaViewer: FC<StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal(
|
export default memo(withGlobal(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
|
const { mediaViewer, shouldSkipHistoryAnimations } = selectTabState(global);
|
||||||
const {
|
const {
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
@ -377,12 +380,12 @@ export default memo(withGlobal(
|
|||||||
avatarOwnerId,
|
avatarOwnerId,
|
||||||
origin,
|
origin,
|
||||||
isHidden,
|
isHidden,
|
||||||
} = global.mediaViewer;
|
} = mediaViewer;
|
||||||
const {
|
const {
|
||||||
animationLevel,
|
animationLevel,
|
||||||
} = global.settings.byKey;
|
} = global.settings.byKey;
|
||||||
|
|
||||||
const { shouldSkipHistoryAnimations, currentUserId } = global;
|
const { currentUserId } = global;
|
||||||
let isChatWithSelf = !!chatId && selectIsChatWithSelf(global, chatId);
|
let isChatWithSelf = !!chatId && selectIsChatWithSelf(global, chatId);
|
||||||
|
|
||||||
if (origin === MediaViewerOrigin.SearchResult) {
|
if (origin === MediaViewerOrigin.SearchResult) {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { MediaViewerOrigin } from '../../types';
|
|||||||
|
|
||||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||||
import {
|
import {
|
||||||
selectChat, selectChatMessage, selectIsMessageProtected, selectScheduledMessage, selectUser,
|
selectChat, selectChatMessage, selectTabState, selectIsMessageProtected, selectScheduledMessage, selectUser,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { calculateMediaViewerDimensions } from '../common/helpers/mediaDimensions';
|
import { calculateMediaViewerDimensions } from '../common/helpers/mediaDimensions';
|
||||||
import { renderMessageText } from '../common/helpers/renderMessageText';
|
import { renderMessageText } from '../common/helpers/renderMessageText';
|
||||||
@ -216,7 +216,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isMuted,
|
isMuted,
|
||||||
playbackRate,
|
playbackRate,
|
||||||
isHidden,
|
isHidden,
|
||||||
} = global.mediaViewer;
|
} = selectTabState(global).mediaViewer;
|
||||||
|
|
||||||
if (origin === MediaViewerOrigin.SearchResult) {
|
if (origin === MediaViewerOrigin.SearchResult) {
|
||||||
if (!(chatId && mediaId)) {
|
if (!(chatId && mediaId)) {
|
||||||
|
|||||||
@ -56,9 +56,11 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
|
|||||||
const handleFocusMessage = useCallback(() => {
|
const handleFocusMessage = useCallback(() => {
|
||||||
closeMediaViewer();
|
closeMediaViewer();
|
||||||
|
|
||||||
|
if (!chatId || !messageId) return;
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toggleChatInfo(false, { forceSyncOnIOs: true });
|
toggleChatInfo({ force: false }, { forceSyncOnIOs: true });
|
||||||
focusMessage({ chatId, messageId });
|
focusMessage({ chatId, messageId });
|
||||||
}, ANIMATION_DURATION);
|
}, ANIMATION_DURATION);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -81,12 +81,12 @@ const VideoPlayer: FC<OwnProps> = ({
|
|||||||
const handleEnterFullscreen = useCallback(() => {
|
const handleEnterFullscreen = useCallback(() => {
|
||||||
// Yandex browser doesn't support PIP when video is hidden
|
// Yandex browser doesn't support PIP when video is hidden
|
||||||
if (IS_YA_BROWSER) return;
|
if (IS_YA_BROWSER) return;
|
||||||
setMediaViewerHidden(true);
|
setMediaViewerHidden({ isHidden: true });
|
||||||
}, [setMediaViewerHidden]);
|
}, [setMediaViewerHidden]);
|
||||||
|
|
||||||
const handleLeaveFullscreen = useCallback(() => {
|
const handleLeaveFullscreen = useCallback(() => {
|
||||||
if (IS_YA_BROWSER) return;
|
if (IS_YA_BROWSER) return;
|
||||||
setMediaViewerHidden(false);
|
setMediaViewerHidden({ isHidden: false });
|
||||||
}, [setMediaViewerHidden]);
|
}, [setMediaViewerHidden]);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
selectIsMessageFocused,
|
selectIsMessageFocused,
|
||||||
selectChat,
|
selectChat,
|
||||||
selectTopicFromMessage,
|
selectTopicFromMessage,
|
||||||
|
selectTabState,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { getMessageHtmlId, isChatChannel } from '../../global/helpers';
|
import { getMessageHtmlId, isChatChannel } from '../../global/helpers';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -242,7 +243,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const isFocused = selectIsMessageFocused(global, message);
|
const isFocused = selectIsMessageFocused(global, message);
|
||||||
const { direction: focusDirection, noHighlight: noFocusHighlight } = (isFocused && global.focusedMessage) || {};
|
const {
|
||||||
|
direction: focusDirection,
|
||||||
|
noHighlight: noFocusHighlight,
|
||||||
|
} = (isFocused && selectTabState(global).focusedMessage) || {};
|
||||||
|
|
||||||
const chat = selectChat(global, message.chatId);
|
const chat = selectChat(global, message.chatId);
|
||||||
const isChat = chat && (isChatChannel(chat) || userId === message.chatId);
|
const isChat = chat && (isChatChannel(chat) || userId === message.chatId);
|
||||||
|
|||||||
@ -27,7 +27,7 @@ const ActionMessageSuggestedAvatar: FC<OwnProps> = ({
|
|||||||
content,
|
content,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openMediaViewer, uploadProfilePhoto, showNotification, requestNextSettingsScreen,
|
openMediaViewer, uploadProfilePhoto, showNotification,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const { isOutgoing } = message;
|
const { isOutgoing } = message;
|
||||||
@ -42,10 +42,15 @@ const ActionMessageSuggestedAvatar: FC<OwnProps> = ({
|
|||||||
showNotification({
|
showNotification({
|
||||||
title: lang('ApplyAvatarHintTitle'),
|
title: lang('ApplyAvatarHintTitle'),
|
||||||
message: lang('ApplyAvatarHint'),
|
message: lang('ApplyAvatarHint'),
|
||||||
action: () => requestNextSettingsScreen(SettingsScreens.Main),
|
action: {
|
||||||
|
action: 'requestNextSettingsScreen',
|
||||||
|
payload: {
|
||||||
|
screen: SettingsScreens.Main,
|
||||||
|
},
|
||||||
|
},
|
||||||
actionText: lang('Open'),
|
actionText: lang('Open'),
|
||||||
});
|
});
|
||||||
}, [lang, requestNextSettingsScreen, showNotification]);
|
}, [lang, showNotification]);
|
||||||
|
|
||||||
const handleSetSuggestedAvatar = useCallback((file: File) => {
|
const handleSetSuggestedAvatar = useCallback((file: File) => {
|
||||||
setCropModalBlob(undefined);
|
setCropModalBlob(undefined);
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import * as mediaLoader from '../../util/mediaLoader';
|
|||||||
import {
|
import {
|
||||||
getMediaDuration, getMessageContent, getMessageMediaHash, getSenderTitle, isMessageLocal,
|
getMediaDuration, getMessageContent, getMessageMediaHash, getSenderTitle, isMessageLocal,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import { selectChat, selectSender } from '../../global/selectors';
|
import { selectChat, selectTabState, selectSender } from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { makeTrackId } from '../../util/audioPlayer';
|
import { makeTrackId } from '../../util/audioPlayer';
|
||||||
import { clearMediaSession } from '../../util/mediaSession';
|
import { clearMediaSession } from '../../util/mediaSession';
|
||||||
@ -320,7 +320,7 @@ export default withGlobal<OwnProps>(
|
|||||||
(global, { message }): StateProps => {
|
(global, { message }): StateProps => {
|
||||||
const sender = selectSender(global, message);
|
const sender = selectSender(global, message);
|
||||||
const chat = selectChat(global, message.chatId);
|
const chat = selectChat(global, message.chatId);
|
||||||
const { volume, playbackRate, isMuted } = global.audioPlayer;
|
const { volume, playbackRate, isMuted } = selectTabState(global).audioPlayer;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sender,
|
sender,
|
||||||
|
|||||||
@ -58,13 +58,13 @@ const ChatReportPanel: FC<OwnProps & StateProps> = ({
|
|||||||
const handleAddContact = useCallback(() => {
|
const handleAddContact = useCallback(() => {
|
||||||
openAddContactDialog({ userId: chatId });
|
openAddContactDialog({ userId: chatId });
|
||||||
if (isAutoArchived) {
|
if (isAutoArchived) {
|
||||||
toggleChatArchived({ chatId });
|
toggleChatArchived({ id: chatId });
|
||||||
}
|
}
|
||||||
}, [openAddContactDialog, isAutoArchived, toggleChatArchived, chatId]);
|
}, [openAddContactDialog, isAutoArchived, toggleChatArchived, chatId]);
|
||||||
|
|
||||||
const handleConfirmBlock = useCallback(() => {
|
const handleConfirmBlock = useCallback(() => {
|
||||||
closeBlockUserModal();
|
closeBlockUserModal();
|
||||||
blockContact({ contactId: chatId, accessHash });
|
blockContact({ contactId: chatId, accessHash: accessHash! });
|
||||||
if (canReportSpam && shouldReportSpam) {
|
if (canReportSpam && shouldReportSpam) {
|
||||||
reportSpam({ chatId });
|
reportSpam({ chatId });
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ const ChatReportPanel: FC<OwnProps & StateProps> = ({
|
|||||||
closeBlockUserModal();
|
closeBlockUserModal();
|
||||||
reportSpam({ chatId });
|
reportSpam({ chatId });
|
||||||
if (isBasicGroup) {
|
if (isBasicGroup) {
|
||||||
deleteChatUser({ chatId, userId: currentUserId });
|
deleteChatUser({ chatId, userId: currentUserId! });
|
||||||
deleteHistory({ chatId, shouldDeleteForAll: false });
|
deleteHistory({ chatId, shouldDeleteForAll: false });
|
||||||
} else {
|
} else {
|
||||||
leaveChannel({ chatId });
|
leaveChannel({ chatId });
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user