From eb7f6f4c4b5036e1c0e46babb9a3e3fafeb65963 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Sep 2020 16:19:42 +0100 Subject: [PATCH] Create new WidgetStore to track all widgets stuff --- src/@types/global.d.ts | 2 + src/stores/WidgetStore.ts | 191 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 src/stores/WidgetStore.ts diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 1a361e7b55..de3eb5e767 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -29,6 +29,7 @@ import {ActiveRoomObserver} from "../ActiveRoomObserver"; import {Notifier} from "../Notifier"; import type {Renderer} from "react-dom"; import RightPanelStore from "../stores/RightPanelStore"; +import {WidgetStore} from "../stores/WidgetStore"; declare global { interface Window { @@ -51,6 +52,7 @@ declare global { mxSettingsStore: SettingsStore; mxNotifier: typeof Notifier; mxRightPanelStore: RightPanelStore; + mxWidgetStore: WidgetStore; } interface Document { diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts new file mode 100644 index 0000000000..b31fc99515 --- /dev/null +++ b/src/stores/WidgetStore.ts @@ -0,0 +1,191 @@ +/* +Copyright 2020 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 { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +import { ActionPayload } from "../dispatcher/payloads"; +import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; +import defaultDispatcher from "../dispatcher/dispatcher"; +import SettingsStore from "../settings/SettingsStore"; +import WidgetEchoStore from "../stores/WidgetEchoStore"; +import WidgetUtils from "../utils/WidgetUtils"; +import {IRRELEVANT_ROOM} from "../settings/WatchManager"; +import {SettingLevel} from "../settings/SettingLevel"; + +interface IState {} + +export interface IApp { + id: string; + type: string; + roomId: string; + eventId: string; + creatorUserId: string; + waitForIframeLoad?: boolean; + // eslint-disable-next-line camelcase + avatar_url: string; // MSC2765 https://github.com/matrix-org/matrix-doc/pull/2765 +} + +interface IRoomWidgets { + widgets: IApp[]; + pinned: Set; +} + +// TODO consolidate WidgetEchoStore into this +// TODO consolidate ActiveWidgetStore into this +export class WidgetStore extends AsyncStoreWithClient { + private static internalInstance = new WidgetStore(); + + private widgetMap = new Map(); + private roomMap = new Map(); + + private constructor() { + super(defaultDispatcher, {}); + + SettingsStore.watchSetting("Widgets.pinned", IRRELEVANT_ROOM, this.onPinnedWidgetsChange); + WidgetEchoStore.on("update", this.onWidgetEchoStoreUpdate); + } + + public static get instance(): WidgetStore { + return WidgetStore.internalInstance; + } + + private initRoom(roomId: string) { + if (!this.roomMap.has(roomId)) { + this.roomMap.set(roomId, { + pinned: new Set(), + widgets: [], + }); + } + } + + protected async onReady(): Promise { + this.matrixClient.on("RoomState.events", this.onRoomStateEvents); + this.matrixClient.getRooms().forEach((room: Room) => { + const pinned = SettingsStore.getValue("Widgets.pinned", room.roomId); + + if (pinned || WidgetUtils.getRoomWidgets(room).length) { + this.initRoom(room.roomId); + } + + if (pinned) { + this.getRoom(room.roomId).pinned = new Set(pinned); + } + + this.loadRoomWidgets(room); + }); + this.emit("update"); + } + + protected async onNotReady(): Promise { + this.matrixClient.off("RoomState.events", this.onRoomStateEvents); + this.widgetMap = new Map(); + this.roomMap = new Map(); + await this.reset({}); + } + + // We don't need this, but our contract says we do. + protected async onAction(payload: ActionPayload) { + return; + } + + private onWidgetEchoStoreUpdate(roomId: string, widgetId: string) { + this.initRoom(roomId); + this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); + this.emit("update"); + } + + private generateApps(room: Room): IApp[] { + return WidgetEchoStore.getEchoedRoomWidgets(room.roomId, WidgetUtils.getRoomWidgets(room)).map((ev) => { + return WidgetUtils.makeAppConfig( + ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(), + ); + }); + } + + private loadRoomWidgets(room: Room) { + const roomInfo = this.roomMap.get(room.roomId); + roomInfo.widgets = []; + this.generateApps(room).forEach(app => { + this.widgetMap.set(app.id, app); + roomInfo.widgets.push(app); + }); + this.emit(room.roomId); + } + + private onRoomStateEvents(ev: MatrixEvent) { + if (ev.getType() !== "im.vector.modular.widgets") return; + const roomId = ev.getRoomId(); + this.initRoom(roomId); + this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); + this.emit("update"); + } + + public getRoomId = (widgetId: string) => { + const app = this.widgetMap.get(widgetId); + if (!app) return null; + return app.roomId; + } + + public getRoom = (roomId: string) => { + return this.roomMap.get(roomId); + }; + + private onPinnedWidgetsChange = (settingName: string, roomId: string) => { + const pinned = SettingsStore.getValue(settingName, roomId); + this.initRoom(roomId); + this.getRoom(roomId).pinned = new Set(pinned); + this.emit(roomId); + this.emit("update"); + }; + + public isPinned(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + // TODO heuristic for Jitsi etc + return roomInfo ? roomInfo.pinned.has(widgetId) : false; + } + + public pinWidget(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + if (!roomInfo) return; + roomInfo.pinned.add(widgetId); + SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); + this.emit(roomId); + this.emit("update"); + } + + public unpinWidget(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + if (!roomInfo) return; + roomInfo.pinned.delete(widgetId); + SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); + this.emit(roomId); + this.emit("update"); + } + + public getApps(room: Room, pinned?: boolean): IApp[] { + const apps = this.getRoom(room.roomId).widgets; + if (pinned) { + return apps.filter(app => this.isPinned(app.id)); + } + return apps + } +} + +window.mxWidgetStore = WidgetStore.instance;