element-portable/src/components/views/settings/PowerLevelSelector.tsx
David Langley 491f0cd08a
Change license (#13)
* Copyright headers 1

* Licence headers 2

* Copyright Headers 3

* Copyright Headers 4

* Copyright Headers 5

* Copyright Headers 6

* Copyright headers 7

* Add copyright headers for html and config file

* Replace license files and update package.json

* Update with CLA

* lint
2024-09-09 13:57:16 +00:00

140 lines
4.8 KiB
TypeScript

/*
* Copyright 2024 New Vector Ltd.
* Copyright 2024 The Matrix.org Foundation C.I.C.
*
* 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, { useState, JSX, PropsWithChildren } from "react";
import { Button } from "@vector-im/compound-web";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import PowerSelector from "../elements/PowerSelector";
import { _t } from "../../../languageHandler";
import SettingsFieldset from "./SettingsFieldset";
/**
* Display in a fieldset, the power level of the users and allow to change them.
* The apply button is disabled until the power level of an user is changed.
* If there is no user to display, the children is displayed instead.
*/
interface PowerLevelSelectorProps {
/**
* The power levels of the users
* The key is the user id and the value is the power level
*/
userLevels: Record<string, number>;
/**
* Whether the user can change the power levels of other users
*/
canChangeLevels: boolean;
/**
* The current user power level
*/
currentUserLevel: number;
/**
* The callback when the apply button is clicked
* @param value - new power level for the user
* @param userId - the user id
*/
onClick: (value: number, userId: string) => void;
/**
* Filter the users to display
* @param user
*/
filter: (user: string) => boolean;
/**
* The title of the fieldset
*/
title: string;
}
export function PowerLevelSelector({
userLevels,
canChangeLevels,
currentUserLevel,
onClick,
filter,
title,
children,
}: PropsWithChildren<PowerLevelSelectorProps>): JSX.Element | null {
const matrixClient = useMatrixClientContext();
const [currentPowerLevel, setCurrentPowerLevel] = useState<{ value: number; userId: string } | null>(null);
// If the power level has changed, we need to enable the apply button
const powerLevelChanged = Boolean(
currentPowerLevel && currentPowerLevel.value !== userLevels[currentPowerLevel?.userId],
);
const collator = new Intl.Collator();
// We sort the users by power level, then we filter them
const users = Object.keys(userLevels)
.sort((userA, userB) => sortUser(collator, userA, userB, userLevels))
.filter(filter);
// No user to display, we return the children into fragment to convert it to JSX.Element type
if (!users.length) return <>{children}</>;
return (
<SettingsFieldset legend={title}>
{users.map((userId) => {
// We only want to display users with a valid power level aka an integer
if (!Number.isInteger(userLevels[userId])) return;
const isMe = userId === matrixClient.getUserId();
// If I can change levels, I can change the level of anyone with a lower level than mine
const canChange = canChangeLevels && (userLevels[userId] < currentUserLevel || isMe);
// When the new power level is selected, the fields are rerendered and we need to keep the current value
const userLevel = currentPowerLevel?.userId === userId ? currentPowerLevel?.value : userLevels[userId];
return (
<PowerSelector
value={userLevel}
disabled={!canChange}
label={userId}
key={userId}
onChange={(value) => setCurrentPowerLevel({ value, userId })}
/>
);
})}
<Button
size="sm"
kind="primary"
// mx_Dialog_nonDialogButton is necessary to avoid the Dialog CSS to override the button style
className="mx_Dialog_nonDialogButton mx_PowerLevelSelector_Button"
onClick={() => {
if (currentPowerLevel !== null) {
onClick(currentPowerLevel.value, currentPowerLevel.userId);
setCurrentPowerLevel(null);
}
}}
disabled={!powerLevelChanged}
aria-label={_t("action|apply")}
>
{_t("action|apply")}
</Button>
</SettingsFieldset>
);
}
/**
* Sort the users by power level, then by name
* @param userA
* @param userB
* @param userLevels
*/
function sortUser(
collator: Intl.Collator,
userA: string,
userB: string,
userLevels: PowerLevelSelectorProps["userLevels"],
): number {
const powerLevelDiff = userLevels[userA] - userLevels[userB];
return powerLevelDiff !== 0
? powerLevelDiff
: collator.compare(userA.toLocaleLowerCase(), userB.toLocaleLowerCase());
}