/* 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. */ import React, { ComponentProps, RefObject, SyntheticEvent, KeyboardEvent, useContext, useRef, useState } from "react"; import classNames from "classnames"; import { RoomType } from "matrix-js-sdk/src/@types/event"; import FocusLock from "react-focus-lock"; import { HistoryVisibility, Preset } from "matrix-js-sdk/src/@types/partials"; import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests"; import { _t } from "../../../languageHandler"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { ChevronFace, ContextMenu } from "../../structures/ContextMenu"; import createRoom, { IOpts as ICreateOpts } from "../../../createRoom"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import SpaceBasicSettings, { SpaceAvatar } from "./SpaceBasicSettings"; import AccessibleButton from "../elements/AccessibleButton"; import Field from "../elements/Field"; import withValidation from "../elements/Validation"; import RoomAliasField from "../elements/RoomAliasField"; import SdkConfig from "../../../SdkConfig"; import Modal from "../../../Modal"; import GenericFeatureFeedbackDialog from "../dialogs/GenericFeatureFeedbackDialog"; import SettingsStore from "../../../settings/SettingsStore"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; import { UserTab } from "../dialogs/UserSettingsDialog"; import { Key } from "../../../Keyboard"; export const createSpace = async ( name: string, isPublic: boolean, alias?: string, topic?: string, avatar?: string | File, createOpts: Partial = {}, otherOpts: Partial> = {}, ) => { return createRoom({ createOpts: { name, preset: isPublic ? Preset.PublicChat : Preset.PrivateChat, power_level_content_override: { // Only allow Admins to write to the timeline to prevent hidden sync spam events_default: 100, ...isPublic ? { invite: 0 } : {}, }, room_alias_name: isPublic && alias ? alias.substr(1, alias.indexOf(":") - 1) : undefined, topic, ...createOpts, }, avatar, roomType: RoomType.Space, historyVisibility: isPublic ? HistoryVisibility.WorldReadable : HistoryVisibility.Invited, spinner: false, encryption: false, andView: true, inlineErrors: true, ...otherOpts, }); }; const SpaceCreateMenuType = ({ title, description, className, onClick }) => { return (

{ title }

{ description }
); }; enum Visibility { Public, Private, } const spaceNameValidator = withValidation({ rules: [ { key: "required", test: async ({ value }) => !!value, invalid: () => _t("Please enter a name for the space"), }, ], }); const nameToAlias = (name: string, domain: string): string => { const localpart = name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9_-]+/gi, ""); return `#${localpart}:${domain}`; }; // XXX: Temporary for the Spaces release only export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => { if (!SdkConfig.get().bug_report_endpoint_url) return null; return
{ _t("Spaces are a new feature.") } { 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)])), }); }} > { _t("Give feedback.") }
; }; type BProps = Omit, "nameDisabled" | "topicDisabled" | "avatarDisabled">; interface ISpaceCreateFormProps extends BProps { busy: boolean; alias: string; nameFieldRef: RefObject; aliasFieldRef: RefObject; showAliasField?: boolean; onSubmit(e: SyntheticEvent): void; setAlias(alias: string): void; } export const SpaceCreateForm: React.FC = ({ busy, onSubmit, avatarUrl, setAvatar, name, setName, nameFieldRef, alias, aliasFieldRef, setAlias, showAliasField, topic, setTopic, children, }) => { const cli = useContext(MatrixClientContext); const domain = cli.getDomain(); const onKeyDown = (ev: KeyboardEvent) => { if (ev.key === Key.ENTER) { onSubmit(ev); } }; return
{ const newName = ev.target.value; if (!alias || alias === nameToAlias(name, domain)) { setAlias(nameToAlias(newName, domain)); } setName(newName); }} onKeyDown={onKeyDown} ref={nameFieldRef} onValidate={spaceNameValidator} disabled={busy} autoComplete="off" /> { showAliasField ? : null } setTopic(ev.target.value)} rows={3} disabled={busy} /> { children } ; }; const SpaceCreateMenu = ({ onFinished }) => { const [visibility, setVisibility] = useState(null); const [busy, setBusy] = useState(false); const [name, setName] = useState(""); const spaceNameField = useRef(); const [alias, setAlias] = useState(""); const spaceAliasField = useRef(); const [avatar, setAvatar] = useState(null); const [topic, setTopic] = useState(""); const onSpaceCreateClick = async (e) => { e.preventDefault(); if (busy) return; setBusy(true); // 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; } // 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; } try { await createSpace(name, visibility === Visibility.Public, alias, topic, avatar); onFinished(); } catch (e) { console.error(e); } }; let body; if (visibility === null) { const onCreateSpaceFromCommunityClick = () => { defaultDispatcher.dispatch({ action: Action.ViewUserSettings, initialTabId: UserTab.Preferences, }); onFinished(); }; body =

{ _t("Create a space") }

{ _t("Spaces are a new way to group rooms and people.") }   { _t("What kind of Space do you want to create?") }   { _t("You can change this later.") }

setVisibility(Visibility.Public)} /> setVisibility(Visibility.Private)} />

{ _t("You can also create a Space from a community.", {}, { a: sub => { sub } , }) }
{ _t("To join an existing space you'll need an invite.") }

; } else { body = setVisibility(null)} title={_t("Go back")} />

{ visibility === Visibility.Public ? _t("Your public space") : _t("Your private space") }

{ _t("Add some details to help people recognise it.") } { _t("You can change these anytime.") }

{ busy ? _t("Creating...") : _t("Create") }
; } return { body } ; }; export default SpaceCreateMenu;