Merge pull request #5777 from matrix-org/t3chguy/spaces4.9
Spaces improve creation journeyspull/21833/head
commit
128c7db28e
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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 })}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 you’ll 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>
|
||||||
|
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 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",
|
||||||
"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",
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue