import React, { MouseEventHandler, useCallback, useEffect, useMemo, useState } from 'react'; import { Box, Text, Button, Icon, Icons, IconButton, Avatar, AvatarImage, AvatarFallback, config, Spinner, Menu, RectCords, PopOut, Checkbox, toRem, Scroll, Header, Line, Chip, } from 'folds'; import FocusTrap from 'focus-trap-react'; import { useAtomValue } from 'jotai'; import { Room } from 'matrix-js-sdk'; import { useGlobalImagePacks, useRoomsImagePacks } from '../../../hooks/useImagePacks'; import { SequenceCardStyle } from '../styles.css'; import { SequenceCard } from '../../../components/sequence-card'; import { SettingTile } from '../../../components/setting-tile'; import { mxcUrlToHttp } from '../../../utils/matrix'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { EmoteRoomsContent, ImagePack, ImageUsage, PackAddress, packAddressEqual, } from '../../../plugins/custom-emoji'; import { LineClamp2 } from '../../../styles/Text.css'; import { allRoomsAtom } from '../../../state/room-list/roomList'; import { AccountDataEvent } from '../../../../types/matrix/accountData'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { stopPropagation } from '../../../utils/keyboard'; function GlobalPackSelector({ packs, useAuthentication, onSelect, }: { packs: ImagePack[]; useAuthentication: boolean; onSelect: (addresses: PackAddress[]) => void; }) { const mx = useMatrixClient(); const roomToPacks = useMemo(() => { const rToP = new Map(); packs .filter((pack) => !pack.deleted) .forEach((pack) => { if (!pack.address) return; const pks = rToP.get(pack.address.roomId) ?? []; pks.push(pack); rToP.set(pack.address.roomId, pks); }); return rToP; }, [packs]); const [selected, setSelected] = useState([]); const toggleSelect = (address: PackAddress) => { setSelected((addresses) => { const newAddresses = addresses.filter((addr) => !packAddressEqual(addr, address)); if (newAddresses.length !== addresses.length) { return newAddresses; } newAddresses.push(address); return newAddresses; }); }; const addSelected = (adds: PackAddress[]) => { setSelected((addresses) => { const newAddresses = Array.from(addresses); adds.forEach((address) => { if (newAddresses.find((addr) => packAddressEqual(addr, address))) { return; } newAddresses.push(address); }); return newAddresses; }); }; const removeSelected = (adds: PackAddress[]) => { setSelected((addresses) => { const newAddresses = addresses.filter( (addr) => !adds.find((address) => packAddressEqual(addr, address)) ); return newAddresses; }); }; const hasSelected = selected.length > 0; return (
Room Packs onSelect(selected)} > {hasSelected ? 'Save' : 'Close'}
{Array.from(roomToPacks.entries()).map(([roomId, roomPacks]) => { const room = mx.getRoom(roomId); if (!room) return null; const roomPackAddresses = roomPacks .map((pack) => pack.address) .filter((addr) => addr !== undefined); const allSelected = roomPackAddresses.every((addr) => selected.find((address) => packAddressEqual(addr, address)) ); return ( {room.name} { if (allSelected) { removeSelected(roomPackAddresses); return; } addSelected(roomPackAddresses); }} > {allSelected ? 'Unselect All' : 'Select All'} {roomPacks.map((pack) => { const avatarMxc = pack.getAvatarUrl(ImageUsage.Emoticon); const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication) : undefined; const { address } = pack; if (!address) return null; const added = !!selected.find((addr) => packAddressEqual(addr, address)); return ( {pack.meta.attribution}} before={ {avatarUrl ? ( ) : ( )} } after={ toggleSelect(address)} /> } /> ); })} ); })} {roomToPacks.size === 0 && ( No Packs Pack from rooms will appear here. You do not have any room with packs yet. )}
); } type GlobalPacksProps = { onViewPack: (imagePack: ImagePack) => void; }; export function GlobalPacks({ onViewPack }: GlobalPacksProps) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const globalPacks = useGlobalImagePacks(); const [menuCords, setMenuCords] = useState(); const roomIds = useAtomValue(allRoomsAtom); const rooms = useMemo(() => { const rs: Room[] = []; roomIds.forEach((rId) => { const r = mx.getRoom(rId); if (r) rs.push(r); }); return rs; }, [mx, roomIds]); const roomsImagePack = useRoomsImagePacks(rooms); const nonGlobalPacks = useMemo( () => roomsImagePack.filter( (pack) => !globalPacks.find((p) => packAddressEqual(pack.address, p.address)) ), [roomsImagePack, globalPacks] ); const [selectedPacks, setSelectedPacks] = useState([]); const [removedPacks, setRemovedPacks] = useState([]); const unselectedGlobalPacks = useMemo( () => nonGlobalPacks.filter( (pack) => !selectedPacks.find((addr) => packAddressEqual(pack.address, addr)) ), [selectedPacks, nonGlobalPacks] ); const handleRemove = (address: PackAddress) => { setRemovedPacks((addresses) => [...addresses, address]); }; const handleUndoRemove = (address: PackAddress) => { setRemovedPacks((addresses) => addresses.filter((addr) => !packAddressEqual(addr, address))); }; const handleSelected = (addresses: PackAddress[]) => { setMenuCords(undefined); if (addresses.length > 0) { setSelectedPacks((a) => [...addresses, ...a]); } }; const [applyState, applyChanges] = useAsyncCallback( useCallback(async () => { const content = mx.getAccountData(AccountDataEvent.PoniesEmoteRooms)?.getContent() ?? {}; const updatedContent: EmoteRoomsContent = JSON.parse(JSON.stringify(content)); selectedPacks.forEach((addr) => { const roomsToState = updatedContent.rooms ?? {}; const stateKeyToObj = roomsToState[addr.roomId] ?? {}; stateKeyToObj[addr.stateKey] = {}; roomsToState[addr.roomId] = stateKeyToObj; updatedContent.rooms = roomsToState; }); removedPacks.forEach((addr) => { if (updatedContent.rooms?.[addr.roomId]?.[addr.stateKey]) { delete updatedContent.rooms?.[addr.roomId][addr.stateKey]; } }); await mx.setAccountData(AccountDataEvent.PoniesEmoteRooms, updatedContent); }, [mx, selectedPacks, removedPacks]) ); const resetChanges = useCallback(() => { setSelectedPacks([]); setRemovedPacks([]); }, []); useEffect(() => { if (applyState.status === AsyncStatus.Success) { resetChanges(); } }, [applyState, resetChanges]); const handleSelectMenu: MouseEventHandler = (evt) => { setMenuCords(evt.currentTarget.getBoundingClientRect()); }; const applyingChanges = applyState.status === AsyncStatus.Loading; const hasChanges = removedPacks.length > 0 || selectedPacks.length > 0; const renderPack = (pack: ImagePack) => { const avatarMxc = pack.getAvatarUrl(ImageUsage.Emoticon); const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication) : undefined; const { address } = pack; if (!address) return null; const removed = !!removedPacks.find((addr) => packAddressEqual(addr, address)); return ( {pack.meta.name ?? 'Unknown'} } description={{pack.meta.attribution}} before={ {removed ? ( handleUndoRemove(address)} disabled={applyingChanges} > ) : ( handleRemove(address)} disabled={applyingChanges} > )} {avatarUrl ? ( ) : ( )} } after={ !removed && ( ) } /> ); }; return ( <> Favorite Packs setMenuCords(undefined), clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown' || evt.key === 'ArrowRight', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', escapeDeactivates: stopPropagation, }} > } /> } /> {globalPacks.map(renderPack)} {nonGlobalPacks .filter((pack) => !!selectedPacks.find((addr) => packAddressEqual(pack.address, addr))) .map(renderPack)} {hasChanges && ( {applyState.status === AsyncStatus.Error ? ( Failed to apply changes! Please try again. ) : ( Changes saved! Apply when ready. )} )} ); }