React to theme changes in widget mode
This commit is contained in:
@@ -15,13 +15,19 @@ import {
|
|||||||
test,
|
test,
|
||||||
vi,
|
vi,
|
||||||
} from "vitest";
|
} from "vitest";
|
||||||
|
import EventEmitter from "events";
|
||||||
|
|
||||||
import { useTheme } from "./useTheme";
|
import { useTheme } from "./useTheme";
|
||||||
import { useUrlParams } from "./UrlParams";
|
import { getUrlParams } from "./UrlParams";
|
||||||
|
import { widget } from "./widget";
|
||||||
|
import { WidgetApiToWidgetAction } from "matrix-widget-api";
|
||||||
|
|
||||||
// Mock the useUrlParams hook
|
vi.mock("./UrlParams", () => ({ getUrlParams: vi.fn() }));
|
||||||
vi.mock("./UrlParams", () => ({
|
vi.mock("./widget", () => ({
|
||||||
useUrlParams: vi.fn(),
|
widget: {
|
||||||
|
api: { transport: { reply: vi.fn() } },
|
||||||
|
lazyActions: new EventEmitter(),
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("useTheme", () => {
|
describe("useTheme", () => {
|
||||||
@@ -46,7 +52,7 @@ describe("useTheme", () => {
|
|||||||
{ setTheme: "light-high-contrast", add: ["cpd-theme-light-hc"] },
|
{ setTheme: "light-high-contrast", add: ["cpd-theme-light-hc"] },
|
||||||
])("apply procedure", ({ setTheme, add }) => {
|
])("apply procedure", ({ setTheme, add }) => {
|
||||||
test(`should apply ${add[0]} theme when ${setTheme} theme is specified`, () => {
|
test(`should apply ${add[0]} theme when ${setTheme} theme is specified`, () => {
|
||||||
(useUrlParams as Mock).mockReturnValue({ theme: setTheme });
|
(getUrlParams as Mock).mockReturnValue({ theme: setTheme });
|
||||||
|
|
||||||
renderHook(() => useTheme());
|
renderHook(() => useTheme());
|
||||||
|
|
||||||
@@ -61,7 +67,7 @@ describe("useTheme", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should not reapply the same theme if it hasn't changed", () => {
|
test("should not reapply the same theme if it hasn't changed", () => {
|
||||||
(useUrlParams as Mock).mockReturnValue({ theme: "dark" });
|
(getUrlParams as Mock).mockReturnValue({ theme: "dark" });
|
||||||
// Simulate a previous theme
|
// Simulate a previous theme
|
||||||
originalClassList.item = vi.fn().mockReturnValue("cpd-theme-dark");
|
originalClassList.item = vi.fn().mockReturnValue("cpd-theme-dark");
|
||||||
|
|
||||||
@@ -75,4 +81,23 @@ describe("useTheme", () => {
|
|||||||
expect(document.body.classList.remove).toHaveBeenCalledWith("no-theme");
|
expect(document.body.classList.remove).toHaveBeenCalledWith("no-theme");
|
||||||
expect(originalClassList.add).not.toHaveBeenCalled();
|
expect(originalClassList.add).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("theme changes in response to widget actions", () => {
|
||||||
|
renderHook(() => useTheme());
|
||||||
|
|
||||||
|
expect(originalClassList.add).toHaveBeenCalledWith("cpd-theme-dark");
|
||||||
|
widget!.lazyActions.emit(
|
||||||
|
WidgetApiToWidgetAction.ThemeChange,
|
||||||
|
new CustomEvent(WidgetApiToWidgetAction.ThemeChange, {
|
||||||
|
detail: { data: { name: "light" } },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(originalClassList.remove).toHaveBeenCalledWith(
|
||||||
|
"cpd-theme-light",
|
||||||
|
"cpd-theme-dark",
|
||||||
|
"cpd-theme-light-hc",
|
||||||
|
"cpd-theme-dark-hc",
|
||||||
|
);
|
||||||
|
expect(originalClassList.add).toHaveBeenLastCalledWith("cpd-theme-light");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,17 +5,46 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useLayoutEffect, useRef } from "react";
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||||
|
import { WidgetApiToWidgetAction } from "matrix-widget-api";
|
||||||
|
import { type IThemeChangeActionRequest } from "matrix-widget-api/lib/interfaces/ThemeChangeAction";
|
||||||
|
|
||||||
import { useUrlParams } from "./UrlParams";
|
import { getUrlParams } from "./UrlParams";
|
||||||
|
import { widget } from "./widget";
|
||||||
|
|
||||||
export const useTheme = (): void => {
|
export const useTheme = (): void => {
|
||||||
const { theme: themeName } = useUrlParams();
|
const [requestedTheme, setRequestedTheme] = useState(
|
||||||
|
() => getUrlParams().theme,
|
||||||
|
);
|
||||||
const previousTheme = useRef<string | null>(document.body.classList.item(0));
|
const previousTheme = useRef<string | null>(document.body.classList.item(0));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (widget) {
|
||||||
|
const onThemeChange = (
|
||||||
|
ev: CustomEvent<IThemeChangeActionRequest>,
|
||||||
|
): void => {
|
||||||
|
ev.preventDefault();
|
||||||
|
if ("name" in ev.detail.data && typeof ev.detail.data.name === "string")
|
||||||
|
setRequestedTheme(ev.detail.data.name);
|
||||||
|
widget!.api.transport.reply(ev.detail, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
widget.lazyActions.on(WidgetApiToWidgetAction.ThemeChange, onThemeChange);
|
||||||
|
return (): void => {
|
||||||
|
widget!.lazyActions.off(
|
||||||
|
WidgetApiToWidgetAction.ThemeChange,
|
||||||
|
onThemeChange,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
// If the url does not contain a theme props we default to "dark".
|
// If no theme has been explicitly requested we default to dark
|
||||||
const theme = themeName?.includes("light") ? "light" : "dark";
|
const theme = requestedTheme?.includes("light") ? "light" : "dark";
|
||||||
const themeHighContrast = themeName?.includes("high-contrast") ? "-hc" : "";
|
const themeHighContrast = requestedTheme?.includes("high-contrast")
|
||||||
|
? "-hc"
|
||||||
|
: "";
|
||||||
const themeString = "cpd-theme-" + theme + themeHighContrast;
|
const themeString = "cpd-theme-" + theme + themeHighContrast;
|
||||||
if (themeString !== previousTheme.current) {
|
if (themeString !== previousTheme.current) {
|
||||||
document.body.classList.remove(
|
document.body.classList.remove(
|
||||||
@@ -28,5 +57,5 @@ export const useTheme = (): void => {
|
|||||||
previousTheme.current = themeString;
|
previousTheme.current = themeString;
|
||||||
}
|
}
|
||||||
document.body.classList.remove("no-theme");
|
document.body.classList.remove("no-theme");
|
||||||
}, [previousTheme, themeName]);
|
}, [previousTheme, requestedTheme]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { createRoomWidgetClient } from "matrix-js-sdk/src/matrix";
|
import { createRoomWidgetClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { WidgetApi, MatrixCapabilities } from "matrix-widget-api";
|
import {
|
||||||
|
WidgetApi,
|
||||||
|
MatrixCapabilities,
|
||||||
|
WidgetApiToWidgetAction,
|
||||||
|
} from "matrix-widget-api";
|
||||||
|
|
||||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||||
@@ -70,6 +74,7 @@ export const widget = ((): WidgetHelpers | null => {
|
|||||||
// intend for the app to handle
|
// intend for the app to handle
|
||||||
const lazyActions = new LazyEventEmitter();
|
const lazyActions = new LazyEventEmitter();
|
||||||
[
|
[
|
||||||
|
WidgetApiToWidgetAction.ThemeChange,
|
||||||
ElementWidgetActions.JoinCall,
|
ElementWidgetActions.JoinCall,
|
||||||
ElementWidgetActions.HangupCall,
|
ElementWidgetActions.HangupCall,
|
||||||
ElementWidgetActions.TileLayout,
|
ElementWidgetActions.TileLayout,
|
||||||
|
|||||||
Reference in New Issue
Block a user