Merge pull request #4803 from matrix-org/travis/room-list/setting-diff
Only fire setting changes for changed settings
This commit is contained in:
commit
26317b6826
11 changed files with 126 additions and 19 deletions
|
@ -25,6 +25,7 @@ import RoomViewStore from "./stores/RoomViewStore";
|
||||||
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import {Capability} from "./widgets/WidgetApi";
|
import {Capability} from "./widgets/WidgetApi";
|
||||||
|
import {objectClone} from "./utils/objects";
|
||||||
|
|
||||||
const WIDGET_API_VERSION = '0.0.2'; // Current API version
|
const WIDGET_API_VERSION = '0.0.2'; // Current API version
|
||||||
const SUPPORTED_WIDGET_API_VERSIONS = [
|
const SUPPORTED_WIDGET_API_VERSIONS = [
|
||||||
|
@ -247,7 +248,7 @@ export default class FromWidgetPostMessageApi {
|
||||||
* @param {Object} res Response data
|
* @param {Object} res Response data
|
||||||
*/
|
*/
|
||||||
sendResponse(event, res) {
|
sendResponse(event, res) {
|
||||||
const data = JSON.parse(JSON.stringify(event.data));
|
const data = objectClone(event.data);
|
||||||
data.response = res;
|
data.response = res;
|
||||||
event.source.postMessage(data, event.origin);
|
event.source.postMessage(data, event.origin);
|
||||||
}
|
}
|
||||||
|
@ -260,7 +261,7 @@ export default class FromWidgetPostMessageApi {
|
||||||
*/
|
*/
|
||||||
sendError(event, msg, nestedError) {
|
sendError(event, msg, nestedError) {
|
||||||
console.error('Action:' + event.data.action + ' failed with message: ' + msg);
|
console.error('Action:' + event.data.action + ' failed with message: ' + msg);
|
||||||
const data = JSON.parse(JSON.stringify(event.data));
|
const data = objectClone(event.data);
|
||||||
data.response = {
|
data.response = {
|
||||||
error: {
|
error: {
|
||||||
message: msg,
|
message: msg,
|
||||||
|
|
|
@ -244,16 +244,17 @@ import RoomViewStore from './stores/RoomViewStore';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
||||||
import {WidgetType} from "./widgets/WidgetType";
|
import {WidgetType} from "./widgets/WidgetType";
|
||||||
|
import {objectClone} from "./utils/objects";
|
||||||
|
|
||||||
function sendResponse(event, res) {
|
function sendResponse(event, res) {
|
||||||
const data = JSON.parse(JSON.stringify(event.data));
|
const data = objectClone(event.data);
|
||||||
data.response = res;
|
data.response = res;
|
||||||
event.source.postMessage(data, event.origin);
|
event.source.postMessage(data, event.origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendError(event, msg, nestedError) {
|
function sendError(event, msg, nestedError) {
|
||||||
console.error("Action:" + event.data.action + " failed with message: " + msg);
|
console.error("Action:" + event.data.action + " failed with message: " + msg);
|
||||||
const data = JSON.parse(JSON.stringify(event.data));
|
const data = objectClone(event.data);
|
||||||
data.response = {
|
data.response = {
|
||||||
error: {
|
error: {
|
||||||
message: msg,
|
message: msg,
|
||||||
|
|
|
@ -18,6 +18,7 @@ import React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import {_t, pickBestLanguage} from "../../../languageHandler";
|
import {_t, pickBestLanguage} from "../../../languageHandler";
|
||||||
import * as sdk from "../../..";
|
import * as sdk from "../../..";
|
||||||
|
import {objectClone} from "../../../utils/objects";
|
||||||
|
|
||||||
export default class InlineTermsAgreement extends React.Component {
|
export default class InlineTermsAgreement extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -56,7 +57,7 @@ export default class InlineTermsAgreement extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_togglePolicy = (index) => {
|
_togglePolicy = (index) => {
|
||||||
const policies = JSON.parse(JSON.stringify(this.state.policies)); // deep & cheap clone
|
const policies = objectClone(this.state.policies);
|
||||||
policies[index].checked = !policies[index].checked;
|
policies[index].checked = !policies[index].checked;
|
||||||
this.setState({policies});
|
this.setState({policies});
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
|
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
|
||||||
import {SettingLevel} from "../SettingsStore";
|
import {SettingLevel} from "../SettingsStore";
|
||||||
|
import {objectClone, objectKeyChanges} from "../../utils/objects";
|
||||||
|
|
||||||
const BREADCRUMBS_LEGACY_EVENT_TYPE = "im.vector.riot.breadcrumb_rooms";
|
const BREADCRUMBS_LEGACY_EVENT_TYPE = "im.vector.riot.breadcrumb_rooms";
|
||||||
const BREADCRUMBS_EVENT_TYPE = "im.vector.setting.breadcrumbs";
|
const BREADCRUMBS_EVENT_TYPE = "im.vector.setting.breadcrumbs";
|
||||||
|
@ -45,7 +46,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
||||||
newClient.on("accountData", this._onAccountData);
|
newClient.on("accountData", this._onAccountData);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onAccountData(event) {
|
_onAccountData(event, prevEvent) {
|
||||||
if (event.getType() === "org.matrix.preview_urls") {
|
if (event.getType() === "org.matrix.preview_urls") {
|
||||||
let val = event.getContent()['disable'];
|
let val = event.getContent()['disable'];
|
||||||
if (typeof(val) !== "boolean") {
|
if (typeof(val) !== "boolean") {
|
||||||
|
@ -56,8 +57,10 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
||||||
|
|
||||||
this._watchers.notifyUpdate("urlPreviewsEnabled", null, SettingLevel.ACCOUNT, val);
|
this._watchers.notifyUpdate("urlPreviewsEnabled", null, SettingLevel.ACCOUNT, val);
|
||||||
} else if (event.getType() === "im.vector.web.settings") {
|
} else if (event.getType() === "im.vector.web.settings") {
|
||||||
// We can't really discern what changed, so trigger updates for everything
|
// Figure out what changed and fire those updates
|
||||||
for (const settingName of Object.keys(event.getContent())) {
|
const prevContent = prevEvent ? prevEvent.getContent() : {};
|
||||||
|
const changedSettings = objectKeyChanges(prevContent, event.getContent());
|
||||||
|
for (const settingName of changedSettings) {
|
||||||
const val = event.getContent()[settingName];
|
const val = event.getContent()[settingName];
|
||||||
this._watchers.notifyUpdate(settingName, null, SettingLevel.ACCOUNT, val);
|
this._watchers.notifyUpdate(settingName, null, SettingLevel.ACCOUNT, val);
|
||||||
}
|
}
|
||||||
|
@ -159,7 +162,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
||||||
|
|
||||||
const event = cli.getAccountData(eventType);
|
const event = cli.getAccountData(eventType);
|
||||||
if (!event || !event.getContent()) return null;
|
if (!event || !event.getContent()) return null;
|
||||||
return event.getContent();
|
return objectClone(event.getContent()); // clone to prevent mutation
|
||||||
}
|
}
|
||||||
|
|
||||||
_notifyBreadcrumbsUpdate(event) {
|
_notifyBreadcrumbsUpdate(event) {
|
||||||
|
|
|
@ -42,6 +42,10 @@ export default class MatrixClientBackedSettingsHandler extends SettingsHandler {
|
||||||
MatrixClientBackedSettingsHandler._instances.push(this);
|
MatrixClientBackedSettingsHandler._instances.push(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get client() {
|
||||||
|
return MatrixClientBackedSettingsHandler._matrixClient;
|
||||||
|
}
|
||||||
|
|
||||||
initMatrixClient() {
|
initMatrixClient() {
|
||||||
console.warn("initMatrixClient not overridden");
|
console.warn("initMatrixClient not overridden");
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
|
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
|
||||||
import {SettingLevel} from "../SettingsStore";
|
import {SettingLevel} from "../SettingsStore";
|
||||||
|
import {objectClone, objectKeyChanges} from "../../utils/objects";
|
||||||
|
|
||||||
const ALLOWED_WIDGETS_EVENT_TYPE = "im.vector.setting.allowed_widgets";
|
const ALLOWED_WIDGETS_EVENT_TYPE = "im.vector.setting.allowed_widgets";
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
|
||||||
newClient.on("Room.accountData", this._onAccountData);
|
newClient.on("Room.accountData", this._onAccountData);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onAccountData(event, room) {
|
_onAccountData(event, room, prevEvent) {
|
||||||
const roomId = room.roomId;
|
const roomId = room.roomId;
|
||||||
|
|
||||||
if (event.getType() === "org.matrix.room.preview_urls") {
|
if (event.getType() === "org.matrix.room.preview_urls") {
|
||||||
|
@ -55,8 +56,10 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
|
||||||
} else if (event.getType() === "org.matrix.room.color_scheme") {
|
} else if (event.getType() === "org.matrix.room.color_scheme") {
|
||||||
this._watchers.notifyUpdate("roomColor", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent());
|
this._watchers.notifyUpdate("roomColor", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent());
|
||||||
} else if (event.getType() === "im.vector.web.settings") {
|
} else if (event.getType() === "im.vector.web.settings") {
|
||||||
// We can't really discern what changed, so trigger updates for everything
|
// Figure out what changed and fire those updates
|
||||||
for (const settingName of Object.keys(event.getContent())) {
|
const prevContent = prevEvent ? prevEvent.getContent() : {};
|
||||||
|
const changedSettings = objectKeyChanges(prevContent, event.getContent());
|
||||||
|
for (const settingName of changedSettings) {
|
||||||
const val = event.getContent()[settingName];
|
const val = event.getContent()[settingName];
|
||||||
this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_ACCOUNT, val);
|
this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_ACCOUNT, val);
|
||||||
}
|
}
|
||||||
|
@ -134,6 +137,6 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
|
||||||
|
|
||||||
const event = room.getAccountData(eventType);
|
const event = room.getAccountData(eventType);
|
||||||
if (!event || !event.getContent()) return null;
|
if (!event || !event.getContent()) return null;
|
||||||
return event.getContent();
|
return objectClone(event.getContent()); // clone to prevent mutation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
|
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
|
||||||
import {SettingLevel} from "../SettingsStore";
|
import {SettingLevel} from "../SettingsStore";
|
||||||
|
import {objectClone, objectKeyChanges} from "../../utils/objects";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets and sets settings at the "room" level.
|
* Gets and sets settings at the "room" level.
|
||||||
|
@ -38,8 +39,15 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
|
||||||
newClient.on("RoomState.events", this._onEvent);
|
newClient.on("RoomState.events", this._onEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onEvent(event) {
|
_onEvent(event, state, prevEvent) {
|
||||||
const roomId = event.getRoomId();
|
const roomId = event.getRoomId();
|
||||||
|
const room = this.client.getRoom(roomId);
|
||||||
|
|
||||||
|
// Note: the tests often fire setting updates that don't have rooms in the store, so
|
||||||
|
// we fail softly here. We shouldn't assume that the state being fired is current
|
||||||
|
// state, but we also don't need to explode just because we didn't find a room.
|
||||||
|
if (!room) console.warn(`Unknown room caused setting update: ${roomId}`);
|
||||||
|
if (room && state !== room.currentState) return; // ignore state updates which are not current
|
||||||
|
|
||||||
if (event.getType() === "org.matrix.room.preview_urls") {
|
if (event.getType() === "org.matrix.room.preview_urls") {
|
||||||
let val = event.getContent()['disable'];
|
let val = event.getContent()['disable'];
|
||||||
|
@ -51,8 +59,10 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
|
||||||
|
|
||||||
this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM, val);
|
this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM, val);
|
||||||
} else if (event.getType() === "im.vector.web.settings") {
|
} else if (event.getType() === "im.vector.web.settings") {
|
||||||
// We can't really discern what changed, so trigger updates for everything
|
// Figure out what changed and fire those updates
|
||||||
for (const settingName of Object.keys(event.getContent())) {
|
const prevContent = prevEvent ? prevEvent.getContent() : {};
|
||||||
|
const changedSettings = objectKeyChanges(prevContent, event.getContent());
|
||||||
|
for (const settingName of changedSettings) {
|
||||||
this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM, event.getContent()[settingName]);
|
this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM, event.getContent()[settingName]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,6 +117,6 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
|
||||||
|
|
||||||
const event = room.currentState.getStateEvents(eventType, "");
|
const event = room.currentState.getStateEvents(eventType, "");
|
||||||
if (!event || !event.getContent()) return null;
|
if (!event || !event.getContent()) return null;
|
||||||
return event.getContent();
|
return objectClone(event.getContent()); // clone to prevent mutation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {IntegrationManagers} from "../integrations/IntegrationManagers";
|
||||||
import {Capability} from "../widgets/WidgetApi";
|
import {Capability} from "../widgets/WidgetApi";
|
||||||
import {Room} from "matrix-js-sdk/src/models/room";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
import {WidgetType} from "../widgets/WidgetType";
|
import {WidgetType} from "../widgets/WidgetType";
|
||||||
|
import {objectClone} from "./objects";
|
||||||
|
|
||||||
export default class WidgetUtils {
|
export default class WidgetUtils {
|
||||||
/* Returns true if user is able to send state events to modify widgets in this room
|
/* Returns true if user is able to send state events to modify widgets in this room
|
||||||
|
@ -222,7 +223,7 @@ export default class WidgetUtils {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
// Get the current widgets and clone them before we modify them, otherwise
|
// Get the current widgets and clone them before we modify them, otherwise
|
||||||
// we'll modify the content of the old event.
|
// we'll modify the content of the old event.
|
||||||
const userWidgets = JSON.parse(JSON.stringify(WidgetUtils.getUserWidgets()));
|
const userWidgets = objectClone(WidgetUtils.getUserWidgets());
|
||||||
|
|
||||||
// Delete existing widget with ID
|
// Delete existing widget with ID
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -46,6 +46,28 @@ export function arrayDiff<T>(a: T[], b: T[]): { added: T[], removed: T[] } {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the union of two arrays.
|
||||||
|
* @param a The first array. Must be defined.
|
||||||
|
* @param b The second array. Must be defined.
|
||||||
|
* @returns The union of the arrays.
|
||||||
|
*/
|
||||||
|
export function arrayUnion<T>(a: T[], b: T[]): T[] {
|
||||||
|
return a.filter(i => b.includes(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges arrays, deduping contents using a Set.
|
||||||
|
* @param a The arrays to merge.
|
||||||
|
* @returns The merged array.
|
||||||
|
*/
|
||||||
|
export function arrayMerge<T>(...a: T[][]): T[] {
|
||||||
|
return Array.from(a.reduce((c, v) => {
|
||||||
|
v.forEach(i => c.add(i));
|
||||||
|
return c;
|
||||||
|
}, new Set<T>()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper functions to perform LINQ-like queries on arrays.
|
* Helper functions to perform LINQ-like queries on arrays.
|
||||||
*/
|
*/
|
||||||
|
|
60
src/utils/objects.ts
Normal file
60
src/utils/objects.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
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 { arrayDiff, arrayMerge, arrayUnion } from "./arrays";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the keys added, changed, and removed between two objects.
|
||||||
|
* For changes, simple triple equal comparisons are done, not in-depth
|
||||||
|
* tree checking.
|
||||||
|
* @param a The first object. Must be defined.
|
||||||
|
* @param b The second object. Must be defined.
|
||||||
|
* @returns The difference between the keys of each object.
|
||||||
|
*/
|
||||||
|
export function objectDiff(a: any, b: any): { changed: string[], added: string[], removed: string[] } {
|
||||||
|
const aKeys = Object.keys(a);
|
||||||
|
const bKeys = Object.keys(b);
|
||||||
|
const keyDiff = arrayDiff(aKeys, bKeys);
|
||||||
|
const possibleChanges = arrayUnion(aKeys, bKeys);
|
||||||
|
const changes = possibleChanges.filter(k => a[k] !== b[k]);
|
||||||
|
|
||||||
|
return {changed: changes, added: keyDiff.added, removed: keyDiff.removed};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the key changes (added, removed, or value difference) between
|
||||||
|
* two objects. Triple equals is used to compare values, not in-depth tree
|
||||||
|
* checking.
|
||||||
|
* @param a The first object. Must be defined.
|
||||||
|
* @param b The second object. Must be defined.
|
||||||
|
* @returns The keys which have been added, removed, or changed between the
|
||||||
|
* two objects.
|
||||||
|
*/
|
||||||
|
export function objectKeyChanges(a: any, b: any): string[] {
|
||||||
|
const diff = objectDiff(a, b);
|
||||||
|
return arrayMerge(diff.removed, diff.added, diff.changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clones an object by running it through JSON parsing. Note that this
|
||||||
|
* will destroy any complicated object types which do not translate to
|
||||||
|
* JSON.
|
||||||
|
* @param obj The object to clone.
|
||||||
|
* @returns The cloned object
|
||||||
|
*/
|
||||||
|
export function objectClone(obj: any): any {
|
||||||
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
||||||
|
|
||||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
|
import { objectClone } from "../utils/objects";
|
||||||
|
|
||||||
export enum Capability {
|
export enum Capability {
|
||||||
Screenshot = "m.capability.screenshot",
|
Screenshot = "m.capability.screenshot",
|
||||||
|
@ -140,7 +141,7 @@ export class WidgetApi extends EventEmitter {
|
||||||
private replyToRequest(payload: ToWidgetRequest, reply: any) {
|
private replyToRequest(payload: ToWidgetRequest, reply: any) {
|
||||||
if (!window.parent) return;
|
if (!window.parent) return;
|
||||||
|
|
||||||
const request = JSON.parse(JSON.stringify(payload));
|
const request = objectClone(payload);
|
||||||
request.response = reply;
|
request.response = reply;
|
||||||
|
|
||||||
window.parent.postMessage(request, this.origin);
|
window.parent.postMessage(request, this.origin);
|
||||||
|
|
Loading…
Reference in a new issue