diff --git a/src/app/components/create-room/AdditionalCreatorInput.tsx b/src/app/components/create-room/AdditionalCreatorInput.tsx
index 51334b49..936b9b94 100644
--- a/src/app/components/create-room/AdditionalCreatorInput.tsx
+++ b/src/app/components/create-room/AdditionalCreatorInput.tsx
@@ -30,9 +30,7 @@ import { SettingTile } from '../setting-tile';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { stopPropagation } from '../../utils/keyboard';
import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch';
-import { findAndReplace } from '../../utils/findAndReplace';
-import { highlightText } from '../../styles/CustomHtml.css';
-import { makeHighlightRegex } from '../../plugins/react-custom-html-parser';
+import { highlightText, makeHighlightRegex } from '../../plugins/react-custom-html-parser';
export const useAdditionalCreators = (defaultCreators?: string[]) => {
const mx = useMatrixClient();
@@ -245,19 +243,9 @@ export function AdditionalCreatorInput({
{queryHighlighRegex
- ? findAndReplace(
+ ? highlightText(queryHighlighRegex, [
getMxIdLocalPart(userId) ?? userId,
- queryHighlighRegex,
- (match, pushIndex) => (
-
- {match[0]}
-
- ),
- (txt) => txt
- )
+ ])
: getMxIdLocalPart(userId)}
diff --git a/src/app/features/add-existing/AddExisting.tsx b/src/app/features/add-existing/AddExisting.tsx
new file mode 100644
index 00000000..cbae018f
--- /dev/null
+++ b/src/app/features/add-existing/AddExisting.tsx
@@ -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(null);
+
+ const [selected, setSelected] = useState([]);
+
+ 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 = 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 = (evt) => {
+ const value = evt.currentTarget.value.trim();
+ if (!value) {
+ resetSearch();
+ return;
+ }
+ searchRoom(value);
+ };
+
+ const [applyState, applyChanges] = useAsyncCallback(
+ 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 = (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 (
+ }>
+
+
+
+
+
+
+ Add Existing
+
+
+
+
+
+
+
+
+
+
+
+ }
+ placeholder="Search"
+ size="400"
+ variant="Background"
+ outlined
+ />
+
+ {vItems.length === 0 && (
+
+
+ {searchResult ? 'No Match Found' : `No ${space ? 'Spaces' : 'Rooms'}`}
+
+
+ {searchResult
+ ? `No match found for "${searchResult.query}".`
+ : `You do not have any ${space ? 'Spaces' : 'Rooms'} to display yet.`}
+
+
+ )}
+
+ {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 (
+
+
+
+ );
+ })}
+
+ {selected.length > 0 && (
+
+ )}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/features/add-existing/index.ts b/src/app/features/add-existing/index.ts
new file mode 100644
index 00000000..3607c062
--- /dev/null
+++ b/src/app/features/add-existing/index.ts
@@ -0,0 +1 @@
+export * from './AddExisting';
diff --git a/src/app/features/create-room/CreateRoomModal.tsx b/src/app/features/create-room/CreateRoomModal.tsx
index c1c9ba3e..c9919ba9 100644
--- a/src/app/features/create-room/CreateRoomModal.tsx
+++ b/src/app/features/create-room/CreateRoomModal.tsx
@@ -54,7 +54,6 @@ function CreateRoomModal({ state }: CreateRoomModalProps) {
style={{
padding: config.space.S200,
paddingLeft: config.space.S400,
- borderBottomWidth: config.borderWidth.B300,
}}
>
diff --git a/src/app/features/lobby/SpaceItem.tsx b/src/app/features/lobby/SpaceItem.tsx
index e881a971..64a97900 100644
--- a/src/app/features/lobby/SpaceItem.tsx
+++ b/src/app/features/lobby/SpaceItem.tsx
@@ -30,12 +30,12 @@ import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import * as css from './SpaceItem.css';
import * as styleCss from './style.css';
import { useDraggableItem } from './DnD';
-import { openSpaceAddExisting } from '../../../client/action/navigation';
import { stopPropagation } from '../../utils/keyboard';
import { mxcUrlToHttp } from '../../utils/matrix';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { useOpenCreateRoomModal } from '../../state/hooks/createRoomModal';
import { useOpenCreateSpaceModal } from '../../state/hooks/createSpaceModal';
+import { AddExistingModal } from '../add-existing';
function SpaceProfileLoading() {
return (
@@ -243,6 +243,7 @@ function RootSpaceProfile({ closed, categoryId, handleClose }: RootSpaceProfileP
function AddRoomButton({ item }: { item: HierarchyItem }) {
const [cords, setCords] = useState();
const openCreateRoomModal = useOpenCreateRoomModal();
+ const [addExisting, setAddExisting] = useState(false);
const handleAddRoom: MouseEventHandler = (evt) => {
setCords(evt.currentTarget.getBoundingClientRect());
@@ -254,7 +255,7 @@ function AddRoomButton({ item }: { item: HierarchyItem }) {
};
const handleAddExisting = () => {
- openSpaceAddExisting(item.roomId);
+ setAddExisting(true);
setCords(undefined);
};
@@ -300,6 +301,9 @@ function AddRoomButton({ item }: { item: HierarchyItem }) {
>
Add Room
+ {addExisting && (
+ setAddExisting(false)} />
+ )}
);
}
@@ -307,6 +311,7 @@ function AddRoomButton({ item }: { item: HierarchyItem }) {
function AddSpaceButton({ item }: { item: HierarchyItem }) {
const [cords, setCords] = useState();
const openCreateSpaceModal = useOpenCreateSpaceModal();
+ const [addExisting, setAddExisting] = useState(false);
const handleAddSpace: MouseEventHandler = (evt) => {
setCords(evt.currentTarget.getBoundingClientRect());
@@ -318,7 +323,7 @@ function AddSpaceButton({ item }: { item: HierarchyItem }) {
};
const handleAddExisting = () => {
- openSpaceAddExisting(item.roomId, true);
+ setAddExisting(true);
setCords(undefined);
};
return (
@@ -363,6 +368,9 @@ function AddSpaceButton({ item }: { item: HierarchyItem }) {
>
Add Space
+ {addExisting && (
+ setAddExisting(false)} />
+ )}
);
}