diff --git a/src/app/molecules/space-add-existing/SpaceAddExisting.jsx b/src/app/molecules/space-add-existing/SpaceAddExisting.jsx
new file mode 100644
index 0000000..5bbb1da
--- /dev/null
+++ b/src/app/molecules/space-add-existing/SpaceAddExisting.jsx
@@ -0,0 +1,202 @@
+import React, { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import './SpaceAddExisting.scss';
+
+import { twemojify } from '../../../util/twemojify';
+
+import initMatrix from '../../../client/initMatrix';
+import cons from '../../../client/state/cons';
+import navigation from '../../../client/state/navigation';
+import { joinRuleToIconSrc } from '../../../util/matrixUtil';
+import { Debounce } from '../../../util/common';
+
+import Text from '../../atoms/text/Text';
+import RawIcon from '../../atoms/system-icons/RawIcon';
+import Button from '../../atoms/button/Button';
+import IconButton from '../../atoms/button/IconButton';
+import Checkbox from '../../atoms/button/Checkbox';
+import Input from '../../atoms/input/Input';
+import Spinner from '../../atoms/spinner/Spinner';
+import RoomSelector from '../room-selector/RoomSelector';
+import Dialog from '../dialog/Dialog';
+
+import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
+import SearchIC from '../../../../public/res/ic/outlined/search.svg';
+
+function SpaceAddExistingContent({ roomId }) {
+ const [debounce] = useState(new Debounce());
+ const [process, setProcess] = useState(null);
+ const [selected, setSelected] = useState([]);
+ const [searchIds, setSearchIds] = useState(null);
+ const mx = initMatrix.matrixClient;
+ const {
+ spaces, rooms, directs, roomIdToParents,
+ } = initMatrix.roomList;
+
+ let allRoomIds = [...spaces, ...rooms, ...directs];
+ allRoomIds = allRoomIds.filter((rId) => (
+ rId !== roomId
+ && !roomIdToParents.get(rId)?.has(roomId)
+ ));
+
+ const toggleSelection = (rId) => {
+ if (process !== null) return;
+ const newSelected = [...selected];
+ const selectedIndex = newSelected.indexOf(rId);
+
+ if (selectedIndex > -1) {
+ newSelected.splice(selectedIndex, 1);
+ setSelected(newSelected);
+ return;
+ }
+ newSelected.push(rId);
+ setSelected(newSelected);
+ };
+
+ const handleSearch = (ev) => {
+ const term = ev.target.value.toLocaleLowerCase().replaceAll(' ', '');
+ if (term === '') {
+ setSearchIds(null);
+ return;
+ }
+
+ debounce._(() => {
+ const searchedIds = allRoomIds.filter((rId) => {
+ let name = mx.getRoom(rId)?.name;
+ if (!name) return false;
+ name = name.normalize('NFKC')
+ .toLocaleLowerCase()
+ .replaceAll(' ', '');
+ return name.includes(term);
+ });
+ setSearchIds(searchedIds);
+ }, 400)();
+ };
+
+ const handleAdd = async () => {
+ setProcess(`Adding ${selected.length} items...`);
+ };
+
+ return (
+ <>
+
+ {searchIds?.length === 0 && No result found}
+ {
+ (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 handleSelect = () => toggleSelection(rId);
+
+ return (
+
+ )}
+ />
+ );
+ })
+ }
+ {selected.length !== 0 && (
+
+ {process && }
+ {process || `${selected.length} item selected`}
+ { !process && (
+
+ )}
+
+ )}
+ >
+ );
+}
+SpaceAddExistingContent.propTypes = {
+ roomId: PropTypes.string.isRequired,
+};
+
+function useVisibilityToggle() {
+ const [roomId, setRoomId] = useState(null);
+
+ useEffect(() => {
+ const handleOpen = (rId) => setRoomId(rId);
+ navigation.on(cons.events.navigation.SPACE_ADDEXISTING_OPENED, handleOpen);
+ return () => {
+ navigation.removeListener(cons.events.navigation.SPACE_ADDEXISTING_OPENED, handleOpen);
+ };
+ }, []);
+
+ const requestClose = () => setRoomId(null);
+
+ return [roomId, requestClose];
+}
+
+function SpaceAddExisting() {
+ const [roomId, requestClose] = useVisibilityToggle();
+ const mx = initMatrix.matrixClient;
+ const room = mx.getRoom(roomId);
+
+ return (
+
+ );
+}
+
+export default SpaceAddExisting;
diff --git a/src/app/molecules/space-add-existing/SpaceAddExisting.scss b/src/app/molecules/space-add-existing/SpaceAddExisting.scss
new file mode 100644
index 0000000..792cdc0
--- /dev/null
+++ b/src/app/molecules/space-add-existing/SpaceAddExisting.scss
@@ -0,0 +1,74 @@
+@use '../../partials/dir';
+@use '../../partials/flex';
+
+.space-add-existing {
+ height: 100%;
+
+ .dialog__content-container {
+ padding: 0;
+ padding-bottom: 80px;
+ @include dir.side(padding, var(--sp-extra-tight), 0);
+
+ & > .text {
+ margin: var(--sp-loose) var(--sp-normal);
+ text-align: center;
+ }
+ }
+
+ & form {
+ @extend .cp-fx__row--s-c;
+ padding: var(--sp-extra-tight);
+ padding-top: var(--sp-normal);
+
+ position: sticky;
+ top: 0;
+ z-index: 999;
+ background-color: var(--bg-surface);
+
+ & > .ic-raw,
+ & > .ic-btn {
+ position: absolute;
+ }
+ & > .ic-raw {
+ margin: 0 var(--sp-tight);
+ }
+ & > .ic-btn {
+ border-radius: calc(var(--bo-radius) / 2);
+ @include dir.prop(right, var(--sp-tight), unset);
+ @include dir.prop(left, unset, var(--sp-tight));
+ }
+ & input {
+ padding: var(--sp-tight) 40px;
+ }
+ }
+
+ .input-container {
+ @extend .cp-fx__item-one;
+ }
+
+ .room-selector__options {
+ display: flex;
+ margin: 0 10px;
+ }
+}
+
+.space-add-existing__footer {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ padding: var(--sp-normal);
+ background-color: var(--bg-surface);
+ border-top: 1px solid var(--bg-surface-border);
+ display: flex;
+ align-items: center;
+
+ & > .text {
+ @extend .cp-fx__item-one;
+ padding: 0 var(--sp-tight);
+ }
+
+ & > button {
+ @include dir.side(margin, var(--sp-normal), 0);
+ }
+}
\ No newline at end of file
diff --git a/src/app/organisms/navigation/DrawerHeader.jsx b/src/app/organisms/navigation/DrawerHeader.jsx
index 198e61e..5052865 100644
--- a/src/app/organisms/navigation/DrawerHeader.jsx
+++ b/src/app/organisms/navigation/DrawerHeader.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React from 'react';
import PropTypes from 'prop-types';
import './DrawerHeader.scss';
@@ -7,9 +7,9 @@ import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import {
- openPublicRooms, openCreateRoom, openInviteUser, openReusableContextMenu,
+ openPublicRooms, openCreateRoom, openSpaceManage,
+ openSpaceAddExisting, openInviteUser, openReusableContextMenu,
} from '../../../client/action/navigation';
-import { createSpaceShortcut, deleteSpaceShortcut } from '../../../client/action/room';
import { getEventCords } from '../../../util/common';
import { blurOnBubbling } from '../../atoms/button/script';
@@ -18,24 +18,83 @@ import Text from '../../atoms/text/Text';
import RawIcon from '../../atoms/system-icons/RawIcon';
import Header, { TitleWrapper } from '../../atoms/header/Header';
import IconButton from '../../atoms/button/IconButton';
-import ContextMenu, { MenuItem, MenuHeader } from '../../atoms/context-menu/ContextMenu';
+import { MenuItem, MenuHeader } from '../../atoms/context-menu/ContextMenu';
import SpaceOptions from '../../molecules/space-options/SpaceOptions';
import PlusIC from '../../../../public/res/ic/outlined/plus.svg';
import HashPlusIC from '../../../../public/res/ic/outlined/hash-plus.svg';
import HashGlobeIC from '../../../../public/res/ic/outlined/hash-globe.svg';
+import HashSearchIC from '../../../../public/res/ic/outlined/hash-search.svg';
+import SpacePlusIC from '../../../../public/res/ic/outlined/space-plus.svg';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
-import PinIC from '../../../../public/res/ic/outlined/pin.svg';
-import PinFilledIC from '../../../../public/res/ic/filled/pin.svg';
+
+function HomeSpaceOptions({ spaceId, afterOptionSelect }) {
+ const mx = initMatrix.matrixClient;
+ const room = mx.getRoom(spaceId);
+ const canManage = room
+ ? room.currentState.maySendStateEvent('m.space.child', mx.getUserId())
+ : true;
+
+ return (
+ <>
+ Add rooms or spaces
+
+
+ { !spaceId && (
+
+ )}
+ { spaceId && (
+
+ )}
+ { spaceId && (
+
+ )}
+ >
+ );
+}
+HomeSpaceOptions.defaultProps = {
+ spaceId: null,
+};
+HomeSpaceOptions.propTypes = {
+ spaceId: PropTypes.string,
+ afterOptionSelect: PropTypes.func.isRequired,
+};
function DrawerHeader({ selectedTab, spaceId }) {
- const [, forceUpdate] = useState({});
const mx = initMatrix.matrixClient;
- const { spaceShortcut } = initMatrix.roomList;
const tabName = selectedTab !== cons.tabs.DIRECTS ? 'Home' : 'Direct messages';
+ const isDMTab = selectedTab === cons.tabs.DIRECTS;
const room = mx.getRoom(spaceId);
- const spaceName = selectedTab === cons.tabs.DIRECTS ? null : (room?.name || null);
+ const spaceName = isDMTab ? null : (room?.name || null);
const openSpaceOptions = (e) => {
e.preventDefault();
@@ -46,6 +105,15 @@ function DrawerHeader({ selectedTab, spaceId }) {
);
};
+ const openHomeSpaceOptions = (e) => {
+ e.preventDefault();
+ openReusableContextMenu(
+ 'right',
+ getEventCords(e, '.ic-btn'),
+ (closeMenu) => ,
+ );
+ };
+
return (
{spaceName ? (
@@ -65,41 +133,9 @@ function DrawerHeader({ selectedTab, spaceId }) {
{tabName}
)}
- {spaceName && (
- {
- if (spaceShortcut.has(spaceId)) deleteSpaceShortcut(spaceId);
- else createSpaceShortcut(spaceId);
- forceUpdate({});
- }}
- />
- )}
- { selectedTab === cons.tabs.DIRECTS && openInviteUser()} tooltip="Start DM" src={PlusIC} size="normal" /> }
- { selectedTab !== cons.tabs.DIRECTS && !spaceName && (
- (
- <>
- Add room
-
-
- >
- )}
- render={(toggleMenu) => ()}
- />
- )}
+
+ { isDMTab && openInviteUser()} tooltip="Start DM" src={PlusIC} size="small" /> }
+ { !isDMTab && }
);
}
diff --git a/src/app/organisms/pw/Dialogs.jsx b/src/app/organisms/pw/Dialogs.jsx
index 0fa89cb..b5038e5 100644
--- a/src/app/organisms/pw/Dialogs.jsx
+++ b/src/app/organisms/pw/Dialogs.jsx
@@ -2,6 +2,7 @@ import React from 'react';
import ReadReceipts from '../read-receipts/ReadReceipts';
import ProfileViewer from '../profile-viewer/ProfileViewer';
+import SpaceAddExisting from '../../molecules/space-add-existing/SpaceAddExisting';
import Search from '../search/Search';
function Dialogs() {
@@ -9,6 +10,7 @@ function Dialogs() {
<>
+
>
);
diff --git a/src/client/action/navigation.js b/src/client/action/navigation.js
index 6f82a42..964a3f5 100644
--- a/src/client/action/navigation.js
+++ b/src/client/action/navigation.js
@@ -38,6 +38,13 @@ export function openSpaceManage(roomId) {
});
}
+export function openSpaceAddExisting(roomId) {
+ appDispatcher.dispatch({
+ type: cons.actions.navigation.OPEN_SPACE_ADDEXISTING,
+ roomId,
+ });
+}
+
export function toggleRoomSettings(tabText) {
appDispatcher.dispatch({
type: cons.actions.navigation.TOGGLE_ROOM_SETTINGS,
diff --git a/src/client/state/cons.js b/src/client/state/cons.js
index 2ac4cbd..2d20dbe 100644
--- a/src/client/state/cons.js
+++ b/src/client/state/cons.js
@@ -32,6 +32,7 @@ const cons = {
SELECT_ROOM: 'SELECT_ROOM',
OPEN_SPACE_SETTINGS: 'OPEN_SPACE_SETTINGS',
OPEN_SPACE_MANAGE: 'OPEN_SPACE_MANAGE',
+ OPEN_SPACE_ADDEXISTING: 'OPEN_SPACE_ADDEXISTING',
TOGGLE_ROOM_SETTINGS: 'TOGGLE_ROOM_SETTINGS',
OPEN_INVITE_LIST: 'OPEN_INVITE_LIST',
OPEN_PUBLIC_ROOMS: 'OPEN_PUBLIC_ROOMS',
@@ -71,6 +72,7 @@ const cons = {
ROOM_SELECTED: 'ROOM_SELECTED',
SPACE_SETTINGS_OPENED: 'SPACE_SETTINGS_OPENED',
SPACE_MANAGE_OPENED: 'SPACE_MANAGE_OPENED',
+ SPACE_ADDEXISTING_OPENED: 'SPACE_ADDEXISTING_OPENED',
ROOM_SETTINGS_TOGGLED: 'ROOM_SETTINGS_TOGGLED',
INVITE_LIST_OPENED: 'INVITE_LIST_OPENED',
PUBLIC_ROOMS_OPENED: 'PUBLIC_ROOMS_OPENED',
diff --git a/src/client/state/navigation.js b/src/client/state/navigation.js
index a7044f5..52d5f15 100644
--- a/src/client/state/navigation.js
+++ b/src/client/state/navigation.js
@@ -95,6 +95,9 @@ class Navigation extends EventEmitter {
[cons.actions.navigation.OPEN_SPACE_MANAGE]: () => {
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);
+ },
[cons.actions.navigation.TOGGLE_ROOM_SETTINGS]: () => {
this.isRoomSettings = !this.isRoomSettings;
this.emit(