mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-10 23:25:52 +00:00
layoutShallowEquals -> shallowEquals
This commit is contained in:
@@ -82,6 +82,7 @@ import {
|
|||||||
type ReactionOption,
|
type ReactionOption,
|
||||||
} from "../../reactions";
|
} from "../../reactions";
|
||||||
import { shallowEquals as shallowArrayEquals } from "../../utils/array";
|
import { shallowEquals as shallowArrayEquals } from "../../utils/array";
|
||||||
|
import { shallowEquals as shallowObjectEquals } from "../../utils/object";
|
||||||
import { type MediaDevices } from "../MediaDevices";
|
import { type MediaDevices } from "../MediaDevices";
|
||||||
import { constant, type Behavior } from "../Behavior";
|
import { constant, type Behavior } from "../Behavior";
|
||||||
import { E2eeType } from "../../e2ee/e2eeType";
|
import { E2eeType } from "../../e2ee/e2eeType";
|
||||||
@@ -91,7 +92,6 @@ import { HeaderStyle, type UrlParams } from "../../UrlParams";
|
|||||||
import { type ProcessorState } from "../../livekit/TrackProcessorContext";
|
import { type ProcessorState } from "../../livekit/TrackProcessorContext";
|
||||||
import { type HostBridge, nullHostBridge } from "../../HostBridge";
|
import { type HostBridge, nullHostBridge } from "../../HostBridge";
|
||||||
import {
|
import {
|
||||||
layoutShallowEquals,
|
|
||||||
type Alignment,
|
type Alignment,
|
||||||
type Layout,
|
type Layout,
|
||||||
type LayoutMedia,
|
type LayoutMedia,
|
||||||
@@ -1028,7 +1028,7 @@ export function createCallViewModel$(
|
|||||||
[ringingMedia$, screenShares$, spotlightSpeaker$, localUserMediaForPip$],
|
[ringingMedia$, screenShares$, spotlightSpeaker$, localUserMediaForPip$],
|
||||||
(ringing, screenShares, speaker, localPip) =>
|
(ringing, screenShares, speaker, localPip) =>
|
||||||
computeSpotlight({ ringing, screenShares, speaker, localPip }),
|
computeSpotlight({ ringing, screenShares, speaker, localPip }),
|
||||||
).pipe(distinctUntilChanged(layoutShallowEquals)),
|
).pipe(distinctUntilChanged(shallowObjectEquals)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const spotlight$ = scope.behavior<MediaViewModel[]>(
|
const spotlight$ = scope.behavior<MediaViewModel[]>(
|
||||||
@@ -1185,7 +1185,7 @@ export function createCallViewModel$(
|
|||||||
desktop: platform === "desktop",
|
desktop: platform === "desktop",
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
distinctUntilChanged(layoutShallowEquals),
|
distinctUntilChanged(shallowObjectEquals<LayoutMedia>),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1486,7 +1486,7 @@ export function createCallViewModel$(
|
|||||||
layoutInternals$.pipe(
|
layoutInternals$.pipe(
|
||||||
map(({ layout }) => layout),
|
map(({ layout }) => layout),
|
||||||
// Drop redundant layout updates before they would hit React.
|
// Drop redundant layout updates before they would hit React.
|
||||||
distinctUntilChanged<Layout>(layoutShallowEquals),
|
distinctUntilChanged(shallowObjectEquals<Layout>),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2026 Element Creations Ltd.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
Please see LICENSE in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { test, expect } from "vitest";
|
|
||||||
import {
|
|
||||||
type Alignment,
|
|
||||||
layoutShallowEquals,
|
|
||||||
type Layout,
|
|
||||||
} from "./layout-types";
|
|
||||||
import {
|
|
||||||
type SpotlightTileViewModel,
|
|
||||||
type GridTileViewModel,
|
|
||||||
} from "./TileViewModel";
|
|
||||||
import { BehaviorSubject } from "rxjs";
|
|
||||||
|
|
||||||
const spotlightTile = {} as unknown as SpotlightTileViewModel;
|
|
||||||
const gridTile = {} as unknown as GridTileViewModel;
|
|
||||||
const pipAlignment$ = new BehaviorSubject<Alignment>({
|
|
||||||
inline: "end",
|
|
||||||
block: "end",
|
|
||||||
});
|
|
||||||
|
|
||||||
const spotlightExpanded: Layout = {
|
|
||||||
type: "spotlight-expanded",
|
|
||||||
spotlight: spotlightTile,
|
|
||||||
pipAlignment$,
|
|
||||||
};
|
|
||||||
|
|
||||||
const spotlightPortrait: Layout = {
|
|
||||||
type: "spotlight-portrait",
|
|
||||||
spotlight: spotlightTile,
|
|
||||||
grid: [gridTile],
|
|
||||||
setVisibleTiles: () => {},
|
|
||||||
};
|
|
||||||
|
|
||||||
test("layoutShallowEquals considers a layout to be equal to its shallow clone", () =>
|
|
||||||
expect(layoutShallowEquals(spotlightExpanded, { ...spotlightExpanded })).toBe(
|
|
||||||
true,
|
|
||||||
));
|
|
||||||
|
|
||||||
test("layoutShallowEquals detects a missing key", () => {
|
|
||||||
expect(
|
|
||||||
layoutShallowEquals(spotlightExpanded, {
|
|
||||||
...spotlightExpanded,
|
|
||||||
pip: gridTile,
|
|
||||||
}),
|
|
||||||
).toBe(false);
|
|
||||||
expect(
|
|
||||||
layoutShallowEquals(
|
|
||||||
{ ...spotlightExpanded, pip: gridTile },
|
|
||||||
spotlightExpanded,
|
|
||||||
),
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("layoutShallowEquals considers grid arrays with equal contents to be equal", () =>
|
|
||||||
expect(
|
|
||||||
layoutShallowEquals(spotlightPortrait, {
|
|
||||||
...spotlightPortrait,
|
|
||||||
grid: [...spotlightPortrait.grid],
|
|
||||||
}),
|
|
||||||
).toBe(true));
|
|
||||||
|
|
||||||
test("layoutShallowEquals detects grid arrays with different contents", () =>
|
|
||||||
expect(
|
|
||||||
layoutShallowEquals(spotlightPortrait, {
|
|
||||||
...spotlightPortrait,
|
|
||||||
grid: [...spotlightPortrait.grid, gridTile],
|
|
||||||
}),
|
|
||||||
).toBe(false));
|
|
||||||
@@ -16,7 +16,6 @@ import {
|
|||||||
type SpotlightTileViewModel,
|
type SpotlightTileViewModel,
|
||||||
} from "./TileViewModel.ts";
|
} from "./TileViewModel.ts";
|
||||||
import { type Behavior } from "./Behavior.ts";
|
import { type Behavior } from "./Behavior.ts";
|
||||||
import { shallowEquals as arrayShallowEquals } from "../utils/array.ts";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The general shape of the space the call is drawn in. Called a window because
|
* The general shape of the space the call is drawn in. Called a window because
|
||||||
@@ -148,31 +147,3 @@ export type Layout =
|
|||||||
| OneOnOneDesktopLayout
|
| OneOnOneDesktopLayout
|
||||||
| OneOnOneMobileLayout
|
| OneOnOneMobileLayout
|
||||||
| PipLayout;
|
| PipLayout;
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests whether the top-level properties and array elements of `a` are equal
|
|
||||||
* to those of `b`. Useful for deduping redundant layout and layout media
|
|
||||||
* updates.
|
|
||||||
*/
|
|
||||||
export function layoutShallowEquals<T extends object>(a: T, b: T): boolean {
|
|
||||||
// If a and b have the same number of keys and every key in a is also in b,
|
|
||||||
// then they have the same keys.
|
|
||||||
const aKeys = Object.keys(a);
|
|
||||||
const bKeys = Object.keys(b);
|
|
||||||
if (aKeys.length !== bKeys.length) return false;
|
|
||||||
|
|
||||||
for (const key of aKeys) {
|
|
||||||
if (!(key in b)) return false;
|
|
||||||
|
|
||||||
// Now check that they have the same values.
|
|
||||||
const aValue = (a as any)[key];
|
|
||||||
const bValue = (b as any)[key];
|
|
||||||
if (Array.isArray(aValue) && Array.isArray(bValue)) {
|
|
||||||
// Special case for arrays so we can detect when the grid tiles arrays are
|
|
||||||
// essentially the same.
|
|
||||||
if (!arrayShallowEquals(aValue, bValue)) return false;
|
|
||||||
} else if (aValue !== bValue) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test, expect } from "vitest";
|
||||||
|
import { shallowEquals } from "./object";
|
||||||
|
|
||||||
|
interface Thing {
|
||||||
|
type: string;
|
||||||
|
items: object[];
|
||||||
|
value: number;
|
||||||
|
extra?: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = {};
|
||||||
|
const withArray: Thing = { type: "a", items: [item], value: 1 };
|
||||||
|
|
||||||
|
test("shallowEquals considers an object to be equal to its shallow clone", () =>
|
||||||
|
expect(shallowEquals(withArray, { ...withArray })).toBe(true));
|
||||||
|
|
||||||
|
test("shallowEquals detects a missing key", () => {
|
||||||
|
expect(shallowEquals(withArray, { ...withArray, extra: item })).toBe(false);
|
||||||
|
expect(shallowEquals({ ...withArray, extra: item }, withArray)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shallowEquals detects a differing value", () =>
|
||||||
|
expect(shallowEquals(withArray, { ...withArray, value: 2 })).toBe(false));
|
||||||
|
|
||||||
|
test("shallowEquals considers arrays with equal contents to be equal", () =>
|
||||||
|
expect(
|
||||||
|
shallowEquals(withArray, { ...withArray, items: [...withArray.items] }),
|
||||||
|
).toBe(true));
|
||||||
|
|
||||||
|
test("shallowEquals detects arrays with different contents", () =>
|
||||||
|
expect(
|
||||||
|
shallowEquals(withArray, { ...withArray, items: [...withArray.items, {}] }),
|
||||||
|
).toBe(false));
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Element Creations Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { shallowEquals as arrayShallowEquals } from "./array.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether two objects are equal by shallow comparison of their
|
||||||
|
* top-level properties. Array-valued properties are themselves compared
|
||||||
|
* shallowly, so two objects holding distinct arrays with the same elements are
|
||||||
|
* considered equal.
|
||||||
|
*/
|
||||||
|
export function shallowEquals<T extends object>(
|
||||||
|
a: Readonly<T>,
|
||||||
|
b: Readonly<T>,
|
||||||
|
): boolean {
|
||||||
|
// If a and b have the same number of keys and every key in a is also in b,
|
||||||
|
// then they have the same keys.
|
||||||
|
const aKeys = Object.keys(a);
|
||||||
|
const bKeys = Object.keys(b);
|
||||||
|
if (aKeys.length !== bKeys.length) return false;
|
||||||
|
|
||||||
|
for (const key of aKeys) {
|
||||||
|
if (!(key in b)) return false;
|
||||||
|
|
||||||
|
// Now check that they have the same values.
|
||||||
|
const aValue = (a as any)[key];
|
||||||
|
const bValue = (b as any)[key];
|
||||||
|
if (Array.isArray(aValue) && Array.isArray(bValue)) {
|
||||||
|
if (!arrayShallowEquals(aValue, bValue)) return false;
|
||||||
|
} else if (aValue !== bValue) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user