mirror of
				https://github.com/cinnyapp/cinny.git
				synced 2025-11-04 06:20:28 +03:00 
			
		
		
		
	New add existing room/space modal (#2451)
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Deploy to Netlify (dev) / Deploy to Netlify (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Deploy to Netlify (dev) / Deploy to Netlify (push) Has been cancelled
				
			This commit is contained in:
		
							parent
							
								
									09b88d164f
								
							
						
					
					
						commit
						78a0d11f24
					
				
					 5 changed files with 390 additions and 19 deletions
				
			
		| 
						 | 
					@ -30,9 +30,7 @@ import { SettingTile } from '../setting-tile';
 | 
				
			||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
 | 
					import { useMatrixClient } from '../../hooks/useMatrixClient';
 | 
				
			||||||
import { stopPropagation } from '../../utils/keyboard';
 | 
					import { stopPropagation } from '../../utils/keyboard';
 | 
				
			||||||
import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch';
 | 
					import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch';
 | 
				
			||||||
import { findAndReplace } from '../../utils/findAndReplace';
 | 
					import { highlightText, makeHighlightRegex } from '../../plugins/react-custom-html-parser';
 | 
				
			||||||
import { highlightText } from '../../styles/CustomHtml.css';
 | 
					 | 
				
			||||||
import { makeHighlightRegex } from '../../plugins/react-custom-html-parser';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useAdditionalCreators = (defaultCreators?: string[]) => {
 | 
					export const useAdditionalCreators = (defaultCreators?: string[]) => {
 | 
				
			||||||
  const mx = useMatrixClient();
 | 
					  const mx = useMatrixClient();
 | 
				
			||||||
| 
						 | 
					@ -245,19 +243,9 @@ export function AdditionalCreatorInput({
 | 
				
			||||||
                                  <Text size="T200" truncate>
 | 
					                                  <Text size="T200" truncate>
 | 
				
			||||||
                                    <b>
 | 
					                                    <b>
 | 
				
			||||||
                                      {queryHighlighRegex
 | 
					                                      {queryHighlighRegex
 | 
				
			||||||
                                        ? findAndReplace(
 | 
					                                        ? highlightText(queryHighlighRegex, [
 | 
				
			||||||
                                            getMxIdLocalPart(userId) ?? userId,
 | 
					                                            getMxIdLocalPart(userId) ?? userId,
 | 
				
			||||||
                                            queryHighlighRegex,
 | 
					                                          ])
 | 
				
			||||||
                                            (match, pushIndex) => (
 | 
					 | 
				
			||||||
                                              <span
 | 
					 | 
				
			||||||
                                                key={`highlight-${pushIndex}`}
 | 
					 | 
				
			||||||
                                                className={highlightText}
 | 
					 | 
				
			||||||
                                              >
 | 
					 | 
				
			||||||
                                                {match[0]}
 | 
					 | 
				
			||||||
                                              </span>
 | 
					 | 
				
			||||||
                                            ),
 | 
					 | 
				
			||||||
                                            (txt) => txt
 | 
					 | 
				
			||||||
                                          )
 | 
					 | 
				
			||||||
                                        : getMxIdLocalPart(userId)}
 | 
					                                        : getMxIdLocalPart(userId)}
 | 
				
			||||||
                                    </b>
 | 
					                                    </b>
 | 
				
			||||||
                                  </Text>
 | 
					                                  </Text>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										375
									
								
								src/app/features/add-existing/AddExisting.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										375
									
								
								src/app/features/add-existing/AddExisting.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,375 @@
 | 
				
			||||||
 | 
					import FocusTrap from 'focus-trap-react';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Avatar,
 | 
				
			||||||
 | 
					  Box,
 | 
				
			||||||
 | 
					  Button,
 | 
				
			||||||
 | 
					  config,
 | 
				
			||||||
 | 
					  Header,
 | 
				
			||||||
 | 
					  Icon,
 | 
				
			||||||
 | 
					  IconButton,
 | 
				
			||||||
 | 
					  Icons,
 | 
				
			||||||
 | 
					  Input,
 | 
				
			||||||
 | 
					  Menu,
 | 
				
			||||||
 | 
					  MenuItem,
 | 
				
			||||||
 | 
					  Modal,
 | 
				
			||||||
 | 
					  Overlay,
 | 
				
			||||||
 | 
					  OverlayBackdrop,
 | 
				
			||||||
 | 
					  OverlayCenter,
 | 
				
			||||||
 | 
					  Scroll,
 | 
				
			||||||
 | 
					  Spinner,
 | 
				
			||||||
 | 
					  Text,
 | 
				
			||||||
 | 
					} from 'folds';
 | 
				
			||||||
 | 
					import React, {
 | 
				
			||||||
 | 
					  ChangeEventHandler,
 | 
				
			||||||
 | 
					  MouseEventHandler,
 | 
				
			||||||
 | 
					  useCallback,
 | 
				
			||||||
 | 
					  useMemo,
 | 
				
			||||||
 | 
					  useRef,
 | 
				
			||||||
 | 
					  useState,
 | 
				
			||||||
 | 
					} from 'react';
 | 
				
			||||||
 | 
					import { useAtomValue } from 'jotai';
 | 
				
			||||||
 | 
					import { useVirtualizer } from '@tanstack/react-virtual';
 | 
				
			||||||
 | 
					import { Room } from 'matrix-js-sdk';
 | 
				
			||||||
 | 
					import { stopPropagation } from '../../utils/keyboard';
 | 
				
			||||||
 | 
					import { useDirects, useRooms, useSpaces } from '../../state/hooks/roomList';
 | 
				
			||||||
 | 
					import { useMatrixClient } from '../../hooks/useMatrixClient';
 | 
				
			||||||
 | 
					import { allRoomsAtom } from '../../state/room-list/roomList';
 | 
				
			||||||
 | 
					import { mDirectAtom } from '../../state/mDirectList';
 | 
				
			||||||
 | 
					import { roomToParentsAtom } from '../../state/room/roomToParents';
 | 
				
			||||||
 | 
					import { useAllJoinedRoomsSet, useGetRoom } from '../../hooks/useGetRoom';
 | 
				
			||||||
 | 
					import { VirtualTile } from '../../components/virtualizer';
 | 
				
			||||||
 | 
					import { getDirectRoomAvatarUrl, getRoomAvatarUrl } from '../../utils/room';
 | 
				
			||||||
 | 
					import { RoomAvatar, RoomIcon } from '../../components/room-avatar';
 | 
				
			||||||
 | 
					import { nameInitials } from '../../utils/common';
 | 
				
			||||||
 | 
					import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
 | 
				
			||||||
 | 
					import { factoryRoomIdByAtoZ } from '../../utils/sort';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  SearchItemStrGetter,
 | 
				
			||||||
 | 
					  useAsyncSearch,
 | 
				
			||||||
 | 
					  UseAsyncSearchOptions,
 | 
				
			||||||
 | 
					} from '../../hooks/useAsyncSearch';
 | 
				
			||||||
 | 
					import { highlightText, makeHighlightRegex } from '../../plugins/react-custom-html-parser';
 | 
				
			||||||
 | 
					import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
 | 
				
			||||||
 | 
					import { StateEvent } from '../../../types/matrix/room';
 | 
				
			||||||
 | 
					import { getViaServers } from '../../plugins/via-servers';
 | 
				
			||||||
 | 
					import { rateLimitedActions } from '../../utils/matrix';
 | 
				
			||||||
 | 
					import { useAlive } from '../../hooks/useAlive';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SEARCH_OPTS: UseAsyncSearchOptions = {
 | 
				
			||||||
 | 
					  limit: 500,
 | 
				
			||||||
 | 
					  matchOptions: {
 | 
				
			||||||
 | 
					    contain: true,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  normalizeOptions: {
 | 
				
			||||||
 | 
					    ignoreWhitespace: false,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AddExistingModalProps = {
 | 
				
			||||||
 | 
					  parentId: string;
 | 
				
			||||||
 | 
					  space?: boolean;
 | 
				
			||||||
 | 
					  requestClose: () => void;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export function AddExistingModal({ parentId, space, requestClose }: AddExistingModalProps) {
 | 
				
			||||||
 | 
					  const mx = useMatrixClient();
 | 
				
			||||||
 | 
					  const useAuthentication = useMediaAuthentication();
 | 
				
			||||||
 | 
					  const alive = useAlive();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const mDirects = useAtomValue(mDirectAtom);
 | 
				
			||||||
 | 
					  const spaces = useSpaces(mx, allRoomsAtom);
 | 
				
			||||||
 | 
					  const rooms = useRooms(mx, allRoomsAtom, mDirects);
 | 
				
			||||||
 | 
					  const directs = useDirects(mx, allRoomsAtom, mDirects);
 | 
				
			||||||
 | 
					  const roomIdToParents = useAtomValue(roomToParentsAtom);
 | 
				
			||||||
 | 
					  const scrollRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [selected, setSelected] = useState<string[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const allRoomsSet = useAllJoinedRoomsSet();
 | 
				
			||||||
 | 
					  const getRoom = useGetRoom(allRoomsSet);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const allItems: string[] = useMemo(() => {
 | 
				
			||||||
 | 
					    const rIds = space ? [...spaces] : [...rooms, ...directs];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return rIds
 | 
				
			||||||
 | 
					      .filter((rId) => rId !== parentId && !roomIdToParents.get(rId)?.has(parentId))
 | 
				
			||||||
 | 
					      .sort(factoryRoomIdByAtoZ(mx));
 | 
				
			||||||
 | 
					  }, [spaces, rooms, directs, space, parentId, roomIdToParents, mx]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const getRoomNameStr: SearchItemStrGetter<string> = useCallback(
 | 
				
			||||||
 | 
					    (rId) => getRoom(rId)?.name ?? rId,
 | 
				
			||||||
 | 
					    [getRoom]
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [searchResult, searchRoom, resetSearch] = useAsyncSearch(
 | 
				
			||||||
 | 
					    allItems,
 | 
				
			||||||
 | 
					    getRoomNameStr,
 | 
				
			||||||
 | 
					    SEARCH_OPTS
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const queryHighlighRegex = searchResult?.query
 | 
				
			||||||
 | 
					    ? makeHighlightRegex(searchResult.query.split(' '))
 | 
				
			||||||
 | 
					    : undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const items = searchResult ? searchResult.items : allItems;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const virtualizer = useVirtualizer({
 | 
				
			||||||
 | 
					    count: items.length,
 | 
				
			||||||
 | 
					    getScrollElement: () => scrollRef.current,
 | 
				
			||||||
 | 
					    estimateSize: () => 32,
 | 
				
			||||||
 | 
					    overscan: 5,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const vItems = virtualizer.getVirtualItems();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleSearchChange: ChangeEventHandler<HTMLInputElement> = (evt) => {
 | 
				
			||||||
 | 
					    const value = evt.currentTarget.value.trim();
 | 
				
			||||||
 | 
					    if (!value) {
 | 
				
			||||||
 | 
					      resetSearch();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    searchRoom(value);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [applyState, applyChanges] = useAsyncCallback<undefined, Error, [Room[]]>(
 | 
				
			||||||
 | 
					    useCallback(
 | 
				
			||||||
 | 
					      async (selectedRooms) => {
 | 
				
			||||||
 | 
					        await rateLimitedActions(selectedRooms, async (room) => {
 | 
				
			||||||
 | 
					          const via = getViaServers(room);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          await mx.sendStateEvent(
 | 
				
			||||||
 | 
					            parentId,
 | 
				
			||||||
 | 
					            StateEvent.SpaceChild as any,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              auto_join: false,
 | 
				
			||||||
 | 
					              suggested: false,
 | 
				
			||||||
 | 
					              via,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            room.roomId
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      [mx, parentId]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const applyingChanges = applyState.status === AsyncStatus.Loading;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleRoomClick: MouseEventHandler<HTMLButtonElement> = (evt) => {
 | 
				
			||||||
 | 
					    const roomId = evt.currentTarget.getAttribute('data-room-id');
 | 
				
			||||||
 | 
					    if (!roomId) return;
 | 
				
			||||||
 | 
					    if (selected?.includes(roomId)) {
 | 
				
			||||||
 | 
					      setSelected(selected?.filter((rId) => rId !== roomId));
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const addedRooms = [...(selected ?? [])];
 | 
				
			||||||
 | 
					    addedRooms.push(roomId);
 | 
				
			||||||
 | 
					    setSelected(addedRooms);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleApplyChanges = () => {
 | 
				
			||||||
 | 
					    const selectedRooms = selected.map((rId) => getRoom(rId)).filter((room) => room !== undefined);
 | 
				
			||||||
 | 
					    applyChanges(selectedRooms).then(() => {
 | 
				
			||||||
 | 
					      if (alive()) {
 | 
				
			||||||
 | 
					        setSelected([]);
 | 
				
			||||||
 | 
					        requestClose();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const resetChanges = () => {
 | 
				
			||||||
 | 
					    setSelected([]);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Overlay open backdrop={<OverlayBackdrop />}>
 | 
				
			||||||
 | 
					      <OverlayCenter>
 | 
				
			||||||
 | 
					        <FocusTrap
 | 
				
			||||||
 | 
					          focusTrapOptions={{
 | 
				
			||||||
 | 
					            initialFocus: false,
 | 
				
			||||||
 | 
					            clickOutsideDeactivates: true,
 | 
				
			||||||
 | 
					            onDeactivate: requestClose,
 | 
				
			||||||
 | 
					            escapeDeactivates: stopPropagation,
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <Modal size="300">
 | 
				
			||||||
 | 
					            <Box grow="Yes" direction="Column">
 | 
				
			||||||
 | 
					              <Header
 | 
				
			||||||
 | 
					                size="500"
 | 
				
			||||||
 | 
					                style={{
 | 
				
			||||||
 | 
					                  padding: config.space.S200,
 | 
				
			||||||
 | 
					                  paddingLeft: config.space.S400,
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <Box grow="Yes">
 | 
				
			||||||
 | 
					                  <Text size="H4">Add Existing</Text>
 | 
				
			||||||
 | 
					                </Box>
 | 
				
			||||||
 | 
					                <Box shrink="No">
 | 
				
			||||||
 | 
					                  <IconButton size="300" radii="300" onClick={requestClose}>
 | 
				
			||||||
 | 
					                    <Icon src={Icons.Cross} />
 | 
				
			||||||
 | 
					                  </IconButton>
 | 
				
			||||||
 | 
					                </Box>
 | 
				
			||||||
 | 
					              </Header>
 | 
				
			||||||
 | 
					              <Box grow="Yes">
 | 
				
			||||||
 | 
					                <Scroll ref={scrollRef} size="300" hideTrack>
 | 
				
			||||||
 | 
					                  <Box
 | 
				
			||||||
 | 
					                    style={{ padding: config.space.S300, paddingRight: 0 }}
 | 
				
			||||||
 | 
					                    direction="Column"
 | 
				
			||||||
 | 
					                    gap="500"
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <Box
 | 
				
			||||||
 | 
					                      direction="Column"
 | 
				
			||||||
 | 
					                      style={{ position: 'sticky', top: config.space.S300, zIndex: 1 }}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      <Input
 | 
				
			||||||
 | 
					                        onChange={handleSearchChange}
 | 
				
			||||||
 | 
					                        before={<Icon size="200" src={Icons.Search} />}
 | 
				
			||||||
 | 
					                        placeholder="Search"
 | 
				
			||||||
 | 
					                        size="400"
 | 
				
			||||||
 | 
					                        variant="Background"
 | 
				
			||||||
 | 
					                        outlined
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					                    </Box>
 | 
				
			||||||
 | 
					                    {vItems.length === 0 && (
 | 
				
			||||||
 | 
					                      <Box
 | 
				
			||||||
 | 
					                        style={{ paddingTop: config.space.S700 }}
 | 
				
			||||||
 | 
					                        grow="Yes"
 | 
				
			||||||
 | 
					                        alignItems="Center"
 | 
				
			||||||
 | 
					                        justifyContent="Center"
 | 
				
			||||||
 | 
					                        direction="Column"
 | 
				
			||||||
 | 
					                        gap="100"
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        <Text size="H6" align="Center">
 | 
				
			||||||
 | 
					                          {searchResult ? 'No Match Found' : `No ${space ? 'Spaces' : 'Rooms'}`}
 | 
				
			||||||
 | 
					                        </Text>
 | 
				
			||||||
 | 
					                        <Text size="T200" align="Center">
 | 
				
			||||||
 | 
					                          {searchResult
 | 
				
			||||||
 | 
					                            ? `No match found for "${searchResult.query}".`
 | 
				
			||||||
 | 
					                            : `You do not have any ${space ? 'Spaces' : 'Rooms'} to display yet.`}
 | 
				
			||||||
 | 
					                        </Text>
 | 
				
			||||||
 | 
					                      </Box>
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
 | 
					                    <Box
 | 
				
			||||||
 | 
					                      style={{
 | 
				
			||||||
 | 
					                        position: 'relative',
 | 
				
			||||||
 | 
					                        height: virtualizer.getTotalSize(),
 | 
				
			||||||
 | 
					                      }}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      {vItems.map((vItem) => {
 | 
				
			||||||
 | 
					                        const roomId = items[vItem.index];
 | 
				
			||||||
 | 
					                        const room = getRoom(roomId);
 | 
				
			||||||
 | 
					                        if (!room) return null;
 | 
				
			||||||
 | 
					                        const selectedItem = selected?.includes(roomId);
 | 
				
			||||||
 | 
					                        const dm = mDirects.has(room.roomId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return (
 | 
				
			||||||
 | 
					                          <VirtualTile
 | 
				
			||||||
 | 
					                            virtualItem={vItem}
 | 
				
			||||||
 | 
					                            style={{ paddingBottom: config.space.S100 }}
 | 
				
			||||||
 | 
					                            ref={virtualizer.measureElement}
 | 
				
			||||||
 | 
					                            key={vItem.index}
 | 
				
			||||||
 | 
					                          >
 | 
				
			||||||
 | 
					                            <MenuItem
 | 
				
			||||||
 | 
					                              data-room-id={roomId}
 | 
				
			||||||
 | 
					                              onClick={handleRoomClick}
 | 
				
			||||||
 | 
					                              variant={selectedItem ? 'Success' : 'Surface'}
 | 
				
			||||||
 | 
					                              size="400"
 | 
				
			||||||
 | 
					                              radii="400"
 | 
				
			||||||
 | 
					                              disabled={applyingChanges}
 | 
				
			||||||
 | 
					                              aria-pressed={selectedItem}
 | 
				
			||||||
 | 
					                              before={
 | 
				
			||||||
 | 
					                                <Avatar size="200" radii={dm ? '400' : '300'}>
 | 
				
			||||||
 | 
					                                  {dm || room.isSpaceRoom() ? (
 | 
				
			||||||
 | 
					                                    <RoomAvatar
 | 
				
			||||||
 | 
					                                      roomId={room.roomId}
 | 
				
			||||||
 | 
					                                      src={
 | 
				
			||||||
 | 
					                                        dm
 | 
				
			||||||
 | 
					                                          ? getDirectRoomAvatarUrl(mx, room, 96, useAuthentication)
 | 
				
			||||||
 | 
					                                          : getRoomAvatarUrl(mx, room, 96, useAuthentication)
 | 
				
			||||||
 | 
					                                      }
 | 
				
			||||||
 | 
					                                      alt={room.name}
 | 
				
			||||||
 | 
					                                      renderFallback={() => (
 | 
				
			||||||
 | 
					                                        <Text as="span" size="H6">
 | 
				
			||||||
 | 
					                                          {nameInitials(room.name)}
 | 
				
			||||||
 | 
					                                        </Text>
 | 
				
			||||||
 | 
					                                      )}
 | 
				
			||||||
 | 
					                                    />
 | 
				
			||||||
 | 
					                                  ) : (
 | 
				
			||||||
 | 
					                                    <RoomIcon size="200" joinRule={room.getJoinRule()} />
 | 
				
			||||||
 | 
					                                  )}
 | 
				
			||||||
 | 
					                                </Avatar>
 | 
				
			||||||
 | 
					                              }
 | 
				
			||||||
 | 
					                              after={selectedItem && <Icon size="200" src={Icons.Check} />}
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                              <Box grow="Yes">
 | 
				
			||||||
 | 
					                                <Text truncate size="T400">
 | 
				
			||||||
 | 
					                                  {queryHighlighRegex
 | 
				
			||||||
 | 
					                                    ? highlightText(queryHighlighRegex, [room.name])
 | 
				
			||||||
 | 
					                                    : room.name}
 | 
				
			||||||
 | 
					                                </Text>
 | 
				
			||||||
 | 
					                              </Box>
 | 
				
			||||||
 | 
					                            </MenuItem>
 | 
				
			||||||
 | 
					                          </VirtualTile>
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                      })}
 | 
				
			||||||
 | 
					                    </Box>
 | 
				
			||||||
 | 
					                    {selected.length > 0 && (
 | 
				
			||||||
 | 
					                      <Menu
 | 
				
			||||||
 | 
					                        style={{
 | 
				
			||||||
 | 
					                          position: 'sticky',
 | 
				
			||||||
 | 
					                          padding: config.space.S200,
 | 
				
			||||||
 | 
					                          paddingLeft: config.space.S400,
 | 
				
			||||||
 | 
					                          bottom: config.space.S400,
 | 
				
			||||||
 | 
					                          left: config.space.S400,
 | 
				
			||||||
 | 
					                          right: 0,
 | 
				
			||||||
 | 
					                          zIndex: 1,
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                        variant="Success"
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        <Box alignItems="Center" gap="400">
 | 
				
			||||||
 | 
					                          <Box grow="Yes" direction="Column">
 | 
				
			||||||
 | 
					                            {applyState.status === AsyncStatus.Error ? (
 | 
				
			||||||
 | 
					                              <Text size="T200">
 | 
				
			||||||
 | 
					                                <b>Failed to apply changes! Please try again.</b>
 | 
				
			||||||
 | 
					                              </Text>
 | 
				
			||||||
 | 
					                            ) : (
 | 
				
			||||||
 | 
					                              <Text size="T200">
 | 
				
			||||||
 | 
					                                <b>Apply when ready. ({selected.length} Selected)</b>
 | 
				
			||||||
 | 
					                              </Text>
 | 
				
			||||||
 | 
					                            )}
 | 
				
			||||||
 | 
					                          </Box>
 | 
				
			||||||
 | 
					                          <Box shrink="No" gap="200">
 | 
				
			||||||
 | 
					                            <Button
 | 
				
			||||||
 | 
					                              size="300"
 | 
				
			||||||
 | 
					                              variant="Success"
 | 
				
			||||||
 | 
					                              fill="None"
 | 
				
			||||||
 | 
					                              radii="300"
 | 
				
			||||||
 | 
					                              disabled={applyingChanges}
 | 
				
			||||||
 | 
					                              onClick={resetChanges}
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                              <Text size="B300">Reset</Text>
 | 
				
			||||||
 | 
					                            </Button>
 | 
				
			||||||
 | 
					                            <Button
 | 
				
			||||||
 | 
					                              size="300"
 | 
				
			||||||
 | 
					                              variant="Success"
 | 
				
			||||||
 | 
					                              radii="300"
 | 
				
			||||||
 | 
					                              disabled={applyingChanges}
 | 
				
			||||||
 | 
					                              before={
 | 
				
			||||||
 | 
					                                applyingChanges && (
 | 
				
			||||||
 | 
					                                  <Spinner variant="Success" fill="Solid" size="100" />
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					                              }
 | 
				
			||||||
 | 
					                              onClick={handleApplyChanges}
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                              <Text size="B300">Apply Changes</Text>
 | 
				
			||||||
 | 
					                            </Button>
 | 
				
			||||||
 | 
					                          </Box>
 | 
				
			||||||
 | 
					                        </Box>
 | 
				
			||||||
 | 
					                      </Menu>
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
 | 
					                  </Box>
 | 
				
			||||||
 | 
					                </Scroll>
 | 
				
			||||||
 | 
					              </Box>
 | 
				
			||||||
 | 
					            </Box>
 | 
				
			||||||
 | 
					          </Modal>
 | 
				
			||||||
 | 
					        </FocusTrap>
 | 
				
			||||||
 | 
					      </OverlayCenter>
 | 
				
			||||||
 | 
					    </Overlay>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								src/app/features/add-existing/index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/app/features/add-existing/index.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					export * from './AddExisting';
 | 
				
			||||||
| 
						 | 
					@ -54,7 +54,6 @@ function CreateRoomModal({ state }: CreateRoomModalProps) {
 | 
				
			||||||
                  style={{
 | 
					                  style={{
 | 
				
			||||||
                    padding: config.space.S200,
 | 
					                    padding: config.space.S200,
 | 
				
			||||||
                    paddingLeft: config.space.S400,
 | 
					                    paddingLeft: config.space.S400,
 | 
				
			||||||
                    borderBottomWidth: config.borderWidth.B300,
 | 
					 | 
				
			||||||
                  }}
 | 
					                  }}
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                  <Box grow="Yes">
 | 
					                  <Box grow="Yes">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,12 +30,12 @@ import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
 | 
				
			||||||
import * as css from './SpaceItem.css';
 | 
					import * as css from './SpaceItem.css';
 | 
				
			||||||
import * as styleCss from './style.css';
 | 
					import * as styleCss from './style.css';
 | 
				
			||||||
import { useDraggableItem } from './DnD';
 | 
					import { useDraggableItem } from './DnD';
 | 
				
			||||||
import { openSpaceAddExisting } from '../../../client/action/navigation';
 | 
					 | 
				
			||||||
import { stopPropagation } from '../../utils/keyboard';
 | 
					import { stopPropagation } from '../../utils/keyboard';
 | 
				
			||||||
import { mxcUrlToHttp } from '../../utils/matrix';
 | 
					import { mxcUrlToHttp } from '../../utils/matrix';
 | 
				
			||||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
 | 
					import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
 | 
				
			||||||
import { useOpenCreateRoomModal } from '../../state/hooks/createRoomModal';
 | 
					import { useOpenCreateRoomModal } from '../../state/hooks/createRoomModal';
 | 
				
			||||||
import { useOpenCreateSpaceModal } from '../../state/hooks/createSpaceModal';
 | 
					import { useOpenCreateSpaceModal } from '../../state/hooks/createSpaceModal';
 | 
				
			||||||
 | 
					import { AddExistingModal } from '../add-existing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function SpaceProfileLoading() {
 | 
					function SpaceProfileLoading() {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
| 
						 | 
					@ -243,6 +243,7 @@ function RootSpaceProfile({ closed, categoryId, handleClose }: RootSpaceProfileP
 | 
				
			||||||
function AddRoomButton({ item }: { item: HierarchyItem }) {
 | 
					function AddRoomButton({ item }: { item: HierarchyItem }) {
 | 
				
			||||||
  const [cords, setCords] = useState<RectCords>();
 | 
					  const [cords, setCords] = useState<RectCords>();
 | 
				
			||||||
  const openCreateRoomModal = useOpenCreateRoomModal();
 | 
					  const openCreateRoomModal = useOpenCreateRoomModal();
 | 
				
			||||||
 | 
					  const [addExisting, setAddExisting] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleAddRoom: MouseEventHandler<HTMLButtonElement> = (evt) => {
 | 
					  const handleAddRoom: MouseEventHandler<HTMLButtonElement> = (evt) => {
 | 
				
			||||||
    setCords(evt.currentTarget.getBoundingClientRect());
 | 
					    setCords(evt.currentTarget.getBoundingClientRect());
 | 
				
			||||||
| 
						 | 
					@ -254,7 +255,7 @@ function AddRoomButton({ item }: { item: HierarchyItem }) {
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleAddExisting = () => {
 | 
					  const handleAddExisting = () => {
 | 
				
			||||||
    openSpaceAddExisting(item.roomId);
 | 
					    setAddExisting(true);
 | 
				
			||||||
    setCords(undefined);
 | 
					    setCords(undefined);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -300,6 +301,9 @@ function AddRoomButton({ item }: { item: HierarchyItem }) {
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <Text size="B300">Add Room</Text>
 | 
					        <Text size="B300">Add Room</Text>
 | 
				
			||||||
      </Chip>
 | 
					      </Chip>
 | 
				
			||||||
 | 
					      {addExisting && (
 | 
				
			||||||
 | 
					        <AddExistingModal parentId={item.roomId} requestClose={() => setAddExisting(false)} />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
    </PopOut>
 | 
					    </PopOut>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -307,6 +311,7 @@ function AddRoomButton({ item }: { item: HierarchyItem }) {
 | 
				
			||||||
function AddSpaceButton({ item }: { item: HierarchyItem }) {
 | 
					function AddSpaceButton({ item }: { item: HierarchyItem }) {
 | 
				
			||||||
  const [cords, setCords] = useState<RectCords>();
 | 
					  const [cords, setCords] = useState<RectCords>();
 | 
				
			||||||
  const openCreateSpaceModal = useOpenCreateSpaceModal();
 | 
					  const openCreateSpaceModal = useOpenCreateSpaceModal();
 | 
				
			||||||
 | 
					  const [addExisting, setAddExisting] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleAddSpace: MouseEventHandler<HTMLButtonElement> = (evt) => {
 | 
					  const handleAddSpace: MouseEventHandler<HTMLButtonElement> = (evt) => {
 | 
				
			||||||
    setCords(evt.currentTarget.getBoundingClientRect());
 | 
					    setCords(evt.currentTarget.getBoundingClientRect());
 | 
				
			||||||
| 
						 | 
					@ -318,7 +323,7 @@ function AddSpaceButton({ item }: { item: HierarchyItem }) {
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleAddExisting = () => {
 | 
					  const handleAddExisting = () => {
 | 
				
			||||||
    openSpaceAddExisting(item.roomId, true);
 | 
					    setAddExisting(true);
 | 
				
			||||||
    setCords(undefined);
 | 
					    setCords(undefined);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
| 
						 | 
					@ -363,6 +368,9 @@ function AddSpaceButton({ item }: { item: HierarchyItem }) {
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <Text size="B300">Add Space</Text>
 | 
					        <Text size="B300">Add Space</Text>
 | 
				
			||||||
      </Chip>
 | 
					      </Chip>
 | 
				
			||||||
 | 
					      {addExisting && (
 | 
				
			||||||
 | 
					        <AddExistingModal space parentId={item.roomId} requestClose={() => setAddExisting(false)} />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
    </PopOut>
 | 
					    </PopOut>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue