mirror of
				https://github.com/cinnyapp/cinny.git
				synced 2025-11-04 14:30:29 +03:00 
			
		
		
		
	add option to change user powers in profile
This commit is contained in:
		
							parent
							
								
									36fa972f2e
								
							
						
					
					
						commit
						3cc3d03ace
					
				
					 2 changed files with 347 additions and 151 deletions
				
			
		
							
								
								
									
										344
									
								
								src/app/components/user-profile/PowerChip.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										344
									
								
								src/app/components/user-profile/PowerChip.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,344 @@
 | 
			
		|||
import {
 | 
			
		||||
  Box,
 | 
			
		||||
  Button,
 | 
			
		||||
  Chip,
 | 
			
		||||
  config,
 | 
			
		||||
  Dialog,
 | 
			
		||||
  Header,
 | 
			
		||||
  Icon,
 | 
			
		||||
  IconButton,
 | 
			
		||||
  Icons,
 | 
			
		||||
  Line,
 | 
			
		||||
  Menu,
 | 
			
		||||
  MenuItem,
 | 
			
		||||
  Overlay,
 | 
			
		||||
  OverlayBackdrop,
 | 
			
		||||
  OverlayCenter,
 | 
			
		||||
  PopOut,
 | 
			
		||||
  RectCords,
 | 
			
		||||
  Spinner,
 | 
			
		||||
  Text,
 | 
			
		||||
  toRem,
 | 
			
		||||
} from 'folds';
 | 
			
		||||
import React, { MouseEventHandler, useCallback, useState } from 'react';
 | 
			
		||||
import FocusTrap from 'focus-trap-react';
 | 
			
		||||
import { isKeyHotkey } from 'is-hotkey';
 | 
			
		||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
 | 
			
		||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
 | 
			
		||||
import { PowerColorBadge, PowerIcon } from '../power';
 | 
			
		||||
import { usePowerLevels, usePowerLevelsAPI } from '../../hooks/usePowerLevels';
 | 
			
		||||
import { getPowers, getTagIconSrc, usePowerLevelTags } from '../../hooks/usePowerLevelTags';
 | 
			
		||||
import { stopPropagation } from '../../utils/keyboard';
 | 
			
		||||
import { StateEvent } from '../../../types/matrix/room';
 | 
			
		||||
import { useOpenRoomSettings } from '../../state/hooks/roomSettings';
 | 
			
		||||
import { RoomSettingsPage } from '../../state/roomSettings';
 | 
			
		||||
import { useRoom } from '../../hooks/useRoom';
 | 
			
		||||
import { useSpaceOptionally } from '../../hooks/useSpace';
 | 
			
		||||
import { CutoutCard } from '../cutout-card';
 | 
			
		||||
import { useOpenSpaceSettings } from '../../state/hooks/spaceSettings';
 | 
			
		||||
import { SpaceSettingsPage } from '../../state/spaceSettings';
 | 
			
		||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
 | 
			
		||||
import { BreakWord } from '../../styles/Text.css';
 | 
			
		||||
 | 
			
		||||
type SelfDemoteAlertProps = {
 | 
			
		||||
  power: number;
 | 
			
		||||
  onCancel: () => void;
 | 
			
		||||
  onChange: (power: number) => void;
 | 
			
		||||
};
 | 
			
		||||
