mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-02-13 16:13:24 +01:00
add existing spaces menu in lobby
This commit is contained in:
parent
4aa56054da
commit
2a2f815e86
4 changed files with 148 additions and 94 deletions
|
@ -303,15 +303,63 @@ function AddRoomButton({ item }: { item: HierarchyItem }) {
|
|||
}
|
||||
|
||||
function AddSpaceButton({ item }: { item: HierarchyItem }) {
|
||||
const [cords, setCords] = useState<RectCords>();
|
||||
|
||||
const handleAddSpace: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
setCords(evt.currentTarget.getBoundingClientRect());
|
||||
};
|
||||
|
||||
const handleCreateSpace = () => {
|
||||
openCreateRoom(true, item.roomId as any);
|
||||
setCords(undefined);
|
||||
};
|
||||
|
||||
const handleAddExisting = () => {
|
||||
openSpaceAddExisting(item.roomId, true);
|
||||
setCords(undefined);
|
||||
};
|
||||
return (
|
||||
<Chip
|
||||
variant="SurfaceVariant"
|
||||
radii="Pill"
|
||||
before={<Icon src={Icons.Plus} size="50" />}
|
||||
onClick={() => openCreateRoom(true, item.roomId as any)}
|
||||
<PopOut
|
||||
anchor={cords}
|
||||
position="Bottom"
|
||||
align="End"
|
||||
content={
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
onDeactivate: () => setCords(undefined),
|
||||
clickOutsideDeactivates: true,
|
||||
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
|
||||
isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
|
||||
}}
|
||||
>
|
||||
<Menu style={{ padding: config.space.S100 }}>
|
||||
<MenuItem
|
||||
size="300"
|
||||
radii="300"
|
||||
variant="Primary"
|
||||
fill="None"
|
||||
onClick={handleCreateSpace}
|
||||
>
|
||||
<Text size="T300">New Space</Text>
|
||||
</MenuItem>
|
||||
<MenuItem size="300" radii="300" fill="None" onClick={handleAddExisting}>
|
||||
<Text size="T300">Existing Space</Text>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</FocusTrap>
|
||||
}
|
||||
>
|
||||
<Text size="B300">Add Space</Text>
|
||||
</Chip>
|
||||
<Chip
|
||||
variant="SurfaceVariant"
|
||||
radii="Pill"
|
||||
before={<Icon src={Icons.Plus} size="50" />}
|
||||
onClick={handleAddSpace}
|
||||
aria-pressed={!!cords}
|
||||
>
|
||||
<Text size="B300">Add Space</Text>
|
||||
</Chip>
|
||||
</PopOut>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './SpaceAddExisting.scss';
|
||||
|
||||
|
@ -25,7 +25,7 @@ import SearchIC from '../../../../public/res/ic/outlined/search.svg';
|
|||
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
|
||||
function SpaceAddExistingContent({ roomId }) {
|
||||
function SpaceAddExistingContent({ roomId, spaces: onlySpaces }) {
|
||||
const mountStore = useStore(roomId);
|
||||
const [debounce] = useState(new Debounce());
|
||||
const [process, setProcess] = useState(null);
|
||||
|
@ -33,16 +33,15 @@ function SpaceAddExistingContent({ roomId }) {
|
|||
const [selected, setSelected] = useState([]);
|
||||
const [searchIds, setSearchIds] = useState(null);
|
||||
const mx = initMatrix.matrixClient;
|
||||
const {
|
||||
spaces, rooms, directs, roomIdToParents,
|
||||
} = initMatrix.roomList;
|
||||
const { spaces, rooms, directs, roomIdToParents } = initMatrix.roomList;
|
||||
|
||||
useEffect(() => {
|
||||
const allIds = [...spaces, ...rooms, ...directs].filter((rId) => (
|
||||
rId !== roomId && !roomIdToParents.get(rId)?.has(roomId)
|
||||
));
|
||||
const roomIds = onlySpaces ? [...spaces] : [...rooms, ...directs];
|
||||
const allIds = roomIds.filter(
|
||||
(rId) => rId !== roomId && !roomIdToParents.get(rId)?.has(roomId)
|
||||
);
|
||||
setAllRoomIds(allIds);
|
||||
}, [roomId]);
|
||||
}, [roomId, onlySpaces]);
|
||||
|
||||
const toggleSelection = (rId) => {
|
||||
if (process !== null) return;
|
||||
|
@ -68,20 +67,26 @@ function SpaceAddExistingContent({ roomId }) {
|
|||
via.push(getIdServer(rId));
|
||||
}
|
||||
|
||||
return mx.sendStateEvent(roomId, 'm.space.child', {
|
||||
auto_join: false,
|
||||
suggested: false,
|
||||
via,
|
||||
}, rId);
|
||||
return mx.sendStateEvent(
|
||||
roomId,
|
||||
'm.space.child',
|
||||
{
|
||||
auto_join: false,
|
||||
suggested: false,
|
||||
via,
|
||||
},
|
||||
rId
|
||||
);
|
||||
});
|
||||
|
||||
mountStore.setItem(true);
|
||||
await Promise.allSettled(promises);
|
||||
if (mountStore.getItem() !== true) return;
|
||||
|
||||
const allIds = [...spaces, ...rooms, ...directs].filter((rId) => (
|
||||
rId !== roomId && !roomIdToParents.get(rId)?.has(roomId) && !selected.includes(rId)
|
||||
));
|
||||
const roomIds = onlySpaces ? [...spaces] : [...rooms, ...directs];
|
||||
const allIds = roomIds.filter(
|
||||
(rId) => rId !== roomId && !roomIdToParents.get(rId)?.has(roomId) && !selected.includes(rId)
|
||||
);
|
||||
setAllRoomIds(allIds);
|
||||
setProcess(null);
|
||||
setSelected([]);
|
||||
|
@ -98,9 +103,7 @@ function SpaceAddExistingContent({ roomId }) {
|
|||
const searchedIds = allRoomIds.filter((rId) => {
|
||||
let name = mx.getRoom(rId)?.name;
|
||||
if (!name) return false;
|
||||
name = name.normalize('NFKC')
|
||||
.toLocaleLowerCase()
|
||||
.replace(/\s/g, '');
|
||||
name = name.normalize('NFKC').toLocaleLowerCase().replace(/\s/g, '');
|
||||
return name.includes(term);
|
||||
});
|
||||
setSearchIds(searchedIds);
|
||||
|
@ -114,66 +117,64 @@ function SpaceAddExistingContent({ roomId }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<form onSubmit={(ev) => { ev.preventDefault(); }}>
|
||||
<form
|
||||
onSubmit={(ev) => {
|
||||
ev.preventDefault();
|
||||
}}
|
||||
>
|
||||
<RawIcon size="small" src={SearchIC} />
|
||||
<Input
|
||||
name="searchInput"
|
||||
onChange={handleSearch}
|
||||
placeholder="Search room"
|
||||
autoFocus
|
||||
/>
|
||||
<Input name="searchInput" onChange={handleSearch} placeholder="Search room" autoFocus />
|
||||
<IconButton size="small" type="button" onClick={handleSearchClear} src={CrossIC} />
|
||||
</form>
|
||||
{searchIds?.length === 0 && <Text>No results found</Text>}
|
||||
{
|
||||
(searchIds || allRoomIds).map((rId) => {
|
||||
const room = mx.getRoom(rId);
|
||||
let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||
if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||
{(searchIds || allRoomIds).map((rId) => {
|
||||
const room = mx.getRoom(rId);
|
||||
let imageSrc =
|
||||
room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||
if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||
|
||||
const parentSet = roomIdToParents.get(rId);
|
||||
const parentNames = parentSet
|
||||
? [...parentSet].map((parentId) => mx.getRoom(parentId).name)
|
||||
: undefined;
|
||||
const parents = parentNames ? parentNames.join(', ') : null;
|
||||
const parentSet = roomIdToParents.get(rId);
|
||||
const parentNames = parentSet
|
||||
? [...parentSet].map((parentId) => mx.getRoom(parentId).name)
|
||||
: undefined;
|
||||
const parents = parentNames ? parentNames.join(', ') : null;
|
||||
|
||||
const handleSelect = () => toggleSelection(rId);
|
||||
const handleSelect = () => toggleSelection(rId);
|
||||
|
||||
return (
|
||||
<RoomSelector
|
||||
key={rId}
|
||||
name={room.name}
|
||||
parentName={parents}
|
||||
roomId={rId}
|
||||
imageSrc={directs.has(rId) ? imageSrc : null}
|
||||
iconSrc={
|
||||
directs.has(rId)
|
||||
? null
|
||||
: joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())
|
||||
}
|
||||
isUnread={false}
|
||||
notificationCount={0}
|
||||
isAlert={false}
|
||||
onClick={handleSelect}
|
||||
options={(
|
||||
<Checkbox
|
||||
isActive={selected.includes(rId)}
|
||||
variant="positive"
|
||||
onToggle={handleSelect}
|
||||
tabIndex={-1}
|
||||
disabled={process !== null}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
return (
|
||||
<RoomSelector
|
||||
key={rId}
|
||||
name={room.name}
|
||||
parentName={parents}
|
||||
roomId={rId}
|
||||
imageSrc={directs.has(rId) ? imageSrc : null}
|
||||
iconSrc={
|
||||
directs.has(rId) ? null : joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())
|
||||
}
|
||||
isUnread={false}
|
||||
notificationCount={0}
|
||||
isAlert={false}
|
||||
onClick={handleSelect}
|
||||
options={
|
||||
<Checkbox
|
||||
isActive={selected.includes(rId)}
|
||||
variant="positive"
|
||||
onToggle={handleSelect}
|
||||
tabIndex={-1}
|
||||
disabled={process !== null}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{selected.length !== 0 && (
|
||||
<div className="space-add-existing__footer">
|
||||
{process && <Spinner size="small" />}
|
||||
<Text weight="medium">{process || `${selected.length} item selected`}</Text>
|
||||
{ !process && (
|
||||
<Button onClick={handleAdd} variant="primary">Add</Button>
|
||||
{!process && (
|
||||
<Button onClick={handleAdd} variant="primary">
|
||||
Add
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
@ -182,47 +183,51 @@ function SpaceAddExistingContent({ roomId }) {
|
|||
}
|
||||
SpaceAddExistingContent.propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
spaces: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
function useVisibilityToggle() {
|
||||
const [roomId, setRoomId] = useState(null);
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleOpen = (rId) => setRoomId(rId);
|
||||
const handleOpen = (roomId, spaces) =>
|
||||
setData({
|
||||
roomId,
|
||||
spaces,
|
||||
});
|
||||
navigation.on(cons.events.navigation.SPACE_ADDEXISTING_OPENED, handleOpen);
|
||||
return () => {
|
||||
navigation.removeListener(cons.events.navigation.SPACE_ADDEXISTING_OPENED, handleOpen);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const requestClose = () => setRoomId(null);
|
||||
const requestClose = () => setData(null);
|
||||
|
||||
return [roomId, requestClose];
|
||||
return [data, requestClose];
|
||||
}
|
||||
|
||||
function SpaceAddExisting() {
|
||||
const [roomId, requestClose] = useVisibilityToggle();
|
||||
const [data, requestClose] = useVisibilityToggle();
|
||||
const mx = initMatrix.matrixClient;
|
||||
const room = mx.getRoom(roomId);
|
||||
const room = mx.getRoom(data?.roomId);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={roomId !== null}
|
||||
isOpen={!!room}
|
||||
className="space-add-existing"
|
||||
title={(
|
||||
title={
|
||||
<Text variant="s1" weight="medium" primary>
|
||||
{roomId && twemojify(room.name)}
|
||||
<span style={{ color: 'var(--tc-surface-low)' }}> — add existing rooms</span>
|
||||
{room && twemojify(room.name)}
|
||||
<span style={{ color: 'var(--tc-surface-low)' }}>
|
||||
{' '}
|
||||
— add existing {data?.spaces ? 'spaces' : 'rooms'}
|
||||
</span>
|
||||
</Text>
|
||||
)}
|
||||
}
|
||||
contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
|
||||
onRequestClose={requestClose}
|
||||
>
|
||||
{
|
||||
roomId
|
||||
? <SpaceAddExistingContent roomId={roomId} />
|
||||
: <div />
|
||||
}
|
||||
{room ? <SpaceAddExistingContent roomId={room.roomId} spaces={data.spaces} /> : <div />}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -45,10 +45,11 @@ export function openSpaceManage(roomId) {
|
|||
});
|
||||
}
|
||||
|
||||
export function openSpaceAddExisting(roomId) {
|
||||
export function openSpaceAddExisting(roomId, spaces = false) {
|
||||
appDispatcher.dispatch({
|
||||
type: cons.actions.navigation.OPEN_SPACE_ADDEXISTING,
|
||||
roomId,
|
||||
spaces,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -300,7 +300,7 @@ class Navigation extends EventEmitter {
|
|||
this.emit(cons.events.navigation.SPACE_MANAGE_OPENED, action.roomId);
|
||||
},
|
||||
[cons.actions.navigation.OPEN_SPACE_ADDEXISTING]: () => {
|
||||
this.emit(cons.events.navigation.SPACE_ADDEXISTING_OPENED, action.roomId);
|
||||
this.emit(cons.events.navigation.SPACE_ADDEXISTING_OPENED, action.roomId, action.spaces);
|
||||
},
|
||||
[cons.actions.navigation.TOGGLE_ROOM_SETTINGS]: () => {
|
||||
this.emit(
|
||||
|
|
Loading…
Reference in a new issue