Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/feat/modal-widgets

This commit is contained in:
Michael Telatynski 2020-10-22 21:40:05 +01:00
commit 0004dd4475
88 changed files with 2241 additions and 1305 deletions

View file

@ -55,7 +55,7 @@ class WidgetEchoStore extends EventEmitter {
const widgetId = w.getStateKey();
// If there's no echo, or the echo still has a widget present, show the *old* widget
// we don't include widgets that have changed for the same reason we don't include new ones,
// ie. we'd need to fake matrix events to do so and therte's currently no need.
// ie. we'd need to fake matrix events to do so and there's currently no need.
if (!roomEchoState[widgetId] || Object.keys(roomEchoState[widgetId]).length !== 0) {
echoedWidgets.push(w);
}

View file

@ -16,12 +16,14 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { IWidget } from "matrix-widget-api";
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 RoomViewStore from "../stores/RoomViewStore";
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
import WidgetUtils from "../utils/WidgetUtils";
import {SettingLevel} from "../settings/SettingLevel";
@ -30,13 +32,9 @@ import {UPDATE_EVENT} from "./AsyncStore";
interface IState {}
export interface IApp {
id: string;
type: string;
export interface IApp extends IWidget {
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
}
@ -46,6 +44,8 @@ interface IRoomWidgets {
pinned: Record<string, boolean>;
}
export const MAX_PINNED = 3;
// TODO consolidate WidgetEchoStore into this
// TODO consolidate ActiveWidgetStore into this
export default class WidgetStore extends AsyncStoreWithClient<IState> {
@ -68,7 +68,7 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
private initRoom(roomId: string) {
if (!this.roomMap.has(roomId)) {
this.roomMap.set(roomId, {
pinned: {},
pinned: {}, // ordered
widgets: [],
});
}
@ -122,6 +122,15 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
if (!room) return;
const roomInfo = this.roomMap.get(room.roomId);
roomInfo.widgets = [];
// first clean out old widgets from the map which originate from this room
// otherwise we are out of sync with the rest of the app with stale widget events during removal
Array.from(this.widgetMap.values()).forEach(app => {
if (app.roomId === room.roomId) {
this.widgetMap.delete(app.id);
}
});
this.generateApps(room).forEach(app => {
this.widgetMap.set(app.id, app);
roomInfo.widgets.push(app);
@ -156,27 +165,34 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
public isPinned(widgetId: string) {
const roomId = this.getRoomId(widgetId);
const roomInfo = this.getRoom(roomId);
let pinned = roomInfo && roomInfo.pinned[widgetId];
// Jitsi widgets should be pinned by default
const widget = this.widgetMap.get(widgetId);
if (pinned === undefined && WidgetType.JITSI.matches(widget?.type)) pinned = true;
return pinned;
return !!this.getPinnedApps(roomId).find(w => w.id === widgetId);
}
public canPin(widgetId: string) {
// only allow pinning up to a max of two as we do not yet have grid splits
// the only case it will go to three is if you have two and then a Jitsi gets added
const roomId = this.getRoomId(widgetId);
const roomInfo = this.getRoom(roomId);
return roomInfo && Object.keys(roomInfo.pinned).filter(k => {
return roomInfo.pinned[k] && roomInfo.widgets.some(app => app.id === k);
}).length < 2;
return this.getPinnedApps(roomId).length < MAX_PINNED;
}
public pinWidget(widgetId: string) {
const roomId = this.getRoomId(widgetId);
const roomInfo = this.getRoom(roomId);
if (!roomInfo) return;
// When pinning, first confirm all the widgets (Jitsi) which were autopinned so that the order is correct
const autoPinned = this.getPinnedApps(roomId).filter(app => !roomInfo.pinned[app.id]);
autoPinned.forEach(app => {
this.setPinned(app.id, true);
});
this.setPinned(widgetId, true);
// Show the apps drawer upon the user pinning a widget
if (RoomViewStore.getRoomId() === this.getRoomId(widgetId)) {
defaultDispatcher.dispatch({
action: "appsDrawer",
show: true,
})
}
}
public unpinWidget(widgetId: string) {
@ -187,6 +203,10 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
const roomId = this.getRoomId(widgetId);
const roomInfo = this.getRoom(roomId);
if (!roomInfo) return;
if (roomInfo.pinned[widgetId] === false && value) {
// delete this before write to maintain the correct object insertion order
delete roomInfo.pinned[widgetId];
}
roomInfo.pinned[widgetId] = value;
// Clean up the pinned record
@ -201,13 +221,61 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
this.emit(UPDATE_EVENT);
}
public getApps(room: Room, pinned?: boolean): IApp[] {
const roomInfo = this.getRoom(room.roomId);
if (!roomInfo) return [];
if (pinned) {
return roomInfo.widgets.filter(app => this.isPinned(app.id));
public movePinnedWidget(widgetId: string, delta: 1 | -1) {
// TODO simplify this by changing the storage medium of pinned to an array once the Jitsi default-on goes away
const roomId = this.getRoomId(widgetId);
const roomInfo = this.getRoom(roomId);
if (!roomInfo || roomInfo.pinned[widgetId] === false) return;
const pinnedApps = this.getPinnedApps(roomId).map(app => app.id);
const i = pinnedApps.findIndex(id => id === widgetId);
if (delta > 0) {
pinnedApps.splice(i, 2, pinnedApps[i + 1], pinnedApps[i]);
} else {
pinnedApps.splice(i - 1, 2, pinnedApps[i], pinnedApps[i - 1]);
}
return roomInfo.widgets;
const reorderedPinned: IRoomWidgets["pinned"] = {};
pinnedApps.forEach(id => {
reorderedPinned[id] = true;
});
Object.keys(roomInfo.pinned).forEach(id => {
if (reorderedPinned[id] === undefined) {
reorderedPinned[id] = roomInfo.pinned[id];
}
});
roomInfo.pinned = reorderedPinned;
SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, roomInfo.pinned);
this.emit(roomId);
this.emit(UPDATE_EVENT);
}
public getPinnedApps(roomId: string): IApp[] {
// returns the apps in the order they were pinned with, up to the maximum
const roomInfo = this.getRoom(roomId);
if (!roomInfo) return [];
// Show Jitsi widgets even if the user already had the maximum pinned, instead of their latest pinned,
// except if the user already explicitly unpinned the Jitsi widget
const priorityWidget = roomInfo.widgets.find(widget => {
return roomInfo.pinned[widget.id] === undefined && WidgetType.JITSI.matches(widget.type);
});
const order = Object.keys(roomInfo.pinned).filter(k => roomInfo.pinned[k]);
let apps = order.map(wId => this.widgetMap.get(wId)).filter(Boolean);
apps = apps.slice(0, priorityWidget ? MAX_PINNED - 1 : MAX_PINNED);
if (priorityWidget) {
apps.push(priorityWidget);
}
return apps;
}
public getApps(roomId: string): IApp[] {
const roomInfo = this.getRoom(roomId);
return roomInfo?.widgets || [];
}
public doesRoomHaveConference(room: Room): boolean {

View file

@ -76,7 +76,7 @@ class ElementWidget extends Widget {
if (WidgetType.JITSI.matches(this.type)) {
return WidgetUtils.getLocalJitsiWrapperUrl({
forLocalRender: true,
auth: super.rawData?.auth, // this.rawData can call templateUrl, do this to prevent looping
auth: super.rawData?.auth as string, // this.rawData can call templateUrl, do this to prevent looping
});
}
return super.templateUrl;
@ -86,7 +86,7 @@ class ElementWidget extends Widget {
if (WidgetType.JITSI.matches(this.type)) {
return WidgetUtils.getLocalJitsiWrapperUrl({
forLocalRender: false, // The only important difference between this and templateUrl()
auth: super.rawData?.auth,
auth: super.rawData?.auth as string,
});
}
return this.templateUrl; // use this instead of super to ensure we get appropriate templating