Merge branch 'develop' into sort-imports
Signed-off-by: Aaron Raimist <aaron@raim.ist>
This commit is contained in:
commit
7b94e13a84
642 changed files with 30052 additions and 8035 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -23,7 +23,6 @@ import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
|
|||
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||
import { arrayHasDiff } from "../utils/arrays";
|
||||
import { SettingLevel } from "../settings/SettingLevel";
|
||||
import SpaceStore from "./SpaceStore";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";
|
||||
|
||||
|
@ -45,6 +44,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
|
|||
|
||||
SettingsStore.monitorSetting("breadcrumb_rooms", null);
|
||||
SettingsStore.monitorSetting("breadcrumbs", null);
|
||||
SettingsStore.monitorSetting("feature_breadcrumbs_v2", null);
|
||||
}
|
||||
|
||||
public static get instance(): BreadcrumbsStore {
|
||||
|
@ -59,8 +59,9 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
|
|||
return this.state.enabled && this.meetsRoomRequirement;
|
||||
}
|
||||
|
||||
private get meetsRoomRequirement(): boolean {
|
||||
return this.matrixClient && this.matrixClient.getVisibleRooms().length >= 20;
|
||||
public get meetsRoomRequirement(): boolean {
|
||||
if (SettingsStore.getValue("feature_breadcrumbs_v2")) return true;
|
||||
return this.matrixClient?.getVisibleRooms().length >= 20;
|
||||
}
|
||||
|
||||
protected async onAction(payload: ActionPayload) {
|
||||
|
@ -70,10 +71,12 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
|
|||
const settingUpdatedPayload = payload as SettingUpdatedPayload;
|
||||
if (settingUpdatedPayload.settingName === 'breadcrumb_rooms') {
|
||||
await this.updateRooms();
|
||||
} else if (settingUpdatedPayload.settingName === 'breadcrumbs') {
|
||||
} else if (settingUpdatedPayload.settingName === 'breadcrumbs' ||
|
||||
settingUpdatedPayload.settingName === 'feature_breadcrumbs_v2'
|
||||
) {
|
||||
await this.updateState({ enabled: SettingsStore.getValue("breadcrumbs", null) });
|
||||
}
|
||||
} else if (payload.action === 'view_room') {
|
||||
} else if (payload.action === Action.ViewRoom) {
|
||||
if (payload.auto_join && !this.matrixClient.getRoom(payload.room_id)) {
|
||||
// Queue the room instead of pushing it immediately. We're probably just
|
||||
// waiting for a room join to complete.
|
||||
|
@ -127,7 +130,6 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
|
|||
}
|
||||
|
||||
private async appendRoom(room: Room) {
|
||||
if (SpaceStore.spacesEnabled && room.isSpaceRoom()) return; // hide space rooms
|
||||
let updated = false;
|
||||
const rooms = (this.state.rooms || []).slice(); // cheap clone
|
||||
|
||||
|
|
|
@ -14,21 +14,22 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import * as utils from "matrix-js-sdk/src/utils";
|
||||
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
|
||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||
import { ActionPayload } from "../dispatcher/payloads";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { EffectiveMembership, getEffectiveMembership } from "../utils/membership";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import * as utils from "matrix-js-sdk/src/utils";
|
||||
import { UPDATE_EVENT } from "./AsyncStore";
|
||||
import FlairStore from "./FlairStore";
|
||||
import GroupFilterOrderStore from "./GroupFilterOrderStore";
|
||||
import GroupStore from "./GroupStore";
|
||||
import dis from "../dispatcher/dispatcher";
|
||||
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
interface IState {
|
||||
// nothing of value - we use account data
|
||||
|
@ -149,7 +150,7 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient<IState> {
|
|||
const chat = this.getGeneralChat(payload.tag);
|
||||
if (chat) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
action: Action.ViewRoom,
|
||||
room_id: chat.roomId,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import { Store } from 'flux/utils';
|
|||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
import dis from '../dispatcher/dispatcher';
|
||||
import { Action } from '../dispatcher/actions';
|
||||
import GroupStore from './GroupStore';
|
||||
import Analytics from '../Analytics';
|
||||
import * as RoomNotifs from "../RoomNotifs";
|
||||
|
@ -55,7 +56,7 @@ class GroupFilterOrderStore extends Store {
|
|||
__onDispatch(payload) { // eslint-disable-line @typescript-eslint/naming-convention
|
||||
switch (payload.action) {
|
||||
// Initialise state after initial sync
|
||||
case 'view_room': {
|
||||
case Action.ViewRoom: {
|
||||
const relatedGroupIds = GroupStore.getGroupIdsForRoomId(payload.room_id);
|
||||
this._updateBadges(relatedGroupIds);
|
||||
break;
|
||||
|
|
|
@ -148,7 +148,7 @@ export default class RightPanelStore extends Store<ActionPayload> {
|
|||
|
||||
__onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention
|
||||
switch (payload.action) {
|
||||
case 'view_room':
|
||||
case Action.ViewRoom:
|
||||
if (payload.room_id === this.lastRoomId) break; // skip this transition, probably a permalink
|
||||
// fallthrough
|
||||
case 'view_group':
|
||||
|
|
|
@ -25,6 +25,7 @@ export enum RightPanelPhases {
|
|||
RoomSummary = 'RoomSummary',
|
||||
Widget = 'Widget',
|
||||
PinnedMessages = "PinnedMessages",
|
||||
Timeline = "Timeline",
|
||||
|
||||
Room3pidMemberInfo = 'Room3pidMemberInfo',
|
||||
// Group stuff
|
||||
|
@ -53,6 +54,7 @@ export const RIGHT_PANEL_PHASES_NO_ARGS = [
|
|||
RightPanelPhases.RoomMemberList,
|
||||
RightPanelPhases.GroupMemberList,
|
||||
RightPanelPhases.GroupRoomList,
|
||||
RightPanelPhases.Timeline,
|
||||
];
|
||||
|
||||
// Subset of phases visible in the Space View
|
||||
|
|
|
@ -106,7 +106,7 @@ class RoomViewStore extends Store<ActionPayload> {
|
|||
// - event_id: '$213456782:matrix.org'
|
||||
// - event_offset: 100
|
||||
// - highlighted: true
|
||||
case 'view_room':
|
||||
case Action.ViewRoom:
|
||||
this.viewRoom(payload);
|
||||
break;
|
||||
// for these events blank out the roomId as we are no longer in the RoomView
|
||||
|
@ -157,7 +157,7 @@ class RoomViewStore extends Store<ActionPayload> {
|
|||
if (payload.event
|
||||
&& payload.event.getRoomId() !== this.state.roomId) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
action: Action.ViewRoom,
|
||||
room_id: payload.event.getRoomId(),
|
||||
replyingToEvent: payload.event,
|
||||
});
|
||||
|
@ -250,7 +250,7 @@ class RoomViewStore extends Store<ActionPayload> {
|
|||
}
|
||||
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
action: Action.ViewRoom,
|
||||
room_id: roomId,
|
||||
event_id: payload.event_id,
|
||||
highlighted: payload.highlighted,
|
||||
|
|
|
@ -37,7 +37,7 @@ export default class TypingStore {
|
|||
this.reset();
|
||||
}
|
||||
|
||||
static sharedInstance(): TypingStore {
|
||||
public static sharedInstance(): TypingStore {
|
||||
if (window.mxTypingStore === undefined) {
|
||||
window.mxTypingStore = new TypingStore();
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ export default class TypingStore {
|
|||
* Clears all cached typing states. Intended to be called when the
|
||||
* MatrixClientPeg client changes.
|
||||
*/
|
||||
reset() {
|
||||
public reset() {
|
||||
this.typingStates = {
|
||||
// "roomId": {
|
||||
// isTyping: bool, // Whether the user is typing or not
|
||||
|
@ -63,9 +63,12 @@ export default class TypingStore {
|
|||
* @param {string} roomId The room ID to set the typing state in.
|
||||
* @param {boolean} isTyping Whether the user is typing or not.
|
||||
*/
|
||||
setSelfTyping(roomId: string, isTyping: boolean): void {
|
||||
public setSelfTyping(roomId: string, threadId: string | null, isTyping: boolean): void {
|
||||
if (!SettingsStore.getValue('sendTypingNotifications')) return;
|
||||
if (SettingsStore.getValue('lowBandwidth')) return;
|
||||
// Disable typing notification for threads for the initial launch
|
||||
// before we figure out a better user experience for them
|
||||
if (SettingsStore.getValue("feature_thread") && threadId) return;
|
||||
|
||||
let currentTyping = this.typingStates[roomId];
|
||||
if ((!isTyping && !currentTyping) || (currentTyping && currentTyping.isTyping === isTyping)) {
|
||||
|
@ -96,7 +99,7 @@ export default class TypingStore {
|
|||
|
||||
if (!currentTyping.userTimer.isRunning()) {
|
||||
currentTyping.userTimer.restart().finished().then(() => {
|
||||
this.setSelfTyping(roomId, false);
|
||||
this.setSelfTyping(roomId, threadId, false);
|
||||
});
|
||||
} else currentTyping.userTimer.restart();
|
||||
}
|
||||
|
|
|
@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import { GenericEchoChamber, implicitlyReverted, PROPERTY_UPDATED } from "./GenericEchoChamber";
|
||||
import { getRoomNotifsState, RoomNotifState, setRoomNotifsState } from "../../RoomNotifs";
|
||||
import { RoomEchoContext } from "./RoomEchoContext";
|
||||
import { _t } from "../../languageHandler";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
export enum CachedRoomKey {
|
||||
NotificationVolume,
|
||||
|
@ -47,7 +47,7 @@ export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedR
|
|||
}
|
||||
|
||||
private onAccountData = (event: MatrixEvent) => {
|
||||
if (event.getType() === "m.push_rules") {
|
||||
if (event.getType() === EventType.PushRules) {
|
||||
const currentVolume = this.properties.get(CachedRoomKey.NotificationVolume) as RoomNotifState;
|
||||
const newVolume = getRoomNotifsState(this.context.room.roomId) as RoomNotifState;
|
||||
if (currentVolume !== newVolume) {
|
||||
|
|
|
@ -20,7 +20,7 @@ import { NotificationColor } from "./NotificationColor";
|
|||
import { TagID } from "../room-list/models";
|
||||
import { arrayDiff } from "../../utils/arrays";
|
||||
import { RoomNotificationState } from "./RoomNotificationState";
|
||||
import { NOTIFICATION_STATE_UPDATE, NotificationState } from "./NotificationState";
|
||||
import { NotificationState, NotificationStateEvents } from "./NotificationState";
|
||||
|
||||
export type FetchRoomFn = (room: Room) => RoomNotificationState;
|
||||
|
||||
|
@ -51,11 +51,11 @@ export class ListNotificationState extends NotificationState {
|
|||
const state = this.states[oldRoom.roomId];
|
||||
if (!state) continue; // We likely just didn't have a badge (race condition)
|
||||
delete this.states[oldRoom.roomId];
|
||||
state.off(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate);
|
||||
state.off(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||
}
|
||||
for (const newRoom of diff.added) {
|
||||
const state = this.getRoomFn(newRoom);
|
||||
state.on(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate);
|
||||
state.on(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||
this.states[newRoom.roomId] = state;
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ export class ListNotificationState extends NotificationState {
|
|||
public destroy() {
|
||||
super.destroy();
|
||||
for (const state of Object.values(this.states)) {
|
||||
state.off(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate);
|
||||
state.off(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||
}
|
||||
this.states = {};
|
||||
}
|
||||
|
|
|
@ -14,15 +14,23 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
import { NotificationColor } from "./NotificationColor";
|
||||
import { IDestroyable } from "../../utils/IDestroyable";
|
||||
import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter";
|
||||
|
||||
export const NOTIFICATION_STATE_UPDATE = "update";
|
||||
export interface INotificationStateSnapshotParams {
|
||||
symbol: string | null;
|
||||
count: number;
|
||||
color: NotificationColor;
|
||||
}
|
||||
|
||||
export abstract class NotificationState extends EventEmitter implements IDestroyable {
|
||||
protected _symbol: string;
|
||||
export enum NotificationStateEvents {
|
||||
Update = "update",
|
||||
}
|
||||
|
||||
export abstract class NotificationState extends TypedEventEmitter<NotificationStateEvents>
|
||||
implements INotificationStateSnapshotParams, IDestroyable {
|
||||
protected _symbol: string | null;
|
||||
protected _count: number;
|
||||
protected _color: NotificationColor;
|
||||
|
||||
|
@ -56,7 +64,7 @@ export abstract class NotificationState extends EventEmitter implements IDestroy
|
|||
|
||||
protected emitIfUpdated(snapshot: NotificationStateSnapshot) {
|
||||
if (snapshot.isDifferentFrom(this)) {
|
||||
this.emit(NOTIFICATION_STATE_UPDATE);
|
||||
this.emit(NotificationStateEvents.Update);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +73,7 @@ export abstract class NotificationState extends EventEmitter implements IDestroy
|
|||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.removeAllListeners(NOTIFICATION_STATE_UPDATE);
|
||||
this.removeAllListeners(NotificationStateEvents.Update);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,13 +82,13 @@ export class NotificationStateSnapshot {
|
|||
private readonly count: number;
|
||||
private readonly color: NotificationColor;
|
||||
|
||||
constructor(state: NotificationState) {
|
||||
constructor(state: INotificationStateSnapshotParams) {
|
||||
this.symbol = state.symbol;
|
||||
this.count = state.count;
|
||||
this.color = state.color;
|
||||
}
|
||||
|
||||
public isDifferentFrom(other: NotificationState): boolean {
|
||||
public isDifferentFrom(other: INotificationStateSnapshotParams): boolean {
|
||||
const before = { count: this.count, symbol: this.symbol, color: this.color };
|
||||
const after = { count: other.count, symbol: other.symbol, color: other.color };
|
||||
return JSON.stringify(before) !== JSON.stringify(after);
|
||||
|
|
|
@ -24,6 +24,7 @@ import { FetchRoomFn, ListNotificationState } from "./ListNotificationState";
|
|||
import { RoomNotificationState } from "./RoomNotificationState";
|
||||
import { SummarizedNotificationState } from "./SummarizedNotificationState";
|
||||
import { VisibilityProvider } from "../room-list/filters/VisibilityProvider";
|
||||
import { ThreadsRoomNotificationState } from "./ThreadsRoomNotificationState";
|
||||
|
||||
interface IState {}
|
||||
|
||||
|
@ -31,6 +32,7 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
|
|||
private static internalInstance = new RoomNotificationStateStore();
|
||||
|
||||
private roomMap = new Map<Room, RoomNotificationState>();
|
||||
private roomThreadsMap = new Map<Room, ThreadsRoomNotificationState>();
|
||||
private listMap = new Map<TagID, ListNotificationState>();
|
||||
|
||||
private constructor() {
|
||||
|
@ -86,10 +88,22 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient<IState> {
|
|||
public getRoomState(room: Room): RoomNotificationState {
|
||||
if (!this.roomMap.has(room)) {
|
||||
this.roomMap.set(room, new RoomNotificationState(room));
|
||||
// Not very elegant, but that way we ensure that we start tracking
|
||||
// threads notification at the same time at rooms.
|
||||
// There are multiple entry points, and it's unclear which one gets
|
||||
// called first
|
||||
this.roomThreadsMap.set(room, new ThreadsRoomNotificationState(room));
|
||||
}
|
||||
return this.roomMap.get(room);
|
||||
}
|
||||
|
||||
public getThreadsRoomState(room: Room): ThreadsRoomNotificationState {
|
||||
if (!this.roomThreadsMap.has(room)) {
|
||||
this.roomThreadsMap.set(room, new ThreadsRoomNotificationState(room));
|
||||
}
|
||||
return this.roomThreadsMap.get(room);
|
||||
}
|
||||
|
||||
public static get instance(): RoomNotificationStateStore {
|
||||
return RoomNotificationStateStore.internalInstance;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
|||
import { NotificationColor } from "./NotificationColor";
|
||||
import { arrayDiff } from "../../utils/arrays";
|
||||
import { RoomNotificationState } from "./RoomNotificationState";
|
||||
import { NOTIFICATION_STATE_UPDATE, NotificationState } from "./NotificationState";
|
||||
import { NotificationState, NotificationStateEvents } from "./NotificationState";
|
||||
import { FetchRoomFn } from "./ListNotificationState";
|
||||
|
||||
export class SpaceNotificationState extends NotificationState {
|
||||
|
@ -42,11 +42,11 @@ export class SpaceNotificationState extends NotificationState {
|
|||
const state = this.states[oldRoom.roomId];
|
||||
if (!state) continue; // We likely just didn't have a badge (race condition)
|
||||
delete this.states[oldRoom.roomId];
|
||||
state.off(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate);
|
||||
state.off(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||
}
|
||||
for (const newRoom of diff.added) {
|
||||
const state = this.getRoomFn(newRoom);
|
||||
state.on(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate);
|
||||
state.on(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||
this.states[newRoom.roomId] = state;
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ export class SpaceNotificationState extends NotificationState {
|
|||
public destroy() {
|
||||
super.destroy();
|
||||
for (const state of Object.values(this.states)) {
|
||||
state.off(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate);
|
||||
state.off(NotificationStateEvents.Update, this.onRoomNotificationStateUpdate);
|
||||
}
|
||||
this.states = {};
|
||||
}
|
||||
|
|
69
src/stores/notifications/ThreadNotificationState.ts
Normal file
69
src/stores/notifications/ThreadNotificationState.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { NotificationColor } from "./NotificationColor";
|
||||
import { IDestroyable } from "../../utils/IDestroyable";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { NotificationState } from "./NotificationState";
|
||||
import { Thread, ThreadEvent } from "matrix-js-sdk/src/models/thread";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
export class ThreadNotificationState extends NotificationState implements IDestroyable {
|
||||
protected _symbol = null;
|
||||
protected _count = 0;
|
||||
protected _color = NotificationColor.None;
|
||||
|
||||
constructor(public readonly room: Room, public readonly thread: Thread) {
|
||||
super();
|
||||
this.thread.on(ThreadEvent.NewReply, this.handleNewThreadReply);
|
||||
this.thread.on(ThreadEvent.ViewThread, this.resetThreadNotification);
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
super.destroy();
|
||||
this.thread.off(ThreadEvent.NewReply, this.handleNewThreadReply);
|
||||
this.thread.off(ThreadEvent.ViewThread, this.resetThreadNotification);
|
||||
}
|
||||
|
||||
private handleNewThreadReply(thread: Thread, event: MatrixEvent) {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
const isOwn = client.getUserId() === event.getSender();
|
||||
if (!isOwn) {
|
||||
const actions = client.getPushActionsForEvent(event, true);
|
||||
|
||||
const color = !!actions.tweaks.highlight
|
||||
? NotificationColor.Red
|
||||
: NotificationColor.Grey;
|
||||
|
||||
this.updateNotificationState(color);
|
||||
}
|
||||
}
|
||||
|
||||
private resetThreadNotification = (): void => {
|
||||
this.updateNotificationState(NotificationColor.None);
|
||||
};
|
||||
|
||||
private updateNotificationState(color: NotificationColor) {
|
||||
const snapshot = this.snapshot();
|
||||
|
||||
this._color = color;
|
||||
|
||||
// finally, publish an update if needed
|
||||
this.emitIfUpdated(snapshot);
|
||||
}
|
||||
}
|
72
src/stores/notifications/ThreadsRoomNotificationState.ts
Normal file
72
src/stores/notifications/ThreadsRoomNotificationState.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IDestroyable } from "../../utils/IDestroyable";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { NotificationState, NotificationStateEvents } from "./NotificationState";
|
||||
import { Thread, ThreadEvent } from "matrix-js-sdk/src/models/thread";
|
||||
import { ThreadNotificationState } from "./ThreadNotificationState";
|
||||
import { NotificationColor } from "./NotificationColor";
|
||||
|
||||
export class ThreadsRoomNotificationState extends NotificationState implements IDestroyable {
|
||||
private threadsState = new Map<Thread, ThreadNotificationState>();
|
||||
|
||||
protected _symbol = null;
|
||||
protected _count = 0;
|
||||
protected _color = NotificationColor.None;
|
||||
|
||||
constructor(public readonly room: Room) {
|
||||
super();
|
||||
this.room.on(ThreadEvent.New, this.onNewThread);
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
super.destroy();
|
||||
this.room.on(ThreadEvent.New, this.onNewThread);
|
||||
for (const [, notificationState] of this.threadsState) {
|
||||
notificationState.off(NotificationStateEvents.Update, this.onThreadUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
private onNewThread = (thread: Thread): void => {
|
||||
const notificationState = new ThreadNotificationState(this.room, thread);
|
||||
this.threadsState.set(
|
||||
thread,
|
||||
notificationState,
|
||||
);
|
||||
notificationState.on(NotificationStateEvents.Update, this.onThreadUpdate);
|
||||
};
|
||||
|
||||
private onThreadUpdate = (): void => {
|
||||
let color = NotificationColor.None;
|
||||
for (const [, notificationState] of this.threadsState) {
|
||||
if (notificationState.color === NotificationColor.Red) {
|
||||
color = NotificationColor.Red;
|
||||
break;
|
||||
} else if (notificationState.color === NotificationColor.Grey) {
|
||||
color = NotificationColor.Grey;
|
||||
}
|
||||
}
|
||||
this.updateNotificationState(color);
|
||||
};
|
||||
|
||||
private updateNotificationState(color: NotificationColor): void {
|
||||
const snapshot = this.snapshot();
|
||||
this._color = color;
|
||||
// finally, publish an update if needed
|
||||
this.emitIfUpdated(snapshot);
|
||||
}
|
||||
}
|
|
@ -37,7 +37,7 @@ import { NameFilterCondition } from "./filters/NameFilterCondition";
|
|||
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
|
||||
import { VisibilityProvider } from "./filters/VisibilityProvider";
|
||||
import { SpaceWatcher } from "./SpaceWatcher";
|
||||
import SpaceStore from "../SpaceStore";
|
||||
import SpaceStore from "../spaces/SpaceStore";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload";
|
||||
|
||||
|
|
|
@ -14,11 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import { RoomListStoreClass } from "./RoomListStore";
|
||||
import { SpaceFilterCondition } from "./filters/SpaceFilterCondition";
|
||||
import SpaceStore, { UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../SpaceStore";
|
||||
import SpaceStore from "../spaces/SpaceStore";
|
||||
import { MetaSpace, SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../spaces";
|
||||
|
||||
/**
|
||||
* Watches for changes in spaces to manage the filter on the provided RoomListStore
|
||||
|
@ -26,11 +25,11 @@ import SpaceStore, { UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../Spa
|
|||
export class SpaceWatcher {
|
||||
private readonly filter = new SpaceFilterCondition();
|
||||
// we track these separately to the SpaceStore as we need to observe transitions
|
||||
private activeSpace: Room = SpaceStore.instance.activeSpace;
|
||||
private activeSpace: SpaceKey = SpaceStore.instance.activeSpace;
|
||||
private allRoomsInHome: boolean = SpaceStore.instance.allRoomsInHome;
|
||||
|
||||
constructor(private store: RoomListStoreClass) {
|
||||
if (!this.allRoomsInHome || this.activeSpace) {
|
||||
if (SpaceWatcher.needsFilter(this.activeSpace, this.allRoomsInHome)) {
|
||||
this.updateFilter();
|
||||
store.addFilter(this.filter);
|
||||
}
|
||||
|
@ -38,21 +37,26 @@ export class SpaceWatcher {
|
|||
SpaceStore.instance.on(UPDATE_HOME_BEHAVIOUR, this.onHomeBehaviourUpdated);
|
||||
}
|
||||
|
||||
private onSelectedSpaceUpdated = (activeSpace?: Room, allRoomsInHome = this.allRoomsInHome) => {
|
||||
private static needsFilter(spaceKey: SpaceKey, allRoomsInHome: boolean): boolean {
|
||||
return !(spaceKey === MetaSpace.Home && allRoomsInHome);
|
||||
}
|
||||
|
||||
private onSelectedSpaceUpdated = (activeSpace: SpaceKey, allRoomsInHome = this.allRoomsInHome) => {
|
||||
if (activeSpace === this.activeSpace && allRoomsInHome === this.allRoomsInHome) return; // nop
|
||||
|
||||
const oldActiveSpace = this.activeSpace;
|
||||
const oldAllRoomsInHome = this.allRoomsInHome;
|
||||
const neededFilter = SpaceWatcher.needsFilter(this.activeSpace, this.allRoomsInHome);
|
||||
const needsFilter = SpaceWatcher.needsFilter(activeSpace, allRoomsInHome);
|
||||
|
||||
this.activeSpace = activeSpace;
|
||||
this.allRoomsInHome = allRoomsInHome;
|
||||
|
||||
if (activeSpace || !allRoomsInHome) {
|
||||
if (needsFilter) {
|
||||
this.updateFilter();
|
||||
}
|
||||
|
||||
if (oldAllRoomsInHome && !oldActiveSpace) {
|
||||
if (!neededFilter && needsFilter) {
|
||||
this.store.addFilter(this.filter);
|
||||
} else if (allRoomsInHome && !activeSpace) {
|
||||
} else if (neededFilter && !needsFilter) {
|
||||
this.store.removeFilter(this.filter);
|
||||
}
|
||||
};
|
||||
|
@ -62,8 +66,8 @@ export class SpaceWatcher {
|
|||
};
|
||||
|
||||
private updateFilter = () => {
|
||||
if (this.activeSpace) {
|
||||
SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => {
|
||||
if (this.activeSpace[0] === "!") {
|
||||
SpaceStore.instance.traverseSpace(this.activeSpace, roomId => {
|
||||
this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ import { EffectiveMembership, getEffectiveMembership, splitRoomsByMembership } f
|
|||
import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
|
||||
import { getListAlgorithmInstance } from "./list-ordering";
|
||||
import { VisibilityProvider } from "../filters/VisibilityProvider";
|
||||
import SpaceStore from "../../SpaceStore";
|
||||
import SpaceStore from "../../spaces/SpaceStore";
|
||||
|
||||
/**
|
||||
* Fired when the Algorithm has determined a list has been updated.
|
||||
|
@ -721,7 +721,8 @@ export class Algorithm extends EventEmitter {
|
|||
cause = RoomUpdateCause.Timeline;
|
||||
didTagChange = true;
|
||||
} else {
|
||||
cause = RoomUpdateCause.Timeline;
|
||||
// This is a tag change update and no tags were changed, nothing to do!
|
||||
return false;
|
||||
}
|
||||
|
||||
if (didTagChange && isSticky) {
|
||||
|
|
|
@ -19,7 +19,8 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
|||
|
||||
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
|
||||
import { IDestroyable } from "../../../utils/IDestroyable";
|
||||
import SpaceStore, { HOME_SPACE } from "../../SpaceStore";
|
||||
import SpaceStore from "../../spaces/SpaceStore";
|
||||
import { MetaSpace, SpaceKey } from "../../spaces";
|
||||
import { setHasDiff } from "../../../utils/sets";
|
||||
|
||||
/**
|
||||
|
@ -30,7 +31,7 @@ import { setHasDiff } from "../../../utils/sets";
|
|||
*/
|
||||
export class SpaceFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable {
|
||||
private roomIds = new Set<string>();
|
||||
private space: Room = null;
|
||||
private space: SpaceKey = MetaSpace.Home;
|
||||
|
||||
public get kind(): FilterKind {
|
||||
return FilterKind.Prefilter;
|
||||
|
@ -55,15 +56,13 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi
|
|||
}
|
||||
};
|
||||
|
||||
private getSpaceEventKey = (space: Room | null) => space ? space.roomId : HOME_SPACE;
|
||||
|
||||
public updateSpace(space: Room) {
|
||||
SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate);
|
||||
SpaceStore.instance.on(this.getSpaceEventKey(this.space = space), this.onStoreUpdate);
|
||||
public updateSpace(space: SpaceKey) {
|
||||
SpaceStore.instance.off(this.space, this.onStoreUpdate);
|
||||
SpaceStore.instance.on(this.space = space, this.onStoreUpdate);
|
||||
this.onStoreUpdate(); // initial update from the change to the space
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate);
|
||||
SpaceStore.instance.off(this.space, this.onStoreUpdate);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
|||
import CallHandler from "../../../CallHandler";
|
||||
import { RoomListCustomisations } from "../../../customisations/RoomList";
|
||||
import VoipUserMapper from "../../../VoipUserMapper";
|
||||
import SpaceStore from "../../SpaceStore";
|
||||
import SpaceStore from "../../spaces/SpaceStore";
|
||||
|
||||
export class VisibilityProvider {
|
||||
private static internalInstance: VisibilityProvider;
|
||||
|
@ -44,7 +44,7 @@ export class VisibilityProvider {
|
|||
}
|
||||
|
||||
if (
|
||||
CallHandler.sharedInstance().getSupportsVirtualRooms() &&
|
||||
CallHandler.instance.getSupportsVirtualRooms() &&
|
||||
VoipUserMapper.sharedInstance().isVirtualRoom(room)
|
||||
) {
|
||||
return false;
|
||||
|
|
|
@ -56,7 +56,9 @@ export class MessageEventPreview implements IPreview {
|
|||
}
|
||||
|
||||
if (hasHtml) {
|
||||
body = getHtmlText(body);
|
||||
const sanitised = getHtmlText(body.replace(/<br\/?>/gi, "\n")); // replace line breaks before removing them
|
||||
// run it through DOMParser to fixup encoded html entities
|
||||
body = new DOMParser().parseFromString(sanitised, "text/html").documentElement.textContent;
|
||||
}
|
||||
|
||||
body = sanitizeForTranslation(body);
|
||||
|
|
|
@ -18,55 +18,52 @@ import { ListIteratee, Many, sortBy, throttle } from "lodash";
|
|||
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
|
||||
import { IRoomCapability } from "matrix-js-sdk/src/client";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
|
||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||
import { ActionPayload } from "../dispatcher/payloads";
|
||||
import RoomListStore from "./room-list/RoomListStore";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import DMRoomMap from "../utils/DMRoomMap";
|
||||
import { FetchRoomFn } from "./notifications/ListNotificationState";
|
||||
import { SpaceNotificationState } from "./notifications/SpaceNotificationState";
|
||||
import { RoomNotificationStateStore } from "./notifications/RoomNotificationStateStore";
|
||||
import { DefaultTagID } from "./room-list/models";
|
||||
import { EnhancedMap, mapDiff } from "../utils/maps";
|
||||
import { setHasDiff } from "../utils/sets";
|
||||
import RoomViewStore from "./RoomViewStore";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
import { arrayHasDiff, arrayHasOrderChange } from "../utils/arrays";
|
||||
import { objectDiff } from "../utils/objects";
|
||||
import { reorderLexicographically } from "../utils/stringOrderField";
|
||||
import { TAG_ORDER } from "../components/views/rooms/RoomList";
|
||||
import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";
|
||||
|
||||
type SpaceKey = string | symbol;
|
||||
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
|
||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
import RoomListStore from "../room-list/RoomListStore";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import DMRoomMap from "../../utils/DMRoomMap";
|
||||
import { FetchRoomFn } from "../notifications/ListNotificationState";
|
||||
import { SpaceNotificationState } from "../notifications/SpaceNotificationState";
|
||||
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
|
||||
import { DefaultTagID } from "../room-list/models";
|
||||
import { EnhancedMap, mapDiff } from "../../utils/maps";
|
||||
import { setHasDiff } from "../../utils/sets";
|
||||
import RoomViewStore from "../RoomViewStore";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { arrayHasDiff, arrayHasOrderChange } from "../../utils/arrays";
|
||||
import { objectDiff } from "../../utils/objects";
|
||||
import { reorderLexicographically } from "../../utils/stringOrderField";
|
||||
import { TAG_ORDER } from "../../components/views/rooms/RoomList";
|
||||
import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload";
|
||||
import {
|
||||
ISuggestedRoom,
|
||||
MetaSpace,
|
||||
SpaceKey,
|
||||
UPDATE_HOME_BEHAVIOUR,
|
||||
UPDATE_INVITED_SPACES,
|
||||
UPDATE_SELECTED_SPACE,
|
||||
UPDATE_SUGGESTED_ROOMS,
|
||||
UPDATE_TOP_LEVEL_SPACES,
|
||||
} from ".";
|
||||
import { getCachedRoomIDForAlias } from "../../RoomAliasCache";
|
||||
|
||||
interface IState {}
|
||||
|
||||
const ACTIVE_SPACE_LS_KEY = "mx_active_space";
|
||||
|
||||
export const HOME_SPACE = Symbol("home-space");
|
||||
export const SUGGESTED_ROOMS = Symbol("suggested-rooms");
|
||||
|
||||
export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces");
|
||||
export const UPDATE_INVITED_SPACES = Symbol("invited-spaces");
|
||||
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
|
||||
export const UPDATE_HOME_BEHAVIOUR = Symbol("home-behaviour");
|
||||
// Space Room ID/HOME_SPACE will be emitted when a Space's children change
|
||||
|
||||
export interface ISuggestedRoom extends IHierarchyRoom {
|
||||
viaServers: string[];
|
||||
}
|
||||
const metaSpaceOrder: MetaSpace[] = [MetaSpace.Home, MetaSpace.Favourites, MetaSpace.People, MetaSpace.Orphans];
|
||||
|
||||
const MAX_SUGGESTED_ROOMS = 20;
|
||||
|
||||
// This setting causes the page to reload and can be costly if read frequently, so read it here only
|
||||
const spacesEnabled = !SettingsStore.getValue("showCommunitiesInsteadOfSpaces");
|
||||
|
||||
const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "HOME_SPACE"}`;
|
||||
const getSpaceContextKey = (space: SpaceKey) => `mx_space_context_${space}`;
|
||||
|
||||
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms]
|
||||
return arr.reduce((result, room: Room) => {
|
||||
|
@ -104,30 +101,41 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
private notificationStateMap = new Map<SpaceKey, SpaceNotificationState>();
|
||||
// Map from space key to Set of room IDs that should be shown as part of that space's filter
|
||||
private spaceFilteredRooms = new Map<SpaceKey, Set<string>>();
|
||||
// The space currently selected in the Space Panel - if null then Home is selected
|
||||
private _activeSpace?: Room = null;
|
||||
// The space currently selected in the Space Panel
|
||||
private _activeSpace?: SpaceKey = MetaSpace.Home; // set properly by onReady
|
||||
private _suggestedRooms: ISuggestedRoom[] = [];
|
||||
private _invitedSpaces = new Set<Room>();
|
||||
private spaceOrderLocalEchoMap = new Map<string, string>();
|
||||
private _restrictedJoinRuleSupport?: IRoomCapability;
|
||||
private _allRoomsInHome: boolean = SettingsStore.getValue("Spaces.allRoomsInHome");
|
||||
private _enabledMetaSpaces: MetaSpace[] = []; // set by onReady
|
||||
|
||||
constructor() {
|
||||
super(defaultDispatcher, {});
|
||||
|
||||
SettingsStore.monitorSetting("Spaces.allRoomsInHome", null);
|
||||
SettingsStore.monitorSetting("Spaces.enabledMetaSpaces", null);
|
||||
}
|
||||
|
||||
public get invitedSpaces(): Room[] {
|
||||
return Array.from(this._invitedSpaces);
|
||||
}
|
||||
|
||||
public get enabledMetaSpaces(): MetaSpace[] {
|
||||
return this._enabledMetaSpaces;
|
||||
}
|
||||
|
||||
public get spacePanelSpaces(): Room[] {
|
||||
return this.rootSpaces;
|
||||
}
|
||||
|
||||
public get activeSpace(): Room | null {
|
||||
return this._activeSpace || null;
|
||||
public get activeSpace(): SpaceKey {
|
||||
return this._activeSpace;
|
||||
}
|
||||
|
||||
public get activeSpaceRoom(): Room | null {
|
||||
if (this._activeSpace[0] !== "!") return null;
|
||||
return this.matrixClient?.getRoom(this._activeSpace);
|
||||
}
|
||||
|
||||
public get suggestedRooms(): ISuggestedRoom[] {
|
||||
|
@ -138,12 +146,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
return this._allRoomsInHome;
|
||||
}
|
||||
|
||||
public setActiveRoomInSpace(space: Room | null): void {
|
||||
if (space && !space.isSpaceRoom()) return;
|
||||
public setActiveRoomInSpace(space: SpaceKey): void {
|
||||
if (space[0] === "!" && !this.matrixClient?.getRoom(space)?.isSpaceRoom()) return;
|
||||
if (space !== this.activeSpace) this.setActiveSpace(space);
|
||||
|
||||
if (space) {
|
||||
const roomId = this.getNotificationState(space.roomId).getFirstRoomWithNotifications();
|
||||
const roomId = this.getNotificationState(space).getFirstRoomWithNotifications();
|
||||
defaultDispatcher.dispatch({
|
||||
action: "view_room",
|
||||
room_id: roomId,
|
||||
|
@ -183,12 +191,20 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
* @param contextSwitch whether to switch the user's context,
|
||||
* should not be done when the space switch is done implicitly due to another event like switching room.
|
||||
*/
|
||||
public setActiveSpace(space: Room | null, contextSwitch = true) {
|
||||
if (!this.matrixClient || space === this.activeSpace || (space && !space.isSpaceRoom())) return;
|
||||
public setActiveSpace(space: SpaceKey, contextSwitch = true) {
|
||||
if (!space || !this.matrixClient || space === this.activeSpace) return;
|
||||
|
||||
let cliSpace: Room;
|
||||
if (space[0] === "!") {
|
||||
cliSpace = this.matrixClient.getRoom(space);
|
||||
if (!cliSpace?.isSpaceRoom()) return;
|
||||
} else if (!this.enabledMetaSpaces.includes(space as MetaSpace)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._activeSpace = space;
|
||||
this.emit(UPDATE_SELECTED_SPACE, this.activeSpace);
|
||||
this.emit(SUGGESTED_ROOMS, this._suggestedRooms = []);
|
||||
this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms = []);
|
||||
|
||||
if (contextSwitch) {
|
||||
// view last selected room from space
|
||||
|
@ -197,7 +213,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
// if the space being selected is an invite then always view that invite
|
||||
// else if the last viewed room in this space is joined then view that
|
||||
// else view space home or home depending on what is being clicked on
|
||||
if (space?.getMyMembership() !== "invite" &&
|
||||
if (cliSpace?.getMyMembership() !== "invite" &&
|
||||
this.matrixClient.getRoom(roomId)?.getMyMembership() === "join" &&
|
||||
this.getSpaceFilteredRoomIds(space).has(roomId)
|
||||
) {
|
||||
|
@ -206,36 +222,33 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
room_id: roomId,
|
||||
context_switch: true,
|
||||
});
|
||||
} else if (space) {
|
||||
} else if (cliSpace) {
|
||||
defaultDispatcher.dispatch({
|
||||
action: "view_room",
|
||||
room_id: space.roomId,
|
||||
room_id: space,
|
||||
context_switch: true,
|
||||
});
|
||||
} else {
|
||||
defaultDispatcher.dispatch({
|
||||
action: "view_home_page",
|
||||
context_switch: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// persist space selected
|
||||
if (space) {
|
||||
window.localStorage.setItem(ACTIVE_SPACE_LS_KEY, space.roomId);
|
||||
} else {
|
||||
window.localStorage.removeItem(ACTIVE_SPACE_LS_KEY);
|
||||
}
|
||||
window.localStorage.setItem(ACTIVE_SPACE_LS_KEY, space);
|
||||
|
||||
if (space) {
|
||||
this.loadSuggestedRooms(space);
|
||||
if (cliSpace) {
|
||||
this.loadSuggestedRooms(cliSpace);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadSuggestedRooms(space: Room): Promise<void> {
|
||||
const suggestedRooms = await this.fetchSuggestedRooms(space);
|
||||
if (this._activeSpace === space) {
|
||||
if (this._activeSpace === space.roomId) {
|
||||
this._suggestedRooms = suggestedRooms;
|
||||
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
|
||||
this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -336,11 +349,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
return this.parentMap.get(roomId) || new Set();
|
||||
}
|
||||
|
||||
public getSpaceFilteredRoomIds = (space: Room | null): Set<string> => {
|
||||
if (!space && this.allRoomsInHome) {
|
||||
public getSpaceFilteredRoomIds = (space: SpaceKey): Set<string> => {
|
||||
if (space === MetaSpace.Home && this.allRoomsInHome) {
|
||||
return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId));
|
||||
}
|
||||
return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set();
|
||||
return this.spaceFilteredRooms.get(space) || new Set();
|
||||
};
|
||||
|
||||
private rebuild = throttle(() => {
|
||||
|
@ -419,12 +432,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
this.parentMap = backrefs;
|
||||
|
||||
// if the currently selected space no longer exists, remove its selection
|
||||
if (this._activeSpace && detachedNodes.has(this._activeSpace)) {
|
||||
this.setActiveSpace(null, false);
|
||||
if (this._activeSpace[0] === "!" && detachedNodes.has(this.matrixClient.getRoom(this._activeSpace))) {
|
||||
this.goToFirstSpace();
|
||||
}
|
||||
|
||||
this.onRoomsUpdate(); // TODO only do this if a change has happened
|
||||
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
|
||||
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces, this.enabledMetaSpaces);
|
||||
|
||||
// build initial state of invited spaces as we would have missed the emitted events about the room at launch
|
||||
this._invitedSpaces = new Set(this.sortRootSpaces(invitedSpaces));
|
||||
|
@ -439,19 +452,22 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
if (this.allRoomsInHome) return true;
|
||||
if (room.isSpaceRoom()) return false;
|
||||
return !this.parentMap.get(room.roomId)?.size // put all orphaned rooms in the Home Space
|
||||
|| DMRoomMap.shared().getUserIdForRoomId(room.roomId) // put all DMs in the Home Space
|
||||
|| RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite); // show all favourites
|
||||
|| DMRoomMap.shared().getUserIdForRoomId(room.roomId); // put all DMs in the Home Space
|
||||
};
|
||||
|
||||
// Update a given room due to its tag changing (e.g DM-ness or Fav-ness)
|
||||
// This can only change whether it shows up in the HOME_SPACE or not
|
||||
private onRoomUpdate = (room: Room) => {
|
||||
if (this.showInHomeSpace(room)) {
|
||||
this.spaceFilteredRooms.get(HOME_SPACE)?.add(room.roomId);
|
||||
this.emit(HOME_SPACE);
|
||||
} else if (!this.orphanedRooms.has(room.roomId)) {
|
||||
this.spaceFilteredRooms.get(HOME_SPACE)?.delete(room.roomId);
|
||||
this.emit(HOME_SPACE);
|
||||
const enabledMetaSpaces = new Set(this.enabledMetaSpaces);
|
||||
// TODO more metaspace stuffs
|
||||
if (enabledMetaSpaces.has(MetaSpace.Home)) {
|
||||
if (this.showInHomeSpace(room)) {
|
||||
this.spaceFilteredRooms.get(MetaSpace.Home)?.add(room.roomId);
|
||||
this.emit(MetaSpace.Home);
|
||||
} else if (!this.orphanedRooms.has(room.roomId)) {
|
||||
this.spaceFilteredRooms.get(MetaSpace.Home)?.delete(room.roomId);
|
||||
this.emit(MetaSpace.Home);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -468,18 +484,41 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
const oldFilteredRooms = this.spaceFilteredRooms;
|
||||
this.spaceFilteredRooms = new Map();
|
||||
|
||||
if (!this.allRoomsInHome) {
|
||||
const enabledMetaSpaces = new Set(this.enabledMetaSpaces);
|
||||
// populate the Home metaspace if it is enabled and is not set to all rooms
|
||||
if (enabledMetaSpaces.has(MetaSpace.Home) && !this.allRoomsInHome) {
|
||||
// put all room invites in the Home Space
|
||||
const invites = visibleRooms.filter(r => !r.isSpaceRoom() && r.getMyMembership() === "invite");
|
||||
this.spaceFilteredRooms.set(HOME_SPACE, new Set<string>(invites.map(room => room.roomId)));
|
||||
this.spaceFilteredRooms.set(MetaSpace.Home, new Set(invites.map(r => r.roomId)));
|
||||
|
||||
visibleRooms.forEach(room => {
|
||||
if (this.showInHomeSpace(room)) {
|
||||
this.spaceFilteredRooms.get(HOME_SPACE).add(room.roomId);
|
||||
this.spaceFilteredRooms.get(MetaSpace.Home).add(room.roomId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// populate the Favourites metaspace if it is enabled
|
||||
if (enabledMetaSpaces.has(MetaSpace.Favourites)) {
|
||||
const favourites = visibleRooms.filter(r => r.tags[DefaultTagID.Favourite]);
|
||||
this.spaceFilteredRooms.set(MetaSpace.Favourites, new Set(favourites.map(r => r.roomId)));
|
||||
}
|
||||
|
||||
// populate the People metaspace if it is enabled
|
||||
if (enabledMetaSpaces.has(MetaSpace.People)) {
|
||||
const people = visibleRooms.filter(r => DMRoomMap.shared().getUserIdForRoomId(r.roomId));
|
||||
this.spaceFilteredRooms.set(MetaSpace.People, new Set(people.map(r => r.roomId)));
|
||||
}
|
||||
|
||||
// populate the Orphans metaspace if it is enabled
|
||||
if (enabledMetaSpaces.has(MetaSpace.Orphans)) {
|
||||
const orphans = visibleRooms.filter(r => {
|
||||
// filter out DMs and rooms with >0 parents
|
||||
return !this.parentMap.get(r.roomId)?.size && !DMRoomMap.shared().getUserIdForRoomId(r.roomId);
|
||||
});
|
||||
this.spaceFilteredRooms.set(MetaSpace.Orphans, new Set(orphans.map(r => r.roomId)));
|
||||
}
|
||||
|
||||
const hiddenChildren = new EnhancedMap<string, Set<string>>();
|
||||
visibleRooms.forEach(room => {
|
||||
if (room.getMyMembership() !== "join") return;
|
||||
|
@ -539,15 +578,23 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
this.emit(k);
|
||||
});
|
||||
|
||||
let dmBadgeSpace: MetaSpace;
|
||||
// only show badges on dms on the most relevant space if such exists
|
||||
if (enabledMetaSpaces.has(MetaSpace.People)) {
|
||||
dmBadgeSpace = MetaSpace.People;
|
||||
} else if (enabledMetaSpaces.has(MetaSpace.Home)) {
|
||||
dmBadgeSpace = MetaSpace.Home;
|
||||
}
|
||||
|
||||
this.spaceFilteredRooms.forEach((roomIds, s) => {
|
||||
if (this.allRoomsInHome && s === HOME_SPACE) return; // we'll be using the global notification state, skip
|
||||
if (this.allRoomsInHome && s === MetaSpace.Home) return; // we'll be using the global notification state, skip
|
||||
|
||||
// Update NotificationStates
|
||||
this.getNotificationState(s).setRooms(visibleRooms.filter(room => {
|
||||
if (!roomIds.has(room.roomId) || room.isSpaceRoom()) return false;
|
||||
|
||||
if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
|
||||
return s === HOME_SPACE;
|
||||
if (dmBadgeSpace && DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
|
||||
return s === dmBadgeSpace;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -558,23 +605,22 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
private switchToRelatedSpace = (roomId: string) => {
|
||||
if (this.suggestedRooms.find(r => r.room_id === roomId)) return;
|
||||
|
||||
let parent = this.getCanonicalParent(roomId);
|
||||
// try to find the canonical parent first
|
||||
let parent: SpaceKey = this.getCanonicalParent(roomId)?.roomId;
|
||||
|
||||
// otherwise, try to find a root space which contains this room
|
||||
if (!parent) {
|
||||
parent = this.rootSpaces.find(s => this.spaceFilteredRooms.get(s.roomId)?.has(roomId));
|
||||
parent = this.rootSpaces.find(s => this.spaceFilteredRooms.get(s.roomId)?.has(roomId))?.roomId;
|
||||
}
|
||||
|
||||
// otherwise, try to find a metaspace which contains this room
|
||||
if (!parent) {
|
||||
const parentIds = Array.from(this.parentMap.get(roomId) || []);
|
||||
for (const parentId of parentIds) {
|
||||
const room = this.matrixClient.getRoom(parentId);
|
||||
if (room) {
|
||||
parent = room;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// search meta spaces in reverse as Home is the first and least specific one
|
||||
parent = [...this.enabledMetaSpaces].reverse().find(s => this.getSpaceFilteredRoomIds(s).has(roomId));
|
||||
}
|
||||
|
||||
// don't trigger a context switch when we are switching a space to match the chosen room
|
||||
this.setActiveSpace(parent || null, false);
|
||||
this.setActiveSpace(parent ?? MetaSpace.Home, false); // TODO
|
||||
};
|
||||
|
||||
private onRoom = (room: Room, newMembership?: string, oldMembership?: string) => {
|
||||
|
@ -596,7 +642,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
const numSuggestedRooms = this._suggestedRooms.length;
|
||||
this._suggestedRooms = this._suggestedRooms.filter(r => r.room_id !== room.roomId);
|
||||
if (numSuggestedRooms !== this._suggestedRooms.length) {
|
||||
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
|
||||
this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms);
|
||||
}
|
||||
|
||||
// if the room currently being viewed was just joined then switch to its related space
|
||||
|
@ -621,10 +667,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
|
||||
if (membership === "join" && room.roomId === RoomViewStore.getRoomId()) {
|
||||
// if the user was looking at the space and then joined: select that space
|
||||
this.setActiveSpace(room, false);
|
||||
} else if (membership === "leave" && room.roomId === this.activeSpace?.roomId) {
|
||||
this.setActiveSpace(room.roomId, false);
|
||||
} else if (membership === "leave" && room.roomId === this.activeSpace) {
|
||||
// user's active space has gone away, go back to home
|
||||
this.setActiveSpace(null, true);
|
||||
this.goToFirstSpace(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -632,7 +678,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
const rootSpaces = this.sortRootSpaces(this.rootSpaces);
|
||||
if (arrayHasOrderChange(this.rootSpaces, rootSpaces)) {
|
||||
this.rootSpaces = rootSpaces;
|
||||
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
|
||||
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces, this.enabledMetaSpaces);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -647,7 +693,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
this.emit(room.roomId);
|
||||
}
|
||||
|
||||
if (room === this.activeSpace && // current space
|
||||
if (room.roomId === this.activeSpace && // current space
|
||||
this.matrixClient.getRoom(ev.getStateKey())?.getMyMembership() !== "join" && // target not joined
|
||||
ev.getPrevContent().suggested !== ev.getContent().suggested // suggested flag changed
|
||||
) {
|
||||
|
@ -693,7 +739,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
if (order !== lastOrder) {
|
||||
this.notifyIfOrderChanged();
|
||||
}
|
||||
} else if (ev.getType() === EventType.Tag && !this.allRoomsInHome) {
|
||||
} else if (ev.getType() === EventType.Tag) {
|
||||
// If the room was in favourites and now isn't or the opposite then update its position in the trees
|
||||
const oldTags = lastEv?.getContent()?.tags || {};
|
||||
const newTags = ev.getContent()?.tags || {};
|
||||
|
@ -703,9 +749,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
}
|
||||
};
|
||||
|
||||
private onAccountData = (ev: MatrixEvent, lastEvent: MatrixEvent) => {
|
||||
private onAccountData = (ev: MatrixEvent, prevEvent?: MatrixEvent) => {
|
||||
if (!this.allRoomsInHome && ev.getType() === EventType.Direct) {
|
||||
const lastContent = lastEvent.getContent();
|
||||
const lastContent = prevEvent?.getContent() ?? {};
|
||||
const content = ev.getContent();
|
||||
|
||||
const diff = objectDiff<Record<string, string[]>>(lastContent, content);
|
||||
|
@ -727,9 +773,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
this.parentMap = new EnhancedMap();
|
||||
this.notificationStateMap = new Map();
|
||||
this.spaceFilteredRooms = new Map();
|
||||
this._activeSpace = null;
|
||||
this._activeSpace = MetaSpace.Home; // set properly by onReady
|
||||
this._suggestedRooms = [];
|
||||
this._invitedSpaces = new Set();
|
||||
this._enabledMetaSpaces = [];
|
||||
}
|
||||
|
||||
protected async onNotReady() {
|
||||
|
@ -759,16 +806,27 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
?.["m.room_versions"]?.["org.matrix.msc3244.room_capabilities"]?.["restricted"];
|
||||
});
|
||||
|
||||
const enabledMetaSpaces = SettingsStore.getValue("Spaces.enabledMetaSpaces");
|
||||
this._enabledMetaSpaces = metaSpaceOrder.filter(k => enabledMetaSpaces[k]) as MetaSpace[];
|
||||
|
||||
await this.onSpaceUpdate(); // trigger an initial update
|
||||
|
||||
// restore selected state from last session if any and still valid
|
||||
const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY);
|
||||
if (lastSpaceId) {
|
||||
if (lastSpaceId && (
|
||||
lastSpaceId[0] === "!" ? this.matrixClient.getRoom(lastSpaceId) : enabledMetaSpaces[lastSpaceId]
|
||||
)) {
|
||||
// don't context switch here as it may break permalinks
|
||||
this.setActiveSpace(this.matrixClient.getRoom(lastSpaceId), false);
|
||||
this.setActiveSpace(lastSpaceId, false);
|
||||
} else {
|
||||
this.goToFirstSpace();
|
||||
}
|
||||
}
|
||||
|
||||
private goToFirstSpace(contextSwitch = false) {
|
||||
this.setActiveSpace(this.enabledMetaSpaces[0] ?? this.spacePanelSpaces[0]?.roomId, contextSwitch);
|
||||
}
|
||||
|
||||
protected async onAction(payload: ActionPayload) {
|
||||
if (!spacesEnabled) return;
|
||||
switch (payload.action) {
|
||||
|
@ -776,17 +834,20 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
// Don't auto-switch rooms when reacting to a context-switch
|
||||
// as this is not helpful and can create loops of rooms/space switching
|
||||
if (payload.context_switch) break;
|
||||
let roomId = payload.room_id;
|
||||
|
||||
if (payload.room_alias && !roomId) {
|
||||
roomId = getCachedRoomIDForAlias(payload.room_alias);
|
||||
}
|
||||
|
||||
if (!roomId) return; // we'll get re-fired with the room ID shortly
|
||||
|
||||
const roomId = payload.room_id;
|
||||
const room = this.matrixClient?.getRoom(roomId);
|
||||
if (room?.isSpaceRoom()) {
|
||||
// Don't context switch when navigating to the space room
|
||||
// as it will cause you to end up in the wrong room
|
||||
this.setActiveSpace(room, false);
|
||||
} else if (
|
||||
(!this.allRoomsInHome || this.activeSpace) &&
|
||||
!this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)
|
||||
) {
|
||||
this.setActiveSpace(room.roomId, false);
|
||||
} else if (!this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)) {
|
||||
this.switchToRelatedSpace(roomId);
|
||||
}
|
||||
|
||||
|
@ -797,32 +858,62 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
break;
|
||||
}
|
||||
|
||||
case "after_leave_room":
|
||||
if (this._activeSpace && payload.room_id === this._activeSpace.roomId) {
|
||||
this.setActiveSpace(null, false);
|
||||
case "view_home_page":
|
||||
if (!payload.context_switch && this.enabledMetaSpaces.includes(MetaSpace.Home)) {
|
||||
this.setActiveSpace(MetaSpace.Home, false);
|
||||
window.localStorage.setItem(getSpaceContextKey(this.activeSpace), "");
|
||||
}
|
||||
break;
|
||||
|
||||
case Action.SwitchSpace:
|
||||
// 1 is Home, 2-9 are the spaces after Home
|
||||
if (payload.num === 1) {
|
||||
this.setActiveSpace(null);
|
||||
} else if (payload.num > 0 && this.spacePanelSpaces.length > payload.num - 2) {
|
||||
this.setActiveSpace(this.spacePanelSpaces[payload.num - 2]);
|
||||
case "after_leave_room":
|
||||
if (this._activeSpace[0] === "!" && payload.room_id === this._activeSpace) {
|
||||
// User has left the current space, go to first space
|
||||
this.goToFirstSpace();
|
||||
}
|
||||
break;
|
||||
|
||||
case Action.SwitchSpace: {
|
||||
// Metaspaces start at 1, Spaces follow
|
||||
if (payload.num < 1 || payload.num > 9) break;
|
||||
const numMetaSpaces = this.enabledMetaSpaces.length;
|
||||
if (payload.num <= numMetaSpaces) {
|
||||
this.setActiveSpace(this.enabledMetaSpaces[payload.num - 1]);
|
||||
} else if (this.spacePanelSpaces.length > payload.num - numMetaSpaces - 1) {
|
||||
this.setActiveSpace(this.spacePanelSpaces[payload.num - numMetaSpaces - 1].roomId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Action.SettingUpdated: {
|
||||
const settingUpdatedPayload = payload as SettingUpdatedPayload;
|
||||
if (settingUpdatedPayload.settingName === "Spaces.allRoomsInHome") {
|
||||
const newValue = SettingsStore.getValue("Spaces.allRoomsInHome");
|
||||
if (this.allRoomsInHome !== newValue) {
|
||||
this._allRoomsInHome = newValue;
|
||||
this.emit(UPDATE_HOME_BEHAVIOUR, this.allRoomsInHome);
|
||||
this.rebuild(); // rebuild everything
|
||||
switch (settingUpdatedPayload.settingName) {
|
||||
case "Spaces.allRoomsInHome": {
|
||||
const newValue = SettingsStore.getValue("Spaces.allRoomsInHome");
|
||||
if (this.allRoomsInHome !== newValue) {
|
||||
this._allRoomsInHome = newValue;
|
||||
this.emit(UPDATE_HOME_BEHAVIOUR, this.allRoomsInHome);
|
||||
this.rebuild(); // rebuild everything
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Spaces.enabledMetaSpaces": {
|
||||
const newValue = SettingsStore.getValue("Spaces.enabledMetaSpaces");
|
||||
const enabledMetaSpaces = metaSpaceOrder.filter(k => newValue[k]) as MetaSpace[];
|
||||
if (arrayHasDiff(this._enabledMetaSpaces, enabledMetaSpaces)) {
|
||||
this._enabledMetaSpaces = enabledMetaSpaces;
|
||||
// if a metaspace currently being viewed was remove, go to another one
|
||||
if (this.activeSpace[0] !== "!" &&
|
||||
!enabledMetaSpaces.includes(this.activeSpace as MetaSpace)
|
||||
) {
|
||||
this.goToFirstSpace();
|
||||
}
|
||||
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces, this.enabledMetaSpaces);
|
||||
this.rebuild(); // rebuild everything
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
55
src/stores/spaces/index.ts
Normal file
55
src/stores/spaces/index.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces";
|
||||
|
||||
import { _t } from "../../languageHandler";
|
||||
|
||||
// The consts & types are moved out here to prevent cyclical imports
|
||||
|
||||
export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces");
|
||||
export const UPDATE_INVITED_SPACES = Symbol("invited-spaces");
|
||||
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
|
||||
export const UPDATE_HOME_BEHAVIOUR = Symbol("home-behaviour");
|
||||
export const UPDATE_SUGGESTED_ROOMS = Symbol("suggested-rooms");
|
||||
// Space Key will be emitted when a Space's children change
|
||||
|
||||
export enum MetaSpace {
|
||||
Home = "home-space",
|
||||
Favourites = "favourites-space",
|
||||
People = "people-space",
|
||||
Orphans = "orphans-space",
|
||||
}
|
||||
|
||||
export const getMetaSpaceName = (spaceKey: MetaSpace, allRoomsInHome = false): string => {
|
||||
switch (spaceKey) {
|
||||
case MetaSpace.Home:
|
||||
return allRoomsInHome ? _t("All rooms") : _t("Home");
|
||||
case MetaSpace.Favourites:
|
||||
return _t("Favourites");
|
||||
case MetaSpace.People:
|
||||
return _t("People");
|
||||
case MetaSpace.Orphans:
|
||||
return _t("Other rooms");
|
||||
}
|
||||
};
|
||||
|
||||
export type SpaceKey = MetaSpace | Room["roomId"];
|
||||
|
||||
export interface ISuggestedRoom extends IHierarchyRoom {
|
||||
viaServers: string[];
|
||||
}
|
|
@ -48,6 +48,7 @@ import { WidgetType } from "../../widgets/WidgetType";
|
|||
import ActiveWidgetStore from "../ActiveWidgetStore";
|
||||
import { objectShallowClone } from "../../utils/objects";
|
||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { ElementWidgetActions, IViewRoomApiRequest } from "./ElementWidgetActions";
|
||||
import { ModalWidgetStore } from "../ModalWidgetStore";
|
||||
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
|
||||
|
@ -291,7 +292,7 @@ export class StopGapWidget extends EventEmitter {
|
|||
|
||||
// at this point we can change rooms, so do that
|
||||
defaultDispatcher.dispatch({
|
||||
action: 'view_room',
|
||||
action: Action.ViewRoom,
|
||||
room_id: targetRoomId,
|
||||
});
|
||||
|
||||
|
|
|
@ -29,11 +29,6 @@ import {
|
|||
WidgetEventCapability,
|
||||
WidgetKind,
|
||||
} from "matrix-widget-api";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
import { IEvent, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { iterableDiff, iterableUnion } from "../../utils/iterables";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import ActiveRoomObserver from "../../ActiveRoomObserver";
|
||||
|
@ -43,10 +38,16 @@ import WidgetCapabilitiesPromptDialog from "../../components/views/dialogs/Widge
|
|||
import { WidgetPermissionCustomisations } from "../../customisations/WidgetPermissions";
|
||||
import { OIDCState, WidgetPermissionStore } from "./WidgetPermissionStore";
|
||||
import { WidgetType } from "../../widgets/WidgetType";
|
||||
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||
import { CHAT_EFFECTS } from "../../effects";
|
||||
import { containsEmoji } from "../../effects/utils";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { tryTransformPermalinkToLocalHref } from "../../utils/permalinks/Permalinks";
|
||||
import { IContent, IEvent, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
|
||||
// TODO: Purge this from the universe
|
||||
|
||||
|
@ -141,7 +142,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||
|
||||
public async sendEvent(
|
||||
eventType: string,
|
||||
content: any,
|
||||
content: IContent,
|
||||
stateKey: string = null,
|
||||
targetRoomId: string = null,
|
||||
): Promise<ISendEventDetails> {
|
||||
|
@ -164,7 +165,12 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||
if (eventType === EventType.RoomMessage) {
|
||||
CHAT_EFFECTS.forEach((effect) => {
|
||||
if (containsEmoji(content, effect.emojis)) {
|
||||
dis.dispatch({ action: `effects.${effect.command}` });
|
||||
// For initial threads launch, chat effects are disabled
|
||||
// see #19731
|
||||
const isNotThread = content["m.relates_to"].rel_type !== RelationType.Thread;
|
||||
if (!SettingsStore.getValue("feature_thread") || isNotThread) {
|
||||
dis.dispatch({ action: `effects.${effect.command}` });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -39,8 +39,7 @@ export enum Container {
|
|||
// changes needed", though this may change in the future.
|
||||
Right = "right",
|
||||
|
||||
// ... more as needed. Note that most of this code assumes that there
|
||||
// are only two containers, and that only the top container is special.
|
||||
Center = "center"
|
||||
}
|
||||
|
||||
export interface IStoredLayout {
|
||||
|
@ -175,7 +174,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
}
|
||||
};
|
||||
|
||||
private recalculateRoom(room: Room) {
|
||||
public recalculateRoom(room: Room) {
|
||||
const widgets = WidgetStore.instance.getApps(room.roomId);
|
||||
if (!widgets?.length) {
|
||||
this.byRoom[room.roomId] = {};
|
||||
|
@ -196,18 +195,26 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
}
|
||||
|
||||
const roomLayout: ILayoutStateEvent = layoutEv ? layoutEv.getContent() : null;
|
||||
|
||||
// We essentially just need to find the top container's widgets because we
|
||||
// only have two containers. Anything not in the top widget by the end of this
|
||||
// function will go into the right container.
|
||||
// We filter for the center container first.
|
||||
// (An error is raised, if there are multiple widgets marked for the center container)
|
||||
// For the right and top container multiple widgets are allowed.
|
||||
const topWidgets: IApp[] = [];
|
||||
const rightWidgets: IApp[] = [];
|
||||
const centerWidgets: IApp[] = [];
|
||||
for (const widget of widgets) {
|
||||
const stateContainer = roomLayout?.widgets?.[widget.id]?.container;
|
||||
const manualContainer = userLayout?.widgets?.[widget.id]?.container;
|
||||
const isLegacyPinned = !!legacyPinned?.[widget.id];
|
||||
const defaultContainer = WidgetType.JITSI.matches(widget.type) ? Container.Top : Container.Right;
|
||||
|
||||
if ((manualContainer) ? manualContainer === Container.Center : stateContainer === Container.Center) {
|
||||
if (centerWidgets.length) {
|
||||
console.error("Tried to push a second widget into the center container");
|
||||
} else {
|
||||
centerWidgets.push(widget);
|
||||
}
|
||||
// The widget won't need to be put in any other container.
|
||||
continue;
|
||||
}
|
||||
let targetContainer = defaultContainer;
|
||||
if (!!manualContainer || !!stateContainer) {
|
||||
targetContainer = (manualContainer) ? manualContainer : stateContainer;
|
||||
|
@ -324,6 +331,11 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
ordered: rightWidgets,
|
||||
};
|
||||
}
|
||||
if (centerWidgets.length) {
|
||||
this.byRoom[room.roomId][Container.Center] = {
|
||||
ordered: centerWidgets,
|
||||
};
|
||||
}
|
||||
|
||||
const afterChanges = JSON.stringify(this.byRoom[room.roomId]);
|
||||
if (afterChanges !== beforeChanges) {
|
||||
|
@ -340,7 +352,11 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
}
|
||||
|
||||
public canAddToContainer(room: Room, container: Container): boolean {
|
||||
return this.getContainerWidgets(room, container).length < MAX_PINNED;
|
||||
switch (container) {
|
||||
case Container.Top: return this.getContainerWidgets(room, container).length < MAX_PINNED;
|
||||
case Container.Right: return this.getContainerWidgets(room, container).length < MAX_PINNED;
|
||||
case Container.Center: return this.getContainerWidgets(room, container).length < 1;
|
||||
}
|
||||
}
|
||||
|
||||
public getResizerDistributions(room: Room, container: Container): string[] { // yes, string.
|
||||
|
@ -424,11 +440,42 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
public moveToContainer(room: Room, widget: IApp, toContainer: Container) {
|
||||
const allWidgets = this.getAllWidgets(room);
|
||||
if (!allWidgets.some(([w]) => w.id === widget.id)) return; // invalid
|
||||
// Prepare other containers (potentially move widgets to obay the following rules)
|
||||
switch (toContainer) {
|
||||
case Container.Right:
|
||||
// new "right" widget
|
||||
break;
|
||||
case Container.Center:
|
||||
// new "center" widget => all other widgets go into "right"
|
||||
for (const w of this.getContainerWidgets(room, Container.Top)) {
|
||||
this.moveToContainer(room, w, Container.Right);
|
||||
}
|
||||
for (const w of this.getContainerWidgets(room, Container.Center)) {
|
||||
this.moveToContainer(room, w, Container.Right);
|
||||
}
|
||||
break;
|
||||
case Container.Top:
|
||||
// new "top" widget => the center widget moves into "right"
|
||||
if (this.hasMaximisedWidget(room)) {
|
||||
this.moveToContainer(room, this.getContainerWidgets(room, Container.Center)[0], Container.Right);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// move widgets into requested container.
|
||||
this.updateUserLayout(room, {
|
||||
[widget.id]: { container: toContainer },
|
||||
});
|
||||
}
|
||||
|
||||
public hasMaximisedWidget(room: Room) {
|
||||
return this.getContainerWidgets(room, Container.Center).length > 0;
|
||||
}
|
||||
|
||||
public hasPinnedWidgets(room: Room) {
|
||||
return this.getContainerWidgets(room, Container.Top).length > 0;
|
||||
}
|
||||
|
||||
public canCopyLayoutToRoom(room: Room): boolean {
|
||||
if (!this.matrixClient) return false; // not ready yet
|
||||
return room.currentState.maySendStateEvent(WIDGET_LAYOUT_EVENT_TYPE, this.matrixClient.getUserId());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue