mirror of
https://github.com/element-hq/element-web.git
synced 2024-12-18 07:41:46 +03:00
Fix notification badge for All Rooms space (#7401)
This commit is contained in:
parent
bd226cd062
commit
fb494a5098
3 changed files with 98 additions and 64 deletions
|
@ -15,7 +15,8 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ComponentType, createRef } from 'react';
|
import React, { ComponentType, createRef } from 'react';
|
||||||
import { createClient } from 'matrix-js-sdk/src/matrix';
|
import { createClient, MatrixClient } from 'matrix-js-sdk/src/matrix';
|
||||||
|
import { ISyncStateData, SyncState } from 'matrix-js-sdk/src/sync';
|
||||||
import { MatrixError } from 'matrix-js-sdk/src/http-api';
|
import { MatrixError } from 'matrix-js-sdk/src/http-api';
|
||||||
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
@ -32,7 +33,7 @@ import 'what-input';
|
||||||
import Analytics from "../../Analytics";
|
import Analytics from "../../Analytics";
|
||||||
import CountlyAnalytics from "../../CountlyAnalytics";
|
import CountlyAnalytics from "../../CountlyAnalytics";
|
||||||
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
|
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
|
||||||
import { MatrixClientPeg, IMatrixClientCreds } from "../../MatrixClientPeg";
|
import { IMatrixClientCreds, MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
import PlatformPeg from "../../PlatformPeg";
|
import PlatformPeg from "../../PlatformPeg";
|
||||||
import SdkConfig from "../../SdkConfig";
|
import SdkConfig from "../../SdkConfig";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
|
@ -59,6 +60,7 @@ import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
||||||
import ToastStore from "../../stores/ToastStore";
|
import ToastStore from "../../stores/ToastStore";
|
||||||
import * as StorageManager from "../../utils/StorageManager";
|
import * as StorageManager from "../../utils/StorageManager";
|
||||||
import type LoggedInViewType from "./LoggedInView";
|
import type LoggedInViewType from "./LoggedInView";
|
||||||
|
import LoggedInView from './LoggedInView';
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
import {
|
import {
|
||||||
hideToast as hideAnalyticsToast,
|
hideToast as hideAnalyticsToast,
|
||||||
|
@ -68,7 +70,10 @@ import {
|
||||||
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
|
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
|
||||||
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||||
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
||||||
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
|
import {
|
||||||
|
RoomNotificationStateStore,
|
||||||
|
UPDATE_STATUS_INDICATOR,
|
||||||
|
} from "../../stores/notifications/RoomNotificationStateStore";
|
||||||
import { SettingLevel } from "../../settings/SettingLevel";
|
import { SettingLevel } from "../../settings/SettingLevel";
|
||||||
import { leaveRoomBehaviour } from "../../utils/membership";
|
import { leaveRoomBehaviour } from "../../utils/membership";
|
||||||
import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog";
|
import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog";
|
||||||
|
@ -92,7 +97,6 @@ import RoomDirectory from './RoomDirectory';
|
||||||
import KeySignatureUploadFailedDialog from "../views/dialogs/KeySignatureUploadFailedDialog";
|
import KeySignatureUploadFailedDialog from "../views/dialogs/KeySignatureUploadFailedDialog";
|
||||||
import IncomingSasDialog from "../views/dialogs/IncomingSasDialog";
|
import IncomingSasDialog from "../views/dialogs/IncomingSasDialog";
|
||||||
import CompleteSecurity from "./auth/CompleteSecurity";
|
import CompleteSecurity from "./auth/CompleteSecurity";
|
||||||
import LoggedInView from './LoggedInView';
|
|
||||||
import Welcome from "../views/auth/Welcome";
|
import Welcome from "../views/auth/Welcome";
|
||||||
import ForgotPassword from "./auth/ForgotPassword";
|
import ForgotPassword from "./auth/ForgotPassword";
|
||||||
import E2eSetup from "./auth/E2eSetup";
|
import E2eSetup from "./auth/E2eSetup";
|
||||||
|
@ -114,6 +118,7 @@ import InfoDialog from "../views/dialogs/InfoDialog";
|
||||||
import FeedbackDialog from "../views/dialogs/FeedbackDialog";
|
import FeedbackDialog from "../views/dialogs/FeedbackDialog";
|
||||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
import { ActionPayload } from "../../dispatcher/payloads";
|
import { ActionPayload } from "../../dispatcher/payloads";
|
||||||
|
import { SummarizedNotificationState } from "../../stores/notifications/SummarizedNotificationState";
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
|
@ -257,8 +262,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
onTokenLoginCompleted: () => {},
|
onTokenLoginCompleted: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
firstSyncComplete: boolean;
|
private firstSyncComplete = false;
|
||||||
firstSyncPromise: IDeferred<void>;
|
private firstSyncPromise: IDeferred<void>;
|
||||||
|
|
||||||
private screenAfterLogin?: IScreen;
|
private screenAfterLogin?: IScreen;
|
||||||
private pageChanging: boolean;
|
private pageChanging: boolean;
|
||||||
|
@ -270,12 +275,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
private prevWindowWidth: number;
|
private prevWindowWidth: number;
|
||||||
|
|
||||||
private readonly loggedInView: React.RefObject<LoggedInViewType>;
|
private readonly loggedInView: React.RefObject<LoggedInViewType>;
|
||||||
private readonly dispatcherRef: any;
|
private readonly dispatcherRef: string;
|
||||||
private readonly themeWatcher: ThemeWatcher;
|
private readonly themeWatcher: ThemeWatcher;
|
||||||
private readonly fontWatcher: FontWatcher;
|
private readonly fontWatcher: FontWatcher;
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props: IProps) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
view: Views.LOADING,
|
view: Views.LOADING,
|
||||||
|
@ -321,6 +326,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// For PersistentElement
|
// For PersistentElement
|
||||||
this.state.resizeNotifier.on("middlePanelResized", this.dispatchTimelineResize);
|
this.state.resizeNotifier.on("middlePanelResized", this.dispatchTimelineResize);
|
||||||
|
|
||||||
|
RoomNotificationStateStore.instance.on(UPDATE_STATUS_INDICATOR, this.onUpdateStatusIndicator);
|
||||||
|
|
||||||
// Force users to go through the soft logout page if they're soft logged out
|
// Force users to go through the soft logout page if they're soft logged out
|
||||||
if (Lifecycle.isSoftLogout()) {
|
if (Lifecycle.isSoftLogout()) {
|
||||||
// When the session loads it'll be detected as soft logged out and a dispatch
|
// When the session loads it'll be detected as soft logged out and a dispatch
|
||||||
|
@ -494,15 +501,15 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getFallbackHsUrl() {
|
private getFallbackHsUrl(): string {
|
||||||
if (this.props.serverConfig && this.props.serverConfig.isDefault) {
|
if (this.props.serverConfig?.isDefault) {
|
||||||
return this.props.config.fallback_hs_url;
|
return this.props.config.fallback_hs_url;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getServerProperties() {
|
private getServerProperties() {
|
||||||
let props = this.state.serverConfig;
|
let props = this.state.serverConfig;
|
||||||
if (!props) props = this.props.serverConfig; // for unit tests
|
if (!props) props = this.props.serverConfig; // for unit tests
|
||||||
if (!props) props = SdkConfig.get()["validated_server_config"];
|
if (!props) props = SdkConfig.get()["validated_server_config"];
|
||||||
|
@ -535,11 +542,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// to try logging out.
|
// to try logging out.
|
||||||
}
|
}
|
||||||
|
|
||||||
startPageChangeTimer() {
|
private startPageChangeTimer() {
|
||||||
PerformanceMonitor.instance.start(PerformanceEntryNames.PAGE_CHANGE);
|
PerformanceMonitor.instance.start(PerformanceEntryNames.PAGE_CHANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
stopPageChangeTimer() {
|
private stopPageChangeTimer() {
|
||||||
const perfMonitor = PerformanceMonitor.instance;
|
const perfMonitor = PerformanceMonitor.instance;
|
||||||
|
|
||||||
perfMonitor.stop(PerformanceEntryNames.PAGE_CHANGE);
|
perfMonitor.stop(PerformanceEntryNames.PAGE_CHANGE);
|
||||||
|
@ -554,13 +561,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldTrackPageChange(prevState: IState, state: IState) {
|
private shouldTrackPageChange(prevState: IState, state: IState): boolean {
|
||||||
return prevState.currentRoomId !== state.currentRoomId ||
|
return prevState.currentRoomId !== state.currentRoomId ||
|
||||||
prevState.view !== state.view ||
|
prevState.view !== state.view ||
|
||||||
prevState.page_type !== state.page_type;
|
prevState.page_type !== state.page_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateForNewView(state: Partial<IState>) {
|
private setStateForNewView(state: Partial<IState>): void {
|
||||||
if (state.view === undefined) {
|
if (state.view === undefined) {
|
||||||
throw new Error("setStateForNewView with no view!");
|
throw new Error("setStateForNewView with no view!");
|
||||||
}
|
}
|
||||||
|
@ -572,7 +579,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload) => {
|
private onAction = (payload: ActionPayload): void => {
|
||||||
// console.log(`MatrixClientPeg.onAction: ${payload.action}`);
|
// console.log(`MatrixClientPeg.onAction: ${payload.action}`);
|
||||||
|
|
||||||
// Start the onboarding process for certain actions
|
// Start the onboarding process for certain actions
|
||||||
|
@ -1486,7 +1493,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
return this.loggedInView.current.canResetTimelineInRoom(roomId);
|
return this.loggedInView.current.canResetTimelineInRoom(roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
cli.on('sync', (state, prevState, data) => {
|
cli.on('sync', (state: SyncState, prevState?: SyncState, data?: ISyncStateData) => {
|
||||||
// LifecycleStore and others cannot directly subscribe to matrix client for
|
// LifecycleStore and others cannot directly subscribe to matrix client for
|
||||||
// events because flux only allows store state changes during flux dispatches.
|
// events because flux only allows store state changes during flux dispatches.
|
||||||
// So dispatch directly from here. Ideally we'd use a SyncStateStore that
|
// So dispatch directly from here. Ideally we'd use a SyncStateStore that
|
||||||
|
@ -1494,21 +1501,20 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// its own dispatch).
|
// its own dispatch).
|
||||||
dis.dispatch({ action: 'sync_state', prevState, state });
|
dis.dispatch({ action: 'sync_state', prevState, state });
|
||||||
|
|
||||||
if (state === "ERROR" || state === "RECONNECTING") {
|
if (state === SyncState.Error || state === SyncState.Reconnecting) {
|
||||||
if (data.error instanceof InvalidStoreError) {
|
if (data.error instanceof InvalidStoreError) {
|
||||||
Lifecycle.handleInvalidStoreError(data.error);
|
Lifecycle.handleInvalidStoreError(data.error);
|
||||||
}
|
}
|
||||||
this.setState({ syncError: data.error || true });
|
this.setState({ syncError: data.error || {} as MatrixError });
|
||||||
} else if (this.state.syncError) {
|
} else if (this.state.syncError) {
|
||||||
this.setState({ syncError: null });
|
this.setState({ syncError: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateStatusIndicator(state, prevState);
|
if (state === SyncState.Syncing && prevState === SyncState.Syncing) {
|
||||||
if (state === "SYNCING" && prevState === "SYNCING") {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.info("MatrixClient sync state => %s", state);
|
logger.info("MatrixClient sync state => %s", state);
|
||||||
if (state !== "PREPARED") { return; }
|
if (state !== SyncState.Prepared) { return; }
|
||||||
|
|
||||||
this.firstSyncComplete = true;
|
this.firstSyncComplete = true;
|
||||||
this.firstSyncPromise.resolve();
|
this.firstSyncPromise.resolve();
|
||||||
|
@ -1766,7 +1772,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showScreen(screen: string, params?: {[key: string]: any}) {
|
public showScreen(screen: string, params?: {[key: string]: any}) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const isLoggedOutOrGuest = !cli || cli.isGuest();
|
const isLoggedOutOrGuest = !cli || cli.isGuest();
|
||||||
if (!isLoggedOutOrGuest && AUTH_SCREENS.includes(screen)) {
|
if (!isLoggedOutOrGuest && AUTH_SCREENS.includes(screen)) {
|
||||||
|
@ -1941,13 +1947,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyNewScreen(screen: string, replaceLast = false) {
|
private notifyNewScreen(screen: string, replaceLast = false) {
|
||||||
if (this.props.onNewScreen) {
|
if (this.props.onNewScreen) {
|
||||||
this.props.onNewScreen(screen, replaceLast);
|
this.props.onNewScreen(screen, replaceLast);
|
||||||
}
|
}
|
||||||
this.setPageSubtitle();
|
this.setPageSubtitle();
|
||||||
}
|
}
|
||||||
onLogoutClick(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
|
|
||||||
|
private onLogoutClick(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'logout',
|
action: 'logout',
|
||||||
});
|
});
|
||||||
|
@ -1955,7 +1962,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResize = () => {
|
private handleResize = () => {
|
||||||
const LHS_THRESHOLD = 1000;
|
const LHS_THRESHOLD = 1000;
|
||||||
const width = UIStore.instance.windowWidth;
|
const width = UIStore.instance.windowWidth;
|
||||||
|
|
||||||
|
@ -1975,28 +1982,28 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
dis.dispatch({ action: 'timeline_resize' });
|
dis.dispatch({ action: 'timeline_resize' });
|
||||||
}
|
}
|
||||||
|
|
||||||
onRegisterClick = () => {
|
private onRegisterClick = () => {
|
||||||
this.showScreen("register");
|
this.showScreen("register");
|
||||||
};
|
};
|
||||||
|
|
||||||
onLoginClick = () => {
|
private onLoginClick = () => {
|
||||||
this.showScreen("login");
|
this.showScreen("login");
|
||||||
};
|
};
|
||||||
|
|
||||||
onForgotPasswordClick = () => {
|
private onForgotPasswordClick = () => {
|
||||||
this.showScreen("forgot_password");
|
this.showScreen("forgot_password");
|
||||||
};
|
};
|
||||||
|
|
||||||
onRegisterFlowComplete = (credentials: IMatrixClientCreds, password: string) => {
|
private onRegisterFlowComplete = (credentials: IMatrixClientCreds, password: string): Promise<void> => {
|
||||||
return this.onUserCompletedLoginFlow(credentials, password);
|
return this.onUserCompletedLoginFlow(credentials, password);
|
||||||
};
|
};
|
||||||
|
|
||||||
// returns a promise which resolves to the new MatrixClient
|
// returns a promise which resolves to the new MatrixClient
|
||||||
onRegistered(credentials: IMatrixClientCreds) {
|
private onRegistered(credentials: IMatrixClientCreds): Promise<MatrixClient> {
|
||||||
return Lifecycle.setLoggedIn(credentials);
|
return Lifecycle.setLoggedIn(credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSendEvent(roomId: string, event: MatrixEvent) {
|
private onSendEvent(roomId: string, event: MatrixEvent): void {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (!cli) return;
|
if (!cli) return;
|
||||||
|
|
||||||
|
@ -2023,17 +2030,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatusIndicator(state: string, prevState: string) {
|
private onUpdateStatusIndicator = (notificationState: SummarizedNotificationState, state: SyncState): void => {
|
||||||
const notificationState = RoomNotificationStateStore.instance.globalState;
|
|
||||||
const numUnreadRooms = notificationState.numUnreadStates; // we know that states === rooms here
|
const numUnreadRooms = notificationState.numUnreadStates; // we know that states === rooms here
|
||||||
|
|
||||||
if (PlatformPeg.get()) {
|
if (PlatformPeg.get()) {
|
||||||
PlatformPeg.get().setErrorStatus(state === 'ERROR');
|
PlatformPeg.get().setErrorStatus(state === SyncState.Error);
|
||||||
PlatformPeg.get().setNotificationCount(numUnreadRooms);
|
PlatformPeg.get().setNotificationCount(numUnreadRooms);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.subTitleStatus = '';
|
this.subTitleStatus = '';
|
||||||
if (state === "ERROR") {
|
if (state === SyncState.Error) {
|
||||||
this.subTitleStatus += `[${_t("Offline")}] `;
|
this.subTitleStatus += `[${_t("Offline")}] `;
|
||||||
}
|
}
|
||||||
if (numUnreadRooms > 0) {
|
if (numUnreadRooms > 0) {
|
||||||
|
@ -2041,13 +2047,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setPageSubtitle();
|
this.setPageSubtitle();
|
||||||
}
|
};
|
||||||
|
|
||||||
onCloseAllSettings() {
|
private onServerConfigChange = (serverConfig: ValidatedServerConfig) => {
|
||||||
dis.dispatch({ action: 'close_settings' });
|
|
||||||
}
|
|
||||||
|
|
||||||
onServerConfigChange = (serverConfig: ValidatedServerConfig) => {
|
|
||||||
this.setState({ serverConfig });
|
this.setState({ serverConfig });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2065,7 +2067,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
* Note: SSO users (and any others using token login) currently do not pass through
|
* Note: SSO users (and any others using token login) currently do not pass through
|
||||||
* this, as they instead jump straight into the app after `attemptTokenLogin`.
|
* this, as they instead jump straight into the app after `attemptTokenLogin`.
|
||||||
*/
|
*/
|
||||||
onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds, password: string) => {
|
private onUserCompletedLoginFlow = async (credentials: IMatrixClientCreds, password: string): Promise<void> => {
|
||||||
this.accountPassword = password;
|
this.accountPassword = password;
|
||||||
// self-destruct the password after 5mins
|
// self-destruct the password after 5mins
|
||||||
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
|
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
|
||||||
|
@ -2083,11 +2085,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// complete security / e2e setup has finished
|
// complete security / e2e setup has finished
|
||||||
onCompleteSecurityE2eSetupFinished = () => {
|
private onCompleteSecurityE2eSetupFinished = (): void => {
|
||||||
this.onLoggedIn();
|
this.onLoggedIn();
|
||||||
};
|
};
|
||||||
|
|
||||||
getFragmentAfterLogin() {
|
private getFragmentAfterLogin(): string {
|
||||||
let fragmentAfterLogin = "";
|
let fragmentAfterLogin = "";
|
||||||
const initialScreenAfterLogin = this.props.initialScreenAfterLogin;
|
const initialScreenAfterLogin = this.props.initialScreenAfterLogin;
|
||||||
if (initialScreenAfterLogin &&
|
if (initialScreenAfterLogin &&
|
||||||
|
|
|
@ -19,6 +19,7 @@ import React, {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
SetStateAction,
|
SetStateAction,
|
||||||
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
useRef,
|
useRef,
|
||||||
|
@ -33,7 +34,7 @@ import { useContextMenu } from "../../structures/ContextMenu";
|
||||||
import SpaceCreateMenu from "./SpaceCreateMenu";
|
import SpaceCreateMenu from "./SpaceCreateMenu";
|
||||||
import { SpaceButton, SpaceItem } from "./SpaceTreeLevel";
|
import { SpaceButton, SpaceItem } from "./SpaceTreeLevel";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
import { useEventEmitter, useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||||
import {
|
import {
|
||||||
getMetaSpaceName,
|
getMetaSpaceName,
|
||||||
|
@ -45,7 +46,10 @@ import {
|
||||||
UPDATE_TOP_LEVEL_SPACES,
|
UPDATE_TOP_LEVEL_SPACES,
|
||||||
} from "../../../stores/spaces";
|
} from "../../../stores/spaces";
|
||||||
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
|
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
|
||||||
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
import {
|
||||||
|
RoomNotificationStateStore,
|
||||||
|
UPDATE_STATUS_INDICATOR,
|
||||||
|
} from "../../../stores/notifications/RoomNotificationStateStore";
|
||||||
import SpaceContextMenu from "../context_menus/SpaceContextMenu";
|
import SpaceContextMenu from "../context_menus/SpaceContextMenu";
|
||||||
import IconizedContextMenu, {
|
import IconizedContextMenu, {
|
||||||
IconizedContextMenuCheckbox,
|
IconizedContextMenuCheckbox,
|
||||||
|
@ -63,6 +67,7 @@ import { useDispatcher } from "../../../hooks/useDispatcher";
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
import { ActionPayload } from "../../../dispatcher/payloads";
|
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
import { NotificationState } from "../../../stores/notifications/NotificationState";
|
||||||
|
|
||||||
const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => {
|
const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => {
|
||||||
const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
|
const invites = useEventEmitterState<Room[]>(SpaceStore.instance, UPDATE_INVITED_SPACES, () => {
|
||||||
|
@ -136,10 +141,22 @@ const MetaSpaceButton = ({ selected, isPanelCollapsed, ...props }: IMetaSpaceBut
|
||||||
</li>;
|
</li>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getHomeNotificationState = (): NotificationState => {
|
||||||
|
return SpaceStore.instance.allRoomsInHome
|
||||||
|
? RoomNotificationStateStore.instance.globalState
|
||||||
|
: SpaceStore.instance.getNotificationState(MetaSpace.Home);
|
||||||
|
};
|
||||||
|
|
||||||
const HomeButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
|
const HomeButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
|
||||||
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
|
const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => {
|
||||||
return SpaceStore.instance.allRoomsInHome;
|
return SpaceStore.instance.allRoomsInHome;
|
||||||
});
|
});
|
||||||
|
const [notificationState, setNotificationState] = useState(getHomeNotificationState());
|
||||||
|
const updateNotificationState = useCallback(() => {
|
||||||
|
setNotificationState(getHomeNotificationState());
|
||||||
|
}, []);
|
||||||
|
useEffect(updateNotificationState, [updateNotificationState, allRoomsInHome]);
|
||||||
|
useEventEmitter(RoomNotificationStateStore.instance, UPDATE_STATUS_INDICATOR, updateNotificationState);
|
||||||
|
|
||||||
return <MetaSpaceButton
|
return <MetaSpaceButton
|
||||||
spaceKey={MetaSpace.Home}
|
spaceKey={MetaSpace.Home}
|
||||||
|
@ -147,9 +164,7 @@ const HomeButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => {
|
||||||
selected={selected}
|
selected={selected}
|
||||||
isPanelCollapsed={isPanelCollapsed}
|
isPanelCollapsed={isPanelCollapsed}
|
||||||
label={getMetaSpaceName(MetaSpace.Home, allRoomsInHome)}
|
label={getMetaSpaceName(MetaSpace.Home, allRoomsInHome)}
|
||||||
notificationState={allRoomsInHome
|
notificationState={notificationState}
|
||||||
? RoomNotificationStateStore.instance.globalState
|
|
||||||
: SpaceStore.instance.getNotificationState(MetaSpace.Home)}
|
|
||||||
ContextMenuComponent={HomeButtonContextMenu}
|
ContextMenuComponent={HomeButtonContextMenu}
|
||||||
contextMenuTooltip={_t("Options")}
|
contextMenuTooltip={_t("Options")}
|
||||||
/>;
|
/>;
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
|
||||||
|
|
||||||
import { ActionPayload } from "../../dispatcher/payloads";
|
import { ActionPayload } from "../../dispatcher/payloads";
|
||||||
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
|
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
|
||||||
|
@ -23,17 +24,20 @@ import { DefaultTagID, TagID } from "../room-list/models";
|
||||||
import { FetchRoomFn, ListNotificationState } from "./ListNotificationState";
|
import { FetchRoomFn, ListNotificationState } from "./ListNotificationState";
|
||||||
import { RoomNotificationState } from "./RoomNotificationState";
|
import { RoomNotificationState } from "./RoomNotificationState";
|
||||||
import { SummarizedNotificationState } from "./SummarizedNotificationState";
|
import { SummarizedNotificationState } from "./SummarizedNotificationState";
|
||||||
import { VisibilityProvider } from "../room-list/filters/VisibilityProvider";
|
|
||||||
import { ThreadsRoomNotificationState } from "./ThreadsRoomNotificationState";
|
import { ThreadsRoomNotificationState } from "./ThreadsRoomNotificationState";
|
||||||
|
import { VisibilityProvider } from "../room-list/filters/VisibilityProvider";
|
||||||
|
|
||||||
interface IState {}
|
interface IState {}
|
||||||
|
|
||||||
|
export const UPDATE_STATUS_INDICATOR = Symbol("update-status-indicator");
|
||||||
|
|
||||||
export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
|
export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
|
||||||
private static internalInstance = new RoomNotificationStateStore();
|
private static internalInstance = new RoomNotificationStateStore();
|
||||||
|
|
||||||
private roomMap = new Map<Room, RoomNotificationState>();
|
private roomMap = new Map<Room, RoomNotificationState>();
|
||||||
private roomThreadsMap = new Map<Room, ThreadsRoomNotificationState>();
|
private roomThreadsMap = new Map<Room, ThreadsRoomNotificationState>();
|
||||||
private listMap = new Map<TagID, ListNotificationState>();
|
private listMap = new Map<TagID, ListNotificationState>();
|
||||||
|
private _globalState = new SummarizedNotificationState();
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
super(defaultDispatcher, {});
|
super(defaultDispatcher, {});
|
||||||
|
@ -44,18 +48,7 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
|
||||||
* on the SummarizedNotificationState is equivalent to rooms.
|
* on the SummarizedNotificationState is equivalent to rooms.
|
||||||
*/
|
*/
|
||||||
public get globalState(): SummarizedNotificationState {
|
public get globalState(): SummarizedNotificationState {
|
||||||
// If we're not ready yet, just return an empty state
|
return this._globalState;
|
||||||
if (!this.matrixClient) return new SummarizedNotificationState();
|
|
||||||
|
|
||||||
// Only count visible rooms to not torment the user with notification counts in rooms they can't see.
|
|
||||||
// This will include highlights from the previous version of the room internally
|
|
||||||
const globalState = new SummarizedNotificationState();
|
|
||||||
for (const room of this.matrixClient.getVisibleRooms()) {
|
|
||||||
if (VisibilityProvider.instance.isRoomVisible(room)) {
|
|
||||||
globalState.add(this.getRoomState(room));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return globalState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -108,6 +101,30 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
|
||||||
return RoomNotificationStateStore.internalInstance;
|
return RoomNotificationStateStore.internalInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onSync = (state: SyncState, prevState?: SyncState, data?: ISyncStateData) => {
|
||||||
|
// Only count visible rooms to not torment the user with notification counts in rooms they can't see.
|
||||||
|
// This will include highlights from the previous version of the room internally
|
||||||
|
const globalState = new SummarizedNotificationState();
|
||||||
|
for (const room of this.matrixClient.getVisibleRooms()) {
|
||||||
|
if (VisibilityProvider.instance.isRoomVisible(room)) {
|
||||||
|
globalState.add(this.getRoomState(room));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.globalState.symbol !== globalState.symbol ||
|
||||||
|
this.globalState.count !== globalState.count ||
|
||||||
|
this.globalState.color !== globalState.color ||
|
||||||
|
this.globalState.numUnreadStates !== globalState.numUnreadStates
|
||||||
|
) {
|
||||||
|
this._globalState = globalState;
|
||||||
|
this.emit(UPDATE_STATUS_INDICATOR, globalState, state, prevState, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected async onReady() {
|
||||||
|
this.matrixClient.on("sync", this.onSync);
|
||||||
|
}
|
||||||
|
|
||||||
protected async onNotReady(): Promise<any> {
|
protected async onNotReady(): Promise<any> {
|
||||||
for (const roomState of this.roomMap.values()) {
|
for (const roomState of this.roomMap.values()) {
|
||||||
roomState.destroy();
|
roomState.destroy();
|
||||||
|
|
Loading…
Reference in a new issue