mirror of
https://github.com/element-hq/element-web.git
synced 2025-12-05 01:10:40 +00:00
Compare commits
12 Commits
hs/user-pr
...
t3chguy/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24d4ecf912 | ||
|
|
9ef3ea630c | ||
|
|
6946b90b11 | ||
|
|
c3f3c9364f | ||
|
|
72f155640d | ||
|
|
b597abf567 | ||
|
|
9443426edb | ||
|
|
6c2334c029 | ||
|
|
eb1a09a912 | ||
|
|
e730074e1b | ||
|
|
c8c5ef5e6e | ||
|
|
b61a2225b7 |
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: ["matrix-org"],
|
||||
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
|
||||
extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"],
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"],
|
||||
@@ -170,6 +170,8 @@ module.exports = {
|
||||
"jsx-a11y/role-supports-aria-props": "off",
|
||||
|
||||
"matrix-org/require-copyright-header": "error",
|
||||
|
||||
"react-compiler/react-compiler": "error",
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
@@ -262,6 +264,7 @@ module.exports = {
|
||||
|
||||
// These are fine in tests
|
||||
"no-restricted-globals": "off",
|
||||
"react-compiler/react-compiler": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ class MockMap extends EventEmitter {
|
||||
setCenter = jest.fn();
|
||||
setStyle = jest.fn();
|
||||
fitBounds = jest.fn();
|
||||
remove = jest.fn();
|
||||
}
|
||||
const MockMapInstance = new MockMap();
|
||||
|
||||
|
||||
@@ -233,6 +233,7 @@
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-matrix-org": "^2.0.2",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-unicorn": "^56.0.0",
|
||||
"express": "^4.18.2",
|
||||
|
||||
@@ -392,6 +392,7 @@ export const useRovingTabIndex = <T extends HTMLElement>(
|
||||
});
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
const isActive = context.state.activeNode === nodeRef.current;
|
||||
return [onFocus, isActive, ref, nodeRef];
|
||||
};
|
||||
|
||||
@@ -142,6 +142,7 @@ export const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
|
||||
{isFocused && suggestions.length ? (
|
||||
<div
|
||||
className="mx_AutocompleteInput_matches"
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
style={{ top: editorContainerRef.current?.clientHeight }}
|
||||
data-testid="autocomplete-matches"
|
||||
>
|
||||
|
||||
@@ -607,6 +607,7 @@ export const useContextMenu = <T extends any = HTMLElement>(inputRef?: RefObject
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
return [button.current ? isOpen : false, button, open, close, setIsOpen];
|
||||
};
|
||||
|
||||
|
||||
@@ -286,9 +286,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||
ref={this.card}
|
||||
header={_t("right_panel|files_button")}
|
||||
>
|
||||
{this.card.current && (
|
||||
<Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />
|
||||
)}
|
||||
<Measured sensor={this.card} onMeasurement={this.onMeasurement} />
|
||||
<SearchWarning isRoomEncrypted={isRoomEncrypted} kind={WarningKind.Files} />
|
||||
<TimelinePanel
|
||||
manageReadReceipts={false}
|
||||
|
||||
@@ -95,7 +95,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
|
||||
onClose={this.props.onClose}
|
||||
withoutScrollContainer={true}
|
||||
>
|
||||
{this.card.current && <Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />}
|
||||
<Measured sensor={this.card} onMeasurement={this.onMeasurement} />
|
||||
{content}
|
||||
</BaseCard>
|
||||
</ScopedRoomContextProvider>
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { forwardRef, useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import React, { forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
ISearchResults,
|
||||
IThreadBundledRelationship,
|
||||
@@ -58,7 +58,7 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
|
||||
const [results, setResults] = useState<ISearchResults | null>(null);
|
||||
const aborted = useRef(false);
|
||||
// A map from room ID to permalink creator
|
||||
const permalinkCreators = useRef(new Map<string, RoomPermalinkCreator>()).current;
|
||||
const permalinkCreators = useMemo(() => new Map<string, RoomPermalinkCreator>(), []);
|
||||
const innerRef = useRef<ScrollPanel | null>();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -273,6 +273,7 @@ function LocalRoomView(props: LocalRoomViewProps): ReactElement {
|
||||
}
|
||||
|
||||
const onRetryClicked = (): void => {
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
room.state = LocalRoomState.NEW;
|
||||
defaultDispatcher.dispatch({
|
||||
action: "local_room_event",
|
||||
@@ -2514,9 +2515,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
mainSplitContentClassName = "mx_MainSplit_timeline";
|
||||
mainSplitBody = (
|
||||
<>
|
||||
{this.roomViewBody.current && (
|
||||
<Measured sensor={this.roomViewBody.current} onMeasurement={this.onMeasurement} />
|
||||
)}
|
||||
<Measured sensor={this.roomViewBody} onMeasurement={this.onMeasurement} />
|
||||
{auxPanel}
|
||||
{pinnedMessageBanner}
|
||||
<main className={timelineClasses}>
|
||||
|
||||
@@ -204,7 +204,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) =>
|
||||
ref={card}
|
||||
closeButtonRef={closeButonRef}
|
||||
>
|
||||
{card.current && <Measured sensor={card.current} onMeasurement={setNarrow} />}
|
||||
<Measured sensor={card} onMeasurement={setNarrow} />
|
||||
{timelineSet ? (
|
||||
<TimelinePanel
|
||||
key={filterOption + ":" + (timelineSet.getFilter()?.filterId ?? roomId)}
|
||||
|
||||
@@ -443,7 +443,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||
PosthogTrackers.trackInteraction("WebThreadViewBackButton", ev);
|
||||
}}
|
||||
>
|
||||
{this.card.current && <Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />}
|
||||
<Measured sensor={this.card} onMeasurement={this.onMeasurement} />
|
||||
<div className="mx_ThreadView_timelinePanelWrapper">{timeline}</div>
|
||||
|
||||
{ContentMessages.sharedInstance().getCurrentUploads(threadRelation).length > 0 && (
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useMemo } from "react";
|
||||
|
||||
type FlexProps = {
|
||||
/**
|
||||
@@ -40,25 +40,6 @@ type FlexProps = {
|
||||
grow?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set or remove a CSS property
|
||||
* @param ref the reference
|
||||
* @param name the CSS property name
|
||||
* @param value the CSS property value
|
||||
*/
|
||||
function addOrRemoveProperty(
|
||||
ref: React.MutableRefObject<HTMLElement | undefined>,
|
||||
name: string,
|
||||
value?: string | null,
|
||||
): void {
|
||||
const style = ref.current!.style;
|
||||
if (value) {
|
||||
style.setProperty(name, value);
|
||||
} else {
|
||||
style.removeProperty(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flex child helper
|
||||
*/
|
||||
@@ -71,12 +52,12 @@ export function Box({
|
||||
children,
|
||||
...props
|
||||
}: React.PropsWithChildren<FlexProps>): JSX.Element {
|
||||
const ref = useRef<HTMLElement>();
|
||||
|
||||
useEffect(() => {
|
||||
addOrRemoveProperty(ref, `--mx-box-flex`, flex);
|
||||
addOrRemoveProperty(ref, `--mx-box-shrink`, shrink);
|
||||
addOrRemoveProperty(ref, `--mx-box-grow`, grow);
|
||||
const style = useMemo(() => {
|
||||
const style: Record<string, any> = {};
|
||||
if (flex) style["--mx-box-flex"] = flex;
|
||||
if (shrink) style["--mx-box-shrink"] = shrink;
|
||||
if (grow) style["--mx-box-grow"] = grow;
|
||||
return style;
|
||||
}, [flex, grow, shrink]);
|
||||
|
||||
return React.createElement(
|
||||
@@ -88,7 +69,7 @@ export function Box({
|
||||
"mx_Box--shrink": !!shrink,
|
||||
"mx_Box--grow": !!grow,
|
||||
}),
|
||||
ref,
|
||||
style,
|
||||
},
|
||||
children,
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useMemo } from "react";
|
||||
|
||||
type FlexProps = {
|
||||
/**
|
||||
@@ -64,15 +64,16 @@ export function Flex({
|
||||
children,
|
||||
...props
|
||||
}: React.PropsWithChildren<FlexProps>): JSX.Element {
|
||||
const ref = useRef<HTMLElement>();
|
||||
const style = useMemo(
|
||||
() => ({
|
||||
"--mx-flex-display": display,
|
||||
"--mx-flex-direction": direction,
|
||||
"--mx-flex-align": align,
|
||||
"--mx-flex-justify": justify,
|
||||
"--mx-flex-gap": gap,
|
||||
}),
|
||||
[align, direction, display, gap, justify],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
ref.current!.style.setProperty(`--mx-flex-display`, display);
|
||||
ref.current!.style.setProperty(`--mx-flex-direction`, direction);
|
||||
ref.current!.style.setProperty(`--mx-flex-align`, align);
|
||||
ref.current!.style.setProperty(`--mx-flex-justify`, justify);
|
||||
ref.current!.style.setProperty(`--mx-flex-gap`, gap);
|
||||
}, [align, direction, display, gap, justify]);
|
||||
|
||||
return React.createElement(as, { ...props, className: classNames("mx_Flex", className), ref }, children);
|
||||
return React.createElement(as, { ...props, className: classNames("mx_Flex", className), style }, children);
|
||||
}
|
||||
|
||||
@@ -58,11 +58,10 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
|
||||
if (canvas) canvas.height = UIStore.instance.windowHeight;
|
||||
UIStore.instance.on(UI_EVENTS.Resize, resize);
|
||||
|
||||
const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored
|
||||
return () => {
|
||||
dis.unregister(dispatcherRef);
|
||||
UIStore.instance.off(UI_EVENTS.Resize, resize);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored
|
||||
for (const effect in currentEffects) {
|
||||
const effectModule: ICanvasEffect = currentEffects.get(effect)!;
|
||||
if (effectModule && effectModule.isRunning) {
|
||||
|
||||
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { RefObject } from "react";
|
||||
|
||||
import UIStore, { UI_EVENTS } from "../../../stores/UIStore";
|
||||
|
||||
interface IProps {
|
||||
sensor: Element;
|
||||
sensor: RefObject<Element>;
|
||||
breakpoint: number;
|
||||
onMeasurement(narrow: boolean): void;
|
||||
}
|
||||
@@ -35,14 +35,14 @@ export default class Measured extends React.PureComponent<IProps> {
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Readonly<IProps>): void {
|
||||
const previous = prevProps.sensor;
|
||||
const current = this.props.sensor;
|
||||
const previous = prevProps.sensor.current;
|
||||
const current = this.props.sensor.current;
|
||||
if (previous === current) return;
|
||||
if (previous) {
|
||||
UIStore.instance.stopTrackingElementDimensions(`Measured${this.instanceId}`);
|
||||
}
|
||||
if (current) {
|
||||
UIStore.instance.trackElementDimensions(`Measured${this.instanceId}`, this.props.sensor);
|
||||
UIStore.instance.trackElementDimensions(`Measured${this.instanceId}`, this.props.sensor.current);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
|
||||
header={_t("right_panel|video_room_chat|title")}
|
||||
ref={this.card}
|
||||
>
|
||||
{this.card.current && <Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />}
|
||||
<Measured sensor={this.card} onMeasurement={this.onMeasurement} />
|
||||
<div className="mx_TimelineCard_timeline">
|
||||
{jumpToBottom}
|
||||
<TimelinePanel
|
||||
|
||||
@@ -109,6 +109,7 @@ export function ReadReceiptGroup({
|
||||
readReceiptPosition = readReceiptMap[userId];
|
||||
if (!readReceiptPosition) {
|
||||
readReceiptPosition = {};
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
readReceiptMap[userId] = readReceiptPosition;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useRef, useState } from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { EventType, KnownMembership, MatrixEvent, Room, RoomStateEvent, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { CryptoApi, CryptoEvent, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
@@ -213,9 +213,11 @@ export const UserIdentityWarning: React.FC<UserIdentityWarningProps> = ({ room }
|
||||
initialisedRef.current = InitialisationStatus.Completed;
|
||||
}, [crypto, room, addMembersWhoNeedApproval, updateCurrentPrompt]);
|
||||
|
||||
loadMembers().catch((e) => {
|
||||
logger.error("Error initialising UserIdentityWarning:", e);
|
||||
});
|
||||
useEffect(() => {
|
||||
loadMembers().catch((e) => {
|
||||
logger.error("Error initialising UserIdentityWarning:", e);
|
||||
});
|
||||
}, [loadMembers]);
|
||||
|
||||
// When a user's verification status changes, we check if they need to be
|
||||
// added/removed from the set of members needing approval.
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { ForwardedRef, forwardRef, MutableRefObject, useRef } from "react";
|
||||
import React, { ForwardedRef, forwardRef, MutableRefObject, useMemo } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import EditorStateTransfer from "../../../../utils/EditorStateTransfer";
|
||||
@@ -44,7 +44,7 @@ export default function EditWysiwygComposer({
|
||||
className,
|
||||
...props
|
||||
}: EditWysiwygComposerProps): JSX.Element {
|
||||
const defaultContextValue = useRef(getDefaultContextValue({ editorStateTransfer }));
|
||||
const defaultContextValue = useMemo(() => getDefaultContextValue({ editorStateTransfer }), [editorStateTransfer]);
|
||||
const initialContent = useInitialContent(editorStateTransfer);
|
||||
const isReady = !editorStateTransfer || initialContent !== undefined;
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function EditWysiwygComposer({
|
||||
}
|
||||
|
||||
return (
|
||||
<ComposerContext.Provider value={defaultContextValue.current}>
|
||||
<ComposerContext.Provider value={defaultContextValue}>
|
||||
<WysiwygComposer
|
||||
className={classNames("mx_EditWysiwygComposer", className)}
|
||||
initialContent={initialContent}
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { ForwardedRef, forwardRef, MutableRefObject, useRef } from "react";
|
||||
import React, { ForwardedRef, forwardRef, MutableRefObject, useMemo } from "react";
|
||||
import { IEventRelation } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { useWysiwygSendActionHandler } from "./hooks/useWysiwygSendActionHandler";
|
||||
@@ -52,10 +52,13 @@ export default function SendWysiwygComposer({
|
||||
...props
|
||||
}: SendWysiwygComposerProps): JSX.Element {
|
||||
const Composer = isRichTextEnabled ? WysiwygComposer : PlainTextComposer;
|
||||
const defaultContextValue = useRef(getDefaultContextValue({ eventRelation: props.eventRelation }));
|
||||
const defaultContextValue = useMemo(
|
||||
() => getDefaultContextValue({ eventRelation: props.eventRelation }),
|
||||
[props.eventRelation],
|
||||
);
|
||||
|
||||
return (
|
||||
<ComposerContext.Provider value={defaultContextValue.current}>
|
||||
<ComposerContext.Provider value={defaultContextValue}>
|
||||
<Composer
|
||||
className="mx_SendWysiwygComposer"
|
||||
leftComponent={e2eStatus && <E2EIcon status={e2eStatus} />}
|
||||
|
||||
@@ -21,6 +21,7 @@ export function useComposerFunctions(
|
||||
() => ({
|
||||
clear: () => {
|
||||
if (ref.current) {
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
ref.current.innerHTML = "";
|
||||
}
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@ export function usePlainTextInitialization(initialContent = "", ref: RefObject<H
|
||||
useEffect(() => {
|
||||
// always read and write the ref.current using .innerHTML for consistency in linebreak and HTML entity handling
|
||||
if (ref.current) {
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
ref.current.innerHTML = initialContent;
|
||||
}
|
||||
}, [ref, initialContent]);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { ChangeEvent, JSX, useCallback, useMemo, useRef, useState } from "react";
|
||||
import React, { ChangeEvent, JSX, useCallback, useMemo, useState } from "react";
|
||||
import {
|
||||
InlineField,
|
||||
ToggleControl,
|
||||
@@ -39,12 +39,12 @@ import { useSettingValue } from "../../../hooks/useSettings";
|
||||
*/
|
||||
export function ThemeChoicePanel(): JSX.Element {
|
||||
const themeState = useTheme();
|
||||
const themeWatcher = useRef(new ThemeWatcher());
|
||||
const themeWatcher = useMemo(() => new ThemeWatcher(), []);
|
||||
const customThemeEnabled = useSettingValue<boolean>("feature_custom_themes");
|
||||
|
||||
return (
|
||||
<SettingsSubsection heading={_t("common|theme")} legacy={false} data-testid="themePanel">
|
||||
{themeWatcher.current.isSystemThemeSupported() && (
|
||||
{themeWatcher.isSystemThemeSupported() && (
|
||||
<SystemTheme systemThemeActivated={themeState.systemThemeActivated} />
|
||||
)}
|
||||
<ThemeSelectors theme={themeState.theme} disabled={themeState.systemThemeActivated} />
|
||||
|
||||
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import { JoinRule, EventType, RoomState, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { RoomPowerLevelsEventContent } from "matrix-js-sdk/src/types";
|
||||
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||
@@ -24,48 +25,49 @@ interface ElementCallSwitchProps {
|
||||
|
||||
const ElementCallSwitch: React.FC<ElementCallSwitchProps> = ({ room }) => {
|
||||
const isPublic = useMemo(() => room.getJoinRule() === JoinRule.Public, [room]);
|
||||
const [content, events, maySend] = useRoomState(
|
||||
const [content, maySend] = useRoomState(
|
||||
room,
|
||||
useCallback(
|
||||
(state: RoomState) => {
|
||||
const content = state?.getStateEvents(EventType.RoomPowerLevels, "")?.getContent();
|
||||
const content = state
|
||||
?.getStateEvents(EventType.RoomPowerLevels, "")
|
||||
?.getContent<RoomPowerLevelsEventContent>();
|
||||
return [
|
||||
content ?? {},
|
||||
content?.["events"] ?? {},
|
||||
state?.maySendStateEvent(EventType.RoomPowerLevels, room.client.getSafeUserId()),
|
||||
];
|
||||
] as const;
|
||||
},
|
||||
[room.client],
|
||||
),
|
||||
);
|
||||
|
||||
const [elementCallEnabled, setElementCallEnabled] = useState<boolean>(() => {
|
||||
return events[ElementCall.MEMBER_EVENT_TYPE.name] === 0;
|
||||
return content.events?.[ElementCall.MEMBER_EVENT_TYPE.name] === 0;
|
||||
});
|
||||
|
||||
const onChange = useCallback(
|
||||
(enabled: boolean): void => {
|
||||
setElementCallEnabled(enabled);
|
||||
|
||||
// Take a copy to avoid mutating the original
|
||||
const newContent = { events: {}, ...content };
|
||||
|
||||
if (enabled) {
|
||||
const userLevel = events[EventType.RoomMessage] ?? content.users_default ?? 0;
|
||||
const userLevel = newContent.events[EventType.RoomMessage] ?? content.users_default ?? 0;
|
||||
const moderatorLevel = content.kick ?? 50;
|
||||
|
||||
events[ElementCall.CALL_EVENT_TYPE.name] = isPublic ? moderatorLevel : userLevel;
|
||||
events[ElementCall.MEMBER_EVENT_TYPE.name] = userLevel;
|
||||
newContent.events[ElementCall.CALL_EVENT_TYPE.name] = isPublic ? moderatorLevel : userLevel;
|
||||
newContent.events[ElementCall.MEMBER_EVENT_TYPE.name] = userLevel;
|
||||
} else {
|
||||
const adminLevel = events[EventType.RoomPowerLevels] ?? content.state_default ?? 100;
|
||||
const adminLevel = newContent.events[EventType.RoomPowerLevels] ?? content.state_default ?? 100;
|
||||
|
||||
events[ElementCall.CALL_EVENT_TYPE.name] = adminLevel;
|
||||
events[ElementCall.MEMBER_EVENT_TYPE.name] = adminLevel;
|
||||
newContent.events[ElementCall.CALL_EVENT_TYPE.name] = adminLevel;
|
||||
newContent.events[ElementCall.MEMBER_EVENT_TYPE.name] = adminLevel;
|
||||
}
|
||||
|
||||
room.client.sendStateEvent(room.roomId, EventType.RoomPowerLevels, {
|
||||
events: events,
|
||||
...content,
|
||||
});
|
||||
room.client.sendStateEvent(room.roomId, EventType.RoomPowerLevels, newContent);
|
||||
},
|
||||
[room.client, room.roomId, content, events, isPublic],
|
||||
[room.client, room.roomId, content, isPublic],
|
||||
);
|
||||
|
||||
const brand = SdkConfig.get("element_call").brand ?? DEFAULTS.element_call.brand;
|
||||
|
||||
@@ -27,7 +27,7 @@ type Props = {
|
||||
const MATCH_SYSTEM_THEME_ID = "MATCH_SYSTEM_THEME_ID";
|
||||
|
||||
const QuickThemeSwitcher: React.FC<Props> = ({ requestClose }) => {
|
||||
const orderedThemes = useMemo(getOrderedThemes, []);
|
||||
const orderedThemes = useMemo(() => getOrderedThemes(), []);
|
||||
|
||||
const themeState = useTheme();
|
||||
const nonHighContrast = findNonHighContrastTheme(themeState.theme);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { ClientEvent, MatrixClient, MatrixEventEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { throttle } from "lodash";
|
||||
|
||||
@@ -42,14 +42,12 @@ export function useUnreadThreadRooms(forceComputation: boolean): Result {
|
||||
setResult(computeUnreadThreadRooms(mxClient, msc3946ProcessDynamicPredecessor, settingTACOnlyNotifs));
|
||||
}, [mxClient, msc3946ProcessDynamicPredecessor, settingTACOnlyNotifs]);
|
||||
|
||||
// The exhautive deps lint rule can't compute dependencies here since it's not a plain inline func.
|
||||
// We make this as simple as possible so its only dep is doUpdate itself.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const scheduleUpdate = useCallback(
|
||||
throttle(doUpdate, MIN_UPDATE_INTERVAL_MS, {
|
||||
leading: false,
|
||||
trailing: true,
|
||||
}),
|
||||
const scheduleUpdate = useMemo(
|
||||
() =>
|
||||
throttle(doUpdate, MIN_UPDATE_INTERVAL_MS, {
|
||||
leading: false,
|
||||
trailing: true,
|
||||
}),
|
||||
[doUpdate],
|
||||
);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
|
||||
import React, { ContextType, createContext, memo, ReactNode, useContext, useEffect, useRef, useState } from "react";
|
||||
import React, { ContextType, createContext, memo, ReactNode, useContext, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { objectKeyChanges } from "../utils/objects.ts";
|
||||
import { useTypedEventEmitter } from "../hooks/useEventEmitter.ts";
|
||||
@@ -48,15 +48,16 @@ const ScopedRoomContext = createContext<EfficientContext<ContextValue> | undefin
|
||||
// Uses react memo and leverages splatting the value to ensure that the context is only updated when the state changes (shallow compare)
|
||||
export const ScopedRoomContextProvider = memo(
|
||||
({ children, ...state }: { children: ReactNode } & ContextValue): JSX.Element => {
|
||||
const contextRef = useRef(new EfficientContext<ContextValue>(state));
|
||||
// eslint-disable-next-line react-compiler/react-compiler,react-hooks/exhaustive-deps
|
||||
const context = useMemo(() => new EfficientContext<ContextValue>(state), []);
|
||||
useEffect(() => {
|
||||
contextRef.current.setState(state);
|
||||
}, [state]);
|
||||
context.setState(state);
|
||||
}, [context, state]);
|
||||
|
||||
// Includes the legacy RoomContext provider for backwards compatibility with class components
|
||||
return (
|
||||
<RoomContext.Provider value={state}>
|
||||
<ScopedRoomContext.Provider value={contextRef.current}>{children}</ScopedRoomContext.Provider>
|
||||
<ScopedRoomContext.Provider value={context}>{children}</ScopedRoomContext.Provider>
|
||||
</RoomContext.Provider>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { ReactNode, createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import { ReactNode, createContext, useCallback, useContext, useEffect, useState, useMemo } from "react";
|
||||
|
||||
/**
|
||||
* A ToastContext helps components display any kind of toast message and can be provided
|
||||
@@ -33,19 +33,19 @@ export function useToastContext(): ToastRack {
|
||||
* the ToastRack object that should be provided to the context
|
||||
*/
|
||||
export function useActiveToast(): [ReactNode | undefined, ToastRack] {
|
||||
const toastRack = useRef(new ToastRack());
|
||||
const toastRack = useMemo(() => new ToastRack(), []);
|
||||
|
||||
const [activeToast, setActiveToast] = useState<ReactNode | undefined>(toastRack.current.getActiveToast());
|
||||
const [activeToast, setActiveToast] = useState<ReactNode | undefined>(toastRack.getActiveToast());
|
||||
|
||||
const updateCallback = useCallback(() => {
|
||||
setActiveToast(toastRack.current.getActiveToast());
|
||||
setActiveToast(toastRack.getActiveToast());
|
||||
}, [setActiveToast, toastRack]);
|
||||
|
||||
useEffect(() => {
|
||||
toastRack.current.setCallback(updateCallback);
|
||||
toastRack.setCallback(updateCallback);
|
||||
}, [toastRack, updateCallback]);
|
||||
|
||||
return [activeToast, toastRack.current];
|
||||
return [activeToast, toastRack];
|
||||
}
|
||||
|
||||
interface DisplayedToast {
|
||||
|
||||
@@ -34,7 +34,7 @@ export function useAsyncRefreshMemo<T>(fn: Fn<T>, deps: DependencyList, initialV
|
||||
return () => {
|
||||
discard = true;
|
||||
};
|
||||
}, deps); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, deps); // eslint-disable-line react-hooks/exhaustive-deps,react-compiler/react-compiler
|
||||
useEffect(refresh, [refresh]);
|
||||
return [value, refresh];
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export const useNotificationState = (room: Room): [RoomNotifState | undefined, (
|
||||
setNotificationState(echoChamber.notificationVolume);
|
||||
}
|
||||
});
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
const setter = useCallback((state: RoomNotifState) => (echoChamber.notificationVolume = state), [echoChamber]);
|
||||
return [notificationState, setter];
|
||||
};
|
||||
|
||||
@@ -22,6 +22,6 @@ export const useTransition = <D extends DependencyList>(callback: (...params: D)
|
||||
useEffect(() => {
|
||||
if (args.current !== null) func.current(...args.current);
|
||||
args.current = deps;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-compiler/react-compiler,react-hooks/exhaustive-deps
|
||||
}, deps);
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ const USER_ONBOARDING_CONTEXT_INTERVAL = 5000;
|
||||
*/
|
||||
function useRefOf<T extends any[], R>(value: (...values: T) => R): (...values: T) => R {
|
||||
const ref = useRef(value);
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
ref.current = value;
|
||||
return useCallback((...values: T) => ref.current(...values), []);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
|
||||
import type { Map as MapLibreMap } from "maplibre-gl";
|
||||
import { createMap } from "./map";
|
||||
@@ -26,29 +26,25 @@ interface UseMapProps {
|
||||
*/
|
||||
export const useMap = ({ interactive, bodyId, onError }: UseMapProps): MapLibreMap | undefined => {
|
||||
const cli = useMatrixClientContext();
|
||||
const [map, setMap] = useState<MapLibreMap>();
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
try {
|
||||
setMap(createMap(cli, !!interactive, bodyId, onError));
|
||||
} catch (error) {
|
||||
console.error("Error encountered in useMap", error);
|
||||
if (error instanceof Error) {
|
||||
onError?.(error);
|
||||
}
|
||||
const map = useMemo(() => {
|
||||
try {
|
||||
return createMap(cli, !!interactive, bodyId, onError);
|
||||
} catch (error) {
|
||||
console.error("Error encountered in useMap", error);
|
||||
if (error instanceof Error) {
|
||||
onError?.(error);
|
||||
}
|
||||
return () => {
|
||||
if (map) {
|
||||
map.remove();
|
||||
setMap(undefined);
|
||||
}
|
||||
};
|
||||
},
|
||||
// map is excluded as a dependency
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[interactive, bodyId, onError],
|
||||
);
|
||||
}
|
||||
}, [bodyId, cli, interactive, onError]);
|
||||
|
||||
// cleanup
|
||||
useEffect(() => {
|
||||
if (!map) return;
|
||||
return () => {
|
||||
map.remove();
|
||||
};
|
||||
}, [map]);
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
28
yarn.lock
28
yarn.lock
@@ -61,7 +61,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.3.tgz#99488264a56b2aded63983abd6a417f03b92ed02"
|
||||
integrity sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==
|
||||
|
||||
"@babel/core@^7.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.18.5", "@babel/core@^7.21.3", "@babel/core@^7.23.9":
|
||||
"@babel/core@^7.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.18.5", "@babel/core@^7.21.3", "@babel/core@^7.23.9", "@babel/core@^7.24.4":
|
||||
version "7.26.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40"
|
||||
integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==
|
||||
@@ -308,7 +308,7 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.25.8"
|
||||
|
||||
"@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3":
|
||||
"@babel/parser@^7.24.4", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3":
|
||||
version "7.26.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234"
|
||||
integrity sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==
|
||||
@@ -5662,6 +5662,18 @@ eslint-plugin-matrix-org@^2.0.2:
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-matrix-org/-/eslint-plugin-matrix-org-2.0.2.tgz#95b86b0f16704ab19740f7c3c62eae69e20365e6"
|
||||
integrity sha512-cQy5Rjeq6uyu1mLXlPZwEJdyM0NmclrnEz68y792FSuuxzMyJNNYLGDQ5CkYW8H+PrD825HUFZ34pNXnjMOzOw==
|
||||
|
||||
eslint-plugin-react-compiler@^19.0.0-beta-df7b47d-20241124:
|
||||
version "19.0.0-beta-df7b47d-20241124"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.0.0-beta-df7b47d-20241124.tgz#468751d3a8a6781189405ee56b39b80545306df8"
|
||||
integrity sha512-82PfnllC8jP/68KdLAbpWuYTcfmtGLzkqy2IW85WopKMTr+4rdQpp+lfliQ/QE79wWrv/dRoADrk3Pdhq25nTw==
|
||||
dependencies:
|
||||
"@babel/core" "^7.24.4"
|
||||
"@babel/parser" "^7.24.4"
|
||||
"@babel/plugin-transform-private-methods" "^7.25.9"
|
||||
hermes-parser "^0.25.1"
|
||||
zod "^3.22.4"
|
||||
zod-validation-error "^3.0.3"
|
||||
|
||||
eslint-plugin-react-hooks@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz#72e2eefbac4b694f5324154619fee44f5f60f101"
|
||||
@@ -6639,6 +6651,18 @@ he@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
hermes-estree@0.25.1:
|
||||
version "0.25.1"
|
||||
resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480"
|
||||
integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==
|
||||
|
||||
hermes-parser@^0.25.1:
|
||||
version "0.25.1"
|
||||
resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1"
|
||||
integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==
|
||||
dependencies:
|
||||
hermes-estree "0.25.1"
|
||||
|
||||
highlight.js@^11.3.1:
|
||||
version "11.10.0"
|
||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.10.0.tgz#6e3600dc4b33d6dc23d5bd94fbf72405f5892b92"
|
||||
|
||||
Reference in New Issue
Block a user