;
}
}
diff --git a/src/createRoom.ts b/src/createRoom.ts
index 613fe26c9e..a3b06fa8ba 100644
--- a/src/createRoom.ts
+++ b/src/createRoom.ts
@@ -18,9 +18,15 @@ limitations under the License.
import { MatrixClient } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
-import { EventType } from "matrix-js-sdk/src/@types/event";
+import { EventType, RoomCreateTypeField, RoomType } from "matrix-js-sdk/src/@types/event";
import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests";
-import { JoinRule, Preset, RestrictedAllowType, Visibility } from "matrix-js-sdk/src/@types/partials";
+import {
+ HistoryVisibility,
+ JoinRule,
+ Preset,
+ RestrictedAllowType,
+ Visibility,
+} from "matrix-js-sdk/src/@types/partials";
import { MatrixClientPeg } from './MatrixClientPeg';
import Modal from './Modal';
@@ -52,6 +58,9 @@ export interface IOpts {
inlineErrors?: boolean;
andView?: boolean;
associatedWithCommunity?: string;
+ avatar?: File | string; // will upload if given file, else mxcUrl is needed
+ roomType?: RoomType | string;
+ historyVisibility?: HistoryVisibility;
parentSpace?: Room;
joinRule?: JoinRule;
}
@@ -112,6 +121,13 @@ export default async function createRoom(opts: IOpts): Promise {
createOpts.is_direct = true;
}
+ if (opts.roomType) {
+ createOpts.creation_content = {
+ ...createOpts.creation_content,
+ [RoomCreateTypeField]: opts.roomType,
+ };
+ }
+
// By default, view the room after creating it
if (opts.andView === undefined) {
opts.andView = true;
@@ -144,12 +160,11 @@ export default async function createRoom(opts: IOpts): Promise {
if (opts.parentSpace) {
createOpts.initial_state.push(makeSpaceParentEvent(opts.parentSpace, true));
- createOpts.initial_state.push({
- type: EventType.RoomHistoryVisibility,
- content: {
- "history_visibility": createOpts.preset === Preset.PublicChat ? "world_readable" : "invited",
- },
- });
+ if (!opts.historyVisibility) {
+ opts.historyVisibility = createOpts.preset === Preset.PublicChat
+ ? HistoryVisibility.WorldReadable
+ : HistoryVisibility.Invited;
+ }
if (opts.joinRule === JoinRule.Restricted) {
if (SpaceStore.instance.restrictedJoinRuleSupport?.preferred) {
@@ -176,6 +191,27 @@ export default async function createRoom(opts: IOpts): Promise {
});
}
+ if (opts.avatar) {
+ let url = opts.avatar;
+ if (opts.avatar instanceof File) {
+ url = await client.uploadContent(opts.avatar);
+ }
+
+ createOpts.initial_state.push({
+ type: EventType.RoomAvatar,
+ content: { url },
+ });
+ }
+
+ if (opts.historyVisibility) {
+ createOpts.initial_state.push({
+ type: EventType.RoomHistoryVisibility,
+ content: {
+ "history_visibility": opts.historyVisibility,
+ },
+ });
+ }
+
let modal;
if (opts.spinner) modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts
index 5732428201..06cbbba46c 100644
--- a/src/dispatcher/actions.ts
+++ b/src/dispatcher/actions.ts
@@ -193,4 +193,9 @@ export enum Action {
* Switches space. Should be used with SwitchSpacePayload.
*/
SwitchSpace = "switch_space",
+
+ /**
+ * Signals to the visible space hierarchy that a change has occurred an that it should refresh.
+ */
+ UpdateSpaceHierarchy = "update_space_hierarchy",
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index f1c033dbea..11d1f2140a 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1005,6 +1005,12 @@
"Name": "Name",
"Description": "Description",
"Please enter a name for the space": "Please enter a name for the space",
+ "Spaces are a new feature.": "Spaces are a new feature.",
+ "Spaces feedback": "Spaces feedback",
+ "Thank you for trying Spaces. Your feedback will help inform the next versions.": "Thank you for trying Spaces. Your feedback will help inform the next versions.",
+ "Give feedback.": "Give feedback.",
+ "e.g. my-space": "e.g. my-space",
+ "Address": "Address",
"Create a space": "Create a space",
"Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.",
"Public": "Public",
@@ -1017,8 +1023,6 @@
"Your private space": "Your private space",
"Add some details to help people recognise it.": "Add some details to help people recognise it.",
"You can change these anytime.": "You can change these anytime.",
- "e.g. my-space": "e.g. my-space",
- "Address": "Address",
"Creating...": "Creating...",
"Create": "Create",
"All rooms": "All rooms",
@@ -1056,6 +1060,7 @@
"Leave space": "Leave space",
"Create new room": "Create new room",
"Add existing room": "Add existing room",
+ "Add space": "Add space",
"Members": "Members",
"Manage & explore rooms": "Manage & explore rooms",
"Explore rooms": "Explore rooms",
@@ -2110,17 +2115,20 @@
"Add a new server...": "Add a new server...",
"%(networkName)s rooms": "%(networkName)s rooms",
"Matrix rooms": "Matrix rooms",
+ "Add existing space": "Add existing space",
+ "Want to add a new space instead?": "Want to add a new space instead?",
+ "Create a new space": "Create a new space",
+ "Search for spaces": "Search for spaces",
"Not all selected were added": "Not all selected were added",
"Adding rooms... (%(progress)s out of %(count)s)|other": "Adding rooms... (%(progress)s out of %(count)s)",
"Adding rooms... (%(progress)s out of %(count)s)|one": "Adding room...",
- "Filter your rooms and spaces": "Filter your rooms and spaces",
- "Feeling experimental?": "Feeling experimental?",
- "You can add existing spaces to a space.": "You can add existing spaces to a space.",
"Direct Messages": "Direct Messages",
"Space selection": "Space selection",
"Add existing rooms": "Add existing rooms",
"Want to add a new room instead?": "Want to add a new room instead?",
"Create a new room": "Create a new room",
+ "Search for rooms": "Search for rooms",
+ "Adding spaces has moved.": "Adding spaces has moved.",
"Matrix ID": "Matrix ID",
"Matrix Room ID": "Matrix Room ID",
"email address": "email address",
@@ -2134,15 +2142,8 @@
"Invite anyway and never warn me again": "Invite anyway and never warn me again",
"Invite anyway": "Invite anyway",
"Close dialog": "Close dialog",
- "Beta feedback": "Beta feedback",
- "Thank you for your feedback, we really appreciate it.": "Thank you for your feedback, we really appreciate it.",
- "Done": "Done",
"%(featureName)s beta feedback": "%(featureName)s beta feedback",
- "Your platform and username will be noted to help us use your feedback as much as we can.": "Your platform and username will be noted to help us use your feedback as much as we can.",
"To leave the beta, visit your settings.": "To leave the beta, visit your settings.",
- "Feedback": "Feedback",
- "You may contact me if you have any follow up questions": "You may contact me if you have any follow up questions",
- "Send feedback": "Send feedback",
"Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.",
"Preparing to send logs": "Preparing to send logs",
"Logs sent": "Logs sent",
@@ -2208,13 +2209,22 @@
"Create a room in %(communityName)s": "Create a room in %(communityName)s",
"Create a public room": "Create a public room",
"Create a private room": "Create a private room",
+ "Topic (optional)": "Topic (optional)",
+ "Room visibility": "Room visibility",
"Private room (invite only)": "Private room (invite only)",
"Public room": "Public room",
"Visible to space members": "Visible to space members",
- "Topic (optional)": "Topic (optional)",
- "Room visibility": "Room visibility",
"Block anyone not part of %(serverName)s from ever joining this room.": "Block anyone not part of %(serverName)s from ever joining this room.",
"Create Room": "Create Room",
+ "Anyone in will be able to find and join.": "Anyone in will be able to find and join.",
+ "Anyone will be able to find and join this space, not just members of .": "Anyone will be able to find and join this space, not just members of .",
+ "Only people invited will be able to find and join this space.": "Only people invited will be able to find and join this space.",
+ "Add a space to a space you manage.": "Add a space to a space you manage.",
+ "Space visibility": "Space visibility",
+ "Private space (invite only)": "Private space (invite only)",
+ "Public space": "Public space",
+ "Want to add an existing space instead?": "Want to add an existing space instead?",
+ "Adding...": "Adding...",
"Sign out": "Sign out",
"To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this",
"You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.",
@@ -2280,8 +2290,10 @@
"Comment": "Comment",
"There are two ways you can provide feedback and help us improve %(brand)s.": "There are two ways you can provide feedback and help us improve %(brand)s.",
"PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.": "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.",
+ "Feedback": "Feedback",
"Report a bug": "Report a bug",
"Please view existing bugs on Github first. No match? Start a new one.": "Please view existing bugs on Github first. No match? Start a new one.",
+ "Send feedback": "Send feedback",
"You don't have permission to do this": "You don't have permission to do this",
"Sending": "Sending",
"Sent": "Sent",
@@ -2289,6 +2301,10 @@
"Forward message": "Forward message",
"Message preview": "Message preview",
"Search for rooms or people": "Search for rooms or people",
+ "Thank you for your feedback, we really appreciate it.": "Thank you for your feedback, we really appreciate it.",
+ "Done": "Done",
+ "Your platform and username will be noted to help us use your feedback as much as we can.": "Your platform and username will be noted to help us use your feedback as much as we can.",
+ "You may contact me if you have any follow up questions": "You may contact me if you have any follow up questions",
"Confirm abort of host creation": "Confirm abort of host creation",
"Are you sure you wish to abort creation of the host? The process cannot be continued.": "Are you sure you wish to abort creation of the host? The process cannot be continued.",
"Abort": "Abort",
@@ -2360,6 +2376,15 @@
"Clear cache and resync": "Clear cache and resync",
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
"Updating %(brand)s": "Updating %(brand)s",
+ "Leave all rooms and spaces": "Leave all rooms and spaces",
+ "Don't leave any": "Don't leave any",
+ "Leave specific rooms and spaces": "Leave specific rooms and spaces",
+ "Search %(spaceName)s": "Search %(spaceName)s",
+ "You won't be able to rejoin unless you are re-invited.": "You won't be able to rejoin unless you are re-invited.",
+ "You're the only admin of this space. Leaving it will mean no one has control over it.": "You're the only admin of this space. Leaving it will mean no one has control over it.",
+ "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.",
+ "Leave %(spaceName)s": "Leave %(spaceName)s",
+ "Are you sure you want to leave ?": "Are you sure you want to leave ?",
"Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
"Start using Key Backup": "Start using Key Backup",
"I don't want my encrypted messages": "I don't want my encrypted messages",
@@ -2800,8 +2825,6 @@
"Search names and descriptions": "Search names and descriptions",
"If you can't find the room you're looking for, ask for an invite or create a new room.": "If you can't find the room you're looking for, ask for an invite or create a new room.",
"Create room": "Create room",
- "Spaces are a beta feature.": "Spaces are a beta feature.",
- "Public space": "Public space",
"Private space": "Private space",
" invites you": " invites you",
"To view %(spaceName)s, turn on the Spaces beta": "To view %(spaceName)s, turn on the Spaces beta",
@@ -2816,6 +2839,7 @@
"Creating rooms...": "Creating rooms...",
"What do you want to organise?": "What do you want to organise?",
"Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.": "Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.",
+ "Search for rooms or spaces": "Search for rooms or spaces",
"Share %(name)s": "Share %(name)s",
"It's just you at the moment, it will be even better with others.": "It's just you at the moment, it will be even better with others.",
"Go to my first room": "Go to my first room",
@@ -2827,7 +2851,7 @@
"Me and my teammates": "Me and my teammates",
"A private space for you and your teammates": "A private space for you and your teammates",
"Teammates might not be able to view or join any private rooms you make.": "Teammates might not be able to view or join any private rooms you make.",
- "We're working on this as part of the beta, but just want to let you know.": "We're working on this as part of the beta, but just want to let you know.",
+ "We're working on this, but just want to let you know.": "We're working on this, but just want to let you know.",
"Failed to invite the following users to your space: %(csvUsers)s": "Failed to invite the following users to your space: %(csvUsers)s",
"Inviting...": "Inviting...",
"Invite your teammates": "Invite your teammates",
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 5aa49df8a1..64edd4c202 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -124,6 +124,7 @@ export interface ISetting {
// not use this for new settings.
invertedSettingName?: string;
+ // XXX: Keep this around for re-use in future Betas
betaInfo?: {
title: string; // _td
caption: string; // _td
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index d064b01257..dfa8bef8be 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -329,7 +329,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}, roomId);
}
- private getChildren(spaceId: string): Room[] {
+ public getChildren(spaceId: string): Room[] {
const room = this.matrixClient?.getRoom(spaceId);
const childEvents = room?.currentState.getStateEvents(EventType.SpaceChild).filter(ev => ev.getContent()?.via);
return sortBy(childEvents, ev => {
diff --git a/src/utils/objects.ts b/src/utils/objects.ts
index c2ee6ce100..e3b7b6cf59 100644
--- a/src/utils/objects.ts
+++ b/src/utils/objects.ts
@@ -141,21 +141,3 @@ export function objectKeyChanges(a: O, b: O): (keyof O)[] {
export function objectClone(obj: O): O {
return JSON.parse(JSON.stringify(obj));
}
-
-/**
- * Converts a series of entries to an object.
- * @param entries The entries to convert.
- * @returns The converted object.
- */
-// NOTE: Deprecated once we have Object.fromEntries() support.
-// @ts-ignore - return type is complaining about non-string keys, but we know better
-export function objectFromEntries(entries: Iterable<[K, V]>): {[k: K]: V} {
- const obj: {
- // @ts-ignore - same as return type
- [k: K]: V;} = {};
- for (const e of entries) {
- // @ts-ignore - same as return type
- obj[e[0]] = e[1];
- }
- return obj;
-}
diff --git a/src/utils/space.tsx b/src/utils/space.tsx
index c238a83bc2..fecb581e65 100644
--- a/src/utils/space.tsx
+++ b/src/utils/space.tsx
@@ -28,6 +28,15 @@ import { _t } from "../languageHandler";
import SpacePublicShare from "../components/views/spaces/SpacePublicShare";
import InfoDialog from "../components/views/dialogs/InfoDialog";
import { showRoomInviteDialog } from "../RoomInvite";
+import CreateSubspaceDialog from "../components/views/dialogs/CreateSubspaceDialog";
+import AddExistingSubspaceDialog from "../components/views/dialogs/AddExistingSubspaceDialog";
+import defaultDispatcher from "../dispatcher/dispatcher";
+import RoomViewStore from "../stores/RoomViewStore";
+import { Action } from "../dispatcher/actions";
+import { leaveRoomBehaviour } from "./membership";
+import Spinner from "../components/views/elements/Spinner";
+import dis from "../dispatcher/dispatcher";
+import LeaveSpaceDialog from "../components/views/dialogs/LeaveSpaceDialog";
export const shouldShowSpaceSettings = (space: Room) => {
const userId = space.client.getUserId();
@@ -54,21 +63,26 @@ export const showSpaceSettings = (space: Room) => {
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
};
-export const showAddExistingRooms = async (space: Room) => {
- return Modal.createTrackedDialog(
+export const showAddExistingRooms = (space: Room): void => {
+ Modal.createTrackedDialog(
"Space Landing",
"Add Existing",
AddExistingToSpaceDialog,
{
- matrixClient: space.client,
- onCreateRoomClick: showCreateNewRoom,
+ onCreateRoomClick: () => showCreateNewRoom(space),
+ onAddSubspaceClick: () => showAddExistingSubspace(space),
space,
+ onFinished: (added: boolean) => {
+ if (added && RoomViewStore.getRoomId() === space.roomId) {
+ defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
+ }
+ },
},
"mx_AddExistingToSpaceDialog_wrapper",
- ).finished;
+ );
};
-export const showCreateNewRoom = async (space: Room) => {
+export const showCreateNewRoom = async (space: Room): Promise => {
const modal = Modal.createTrackedDialog<[boolean, IOpts]>(
"Space Landing",
"Create Room",
@@ -85,7 +99,7 @@ export const showCreateNewRoom = async (space: Room) => {
return shouldCreate;
};
-export const showSpaceInvite = (space: Room, initialText = "") => {
+export const showSpaceInvite = (space: Room, initialText = ""): void => {
if (space.getJoinRule() === "public") {
const modal = Modal.createTrackedDialog("Space Invite", "User Menu", InfoDialog, {
title: _t("Invite to %(spaceName)s", { spaceName: space.name }),
@@ -102,3 +116,60 @@ export const showSpaceInvite = (space: Room, initialText = "") => {
showRoomInviteDialog(space.roomId, initialText);
}
};
+
+export const showAddExistingSubspace = (space: Room): void => {
+ Modal.createTrackedDialog(
+ "Space Landing",
+ "Create Subspace",
+ AddExistingSubspaceDialog,
+ {
+ space,
+ onCreateSubspaceClick: () => showCreateNewSubspace(space),
+ onFinished: (added: boolean) => {
+ if (added && RoomViewStore.getRoomId() === space.roomId) {
+ defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
+ }
+ },
+ },
+ "mx_AddExistingToSpaceDialog_wrapper",
+ );
+};
+
+export const showCreateNewSubspace = (space: Room): void => {
+ Modal.createTrackedDialog(
+ "Space Landing",
+ "Create Subspace",
+ CreateSubspaceDialog,
+ {
+ space,
+ onAddExistingSpaceClick: () => showAddExistingSubspace(space),
+ onFinished: (added: boolean) => {
+ if (added && RoomViewStore.getRoomId() === space.roomId) {
+ defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
+ }
+ },
+ },
+ "mx_CreateSubspaceDialog_wrapper",
+ );
+};
+
+export const leaveSpace = (space: Room) => {
+ Modal.createTrackedDialog("Leave Space", "", LeaveSpaceDialog, {
+ space,
+ onFinished: async (leave: boolean, rooms: Room[]) => {
+ if (!leave) return;
+ const modal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner");
+ try {
+ await Promise.all(rooms.map(r => leaveRoomBehaviour(r.roomId)));
+ await leaveRoomBehaviour(space.roomId);
+ } finally {
+ modal.close();
+ }
+
+ dis.dispatch({
+ action: "after_leave_room",
+ room_id: space.roomId,
+ });
+ },
+ }, "mx_LeaveSpaceDialog_wrapper");
+};
diff --git a/test/utils/arrays-test.ts b/test/utils/arrays-test.ts
index cf9a5f0089..277260bf29 100644
--- a/test/utils/arrays-test.ts
+++ b/test/utils/arrays-test.ts
@@ -29,7 +29,6 @@ import {
ArrayUtil,
GroupedArray,
} from "../../src/utils/arrays";
-import { objectFromEntries } from "../../src/utils/objects";
function expectSample(i: number, input: number[], expected: number[], smooth = false) {
console.log(`Resample case index: ${i}`); // for debugging test failures
@@ -336,7 +335,7 @@ describe('arrays', () => {
expect(result).toBeDefined();
expect(result.value).toBeDefined();
- const asObject = objectFromEntries(result.value.entries());
+ const asObject = Object.fromEntries(result.value.entries());
expect(asObject).toMatchObject(output);
});
});
diff --git a/test/utils/objects-test.ts b/test/utils/objects-test.ts
index 154fa3604f..b360fbd1d1 100644
--- a/test/utils/objects-test.ts
+++ b/test/utils/objects-test.ts
@@ -18,7 +18,6 @@ import {
objectClone,
objectDiff,
objectExcluding,
- objectFromEntries,
objectHasDiff,
objectKeyChanges,
objectShallowClone,
@@ -242,21 +241,4 @@ describe('objects', () => {
expect(result.test.third).not.toBe(a.test.third);
});
});
-
- describe('objectFromEntries', () => {
- it('should create an object from an array of entries', () => {
- const output = { a: 1, b: 2, c: 3 };
- const result = objectFromEntries(Object.entries(output));
- expect(result).toBeDefined();
- expect(result).toMatchObject(output);
- });
-
- it('should maintain pointers in values', () => {
- const output = { a: {}, b: 2, c: 3 };
- const result = objectFromEntries(Object.entries(output));
- expect(result).toBeDefined();
- expect(result).toMatchObject(output);
- expect(result['a']).toBe(output.a);
- });
- });
});