Merge pull request #6006 from matrix-org/t3chguy/fix/17227

Allow user to progress through space creation & setup using Enter
pull/21833/head
Michael Telatynski 2021-05-11 12:02:14 +01:00 committed by GitHub
commit 2b2e74b65e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 121 additions and 41 deletions

View File

@ -103,6 +103,10 @@ $SpaceRoomViewInnerWidth: 428px;
padding: 8px 22px; padding: 8px 22px;
margin-left: 16px; margin-left: 16px;
} }
input.mx_AccessibleButton {
border: none; // override default styles
}
} }
.mx_Field { .mx_Field {

View File

@ -385,7 +385,9 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
/>; />;
}); });
const onNextClick = async () => { const onNextClick = async (ev) => {
ev.preventDefault();
if (busy) return;
setError(""); setError("");
setBusy(true); setBusy(true);
try { try {
@ -410,7 +412,10 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
setBusy(false); setBusy(false);
}; };
let onClick = onFinished; let onClick = (ev) => {
ev.preventDefault();
onFinished();
};
let buttonLabel = _t("Skip for now"); let buttonLabel = _t("Skip for now");
if (roomNames.some(name => name.trim())) { if (roomNames.some(name => name.trim())) {
onClick = onNextClick; onClick = onNextClick;
@ -422,16 +427,20 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
<div className="mx_SpaceRoomView_description">{ description }</div> <div className="mx_SpaceRoomView_description">{ description }</div>
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> } { error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
{ fields } <form onSubmit={onClick} id="mx_SpaceSetupFirstRooms">
{ fields }
</form>
<div className="mx_SpaceRoomView_buttons"> <div className="mx_SpaceRoomView_buttons">
<AccessibleButton <AccessibleButton
kind="primary" kind="primary"
disabled={busy} disabled={busy}
onClick={onClick} onClick={onClick}
> element="input"
{ buttonLabel } type="submit"
</AccessibleButton> form="mx_SpaceSetupFirstRooms"
value={buttonLabel}
/>
</div> </div>
</div>; </div>;
}; };
@ -575,7 +584,9 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
/>; />;
}); });
const onNextClick = async () => { const onNextClick = async (ev) => {
ev.preventDefault();
if (busy) return;
setError(""); setError("");
for (let i = 0; i < fieldRefs.length; i++) { for (let i = 0; i < fieldRefs.length; i++) {
const fieldRef = fieldRefs[i]; const fieldRef = fieldRefs[i];
@ -609,7 +620,10 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
setBusy(false); setBusy(false);
}; };
let onClick = onFinished; let onClick = (ev) => {
ev.preventDefault();
onFinished();
};
let buttonLabel = _t("Skip for now"); let buttonLabel = _t("Skip for now");
if (emailAddresses.some(name => name.trim())) { if (emailAddresses.some(name => name.trim())) {
onClick = onNextClick; onClick = onNextClick;
@ -623,7 +637,9 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
</div> </div>
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> } { error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
{ fields } <form onSubmit={onClick} id="mx_SpaceSetupPrivateInvite">
{ fields }
</form>
<div className="mx_SpaceRoomView_inviteTeammates_buttons"> <div className="mx_SpaceRoomView_inviteTeammates_buttons">
<AccessibleButton <AccessibleButton
@ -635,9 +651,15 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
</div> </div>
<div className="mx_SpaceRoomView_buttons"> <div className="mx_SpaceRoomView_buttons">
<AccessibleButton kind="primary" disabled={busy} onClick={onClick}> <AccessibleButton
{ buttonLabel } kind="primary"
</AccessibleButton> disabled={busy}
onClick={onClick}
element="input"
type="submit"
form="mx_SpaceSetupPrivateInvite"
value={buttonLabel}
/>
</div> </div>
</div>; </div>;
}; };

View File

@ -32,17 +32,11 @@ interface IProps {
setTopic(topic: string): void; setTopic(topic: string): void;
} }
const SpaceBasicSettings = ({ export const SpaceAvatar = ({
avatarUrl, avatarUrl,
avatarDisabled = false, avatarDisabled = false,
setAvatar, setAvatar,
name = "", }: Pick<IProps, "avatarUrl" | "avatarDisabled" | "setAvatar">) => {
nameDisabled = false,
setName,
topic = "",
topicDisabled = false,
setTopic,
}: IProps) => {
const avatarUploadRef = useRef<HTMLInputElement>(); const avatarUploadRef = useRef<HTMLInputElement>();
const [avatar, setAvatarDataUrl] = useState(avatarUrl); // avatar data url cache const [avatar, setAvatarDataUrl] = useState(avatarUrl); // avatar data url cache
@ -81,20 +75,34 @@ const SpaceBasicSettings = ({
} }
} }
return <div className="mx_SpaceBasicSettings_avatarContainer">
{ avatarSection }
<input type="file" ref={avatarUploadRef} onChange={(e) => {
if (!e.target.files?.length) return;
const file = e.target.files[0];
setAvatar(file);
const reader = new FileReader();
reader.onload = (ev) => {
setAvatarDataUrl(ev.target.result as string);
};
reader.readAsDataURL(file);
}} accept="image/*" />
</div>;
};
const SpaceBasicSettings = ({
avatarUrl,
avatarDisabled = false,
setAvatar,
name = "",
nameDisabled = false,
setName,
topic = "",
topicDisabled = false,
setTopic,
}: IProps) => {
return <div className="mx_SpaceBasicSettings"> return <div className="mx_SpaceBasicSettings">
<div className="mx_SpaceBasicSettings_avatarContainer"> <SpaceAvatar avatarUrl={avatarUrl} avatarDisabled={avatarDisabled} setAvatar={setAvatar} />
{ avatarSection }
<input type="file" ref={avatarUploadRef} onChange={(e) => {
if (!e.target.files?.length) return;
const file = e.target.files[0];
setAvatar(file);
const reader = new FileReader();
reader.onload = (ev) => {
setAvatarDataUrl(ev.target.result as string);
};
reader.readAsDataURL(file);
}} accept="image/*" />
</div>
<Field <Field
name="spaceName" name="spaceName"

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {useContext, useState} from "react"; import React, {useContext, useRef, useState} from "react";
import classNames from "classnames"; import classNames from "classnames";
import {EventType, RoomType, RoomCreateTypeField} from "matrix-js-sdk/src/@types/event"; import {EventType, RoomType, RoomCreateTypeField} from "matrix-js-sdk/src/@types/event";
@ -23,9 +23,11 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import {ChevronFace, ContextMenu} from "../../structures/ContextMenu"; import {ChevronFace, ContextMenu} from "../../structures/ContextMenu";
import createRoom, {IStateEvent, Preset} from "../../../createRoom"; import createRoom, {IStateEvent, Preset} from "../../../createRoom";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import SpaceBasicSettings from "./SpaceBasicSettings"; import {SpaceAvatar} from "./SpaceBasicSettings";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import FocusLock from "react-focus-lock"; import FocusLock from "react-focus-lock";
import Field from "../elements/Field";
import withValidation from "../elements/Validation";
const SpaceCreateMenuType = ({ title, description, className, onClick }) => { const SpaceCreateMenuType = ({ title, description, className, onClick }) => {
return ( return (
@ -41,17 +43,39 @@ enum Visibility {
Private, Private,
} }
const spaceNameValidator = withValidation({
rules: [
{
key: "required",
test: async ({ value }) => !!value,
invalid: () => _t("Please enter a name for the space"),
},
],
});
const SpaceCreateMenu = ({ onFinished }) => { const SpaceCreateMenu = ({ onFinished }) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const [visibility, setVisibility] = useState<Visibility>(null); const [visibility, setVisibility] = useState<Visibility>(null);
const [name, setName] = useState("");
const [avatar, setAvatar] = useState<File>(null);
const [topic, setTopic] = useState<string>("");
const [busy, setBusy] = useState<boolean>(false); const [busy, setBusy] = useState<boolean>(false);
const onSpaceCreateClick = async () => { const [name, setName] = useState("");
const spaceNameField = useRef<Field>();
const [avatar, setAvatar] = useState<File>(null);
const [topic, setTopic] = useState<string>("");
const onSpaceCreateClick = async (e) => {
e.preventDefault();
if (busy) return; if (busy) return;
setBusy(true); 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;
}
const initialState: IStateEvent[] = [ const initialState: IStateEvent[] = [
{ {
type: EventType.RoomHistoryVisibility, type: EventType.RoomHistoryVisibility,
@ -146,9 +170,30 @@ const SpaceCreateMenu = ({ onFinished }) => {
} }
</p> </p>
<SpaceBasicSettings setAvatar={setAvatar} name={name} setName={setName} topic={topic} setTopic={setTopic} /> <form className="mx_SpaceBasicSettings" onSubmit={onSpaceCreateClick}>
<SpaceAvatar setAvatar={setAvatar} />
<AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={!name || busy}> <Field
name="spaceName"
label={_t("Name")}
autoFocus={true}
value={name}
onChange={ev => setName(ev.target.value)}
ref={spaceNameField}
onValidate={spaceNameValidator}
/>
<Field
name="spaceTopic"
element="textarea"
label={_t("Description")}
value={topic}
onChange={ev => setTopic(ev.target.value)}
rows={3}
/>
</form>
<AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={busy}>
{ busy ? _t("Creating...") : _t("Create") } { busy ? _t("Creating...") : _t("Create") }
</AccessibleButton> </AccessibleButton>
</React.Fragment>; </React.Fragment>;

View File

@ -996,6 +996,7 @@
"Upload": "Upload", "Upload": "Upload",
"Name": "Name", "Name": "Name",
"Description": "Description", "Description": "Description",
"Please enter a name for the space": "Please enter a name for the space",
"Create a space": "Create a space", "Create a space": "Create a space",
"Spaces are new ways to group rooms and people. To join an existing space you'll need an invite.": "Spaces are new ways to group rooms and people. To join an existing space you'll need an invite.", "Spaces are new ways to group rooms and people. To join an existing space you'll need an invite.": "Spaces are new ways to group rooms and people. To join an existing space you'll need an invite.",
"Public": "Public", "Public": "Public",