Merge pull request #5777 from matrix-org/t3chguy/spaces4.9

Spaces improve creation journeys
pull/21833/head
Michael Telatynski 2021-03-22 13:24:10 +00:00 committed by GitHub
commit 128c7db28e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 226 additions and 224 deletions

View File

@ -63,7 +63,7 @@ $activeBorderColor: $secondary-fg-color;
} }
.mx_AutoHideScrollbar { .mx_AutoHideScrollbar {
padding: 16px 0; padding: 8px 0 16px;
} }
.mx_SpaceButton_toggleCollapse { .mx_SpaceButton_toggleCollapse {
@ -99,7 +99,6 @@ $activeBorderColor: $secondary-fg-color;
.mx_SpaceButton { .mx_SpaceButton {
border-radius: 8px; border-radius: 8px;
margin-bottom: 2px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 4px 4px 4px 0; padding: 4px 4px 4px 0;

View File

@ -16,6 +16,51 @@ limitations under the License.
$SpaceRoomViewInnerWidth: 428px; $SpaceRoomViewInnerWidth: 428px;
@define-mixin SpacePillButton {
position: relative;
padding: 16px 32px 16px 72px;
width: 432px;
box-sizing: border-box;
border-radius: 8px;
border: 1px solid $input-darker-bg-color;
font-size: $font-15px;
margin: 20px 0;
> h3 {
font-weight: $font-semi-bold;
margin: 0 0 4px;
}
> span {
color: $secondary-fg-color;
}
&::before {
position: absolute;
content: '';
width: 32px;
height: 32px;
top: 24px;
left: 20px;
mask-position: center;
mask-repeat: no-repeat;
mask-size: 24px;
background-color: $tertiary-fg-color;
}
&:hover {
border-color: $accent-color;
&::before {
background-color: $accent-color;
}
> span {
color: $primary-fg-color;
}
}
}
.mx_SpaceRoomView { .mx_SpaceRoomView {
.mx_MainSplit > div:first-child { .mx_MainSplit > div:first-child {
padding: 80px 60px; padding: 80px 60px;
@ -331,64 +376,8 @@ $SpaceRoomViewInnerWidth: 428px;
} }
.mx_SpaceRoomView_privateScope { .mx_SpaceRoomView_privateScope {
.mx_RadioButton { .mx_AccessibleButton {
width: $SpaceRoomViewInnerWidth; @mixin SpacePillButton;
border-radius: 8px;
border: 1px solid $space-button-outline-color;
padding: 16px 16px 16px 72px;
margin-top: 36px;
cursor: pointer;
box-sizing: border-box;
position: relative;
> div:first-of-type {
// hide radio dot
display: none;
}
.mx_RadioButton_content {
margin: 0;
> h3 {
margin: 0 0 4px;
font-size: $font-15px;
font-weight: $font-semi-bold;
line-height: $font-18px;
}
> div {
color: $secondary-fg-color;
font-size: $font-15px;
line-height: $font-24px;
}
}
&::before {
content: "";
position: absolute;
height: 32px;
width: 32px;
top: 24px;
left: 20px;
background-color: $secondary-fg-color;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
}
}
.mx_RadioButton_checked {
border-color: $accent-color;
.mx_RadioButton_content {
> div {
color: $primary-fg-color;
}
}
&::before {
background-color: $accent-color;
}
} }
.mx_SpaceRoomView_privateScope_justMeButton::before { .mx_SpaceRoomView_privateScope_justMeButton::before {

View File

@ -14,10 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// TODO: the space panel currently does not have a fixed width, $spacePanelWidth: 71px;
// just the headers at each level have a max-width of 150px
// so this will look slightly off for now. We should probably use css grid for the whole main layout...
$spacePanelWidth: 200px;
.mx_SpaceCreateMenu_wrapper { .mx_SpaceCreateMenu_wrapper {
// background blur everything except SpacePanel // background blur everything except SpacePanel
@ -48,53 +45,11 @@ $spacePanelWidth: 200px;
} }
.mx_SpaceCreateMenuType { .mx_SpaceCreateMenuType {
position: relative; @mixin SpacePillButton;
padding: 16px 32px 16px 72px;
width: 432px;
box-sizing: border-box;
border-radius: 8px;
border: 1px solid $input-darker-bg-color;
font-size: $font-15px;
margin: 20px 0;
> h3 {
font-weight: $font-semi-bold;
margin: 0 0 4px;
}
> span {
color: $secondary-fg-color;
}
&::before {
position: absolute;
content: '';
width: 32px;
height: 32px;
top: 24px;
left: 20px;
mask-position: center;
mask-repeat: no-repeat;
mask-size: 32px;
background-color: $tertiary-fg-color;
}
&:hover {
border-color: $accent-color;
&::before {
background-color: $accent-color;
}
> span {
color: $primary-fg-color;
}
}
} }
.mx_SpaceCreateMenuType_public::before { .mx_SpaceCreateMenuType_public::before {
mask-image: url('$(res)/img/globe.svg'); mask-image: url('$(res)/img/globe.svg');
mask-size: 26px;
} }
.mx_SpaceCreateMenuType_private::before { .mx_SpaceCreateMenuType_private::before {
mask-image: url('$(res)/img/element-icons/lock.svg'); mask-image: url('$(res)/img/element-icons/lock.svg');

View File

@ -16,38 +16,7 @@ limitations under the License.
.mx_SpacePublicShare { .mx_SpacePublicShare {
.mx_AccessibleButton { .mx_AccessibleButton {
border: 1px solid $space-button-outline-color; @mixin SpacePillButton;
box-sizing: border-box;
border-radius: 8px;
padding: 12px 24px 12px 52px;
margin-top: 16px;
width: $SpaceRoomViewInnerWidth;
font-size: $font-15px;
line-height: $font-24px;
position: relative;
display: flex;
> span {
color: #368bd6;
margin-left: auto;
}
&:hover {
background-color: rgba(141, 151, 165, 0.1);
}
&::before {
content: "";
position: absolute;
width: 30px;
height: 30px;
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
background: $muted-fg-color;
left: 12px;
top: 9px;
}
&.mx_SpacePublicShare_shareButton::before { &.mx_SpacePublicShare_shareButton::before {
mask-image: url('$(res)/img/element-icons/link.svg'); mask-image: url('$(res)/img/element-icons/link.svg');

View File

@ -17,6 +17,7 @@ limitations under the License.
import React, {RefObject, useContext, useRef, useState} from "react"; import React, {RefObject, useContext, useRef, useState} from "react";
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event"; import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
import {Room} from "matrix-js-sdk/src/models/room"; import {Room} from "matrix-js-sdk/src/models/room";
import {EventSubscription} from "fbemitter";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import RoomAvatar from "../views/avatars/RoomAvatar"; import RoomAvatar from "../views/avatars/RoomAvatar";
@ -31,7 +32,6 @@ import {useRoomMembers} from "../../hooks/useRoomMembers";
import createRoom, {IOpts, Preset} from "../../createRoom"; import createRoom, {IOpts, Preset} from "../../createRoom";
import Field from "../views/elements/Field"; import Field from "../views/elements/Field";
import {useEventEmitter} from "../../hooks/useEventEmitter"; import {useEventEmitter} from "../../hooks/useEventEmitter";
import StyledRadioGroup from "../views/elements/StyledRadioGroup";
import withValidation from "../views/elements/Validation"; import withValidation from "../views/elements/Validation";
import * as Email from "../../email"; import * as Email from "../../email";
import defaultDispatcher from "../../dispatcher/dispatcher"; import defaultDispatcher from "../../dispatcher/dispatcher";
@ -42,7 +42,6 @@ import ErrorBoundary from "../views/elements/ErrorBoundary";
import {ActionPayload} from "../../dispatcher/payloads"; import {ActionPayload} from "../../dispatcher/payloads";
import RightPanel from "./RightPanel"; import RightPanel from "./RightPanel";
import RightPanelStore from "../../stores/RightPanelStore"; import RightPanelStore from "../../stores/RightPanelStore";
import {EventSubscription} from "fbemitter";
import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload"; import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload";
import {useStateArray} from "../../hooks/useStateArray"; import {useStateArray} from "../../hooks/useStateArray";
@ -54,6 +53,7 @@ import {EnhancedMap} from "../../utils/maps";
import AutoHideScrollbar from "./AutoHideScrollbar"; import AutoHideScrollbar from "./AutoHideScrollbar";
import MemberAvatar from "../views/avatars/MemberAvatar"; import MemberAvatar from "../views/avatars/MemberAvatar";
import {useStateToggle} from "../../hooks/useStateToggle"; import {useStateToggle} from "../../hooks/useStateToggle";
import SpaceStore from "../../stores/SpaceStore";
interface IProps { interface IProps {
space: Room; space: Room;
@ -66,6 +66,7 @@ interface IProps {
interface IState { interface IState {
phase: Phase; phase: Phase;
showRightPanel: boolean; showRightPanel: boolean;
myMembership: string;
} }
enum Phase { enum Phase {
@ -98,6 +99,8 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const myMembership = useMyRoomMembership(space); const myMembership = useMyRoomMembership(space);
const [busy, setBusy] = useState(false);
let inviterSection; let inviterSection;
let joinButtons; let joinButtons;
if (myMembership === "invite") { if (myMembership === "invite") {
@ -121,11 +124,35 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
} }
joinButtons = <> joinButtons = <>
<FormButton label={_t("Reject")} kind="secondary" onClick={onRejectButtonClicked} /> <FormButton
<FormButton label={_t("Accept")} onClick={onJoinButtonClicked} /> label={_t("Reject")}
kind="secondary"
onClick={() => {
setBusy(true);
onRejectButtonClicked();
}} />
<FormButton
label={_t("Accept")}
onClick={() => {
setBusy(true);
onJoinButtonClicked();
}}
/>
</>; </>;
} else { } else {
joinButtons = <FormButton label={_t("Join")} onClick={onJoinButtonClicked} /> joinButtons = (
<FormButton
label={_t("Join")}
onClick={() => {
setBusy(true);
onJoinButtonClicked();
}}
/>
)
}
if (busy) {
joinButtons = <InlineSpinner />;
} }
let visibilitySection; let visibilitySection;
@ -337,6 +364,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
placeholder={placeholders[i]} placeholder={placeholders[i]}
value={roomNames[i]} value={roomNames[i]}
onChange={ev => setRoomName(i, ev.target.value)} onChange={ev => setRoomName(i, ev.target.value)}
autoFocus={i === 2}
/>; />;
}); });
@ -369,7 +397,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, 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;
buttonLabel = busy ? _t("Creating rooms...") : _t("Next") buttonLabel = busy ? _t("Creating rooms...") : _t("Continue")
} }
return <div> return <div>
@ -391,50 +419,40 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
const SpaceSetupPublicShare = ({ space, onFinished }) => { const SpaceSetupPublicShare = ({ space, onFinished }) => {
return <div className="mx_SpaceRoomView_publicShare"> return <div className="mx_SpaceRoomView_publicShare">
<h1>{ _t("Share your public space") }</h1> <h1>{ _t("Share %(name)s", { name: space.name }) }</h1>
<div className="mx_SpacePublicShare_description">{ _t("At the moment only you can see it.") }</div> <div className="mx_SpacePublicShare_description">
{ _t("It's just you at the moment, it will be even better with others.") }
</div>
<SpacePublicShare space={space} onFinished={onFinished} /> <SpacePublicShare space={space} onFinished={onFinished} />
<div className="mx_SpaceRoomView_buttons"> <div className="mx_SpaceRoomView_buttons">
<FormButton label={_t("Finish")} onClick={onFinished} /> <FormButton label={_t("Go to my first room")} onClick={onFinished} />
</div> </div>
</div>; </div>;
}; };
const SpaceSetupPrivateScope = ({ onFinished }) => { const SpaceSetupPrivateScope = ({ space, onFinished }) => {
const [option, setOption] = useState<string>(null);
return <div className="mx_SpaceRoomView_privateScope"> return <div className="mx_SpaceRoomView_privateScope">
<h1>{ _t("Who are you working with?") }</h1> <h1>{ _t("Who are you working with?") }</h1>
<div className="mx_SpaceRoomView_description">{ _t("Ensure the right people have access to the space.") }</div> <div className="mx_SpaceRoomView_description">
{ _t("Make sure the right people have access to %(name)s", { name: space.name }) }
<StyledRadioGroup
name="privateSpaceScope"
value={option}
onChange={setOption}
definitions={[
{
value: "justMe",
className: "mx_SpaceRoomView_privateScope_justMeButton",
label: <React.Fragment>
<h3>{ _t("Just Me") }</h3>
<div>{ _t("A private space just for you") }</div>
</React.Fragment>,
}, {
value: "meAndMyTeammates",
className: "mx_SpaceRoomView_privateScope_meAndMyTeammatesButton",
label: <React.Fragment>
<h3>{ _t("Me and my teammates") }</h3>
<div>{ _t("A private space for you and your teammates") }</div>
</React.Fragment>,
},
]}
/>
<div className="mx_SpaceRoomView_buttons">
<FormButton label={_t("Next")} disabled={!option} onClick={() => onFinished(option !== "justMe")} />
</div> </div>
<AccessibleButton
className="mx_SpaceRoomView_privateScope_justMeButton"
onClick={() => { onFinished(false) }}
>
<h3>{ _t("Just me") }</h3>
<div>{ _t("A private space to organise your rooms") }</div>
</AccessibleButton>
<AccessibleButton
className="mx_SpaceRoomView_privateScope_meAndMyTeammatesButton"
onClick={() => { onFinished(true) }}
>
<h3>{ _t("Me and my teammates") }</h3>
<div>{ _t("A private space for you and your teammates") }</div>
</AccessibleButton>
</div>; </div>;
}; };
@ -464,6 +482,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
onChange={ev => setEmailAddress(i, ev.target.value)} onChange={ev => setEmailAddress(i, ev.target.value)}
ref={fieldRefs[i]} ref={fieldRefs[i]}
onValidate={validateEmailRules} onValidate={validateEmailRules}
autoFocus={i === 0}
/>; />;
}); });
@ -501,9 +520,18 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
setBusy(false); setBusy(false);
}; };
let onClick = onFinished;
let buttonLabel = _t("Skip for now");
if (emailAddresses.some(name => name.trim())) {
onClick = onNextClick;
buttonLabel = busy ? _t("Inviting...") : _t("Continue")
}
return <div className="mx_SpaceRoomView_inviteTeammates"> return <div className="mx_SpaceRoomView_inviteTeammates">
<h1>{ _t("Invite your teammates") }</h1> <h1>{ _t("Invite your teammates") }</h1>
<div className="mx_SpaceRoomView_description">{ _t("Ensure the right people have access to the space.") }</div> <div className="mx_SpaceRoomView_description">
{ _t("Make sure the right people have access. You can invite more later.") }
</div>
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> } { error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
{ fields } { fields }
@ -518,8 +546,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
</div> </div>
<div className="mx_SpaceRoomView_buttons"> <div className="mx_SpaceRoomView_buttons">
<AccessibleButton onClick={onFinished} kind="link">{_t("Skip for now")}</AccessibleButton> <FormButton label={buttonLabel} disabled={busy} onClick={onClick} />
<FormButton label={busy ? _t("Inviting...") : _t("Next")} disabled={busy} onClick={onNextClick} />
</div> </div>
</div>; </div>;
}; };
@ -547,17 +574,26 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
this.state = { this.state = {
phase, phase,
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
myMembership: this.props.space.getMyMembership(),
}; };
this.dispatcherRef = defaultDispatcher.register(this.onAction); this.dispatcherRef = defaultDispatcher.register(this.onAction);
this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate); this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate);
this.context.on("Room.myMembership", this.onMyMembership);
} }
componentWillUnmount() { componentWillUnmount() {
defaultDispatcher.unregister(this.dispatcherRef); defaultDispatcher.unregister(this.dispatcherRef);
this.rightPanelStoreToken.remove(); this.rightPanelStoreToken.remove();
this.context.off("Room.myMembership", this.onMyMembership);
} }
private onMyMembership = (room: Room, myMembership: string) => {
if (room.roomId === this.props.space.roomId) {
this.setState({ myMembership });
}
};
private onRightPanelStoreUpdate = () => { private onRightPanelStoreUpdate = () => {
this.setState({ this.setState({
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
@ -594,10 +630,43 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
} }
}; };
private goToFirstRoom = async () => {
const childRooms = SpaceStore.instance.getChildRooms(this.props.space.roomId);
if (childRooms.length) {
const room = childRooms[0];
defaultDispatcher.dispatch({
action: "view_room",
room_id: room.roomId,
});
return;
}
let suggestedRooms = SpaceStore.instance.suggestedRooms;
if (SpaceStore.instance.activeSpace !== this.props.space) {
// the space store has the suggested rooms loaded for a different space, fetch the right ones
suggestedRooms = (await SpaceStore.instance.fetchSuggestedRooms(this.props.space, 1)).rooms;
}
if (suggestedRooms.length) {
const room = suggestedRooms[0];
defaultDispatcher.dispatch({
action: "view_room",
room_id: room.room_id,
oobData: {
avatarUrl: room.avatar_url,
name: room.name || room.canonical_alias || room.aliases.pop() || _t("Empty room"),
},
});
return;
}
this.setState({ phase: Phase.Landing });
};
private renderBody() { private renderBody() {
switch (this.state.phase) { switch (this.state.phase) {
case Phase.Landing: case Phase.Landing:
if (this.props.space.getMyMembership() === "join") { if (this.state.myMembership === "join") {
return <SpaceLanding space={this.props.space} />; return <SpaceLanding space={this.props.space} />;
} else { } else {
return <SpacePreview return <SpacePreview
@ -610,17 +679,16 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
return <SpaceSetupFirstRooms return <SpaceSetupFirstRooms
space={this.props.space} space={this.props.space}
title={_t("What are some things you want to discuss?")} title={_t("What are some things you want to discuss?")}
description={_t("We'll create rooms for each topic.")} description={_t("Let's create a room for each of them. " +
"You can add more later too, including already existing ones.")}
onFinished={() => this.setState({ phase: Phase.PublicShare })} onFinished={() => this.setState({ phase: Phase.PublicShare })}
/>; />;
case Phase.PublicShare: case Phase.PublicShare:
return <SpaceSetupPublicShare return <SpaceSetupPublicShare space={this.props.space} onFinished={this.goToFirstRoom} />;
space={this.props.space}
onFinished={() => this.setState({ phase: Phase.Landing })}
/>;
case Phase.PrivateScope: case Phase.PrivateScope:
return <SpaceSetupPrivateScope return <SpaceSetupPrivateScope
space={this.props.space}
onFinished={(invite: boolean) => { onFinished={(invite: boolean) => {
this.setState({ phase: invite ? Phase.PrivateInvite : Phase.PrivateCreateRooms }); this.setState({ phase: invite ? Phase.PrivateInvite : Phase.PrivateCreateRooms });
}} }}
@ -634,7 +702,8 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
return <SpaceSetupFirstRooms return <SpaceSetupFirstRooms
space={this.props.space} space={this.props.space}
title={_t("What projects are you working on?")} title={_t("What projects are you working on?")}
description={_t("We'll create rooms for each of them. You can add existing rooms after setup.")} description={_t("We'll create rooms for each of them. " +
"You can add more later too, including already existing ones.")}
onFinished={() => this.setState({ phase: Phase.Landing })} onFinished={() => this.setState({ phase: Phase.Landing })}
/>; />;
} }

View File

@ -69,6 +69,7 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
const existingRoomsSet = new Set(existingRooms); const existingRoomsSet = new Set(existingRooms);
const rooms = cli.getVisibleRooms().filter(room => { const rooms = cli.getVisibleRooms().filter(room => {
return !existingRoomsSet.has(room) // not already in space return !existingRoomsSet.has(room) // not already in space
&& !room.isSpaceRoom() // not a space itself
&& room.name.toLowerCase().includes(lcQuery) // contains query && room.name.toLowerCase().includes(lcQuery) // contains query
&& !DMRoomMap.shared().getUserIdForRoomId(room.roomId); // not a DM && !DMRoomMap.shared().getUserIdForRoomId(room.roomId); // not a DM
}); });

View File

@ -108,7 +108,7 @@ const SpaceCreateMenu = ({ onFinished }) => {
body = <React.Fragment> body = <React.Fragment>
<h2>{ _t("Create a space") }</h2> <h2>{ _t("Create a space") }</h2>
<p>{ _t("Spaces are new ways to group rooms and people. " + <p>{ _t("Spaces are new ways to group rooms and people. " +
"To join an existing space youll need an invite") }</p> "To join an existing space you'll need an invite.") }</p>
<SpaceCreateMenuType <SpaceCreateMenuType
title={_t("Public")} title={_t("Public")}
@ -140,9 +140,9 @@ const SpaceCreateMenu = ({ onFinished }) => {
</h2> </h2>
<p> <p>
{ {
_t("Give it a photo, name and description to help you identify it.") _t("Add some details to help people recognise it.")
} { } {
_t("You can change these at any point.") _t("You can change these anytime.")
} }
</p> </p>

View File

@ -220,13 +220,19 @@ const SpacePanel = () => {
<SpaceButton <SpaceButton
className={newClasses} className={newClasses}
tooltip={menuDisplayed ? _t("Cancel") : _t("Create a space")} tooltip={menuDisplayed ? _t("Cancel") : _t("Create a space")}
onClick={menuDisplayed ? closeMenu : openMenu} onClick={menuDisplayed ? closeMenu : () => {
openMenu();
if (!isPanelCollapsed) setPanelCollapsed(true);
}}
isNarrow={isPanelCollapsed} isNarrow={isPanelCollapsed}
/> />
</AutoHideScrollbar> </AutoHideScrollbar>
<AccessibleTooltipButton <AccessibleTooltipButton
className={classNames("mx_SpacePanel_toggleCollapse", {expanded: !isPanelCollapsed})} className={classNames("mx_SpacePanel_toggleCollapse", {expanded: !isPanelCollapsed})}
onClick={evt => setPanelCollapsed(!isPanelCollapsed)} onClick={() => {
setPanelCollapsed(!isPanelCollapsed);
if (menuDisplayed) closeMenu();
}}
title={expandCollapseButtonTitle} title={expandCollapseButtonTitle}
/> />
{ contextMenu } { contextMenu }

View File

@ -41,13 +41,13 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => {
const success = await copyPlaintext(permalinkCreator.forRoom()); const success = await copyPlaintext(permalinkCreator.forRoom());
const text = success ? _t("Copied!") : _t("Failed to copy"); const text = success ? _t("Copied!") : _t("Failed to copy");
setCopiedText(text); setCopiedText(text);
await sleep(10); await sleep(5000);
if (copiedText === text) { // if the text hasn't changed by another click then clear it after some time if (copiedText === text) { // if the text hasn't changed by another click then clear it after some time
setCopiedText(_t("Click to copy")); setCopiedText(_t("Click to copy"));
} }
}} }}
> >
{ _t("Share invite link") } <h3>{ _t("Share invite link") }</h3>
<span>{ copiedText }</span> <span>{ copiedText }</span>
</AccessibleButton> </AccessibleButton>
<AccessibleButton <AccessibleButton
@ -57,7 +57,8 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => {
onFinished(); onFinished();
}} }}
> >
{ _t("Invite by email or username") } <h3>{ _t("Invite people") }</h3>
<span>{ _t("Invite with email or username") }</span>
</AccessibleButton> </AccessibleButton>
</div>; </div>;
}; };

View File

@ -989,7 +989,7 @@
"Name": "Name", "Name": "Name",
"Description": "Description", "Description": "Description",
"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 youll need an invite": "Spaces are new ways to group rooms and people. To join an existing space youll 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",
"Open space for anyone, best for communities": "Open space for anyone, best for communities", "Open space for anyone, best for communities": "Open space for anyone, best for communities",
"Private": "Private", "Private": "Private",
@ -998,8 +998,8 @@
"Go back": "Go back", "Go back": "Go back",
"Your public space": "Your public space", "Your public space": "Your public space",
"Your private space": "Your private space", "Your private space": "Your private space",
"Give it a photo, name and description to help you identify it.": "Give it a photo, name and description to help you identify it.", "Add some details to help people recognise it.": "Add some details to help people recognise it.",
"You can change these at any point.": "You can change these at any point.", "You can change these anytime.": "You can change these anytime.",
"Creating...": "Creating...", "Creating...": "Creating...",
"Create": "Create", "Create": "Create",
"Expand space panel": "Expand space panel", "Expand space panel": "Expand space panel",
@ -1009,10 +1009,10 @@
"Copied!": "Copied!", "Copied!": "Copied!",
"Failed to copy": "Failed to copy", "Failed to copy": "Failed to copy",
"Share invite link": "Share invite link", "Share invite link": "Share invite link",
"Invite by email or username": "Invite by email or username", "Invite people": "Invite people",
"Invite with email or username": "Invite with email or username",
"Invite members": "Invite members", "Invite members": "Invite members",
"Share your public space": "Share your public space", "Share your public space": "Share your public space",
"Invite people": "Invite people",
"Settings": "Settings", "Settings": "Settings",
"Leave space": "Leave space", "Leave space": "Leave space",
"New room": "New room", "New room": "New room",
@ -2633,22 +2633,24 @@
"Failed to create initial space rooms": "Failed to create initial space rooms", "Failed to create initial space rooms": "Failed to create initial space rooms",
"Skip for now": "Skip for now", "Skip for now": "Skip for now",
"Creating rooms...": "Creating rooms...", "Creating rooms...": "Creating rooms...",
"At the moment only you can see it.": "At the moment only you can see it.", "Share %(name)s": "Share %(name)s",
"Finish": "Finish", "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",
"Who are you working with?": "Who are you working with?", "Who are you working with?": "Who are you working with?",
"Ensure the right people have access to the space.": "Ensure the right people have access to the space.", "Make sure the right people have access to %(name)s": "Make sure the right people have access to %(name)s",
"Just Me": "Just Me", "Just me": "Just me",
"A private space just for you": "A private space just for you", "A private space to organise your rooms": "A private space to organise your rooms",
"Me and my teammates": "Me and my teammates", "Me and my teammates": "Me and my teammates",
"A private space for you and your teammates": "A private space for you and your teammates", "A private space for you and your teammates": "A private space for you and your teammates",
"Failed to invite the following users to your space: %(csvUsers)s": "Failed to invite the following users to your space: %(csvUsers)s", "Failed to invite the following users to your space: %(csvUsers)s": "Failed to invite the following users to your space: %(csvUsers)s",
"Invite your teammates": "Invite your teammates",
"Invite by username": "Invite by username",
"Inviting...": "Inviting...", "Inviting...": "Inviting...",
"Invite your teammates": "Invite your teammates",
"Make sure the right people have access. You can invite more later.": "Make sure the right people have access. You can invite more later.",
"Invite by username": "Invite by username",
"What are some things you want to discuss?": "What are some things you want to discuss?", "What are some things you want to discuss?": "What are some things you want to discuss?",
"We'll create rooms for each topic.": "We'll create rooms for each topic.", "Let's create a room for each of them. You can add more later too, including already existing ones.": "Let's create a room for each of them. You can add more later too, including already existing ones.",
"What projects are you working on?": "What projects are you working on?", "What projects are you working on?": "What projects are you working on?",
"We'll create rooms for each of them. You can add existing rooms after setup.": "We'll create rooms for each of them. You can add existing rooms after setup.", "We'll create rooms for each of them. You can add more later too, including already existing ones.": "We'll create rooms for each of them. You can add more later too, including already existing ones.",
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.",
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.",
"Failed to load timeline position": "Failed to load timeline position", "Failed to load timeline position": "Failed to load timeline position",

View File

@ -118,23 +118,32 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
if (space) { if (space) {
try { const data = await this.fetchSuggestedRooms(space);
const data: { if (this._activeSpace === space) {
rooms: ISpaceSummaryRoom[]; this._suggestedRooms = data.rooms.filter(roomInfo => {
events: ISpaceSummaryEvent[]; return roomInfo.room_type !== RoomType.Space && !this.matrixClient.getRoom(roomInfo.room_id);
} = await this.matrixClient.getSpaceSummary(space.roomId, 0, true, false, MAX_SUGGESTED_ROOMS); });
if (this._activeSpace === space) { this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
this._suggestedRooms = data.rooms.filter(roomInfo => {
return roomInfo.room_type !== RoomType.Space && !this.matrixClient.getRoom(roomInfo.room_id);
});
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
}
} catch (e) {
console.error(e);
} }
} }
} }
public fetchSuggestedRooms = async (space: Room, limit = MAX_SUGGESTED_ROOMS) => {
try {
const data: {
rooms: ISpaceSummaryRoom[];
events: ISpaceSummaryEvent[];
} = await this.matrixClient.getSpaceSummary(space.roomId, 0, true, false, limit);
return data;
} catch (e) {
console.error(e);
}
return {
rooms: [],
events: [],
};
};
public addRoomToSpace(space: Room, roomId: string, via: string[], suggested = false, autoJoin = false) { public addRoomToSpace(space: Room, roomId: string, via: string[], suggested = false, autoJoin = false) {
return this.matrixClient.sendStateEvent(space.roomId, EventType.SpaceChild, { return this.matrixClient.sendStateEvent(space.roomId, EventType.SpaceChild, {
via, via,
@ -385,7 +394,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEvent: MatrixEvent) => { private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEvent: MatrixEvent) => {
if (ev.getType() === EventType.Tag && !room.isSpaceRoom()) { if (ev.getType() === EventType.Tag && !room.isSpaceRoom()) {
// If the room was in favourites and now isn't or the opposite then update its position in the trees // If the room was in favourites and now isn't or the opposite then update its position in the trees
if (!!ev.getContent()[DefaultTagID.Favourite] !== !!lastEvent.getContent()[DefaultTagID.Favourite]) { const oldTags = lastEvent.getContent()?.tags;
const newTags = ev.getContent()?.tags;
if (!!oldTags[DefaultTagID.Favourite] !== !!newTags[DefaultTagID.Favourite]) {
this.onRoomUpdate(room); this.onRoomUpdate(room);
} }
} }