2021-03-01 17:27:09 +00:00
|
|
|
/*
|
|
|
|
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
2021-06-29 13:11:58 +01:00
|
|
|
import React, { useContext, useRef, useState } from "react";
|
2021-03-01 17:27:09 +00:00
|
|
|
import classNames from "classnames";
|
2021-06-29 13:11:58 +01:00
|
|
|
import { EventType, RoomType, RoomCreateTypeField } from "matrix-js-sdk/src/@types/event";
|
2021-04-28 12:10:03 +01:00
|
|
|
import FocusLock from "react-focus-lock";
|
2021-03-01 17:27:09 +00:00
|
|
|
|
2021-06-29 13:11:58 +01:00
|
|
|
import { _t } from "../../../languageHandler";
|
2021-03-01 17:27:09 +00:00
|
|
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
2021-06-29 13:11:58 +01:00
|
|
|
import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
|
2021-06-14 13:31:58 -06:00
|
|
|
import createRoom from "../../../createRoom";
|
2021-03-01 17:27:09 +00:00
|
|
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
2021-06-29 13:11:58 +01:00
|
|
|
import { SpaceAvatar } from "./SpaceBasicSettings";
|
2021-03-01 17:27:09 +00:00
|
|
|
import AccessibleButton from "../elements/AccessibleButton";
|
2021-05-10 14:39:10 +01:00
|
|
|
import Field from "../elements/Field";
|
|
|
|
|
import withValidation from "../elements/Validation";
|
2021-06-14 13:31:58 -06:00
|
|
|
import { Preset } from "matrix-js-sdk/src/@types/partials";
|
|
|
|
|
import { ICreateRoomStateEvent } from "matrix-js-sdk/src/@types/requests";
|
2021-06-07 08:57:39 +01:00
|
|
|
import RoomAliasField from "../elements/RoomAliasField";
|
2021-07-23 14:34:29 +01:00
|
|
|
import SdkConfig from "../../../SdkConfig";
|
|
|
|
|
import Modal from "../../../Modal";
|
2021-07-23 15:47:11 +01:00
|
|
|
import GenericFeatureFeedbackDialog from "../dialogs/GenericFeatureFeedbackDialog";
|
|
|
|
|
import SettingsStore from "../../../settings/SettingsStore";
|
2021-03-01 17:27:09 +00:00
|
|
|
|
|
|
|
|
const SpaceCreateMenuType = ({ title, description, className, onClick }) => {
|
|
|
|
|
return (
|
|
|
|
|
<AccessibleButton className={classNames("mx_SpaceCreateMenuType", className)} onClick={onClick}>
|
|
|
|
|
<h3>{ title }</h3>
|
|
|
|
|
<span>{ description }</span>
|
|
|
|
|
</AccessibleButton>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum Visibility {
|
|
|
|
|
Public,
|
|
|
|
|
Private,
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-10 14:39:10 +01:00
|
|
|
const spaceNameValidator = withValidation({
|
|
|
|
|
rules: [
|
|
|
|
|
{
|
|
|
|
|
key: "required",
|
|
|
|
|
test: async ({ value }) => !!value,
|
|
|
|
|
invalid: () => _t("Please enter a name for the space"),
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
2021-06-07 08:57:39 +01:00
|
|
|
const nameToAlias = (name: string, domain: string): string => {
|
|
|
|
|
const localpart = name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9_-]+/gi, "");
|
|
|
|
|
return `#${localpart}:${domain}`;
|
|
|
|
|
};
|
|
|
|
|
|
2021-07-23 14:34:29 +01:00
|
|
|
// XXX: Temporary for the Spaces release only
|
|
|
|
|
const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => {
|
|
|
|
|
if (!SdkConfig.get().bug_report_endpoint_url) return null;
|
|
|
|
|
|
|
|
|
|
return <div className="mx_SpaceFeedbackPrompt">
|
|
|
|
|
<hr />
|
|
|
|
|
<div>
|
|
|
|
|
<span className="mx_SpaceFeedbackPrompt_text">{ _t("Spaces are a new feature.") }</span>
|
2021-07-23 18:31:31 +01:00
|
|
|
<AccessibleButton
|
|
|
|
|
kind="link"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
if (onClick) onClick();
|
|
|
|
|
Modal.createTrackedDialog("Spaces Feedback", "", GenericFeatureFeedbackDialog, {
|
|
|
|
|
title: _t("Spaces feedback"),
|
|
|
|
|
subheading: _t("Thank you for trying Spaces. " +
|
|
|
|
|
"Your feedback will help inform the next versions."),
|
|
|
|
|
rageshakeLabel: "spaces-feedback",
|
|
|
|
|
rageshakeData: Object.fromEntries([
|
|
|
|
|
"feature_spaces.all_rooms",
|
|
|
|
|
"feature_spaces.space_member_dms",
|
|
|
|
|
"feature_spaces.space_dm_badges",
|
|
|
|
|
].map(k => [k, SettingsStore.getValue(k)])),
|
|
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
>
|
2021-07-23 14:34:29 +01:00
|
|
|
{ _t("Give feedback.") }
|
|
|
|
|
</AccessibleButton>
|
|
|
|
|
</div>
|
|
|
|
|
</div>;
|
|
|
|
|
};
|
|
|
|
|
|
2021-03-01 17:27:09 +00:00
|
|
|
const SpaceCreateMenu = ({ onFinished }) => {
|
|
|
|
|
const cli = useContext(MatrixClientContext);
|
|
|
|
|
const [visibility, setVisibility] = useState<Visibility>(null);
|
|
|
|
|
const [busy, setBusy] = useState<boolean>(false);
|
|
|
|
|
|
2021-05-10 14:39:10 +01:00
|
|
|
const [name, setName] = useState("");
|
|
|
|
|
const spaceNameField = useRef<Field>();
|
2021-06-07 08:57:39 +01:00
|
|
|
const [alias, setAlias] = useState("");
|
|
|
|
|
const spaceAliasField = useRef<RoomAliasField>();
|
2021-05-10 14:39:10 +01:00
|
|
|
const [avatar, setAvatar] = useState<File>(null);
|
|
|
|
|
const [topic, setTopic] = useState<string>("");
|
|
|
|
|
|
|
|
|
|
const onSpaceCreateClick = async (e) => {
|
|
|
|
|
e.preventDefault();
|
2021-03-01 17:27:09 +00:00
|
|
|
if (busy) return;
|
2021-05-10 14:39:10 +01:00
|
|
|
|
2021-03-01 17:27:09 +00:00
|
|
|
setBusy(true);
|
2021-05-10 14:39:10 +01:00
|
|
|
// require & validate the space name field
|
|
|
|
|
if (!await spaceNameField.current.validate({ allowEmpty: false })) {
|
|
|
|
|
spaceNameField.current.focus();
|
|
|
|
|
spaceNameField.current.validate({ allowEmpty: false, focused: true });
|
|
|
|
|
setBusy(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-06-07 08:57:39 +01:00
|
|
|
// validate the space name alias field but do not require it
|
|
|
|
|
if (visibility === Visibility.Public && !await spaceAliasField.current.validate({ allowEmpty: true })) {
|
|
|
|
|
spaceAliasField.current.focus();
|
|
|
|
|
spaceAliasField.current.validate({ allowEmpty: true, focused: true });
|
|
|
|
|
setBusy(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-05-10 14:39:10 +01:00
|
|
|
|
2021-06-14 13:31:58 -06:00
|
|
|
const initialState: ICreateRoomStateEvent[] = [
|
2021-03-01 17:27:09 +00:00
|
|
|
{
|
|
|
|
|
type: EventType.RoomHistoryVisibility,
|
|
|
|
|
content: {
|
|
|
|
|
"history_visibility": visibility === Visibility.Public ? "world_readable" : "invited",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
if (avatar) {
|
|
|
|
|
const url = await cli.uploadContent(avatar);
|
|
|
|
|
|
|
|
|
|
initialState.push({
|
|
|
|
|
type: EventType.RoomAvatar,
|
|
|
|
|
content: { url },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await createRoom({
|
|
|
|
|
createOpts: {
|
|
|
|
|
preset: visibility === Visibility.Public ? Preset.PublicChat : Preset.PrivateChat,
|
|
|
|
|
name,
|
|
|
|
|
creation_content: {
|
|
|
|
|
[RoomCreateTypeField]: RoomType.Space,
|
|
|
|
|
},
|
|
|
|
|
initial_state: initialState,
|
|
|
|
|
power_level_content_override: {
|
|
|
|
|
// Only allow Admins to write to the timeline to prevent hidden sync spam
|
|
|
|
|
events_default: 100,
|
2021-03-24 16:42:21 +00:00
|
|
|
...Visibility.Public ? { invite: 0 } : {},
|
2021-03-01 17:27:09 +00:00
|
|
|
},
|
2021-06-25 10:41:13 +01:00
|
|
|
room_alias_name: visibility === Visibility.Public && alias
|
|
|
|
|
? alias.substr(1, alias.indexOf(":") - 1)
|
|
|
|
|
: undefined,
|
2021-06-07 08:57:39 +01:00
|
|
|
topic,
|
2021-03-01 17:27:09 +00:00
|
|
|
},
|
|
|
|
|
spinner: false,
|
|
|
|
|
encryption: false,
|
|
|
|
|
andView: true,
|
|
|
|
|
inlineErrors: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onFinished();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let body;
|
|
|
|
|
if (visibility === null) {
|
|
|
|
|
body = <React.Fragment>
|
|
|
|
|
<h2>{ _t("Create a space") }</h2>
|
2021-05-07 11:56:41 +01:00
|
|
|
<p>{ _t("Spaces are a new way to group rooms and people. " +
|
2021-03-19 13:20:04 +00:00
|
|
|
"To join an existing space you'll need an invite.") }</p>
|
2021-03-01 17:27:09 +00:00
|
|
|
|
|
|
|
|
<SpaceCreateMenuType
|
|
|
|
|
title={_t("Public")}
|
|
|
|
|
description={_t("Open space for anyone, best for communities")}
|
|
|
|
|
className="mx_SpaceCreateMenuType_public"
|
|
|
|
|
onClick={() => setVisibility(Visibility.Public)}
|
|
|
|
|
/>
|
|
|
|
|
<SpaceCreateMenuType
|
|
|
|
|
title={_t("Private")}
|
2021-03-03 11:34:29 +00:00
|
|
|
description={_t("Invite only, best for yourself or teams")}
|
2021-03-01 17:27:09 +00:00
|
|
|
className="mx_SpaceCreateMenuType_private"
|
|
|
|
|
onClick={() => setVisibility(Visibility.Private)}
|
|
|
|
|
/>
|
|
|
|
|
|
2021-03-03 11:34:29 +00:00
|
|
|
<p>{ _t("You can change this later") }</p>
|
2021-05-11 17:01:31 +01:00
|
|
|
|
|
|
|
|
<SpaceFeedbackPrompt onClick={onFinished} />
|
2021-03-01 17:27:09 +00:00
|
|
|
</React.Fragment>;
|
|
|
|
|
} else {
|
2021-06-07 08:57:39 +01:00
|
|
|
const domain = cli.getDomain();
|
2021-03-01 17:27:09 +00:00
|
|
|
body = <React.Fragment>
|
|
|
|
|
<AccessibleTooltipButton
|
|
|
|
|
className="mx_SpaceCreateMenu_back"
|
|
|
|
|
onClick={() => setVisibility(null)}
|
|
|
|
|
title={_t("Go back")}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<h2>
|
|
|
|
|
{
|
2021-03-03 11:34:29 +00:00
|
|
|
visibility === Visibility.Public ? _t("Your public space") : _t("Your private space")
|
2021-03-01 17:27:09 +00:00
|
|
|
}
|
|
|
|
|
</h2>
|
|
|
|
|
<p>
|
|
|
|
|
{
|
2021-03-16 11:39:06 +00:00
|
|
|
_t("Add some details to help people recognise it.")
|
2021-03-01 17:27:09 +00:00
|
|
|
} {
|
2021-03-16 11:39:06 +00:00
|
|
|
_t("You can change these anytime.")
|
2021-03-01 17:27:09 +00:00
|
|
|
}
|
|
|
|
|
</p>
|
|
|
|
|
|
2021-05-10 14:39:10 +01:00
|
|
|
<form className="mx_SpaceBasicSettings" onSubmit={onSpaceCreateClick}>
|
2021-05-12 13:30:00 +01:00
|
|
|
<SpaceAvatar setAvatar={setAvatar} avatarDisabled={busy} />
|
2021-03-01 17:27:09 +00:00
|
|
|
|
2021-05-10 14:39:10 +01:00
|
|
|
<Field
|
|
|
|
|
name="spaceName"
|
|
|
|
|
label={_t("Name")}
|
|
|
|
|
autoFocus={true}
|
|
|
|
|
value={name}
|
2021-06-07 08:57:39 +01:00
|
|
|
onChange={ev => {
|
|
|
|
|
const newName = ev.target.value;
|
|
|
|
|
if (!alias || alias === nameToAlias(name, domain)) {
|
|
|
|
|
setAlias(nameToAlias(newName, domain));
|
|
|
|
|
}
|
|
|
|
|
setName(newName);
|
|
|
|
|
}}
|
2021-05-10 14:39:10 +01:00
|
|
|
ref={spaceNameField}
|
|
|
|
|
onValidate={spaceNameValidator}
|
2021-05-12 13:30:00 +01:00
|
|
|
disabled={busy}
|
2021-05-10 14:39:10 +01:00
|
|
|
/>
|
|
|
|
|
|
2021-06-07 08:57:39 +01:00
|
|
|
{ visibility === Visibility.Public
|
|
|
|
|
? <RoomAliasField
|
|
|
|
|
ref={spaceAliasField}
|
|
|
|
|
onChange={setAlias}
|
|
|
|
|
domain={domain}
|
|
|
|
|
value={alias}
|
|
|
|
|
placeholder={name ? nameToAlias(name, domain) : _t("e.g. my-space")}
|
|
|
|
|
label={_t("Address")}
|
2021-07-08 13:24:56 +01:00
|
|
|
disabled={busy}
|
2021-06-07 08:57:39 +01:00
|
|
|
/>
|
|
|
|
|
: null
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-10 14:39:10 +01:00
|
|
|
<Field
|
|
|
|
|
name="spaceTopic"
|
|
|
|
|
element="textarea"
|
|
|
|
|
label={_t("Description")}
|
|
|
|
|
value={topic}
|
|
|
|
|
onChange={ev => setTopic(ev.target.value)}
|
|
|
|
|
rows={3}
|
2021-05-12 13:30:00 +01:00
|
|
|
disabled={busy}
|
2021-05-10 14:39:10 +01:00
|
|
|
/>
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={busy}>
|
2021-03-24 13:15:31 +00:00
|
|
|
{ busy ? _t("Creating...") : _t("Create") }
|
|
|
|
|
</AccessibleButton>
|
2021-03-01 17:27:09 +00:00
|
|
|
</React.Fragment>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return <ContextMenu
|
|
|
|
|
left={72}
|
|
|
|
|
top={62}
|
|
|
|
|
chevronOffset={0}
|
|
|
|
|
chevronFace={ChevronFace.None}
|
|
|
|
|
onFinished={onFinished}
|
|
|
|
|
wrapperClassName="mx_SpaceCreateMenu_wrapper"
|
|
|
|
|
managed={false}
|
|
|
|
|
>
|
|
|
|
|
<FocusLock returnFocus={true}>
|
|
|
|
|
{ body }
|
|
|
|
|
</FocusLock>
|
|
|
|
|
</ContextMenu>;
|
2021-06-29 13:11:58 +01:00
|
|
|
};
|
2021-03-01 17:27:09 +00:00
|
|
|
|
|
|
|
|
export default SpaceCreateMenu;
|