mirror of https://github.com/vector-im/riot-web
Merge pull request #5086 from matrix-org/t3chguy/notifications12
Create Map comparison utilities and convert Hooks to Typescriptpull/21833/head
commit
0da31de5ad
|
@ -245,6 +245,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
if (doUpdate) {
|
if (doUpdate) {
|
||||||
// We have to break our reference to the room list store if we want to be able to
|
// We have to break our reference to the room list store if we want to be able to
|
||||||
// diff the object for changes, so do that.
|
// diff the object for changes, so do that.
|
||||||
|
// @ts-ignore - ITagMap is ts-ignored so this will have to be too
|
||||||
const newSublists = objectWithOnly(newLists, newListIds);
|
const newSublists = objectWithOnly(newLists, newListIds);
|
||||||
const sublists = objectShallowClone(newSublists, (k, v) => arrayFastClone(v));
|
const sublists = objectShallowClone(newSublists, (k, v) => arrayFastClone(v));
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import {useState, useEffect, DependencyList} from 'react';
|
||||||
|
|
||||||
export const useAsyncMemo = (fn, deps, initialValue) => {
|
type Fn<T> = () => Promise<T>;
|
||||||
|
|
||||||
|
export const useAsyncMemo = <T>(fn: Fn<T>, deps: DependencyList, initialValue?: T) => {
|
||||||
const [value, setValue] = useState(initialValue);
|
const [value, setValue] = useState(initialValue);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fn().then(setValue);
|
fn().then(setValue);
|
|
@ -15,11 +15,14 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {useRef, useEffect} from "react";
|
import {useRef, useEffect} from "react";
|
||||||
|
import type {EventEmitter} from "events";
|
||||||
|
|
||||||
|
type Handler = (...args: any[]) => void;
|
||||||
|
|
||||||
// Hook to wrap event emitter on and removeListener in hook lifecycle
|
// Hook to wrap event emitter on and removeListener in hook lifecycle
|
||||||
export const useEventEmitter = (emitter, eventName, handler) => {
|
export const useEventEmitter = (emitter: EventEmitter, eventName: string | symbol, handler: Handler) => {
|
||||||
// Create a ref that stores handler
|
// Create a ref that stores handler
|
||||||
const savedHandler = useRef();
|
const savedHandler = useRef(handler);
|
||||||
|
|
||||||
// Update ref.current value if handler changes.
|
// Update ref.current value if handler changes.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
|
@ -18,7 +18,7 @@ import {useEffect, useState} from "react";
|
||||||
import SettingsStore from '../settings/SettingsStore';
|
import SettingsStore from '../settings/SettingsStore';
|
||||||
|
|
||||||
// Hook to fetch the value of a setting and dynamically update when it changes
|
// Hook to fetch the value of a setting and dynamically update when it changes
|
||||||
export const useSettingValue = (settingName, roomId = null, excludeDefault = false) => {
|
export const useSettingValue = (settingName: string, roomId: string = null, excludeDefault = false) => {
|
||||||
const [value, setValue] = useState(SettingsStore.getValue(settingName, roomId, excludeDefault));
|
const [value, setValue] = useState(SettingsStore.getValue(settingName, roomId, excludeDefault));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -35,7 +35,7 @@ export const useSettingValue = (settingName, roomId = null, excludeDefault = fal
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hook to fetch whether a feature is enabled and dynamically update when that changes
|
// Hook to fetch whether a feature is enabled and dynamically update when that changes
|
||||||
export const useFeatureEnabled = (featureName, roomId = null) => {
|
export const useFeatureEnabled = (featureName: string, roomId: string = null) => {
|
||||||
const [enabled, setEnabled] = useState(SettingsStore.isFeatureEnabled(featureName, roomId));
|
const [enabled, setEnabled] = useState(SettingsStore.isFeatureEnabled(featureName, roomId));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
|
@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {useState} from 'react';
|
import {useState} from "react";
|
||||||
|
|
||||||
// Hook to simplify toggling of a boolean state value
|
// Hook to simplify toggling of a boolean state value
|
||||||
// Returns value, method to toggle boolean value and method to set the boolean value
|
// Returns value, method to toggle boolean value and method to set the boolean value
|
||||||
export const useStateToggle = (initialValue) => {
|
export const useStateToggle = (initialValue: boolean) => {
|
||||||
const [value, setValue] = useState(Boolean(initialValue));
|
const [value, setValue] = useState(initialValue);
|
||||||
const toggleValue = () => {
|
const toggleValue = () => {
|
||||||
setValue(!value);
|
setValue(!value);
|
||||||
};
|
};
|
|
@ -59,7 +59,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
||||||
} else if (event.getType() === "im.vector.web.settings") {
|
} else if (event.getType() === "im.vector.web.settings") {
|
||||||
// Figure out what changed and fire those updates
|
// Figure out what changed and fire those updates
|
||||||
const prevContent = prevEvent ? prevEvent.getContent() : {};
|
const prevContent = prevEvent ? prevEvent.getContent() : {};
|
||||||
const changedSettings = objectKeyChanges(prevContent, event.getContent());
|
const changedSettings = objectKeyChanges<Record<string, any>>(prevContent, event.getContent());
|
||||||
for (const settingName of changedSettings) {
|
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);
|
||||||
|
|
|
@ -59,7 +59,7 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
|
||||||
} else if (event.getType() === "im.vector.web.settings") {
|
} else if (event.getType() === "im.vector.web.settings") {
|
||||||
// Figure out what changed and fire those updates
|
// Figure out what changed and fire those updates
|
||||||
const prevContent = prevEvent ? prevEvent.getContent() : {};
|
const prevContent = prevEvent ? prevEvent.getContent() : {};
|
||||||
const changedSettings = objectKeyChanges(prevContent, event.getContent());
|
const changedSettings = objectKeyChanges<Record<string, any>>(prevContent, event.getContent());
|
||||||
for (const settingName of changedSettings) {
|
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);
|
||||||
|
|
|
@ -65,9 +65,10 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
|
||||||
} else if (event.getType() === "im.vector.web.settings") {
|
} else if (event.getType() === "im.vector.web.settings") {
|
||||||
// Figure out what changed and fire those updates
|
// Figure out what changed and fire those updates
|
||||||
const prevContent = prevEvent ? prevEvent.getContent() : {};
|
const prevContent = prevEvent ? prevEvent.getContent() : {};
|
||||||
const changedSettings = objectKeyChanges(prevContent, event.getContent());
|
const changedSettings = objectKeyChanges<Record<string, any>>(prevContent, event.getContent());
|
||||||
for (const settingName of changedSettings) {
|
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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
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 Maps.
|
||||||
|
* For changes, simple triple equal comparisons are done, not in-depth tree checking.
|
||||||
|
* @param a The first Map. Must be defined.
|
||||||
|
* @param b The second Map. Must be defined.
|
||||||
|
* @returns The difference between the keys of each Map.
|
||||||
|
*/
|
||||||
|
export function mapDiff<K, V>(a: Map<K, V>, b: Map<K, V>): { changed: K[], added: K[], removed: K[] } {
|
||||||
|
const aKeys = [...a.keys()];
|
||||||
|
const bKeys = [...b.keys()];
|
||||||
|
const keyDiff = arrayDiff(aKeys, bKeys);
|
||||||
|
const possibleChanges = arrayUnion(aKeys, bKeys);
|
||||||
|
const changes = possibleChanges.filter(k => a.get(k) !== b.get(k));
|
||||||
|
|
||||||
|
return {changed: changes, added: keyDiff.added, removed: keyDiff.removed};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the key changes (added, removed, or value difference) between two Maps.
|
||||||
|
* Triple equals is used to compare values, not in-depth tree checking.
|
||||||
|
* @param a The first Map. Must be defined.
|
||||||
|
* @param b The second Map. Must be defined.
|
||||||
|
* @returns The keys which have been added, removed, or changed between the two Maps.
|
||||||
|
*/
|
||||||
|
export function mapKeyChanges<K, V>(a: Map<K, V>, b: Map<K, V>): K[] {
|
||||||
|
const diff = mapDiff(a, b);
|
||||||
|
return arrayMerge(diff.removed, diff.added, diff.changed);
|
||||||
|
}
|
|
@ -16,15 +16,17 @@ limitations under the License.
|
||||||
|
|
||||||
import { arrayDiff, arrayHasDiff, arrayMerge, arrayUnion } from "./arrays";
|
import { arrayDiff, arrayHasDiff, arrayMerge, arrayUnion } from "./arrays";
|
||||||
|
|
||||||
|
type ObjectExcluding<O extends {}, P extends (keyof O)[]> = {[k in Exclude<keyof O, P[number]>]: O[k]};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a new object which represents the provided object, excluding some properties.
|
* Gets a new object which represents the provided object, excluding some properties.
|
||||||
* @param a The object to strip properties of. Must be defined.
|
* @param a The object to strip properties of. Must be defined.
|
||||||
* @param props The property names to remove.
|
* @param props The property names to remove.
|
||||||
* @returns The new object without the provided properties.
|
* @returns The new object without the provided properties.
|
||||||
*/
|
*/
|
||||||
export function objectExcluding(a: any, props: string[]): any {
|
export function objectExcluding<O extends {}, P extends Array<keyof O>>(a: O, props: P): ObjectExcluding<O, P> {
|
||||||
// We use a Map to avoid hammering the `delete` keyword, which is slow and painful.
|
// We use a Map to avoid hammering the `delete` keyword, which is slow and painful.
|
||||||
const tempMap = new Map<string, any>(Object.entries(a));
|
const tempMap = new Map<keyof O, any>(Object.entries(a) as [keyof O, any][]);
|
||||||
for (const prop of props) {
|
for (const prop of props) {
|
||||||
tempMap.delete(prop);
|
tempMap.delete(prop);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +35,7 @@ export function objectExcluding(a: any, props: string[]): any {
|
||||||
return Array.from(tempMap.entries()).reduce((c, [k, v]) => {
|
return Array.from(tempMap.entries()).reduce((c, [k, v]) => {
|
||||||
c[k] = v;
|
c[k] = v;
|
||||||
return c;
|
return c;
|
||||||
}, {});
|
}, {} as O);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,13 +45,13 @@ export function objectExcluding(a: any, props: string[]): any {
|
||||||
* @param props The property names to keep.
|
* @param props The property names to keep.
|
||||||
* @returns The new object with only the provided properties.
|
* @returns The new object with only the provided properties.
|
||||||
*/
|
*/
|
||||||
export function objectWithOnly(a: any, props: string[]): any {
|
export function objectWithOnly<O extends {}, P extends Array<keyof O>>(a: O, props: P): {[k in P[number]]: O[k]} {
|
||||||
const existingProps = Object.keys(a);
|
const existingProps = Object.keys(a) as (keyof O)[];
|
||||||
const diff = arrayDiff(existingProps, props);
|
const diff = arrayDiff(existingProps, props);
|
||||||
if (diff.removed.length === 0) {
|
if (diff.removed.length === 0) {
|
||||||
return objectShallowClone(a);
|
return objectShallowClone(a);
|
||||||
} else {
|
} else {
|
||||||
return objectExcluding(a, diff.removed);
|
return objectExcluding(a, diff.removed) as {[k in P[number]]: O[k]};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,9 +66,9 @@ export function objectWithOnly(a: any, props: string[]): any {
|
||||||
* First argument is the property key with the second being the current value.
|
* First argument is the property key with the second being the current value.
|
||||||
* @returns A cloned object.
|
* @returns A cloned object.
|
||||||
*/
|
*/
|
||||||
export function objectShallowClone(a: any, propertyCloner?: (k: string, v: any) => any): any {
|
export function objectShallowClone<O extends {}>(a: O, propertyCloner?: (k: keyof O, v: O[keyof O]) => any): O {
|
||||||
const newObj = {};
|
const newObj = {} as O;
|
||||||
for (const [k, v] of Object.entries(a)) {
|
for (const [k, v] of Object.entries(a) as [keyof O, O[keyof O]][]) {
|
||||||
newObj[k] = v;
|
newObj[k] = v;
|
||||||
if (propertyCloner) {
|
if (propertyCloner) {
|
||||||
newObj[k] = propertyCloner(k, v);
|
newObj[k] = propertyCloner(k, v);
|
||||||
|
@ -83,7 +85,7 @@ export function objectShallowClone(a: any, propertyCloner?: (k: string, v: any)
|
||||||
* @param b The second object. Must be defined.
|
* @param b The second object. Must be defined.
|
||||||
* @returns True if there's a difference between the objects, false otherwise
|
* @returns True if there's a difference between the objects, false otherwise
|
||||||
*/
|
*/
|
||||||
export function objectHasDiff(a: any, b: any): boolean {
|
export function objectHasDiff<O extends {}>(a: O, b: O): boolean {
|
||||||
const aKeys = Object.keys(a);
|
const aKeys = Object.keys(a);
|
||||||
const bKeys = Object.keys(b);
|
const bKeys = Object.keys(b);
|
||||||
if (arrayHasDiff(aKeys, bKeys)) return true;
|
if (arrayHasDiff(aKeys, bKeys)) return true;
|
||||||
|
@ -92,6 +94,8 @@ export function objectHasDiff(a: any, b: any): boolean {
|
||||||
return possibleChanges.some(k => a[k] !== b[k]);
|
return possibleChanges.some(k => a[k] !== b[k]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Diff<K> = { changed: K[], added: K[], removed: K[] };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the keys added, changed, and removed between two objects.
|
* Determines the keys added, changed, and removed between two objects.
|
||||||
* For changes, simple triple equal comparisons are done, not in-depth
|
* For changes, simple triple equal comparisons are done, not in-depth
|
||||||
|
@ -100,9 +104,9 @@ export function objectHasDiff(a: any, b: any): boolean {
|
||||||
* @param b The second object. Must be defined.
|
* @param b The second object. Must be defined.
|
||||||
* @returns The difference between the keys of each object.
|
* @returns The difference between the keys of each object.
|
||||||
*/
|
*/
|
||||||
export function objectDiff(a: any, b: any): { changed: string[], added: string[], removed: string[] } {
|
export function objectDiff<O extends {}>(a: O, b: O): Diff<keyof O> {
|
||||||
const aKeys = Object.keys(a);
|
const aKeys = Object.keys(a) as (keyof O)[];
|
||||||
const bKeys = Object.keys(b);
|
const bKeys = Object.keys(b) as (keyof O)[];
|
||||||
const keyDiff = arrayDiff(aKeys, bKeys);
|
const keyDiff = arrayDiff(aKeys, bKeys);
|
||||||
const possibleChanges = arrayUnion(aKeys, bKeys);
|
const possibleChanges = arrayUnion(aKeys, bKeys);
|
||||||
const changes = possibleChanges.filter(k => a[k] !== b[k]);
|
const changes = possibleChanges.filter(k => a[k] !== b[k]);
|
||||||
|
@ -119,7 +123,7 @@ export function objectDiff(a: any, b: any): { changed: string[], added: string[]
|
||||||
* @returns The keys which have been added, removed, or changed between the
|
* @returns The keys which have been added, removed, or changed between the
|
||||||
* two objects.
|
* two objects.
|
||||||
*/
|
*/
|
||||||
export function objectKeyChanges(a: any, b: any): string[] {
|
export function objectKeyChanges<O extends {}>(a: O, b: O): (keyof O)[] {
|
||||||
const diff = objectDiff(a, b);
|
const diff = objectDiff(a, b);
|
||||||
return arrayMerge(diff.removed, diff.added, diff.changed);
|
return arrayMerge(diff.removed, diff.added, diff.changed);
|
||||||
}
|
}
|
||||||
|
@ -131,6 +135,6 @@ export function objectKeyChanges(a: any, b: any): string[] {
|
||||||
* @param obj The object to clone.
|
* @param obj The object to clone.
|
||||||
* @returns The cloned object
|
* @returns The cloned object
|
||||||
*/
|
*/
|
||||||
export function objectClone(obj: any): any {
|
export function objectClone<O extends {}>(obj: O): O {
|
||||||
return JSON.parse(JSON.stringify(obj));
|
return JSON.parse(JSON.stringify(obj));
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,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 = objectClone(payload);
|
const request: ToWidgetRequest & {response?: any} = objectClone(payload);
|
||||||
request.response = reply;
|
request.response = reply;
|
||||||
|
|
||||||
window.parent.postMessage(request, this.origin);
|
window.parent.postMessage(request, this.origin);
|
||||||
|
|
Loading…
Reference in New Issue