function SelfDemoteAlert({ power, onCancel, onChange }: SelfDemoteAlertProps) {
 | 
			
		||||
  return (
 | 
			
		||||
    <Overlay open backdrop={<OverlayBackdrop />}>
 | 
			
		||||
      <OverlayCenter>
 | 
			
		||||
        <FocusTrap
 | 
			
		||||
          focusTrapOptions={{
 | 
			
		||||
            initialFocus: false,
 | 
			
		||||
            onDeactivate: onCancel,
 | 
			
		||||
            clickOutsideDeactivates: true,
 | 
			
		||||
            escapeDeactivates: stopPropagation,
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <Dialog variant="Surface">
 | 
			
		||||
            <Header
 | 
			
		||||
              style={{ padding: `0 ${config.space.S200} 0 ${config.space.S400}` }}
 | 
			
		||||
              variant="Surface"
 | 
			
		||||
              size="500"
 | 
			
		||||
            >
 | 
			
		||||
              <Box grow="Yes">
 | 
			
		||||
                <Text size="H4">Self Demotion</Text>
 | 
			
		||||
              </Box>
 | 
			
		||||
              <IconButton size="300" onClick={onCancel} radii="300">
 | 
			
		||||
                <Icon src={Icons.Cross} />
 | 
			
		||||
              </IconButton>
 | 
			
		||||
            </Header>
 | 
			
		||||
            <Box style={{ padding: config.space.S400, paddingTop: 0 }} direction="Column" gap="500">
 | 
			
		||||
              <Box direction="Column" gap="200">
 | 
			
		||||
                <Text priority="400">
 | 
			
		||||
                  You are about to demote yourself! You will not be able to regain this power
 | 
			
		||||
                  yourself. Are you sure?
 | 
			
		||||
                </Text>
 | 
			
		||||
              </Box>
 | 
			
		||||
              <Box direction="Column" gap="200">
 | 
			
		||||
                <Button type="submit" variant="Warning" onClick={() => onChange(power)}>
 | 
			
		||||
                  <Text size="B400">Demote</Text>
 | 
			
		||||
                </Button>
 | 
			
		||||
              </Box>
 | 
			
		||||
            </Box>
 | 
			
		||||
          </Dialog>
 | 
			
		||||
        </FocusTrap>
 | 
			
		||||
      </OverlayCenter>
 | 
			
		||||
    </Overlay>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SharedPowerAlertProps = {
 | 
			
		||||
  power: number;
 | 
			
		||||
  onCancel: () => void;
 | 
			
		||||
  onChange: (power: number) => void;
 | 
			
		||||
};
 | 
			
		||||
function SharedPowerAlert({ power, onCancel, onChange }: SharedPowerAlertProps) {
 | 
			
		||||
  return (
 | 
			
		||||
    <Overlay open backdrop={<OverlayBackdrop />}>
 | 
			
		||||
      <OverlayCenter>
 | 
			
		||||
        <FocusTrap
 | 
			
		||||
          focusTrapOptions={{
 | 
			
		||||
            initialFocus: false,
 | 
			
		||||
            onDeactivate: onCancel,
 | 
			
		||||
            clickOutsideDeactivates: true,
 | 
			
		||||
            escapeDeactivates: stopPropagation,
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <Dialog variant="Surface">
 | 
			
		||||
            <Header
 | 
			
		||||
              style={{ padding: `0 ${config.space.S200} 0 ${config.space.S400}` }}
 | 
			
		||||
              variant="Surface"
 | 
			
		||||
              size="500"
 | 
			
		||||
            >
 | 
			
		||||
              <Box grow="Yes">
 | 
			
		||||
                <Text size="H4">Shared Power</Text>
 | 
			
		||||
              </Box>
 | 
			
		||||
              <IconButton size="300" onClick={onCancel} radii="300">
 | 
			
		||||
                <Icon src={Icons.Cross} />
 | 
			
		||||
              </IconButton>
 | 
			
		||||
            </Header>
 | 
			
		||||
            <Box style={{ padding: config.space.S400, paddingTop: 0 }} direction="Column" gap="500">
 | 
			
		||||
              <Box direction="Column" gap="200">
 | 
			
		||||
                <Text priority="400">
 | 
			
		||||
                  You are promoting the user to have the same power as yourself! You will not be
 | 
			
		||||
                  able to change their power afterward. Are you sure?
 | 
			
		||||
                </Text>
 | 
			
		||||
              </Box>
 | 
			
		||||
              <Box direction="Column" gap="200">
 | 
			
		||||
                <Button type="submit" variant="Warning" onClick={() => onChange(power)}>
 | 
			
		||||
                  <Text size="B400">Promote</Text>
 | 
			
		||||
                </Button>
 | 
			
		||||
              </Box>
 | 
			
		||||
            </Box>
 | 
			
		||||
          </Dialog>
 | 
			
		||||
        </FocusTrap>
 | 
			
		||||
      </OverlayCenter>
 | 
			
		||||
    </Overlay>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function PowerChip({ userId }: { userId: string }) {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
  const room = useRoom();
 | 
			
		||||
  const space = useSpaceOptionally();
 | 
			
		||||
  const useAuthentication = useMediaAuthentication();
 | 
			
		||||
  const openRoomSettings = useOpenRoomSettings();
 | 
			
		||||
  const openSpaceSettings = useOpenSpaceSettings();
 | 
			
		||||
 | 
			
		||||
  const powerLevels = usePowerLevels(room);
 | 
			
		||||
  const { getPowerLevel, canSendStateEvent } = usePowerLevelsAPI(powerLevels);
 | 
			
		||||
  const [powerLevelTags, getPowerLevelTag] = usePowerLevelTags(room, powerLevels);
 | 
			
		||||
  const myPower = getPowerLevel(mx.getSafeUserId());
 | 
			
		||||
  const userPower = getPowerLevel(userId);
 | 
			
		||||
  const canChangePowers =
 | 
			
		||||
    canSendStateEvent(StateEvent.RoomPowerLevels, myPower) &&
 | 
			
		||||
    (mx.getSafeUserId() === userId ? true : myPower > userPower);
 | 
			
		||||
 | 
			
		||||
  const tag = getPowerLevelTag(userPower);
 | 
			
		||||
  const tagIconSrc = tag.icon && getTagIconSrc(mx, useAuthentication, tag.icon);
 | 
			
		||||
 | 
			
		||||
  const [cords, setCords] = useState<RectCords>();
 | 
			
		||||
 | 
			
		||||
  const open: MouseEventHandler<HTMLButtonElement> = (evt) => {
 | 
			
		||||
    setCords(evt.currentTarget.getBoundingClientRect());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const close = () => setCords(undefined);
 | 
			
		||||
 | 
			
		||||
  const [powerState, changePower] = useAsyncCallback<undefined, Error, [number]>(
 | 
			
		||||
    useCallback(
 | 
			
		||||
      async (power: number) => {
 | 
			
		||||
        await mx.setPowerLevel(room.roomId, userId, power);
 | 
			
		||||
      },
 | 
			
		||||
      [mx, userId, room]
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
  const changing = powerState.status === AsyncStatus.Loading;
 | 
			
		||||
  const error = powerState.status === AsyncStatus.Error;
 | 
			
		||||
  const [selfDemote, setSelfDemote] = useState<number>();
 | 
			
		||||
  const [sharedPower, setSharedPower] = useState<number>();
 | 
			
		||||
 | 
			
		||||
  const handlePowerSelect = (power: number): void => {
 | 
			
		||||
    close();
 | 
			
		||||
    if (!canChangePowers) return;
 | 
			
		||||
    if (power === userPower) return;
 | 
			
		||||
 | 
			
		||||
    if (userId === mx.getSafeUserId()) {
 | 
			
		||||
      setSelfDemote(power);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (power === myPower) {
 | 
			
		||||
      setSharedPower(power);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    changePower(power);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleSelfDemote = (power: number) => {
 | 
			
		||||
    setSelfDemote(undefined);
 | 
			
		||||
    changePower(power);
 | 
			
		||||
  };
 | 
			
		||||
  const handleSharedPower = (power: number) => {
 | 
			
		||||
    setSharedPower(undefined);
 | 
			
		||||
    changePower(power);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <PopOut
 | 
			
		||||
        anchor={cords}
 | 
			
		||||
        position="Bottom"
 | 
			
		||||
        align="Start"
 | 
			
		||||
        offset={4}
 | 
			
		||||
        content={
 | 
			
		||||
          <FocusTrap
 | 
			
		||||
            focusTrapOptions={{
 | 
			
		||||
              initialFocus: false,
 | 
			
		||||
              onDeactivate: close,
 | 
			
		||||
              clickOutsideDeactivates: true,
 | 
			
		||||
              escapeDeactivates: stopPropagation,
 | 
			
		||||
              isKeyForward: (evt: KeyboardEvent) => isKeyHotkey('arrowdown', evt),
 | 
			
		||||
              isKeyBackward: (evt: KeyboardEvent) => isKeyHotkey('arrowup', evt),
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <Menu>
 | 
			
		||||
              <Box
 | 
			
		||||
                direction="Column"
 | 
			
		||||
                gap="100"
 | 
			
		||||
                style={{ padding: config.space.S100, maxWidth: toRem(200) }}
 | 
			
		||||
              >
 | 
			
		||||
                {error && (
 | 
			
		||||
                  <CutoutCard style={{ padding: config.space.S200 }} variant="Critical">
 | 
			
		||||
                    <Text size="L400">Error: {powerState.error.name}</Text>
 | 
			
		||||
                    <Text className={BreakWord} size="T200">
 | 
			
		||||
                      {powerState.error.message}
 | 
			
		||||
                    </Text>
 | 
			
		||||
                  </CutoutCard>
 | 
			
		||||
                )}
 | 
			
		||||
                {getPowers(powerLevelTags).map((power) => {
 | 
			
		||||
                  const powerTag = powerLevelTags[power];
 | 
			
		||||
                  const powerTagIconSrc =
 | 
			
		||||
                    powerTag.icon && getTagIconSrc(mx, useAuthentication, powerTag.icon);
 | 
			
		||||
 | 
			
		||||
                  const canAssignPower = power <= myPower;
 | 
			
		||||
 | 
			
		||||
                  return (
 | 
			
		||||
                    <MenuItem
 | 
			
		||||
                      key={power}
 | 
			
		||||
                      variant={userPower === power ? 'Primary' : 'Surface'}
 | 
			
		||||
                      fill="None"
 | 
			
		||||
                      size="300"
 | 
			
		||||
                      radii="300"
 | 
			
		||||
                      aria-disabled={changing || !canChangePowers || !canAssignPower}
 | 
			
		||||
                      aria-pressed={userPower === power}
 | 
			
		||||
                      before={<PowerColorBadge color={powerTag.color} />}
 | 
			
		||||
                      after={
 | 
			
		||||
                        powerTagIconSrc ? (
 | 
			
		||||
                          <PowerIcon size="50" iconSrc={powerTagIconSrc} />
 | 
			
		||||
                        ) : undefined
 | 
			
		||||
                      }
 | 
			
		||||
                      onClick={
 | 
			
		||||
                        canChangePowers && canAssignPower
 | 
			
		||||
                          ? () => handlePowerSelect(power)
 | 
			
		||||
                          : undefined
 | 
			
		||||
                      }
 | 
			
		||||
                    >
 | 
			
		||||
                      <Text size="B300">{powerTag.name}</Text>
 | 
			
		||||
                    </MenuItem>
 | 
			
		||||
                  );
 | 
			
		||||
                })}
 | 
			
		||||
              </Box>
 | 
			
		||||
              <Line size="300" />
 | 
			
		||||
              <div style={{ padding: config.space.S100 }}>
 | 
			
		||||
                <MenuItem
 | 
			
		||||
                  variant="Surface"
 | 
			
		||||
                  fill="None"
 | 
			
		||||
                  size="300"
 | 
			
		||||
                  radii="300"
 | 
			
		||||
                  onClick={() => {
 | 
			
		||||
                    if (room.isSpaceRoom()) {
 | 
			
		||||
                      openSpaceSettings(
 | 
			
		||||
                        room.roomId,
 | 
			
		||||
                        space?.roomId,
 | 
			
		||||
                        SpaceSettingsPage.PermissionsPage
 | 
			
		||||
                      );
 | 
			
		||||
                    } else {
 | 
			
		||||
                      openRoomSettings(
 | 
			
		||||
                        room.roomId,
 | 
			
		||||
                        space?.roomId,
 | 
			
		||||
                        RoomSettingsPage.PermissionsPage
 | 
			
		||||
                      );
 | 
			
		||||
                    }
 | 
			
		||||
                    close();
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  <Text size="B300">Manage Powers</Text>
 | 
			
		||||
                </MenuItem>
 | 
			
		||||
              </div>
 | 
			
		||||
            </Menu>
 | 
			
		||||
          </FocusTrap>
 | 
			
		||||
        }
 | 
			
		||||
      >
 | 
			
		||||
        <Chip
 | 
			
		||||
          variant={error ? 'Critical' : 'SurfaceVariant'}
 | 
			
		||||
          radii="Pill"
 | 
			
		||||
          before={
 | 
			
		||||
            cords ? (
 | 
			
		||||
              <Icon size="50" src={Icons.ChevronBottom} />
 | 
			
		||||
            ) : (
 | 
			
		||||
              <>
 | 
			
		||||
                {!changing && <PowerColorBadge color={tag.color} />}
 | 
			
		||||
                {changing && <Spinner size="50" variant="Secondary" fill="Soft" />}
 | 
			
		||||
              </>
 | 
			
		||||
            )
 | 
			
		||||
          }
 | 
			
		||||
          after={tagIconSrc ? <PowerIcon size="50" iconSrc={tagIconSrc} /> : undefined}
 | 
			
		||||
          onClick={open}
 | 
			
		||||
          aria-pressed={!!cords}
 | 
			
		||||
        >
 | 
			
		||||
          <Text size="B300" truncate>
 | 
			
		||||
            {tag.name}
 | 
			
		||||
          </Text>
 | 
			
		||||
        </Chip>
 | 
			
		||||
      </PopOut>
 | 
			
		||||
      {typeof selfDemote === 'number' ? (
 | 
			
		||||
        <SelfDemoteAlert
 | 
			
		||||
          power={selfDemote}
 | 
			
		||||
          onCancel={() => setSelfDemote(undefined)}
 | 
			
		||||
          onChange={handleSelfDemote}
 | 
			
		||||
        />
 | 
			
		||||
      ) : null}
 | 
			
		||||
      {typeof sharedPower === 'number' ? (
 | 
			
		||||
        <SharedPowerAlert
 | 
			
		||||
          power={sharedPower}
 | 
			
		||||
          onCancel={() => setSharedPower(undefined)}
 | 
			
		||||
          onChange={handleSharedPower}
 | 
			
		||||
        />
 | 
			
		||||
      ) : null}
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,172 +1,24 @@
 | 
			
		|||
import {
 | 
			
		||||
  Box,
 | 
			
		||||
  Button,
 | 
			
		||||
  Chip,
 | 
			
		||||
  color,
 | 
			
		||||
  config,
 | 
			
		||||
  Icon,
 | 
			
		||||
  Icons,
 | 
			
		||||
  Line,
 | 
			
		||||
  Menu,
 | 
			
		||||
  MenuItem,
 | 
			
		||||
  PopOut,
 | 
			
		||||
  RectCords,
 | 
			
		||||
  Spinner,
 | 
			
		||||
  Text,
 | 
			
		||||
} from 'folds';
 | 
			
		||||
import React, { MouseEventHandler, useCallback, useState } from 'react';
 | 
			
		||||
import FocusTrap from 'focus-trap-react';
 | 
			
		||||
import { isKeyHotkey } from 'is-hotkey';
 | 
			
		||||
import { Box, Button, color, config, Icon, Icons, MenuItem, Spinner, Text } from 'folds';
 | 
			
		||||
import React, { useCallback } from 'react';
 | 
			
		||||
import { UserHero, UserHeroName } from './UserHero';
 | 
			
		||||
import { getDMRoomFor, getMxIdServer, mxcUrlToHttp } from '../../utils/matrix';
 | 
			
		||||
import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room';
 | 
			
		||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
 | 
			
		||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
 | 
			
		||||
import { PowerColorBadge, PowerIcon } from '../power';
 | 
			
		||||
import { usePowerLevels, usePowerLevelsAPI } from '../../hooks/usePowerLevels';
 | 
			
		||||
import { getPowers, getTagIconSrc, usePowerLevelTags } from '../../hooks/usePowerLevelTags';
 | 
			
		||||
import { stopPropagation } from '../../utils/keyboard';
 | 
			
		||||
import { StateEvent } from '../../../types/matrix/room';
 | 
			
		||||
import { useOpenRoomSettings } from '../../state/hooks/roomSettings';
 | 
			
		||||
import { RoomSettingsPage } from '../../state/roomSettings';
 | 
			
		||||
import { useRoom } from '../../hooks/useRoom';
 | 
			
		||||
import { useSpaceOptionally } from '../../hooks/useSpace';
 | 
			
		||||
import { useUserPresence } from '../../hooks/useUserPresence';
 | 
			
		||||
import { SequenceCard } from '../sequence-card';
 | 
			
		||||
import { MutualRoomsChip, ServerChip } from './UserChips';
 | 
			
		||||
import { CutoutCard } from '../cutout-card';
 | 
			
		||||
import { SettingTile } from '../setting-tile';
 | 
			
		||||
import { useOpenSpaceSettings } from '../../state/hooks/spaceSettings';
 | 
			
		||||
import { SpaceSettingsPage } from '../../state/spaceSettings';
 | 
			
		||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
 | 
			
		||||
import { createDM } from '../../../client/action/room';
 | 
			
		||||
import { hasDevices } from '../../../util/matrixUtil';
 | 
			
		||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
 | 
			
		||||
import { useAlive } from '../../hooks/useAlive';
 | 
			
		||||
import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile';
 | 
			
		||||
 | 
			
		||||
function PowerChip({ userId }: { userId: string }) {
 | 
			
		||||
  const mx = useMatrixClient();
 | 
			
		||||
  const room = useRoom();
 | 
			
		||||
  const space = useSpaceOptionally();
 | 
			
		||||
  const useAuthentication = useMediaAuthentication();
 | 
			
		||||
  const openRoomSettings = useOpenRoomSettings();
 | 
			
		||||
  const openSpaceSettings = useOpenSpaceSettings();
 | 
			
		||||
 | 
			
		||||
  const powerLevels = usePowerLevels(room);
 | 
			
		||||
  const { getPowerLevel, canSendStateEvent } = usePowerLevelsAPI(powerLevels);
 | 
			
		||||
  const [powerLevelTags, getPowerLevelTag] = usePowerLevelTags(room, powerLevels);
 | 
			
		||||
  const myPower = getPowerLevel(mx.getSafeUserId());
 | 
			
		||||
  const canChangePowers = canSendStateEvent(StateEvent.RoomPowerLevels, myPower);
 | 
			
		||||
 | 
			
		||||
  const userPower = getPowerLevel(userId);
 | 
			
		||||
  const tag = getPowerLevelTag(userPower);
 | 
			
		||||
  const tagIconSrc = tag.icon && getTagIconSrc(mx, useAuthentication, tag.icon);
 | 
			
		||||
 | 
			
		||||
  const [cords, setCords] = useState<RectCords>();
 | 
			
		||||
 | 
			
		||||
  const open: MouseEventHandler<HTMLButtonElement> = (evt) => {
 | 
			
		||||
    setCords(evt.currentTarget.getBoundingClientRect());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const close = () => setCords(undefined);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <PopOut
 | 
			
		||||
      anchor={cords}
 | 
			
		||||
      position="Bottom"
 | 
			
		||||
      align="Start"
 | 
			
		||||
      offset={4}
 | 
			
		||||
      content={
 | 
			
		||||
        <FocusTrap
 | 
			
		||||
          focusTrapOptions={{
 | 
			
		||||
            initialFocus: false,
 | 
			
		||||
            onDeactivate: close,
 | 
			
		||||
            clickOutsideDeactivates: true,
 | 
			
		||||
            escapeDeactivates: stopPropagation,
 | 
			
		||||
            isKeyForward: (evt: KeyboardEvent) => isKeyHotkey('arrowdown', evt),
 | 
			
		||||
            isKeyBackward: (evt: KeyboardEvent) => isKeyHotkey('arrowup', evt),
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <Menu>
 | 
			
		||||
            <div style={{ padding: config.space.S100 }}>
 | 
			
		||||
              {getPowers(powerLevelTags).map((power) => {
 | 
			
		||||
                const powerTag = powerLevelTags[power];
 | 
			
		||||
                const powerTagIconSrc =
 | 
			
		||||
                  powerTag.icon && getTagIconSrc(mx, useAuthentication, powerTag.icon);
 | 
			
		||||
 | 
			
		||||
                return (
 | 
			
		||||
                  <MenuItem
 | 
			
		||||
                    key={power}
 | 
			
		||||
                    variant="Surface"
 | 
			
		||||
                    fill="None"
 | 
			
		||||
                    size="300"
 | 
			
		||||
                    radii="300"
 | 
			
		||||
                    aria-disabled={!canChangePowers}
 | 
			
		||||
                    aria-pressed={userPower === power}
 | 
			
		||||
                    before={<PowerColorBadge color={powerTag.color} />}
 | 
			
		||||
                    after={
 | 
			
		||||
                      powerTagIconSrc ? (
 | 
			
		||||
                        <PowerIcon size="50" iconSrc={powerTagIconSrc} />
 | 
			
		||||
                      ) : undefined
 | 
			
		||||
                    }
 | 
			
		||||
                    onClick={canChangePowers ? undefined : undefined}
 | 
			
		||||
                  >
 | 
			
		||||
                    <Text size="B300">{powerTag.name}</Text>
 | 
			
		||||
                  </MenuItem>
 | 
			
		||||
                );
 | 
			
		||||
              })}
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <Line size="300" />
 | 
			
		||||
            <div style={{ padding: config.space.S100 }}>
 | 
			
		||||
              <MenuItem
 | 
			
		||||
                variant="Surface"
 | 
			
		||||
                fill="None"
 | 
			
		||||
                size="300"
 | 
			
		||||
                radii="300"
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  console.log(room.roomId, space?.roomId);
 | 
			
		||||
                  if (room.isSpaceRoom()) {
 | 
			
		||||
                    openSpaceSettings(
 | 
			
		||||
                      room.roomId,
 | 
			
		||||
                      space?.roomId,
 | 
			
		||||
                      SpaceSettingsPage.PermissionsPage
 | 
			
		||||
                    );
 | 
			
		||||
                  } else {
 | 
			
		||||
                    openRoomSettings(room.roomId, space?.roomId, RoomSettingsPage.PermissionsPage);
 | 
			
		||||
                  }
 | 
			
		||||
                  close();
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                <Text size="B300">Manage Powers</Text>
 | 
			
		||||
              </MenuItem>
 | 
			
		||||
            </div>
 | 
			
		||||
          </Menu>
 | 
			
		||||
        </FocusTrap>
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <Chip
 | 
			
		||||
        variant="SurfaceVariant"
 | 
			
		||||
        radii="Pill"
 | 
			
		||||
        before={
 | 
			
		||||
          cords ? (
 | 
			
		||||
            <Icon size="50" src={Icons.ChevronBottom} />
 | 
			
		||||
          ) : (
 | 
			
		||||
            <PowerColorBadge color={tag.color} />
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
        after={tagIconSrc ? <PowerIcon size="50" iconSrc={tagIconSrc} /> : undefined}
 | 
			
		||||
        onClick={open}
 | 
			
		||||
        aria-pressed={!!cords}
 | 
			
		||||
      >
 | 
			
		||||
        <Text size="B300" truncate>
 | 
			
		||||
          {tag.name}
 | 
			
		||||
        </Text>
 | 
			
		||||
      </Chip>
 | 
			
		||||
    </PopOut>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
import { PowerChip } from './PowerChip';
 | 
			
		||||
 | 
			
		||||
type UserBanAlertProps = {
 | 
			
		||||
  userId: string;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue