Add a basic Space Settings view
This commit is contained in:
parent
926e226a78
commit
ab4b7b73ea
8 changed files with 260 additions and 1 deletions
|
@ -91,6 +91,7 @@
|
||||||
@import "./views/dialogs/_SettingsDialog.scss";
|
@import "./views/dialogs/_SettingsDialog.scss";
|
||||||
@import "./views/dialogs/_ShareDialog.scss";
|
@import "./views/dialogs/_ShareDialog.scss";
|
||||||
@import "./views/dialogs/_SlashCommandHelpDialog.scss";
|
@import "./views/dialogs/_SlashCommandHelpDialog.scss";
|
||||||
|
@import "./views/dialogs/_SpaceSettingsDialog.scss";
|
||||||
@import "./views/dialogs/_TabbedIntegrationManagerDialog.scss";
|
@import "./views/dialogs/_TabbedIntegrationManagerDialog.scss";
|
||||||
@import "./views/dialogs/_TermsDialog.scss";
|
@import "./views/dialogs/_TermsDialog.scss";
|
||||||
@import "./views/dialogs/_UploadConfirmDialog.scss";
|
@import "./views/dialogs/_UploadConfirmDialog.scss";
|
||||||
|
|
|
@ -187,6 +187,16 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_SpaceRoomView_landing_settingsButton {
|
||||||
|
&::before {
|
||||||
|
background-color: #5c56f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
mask-image: url('$(res)/img/element-icons/settings.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
55
res/css/views/dialogs/_SpaceSettingsDialog.scss
Normal file
55
res/css/views/dialogs/_SpaceSettingsDialog.scss
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_SpaceSettingsDialog {
|
||||||
|
width: 480px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
|
||||||
|
.mx_SpaceSettings_errorText {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $notice-primary-color;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ToggleSwitch {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_danger {
|
||||||
|
margin-top: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceSettingsDialog_buttons {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 64px;
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_FormButton {
|
||||||
|
padding: 8px 22px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1118,6 +1118,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||||
|
|
||||||
d.finally(() => modal.close());
|
d.finally(() => modal.close());
|
||||||
|
dis.dispatch({
|
||||||
|
action: "after_leave_room",
|
||||||
|
room_id: roomId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -46,7 +46,7 @@ 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";
|
||||||
import SpacePublicShare from "../views/spaces/SpacePublicShare";
|
import SpacePublicShare from "../views/spaces/SpacePublicShare";
|
||||||
import {shouldShowSpaceSettings} from "../../utils/space";
|
import {shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
|
||||||
import MemberAvatar from "../views/avatars/MemberAvatar";
|
import MemberAvatar from "../views/avatars/MemberAvatar";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
@ -119,6 +119,15 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let settingsButton;
|
||||||
|
if (shouldShowSpaceSettings(cli, space)) {
|
||||||
|
settingsButton = <AccessibleButton className="mx_SpaceRoomView_landing_settingsButton" onClick={() => {
|
||||||
|
showSpaceSettings(cli, space);
|
||||||
|
}}>
|
||||||
|
{ _t("Settings") }
|
||||||
|
</AccessibleButton>;
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="mx_SpaceRoomView_landing">
|
return <div className="mx_SpaceRoomView_landing">
|
||||||
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
||||||
<div className="mx_SpaceRoomView_landing_name">
|
<div className="mx_SpaceRoomView_landing_name">
|
||||||
|
@ -180,6 +189,7 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
{ joinButtons }
|
{ joinButtons }
|
||||||
<div className="mx_SpaceRoomView_landing_adminButtons">
|
<div className="mx_SpaceRoomView_landing_adminButtons">
|
||||||
{ inviteButton }
|
{ inviteButton }
|
||||||
|
{ settingsButton }
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
162
src/components/views/dialogs/SpaceSettingsDialog.tsx
Normal file
162
src/components/views/dialogs/SpaceSettingsDialog.tsx
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
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, {useState} from 'react';
|
||||||
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
|
import {EventType} from "matrix-js-sdk/src/@types/event";
|
||||||
|
|
||||||
|
import {_t} from '../../../languageHandler';
|
||||||
|
import {IDialogProps} from "./IDialogProps";
|
||||||
|
import BaseDialog from "./BaseDialog";
|
||||||
|
import DevtoolsDialog from "./DevtoolsDialog";
|
||||||
|
import SpaceBasicSettings from '../spaces/SpaceBasicSettings';
|
||||||
|
import {getTopic} from "../elements/RoomTopic";
|
||||||
|
import {avatarUrlForRoom} from "../../../Avatar";
|
||||||
|
import ToggleSwitch from "../elements/ToggleSwitch";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
import FormButton from "../elements/FormButton";
|
||||||
|
import Modal from "../../../Modal";
|
||||||
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
|
import {allSettled} from "../../../utils/promise";
|
||||||
|
import {useDispatcher} from "../../../hooks/useDispatcher";
|
||||||
|
|
||||||
|
interface IProps extends IDialogProps {
|
||||||
|
matrixClient: MatrixClient;
|
||||||
|
space: Room;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFinished }) => {
|
||||||
|
useDispatcher(defaultDispatcher, ({action, ...params}) => {
|
||||||
|
if (action === "after_leave_room" && params.room_id === space.roomId) {
|
||||||
|
onFinished(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
const userId = cli.getUserId();
|
||||||
|
|
||||||
|
const [newAvatar, setNewAvatar] = useState<File>(null); // undefined means to remove avatar
|
||||||
|
const canSetAvatar = space.currentState.maySendStateEvent(EventType.RoomAvatar, userId);
|
||||||
|
const avatarChanged = newAvatar !== null;
|
||||||
|
|
||||||
|
const [name, setName] = useState<string>(space.name);
|
||||||
|
const canSetName = space.currentState.maySendStateEvent(EventType.RoomName, userId);
|
||||||
|
const nameChanged = name !== space.name;
|
||||||
|
|
||||||
|
const currentTopic = getTopic(space);
|
||||||
|
const [topic, setTopic] = useState<string>(currentTopic);
|
||||||
|
const canSetTopic = space.currentState.maySendStateEvent(EventType.RoomTopic, userId);
|
||||||
|
const topicChanged = topic !== currentTopic;
|
||||||
|
|
||||||
|
const currentJoinRule = space.getJoinRule();
|
||||||
|
const [joinRule, setJoinRule] = useState(currentJoinRule);
|
||||||
|
const canSetJoinRule = space.currentState.maySendStateEvent(EventType.RoomJoinRules, userId);
|
||||||
|
const joinRuleChanged = joinRule !== currentJoinRule;
|
||||||
|
|
||||||
|
const onSave = async () => {
|
||||||
|
setBusy(true);
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
if (avatarChanged) {
|
||||||
|
promises.push(cli.sendStateEvent(space.roomId, EventType.RoomAvatar, {
|
||||||
|
url: await cli.uploadContent(newAvatar),
|
||||||
|
}, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameChanged) {
|
||||||
|
promises.push(cli.setRoomName(space.roomId, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topicChanged) {
|
||||||
|
promises.push(cli.setRoomTopic(space.roomId, topic));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (joinRuleChanged) {
|
||||||
|
promises.push(cli.sendStateEvent(space.roomId, EventType.RoomJoinRules, { join_rule: joinRule }, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await allSettled(promises);
|
||||||
|
setBusy(false);
|
||||||
|
const failures = results.filter(r => r.status === "rejected");
|
||||||
|
if (failures.length > 0) {
|
||||||
|
console.error("Failed to save space settings: ", failures);
|
||||||
|
setError(_t("Failed to save space settings."));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <BaseDialog
|
||||||
|
title={_t("Space settings")}
|
||||||
|
className="mx_SpaceSettingsDialog"
|
||||||
|
contentId="mx_SpaceSettingsDialog"
|
||||||
|
onFinished={onFinished}
|
||||||
|
fixedWidth={false}
|
||||||
|
>
|
||||||
|
<div className="mx_SpaceSettingsDialog_content" id="mx_SpaceSettingsDialog">
|
||||||
|
<div>{ _t("Edit settings relating to your space.") }</div>
|
||||||
|
|
||||||
|
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
||||||
|
|
||||||
|
<SpaceBasicSettings
|
||||||
|
avatarUrl={avatarUrlForRoom(space, 80, 80, "crop")}
|
||||||
|
avatarDisabled={!canSetAvatar}
|
||||||
|
setAvatar={setNewAvatar}
|
||||||
|
name={name}
|
||||||
|
nameDisabled={!canSetName}
|
||||||
|
setName={setName}
|
||||||
|
topic={topic}
|
||||||
|
topicDisabled={!canSetTopic}
|
||||||
|
setTopic={setTopic}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{ _t("Make this space private") }
|
||||||
|
<ToggleSwitch
|
||||||
|
checked={joinRule === "private"}
|
||||||
|
onChange={checked => setJoinRule(checked ? "private" : "invite")}
|
||||||
|
disabled={!canSetJoinRule}
|
||||||
|
aria-label={_t("Make this space private")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormButton
|
||||||
|
kind="danger"
|
||||||
|
label={_t("Leave Space")}
|
||||||
|
onClick={() => {
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: "leave_room",
|
||||||
|
room_id: space.roomId,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mx_SpaceSettingsDialog_buttons">
|
||||||
|
<AccessibleButton onClick={() => Modal.createDialog(DevtoolsDialog, {roomId: space.roomId})}>
|
||||||
|
{ _t("View dev tools") }
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton onClick={onFinished} disabled={busy} kind="link">
|
||||||
|
{ _t("Cancel") }
|
||||||
|
</AccessibleButton>
|
||||||
|
<FormButton onClick={onSave} disabled={busy} label={busy ? _t("Saving...") : _t("Save Changes")} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BaseDialog>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SpaceSettingsDialog;
|
||||||
|
|
|
@ -2282,6 +2282,14 @@
|
||||||
"Link to selected message": "Link to selected message",
|
"Link to selected message": "Link to selected message",
|
||||||
"Copy": "Copy",
|
"Copy": "Copy",
|
||||||
"Command Help": "Command Help",
|
"Command Help": "Command Help",
|
||||||
|
"Failed to save space settings.": "Failed to save space settings.",
|
||||||
|
"Space settings": "Space settings",
|
||||||
|
"Edit settings relating to your space.": "Edit settings relating to your space.",
|
||||||
|
"Make this space private": "Make this space private",
|
||||||
|
"Leave Space": "Leave Space",
|
||||||
|
"View dev tools": "View dev tools",
|
||||||
|
"Saving...": "Saving...",
|
||||||
|
"Save Changes": "Save Changes",
|
||||||
"To help us prevent this in future, please <a>send us logs</a>.": "To help us prevent this in future, please <a>send us logs</a>.",
|
"To help us prevent this in future, please <a>send us logs</a>.": "To help us prevent this in future, please <a>send us logs</a>.",
|
||||||
"Missing session data": "Missing session data",
|
"Missing session data": "Missing session data",
|
||||||
"Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.",
|
"Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.",
|
||||||
|
|
|
@ -19,6 +19,8 @@ import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
import {EventType} from "matrix-js-sdk/src/@types/event";
|
import {EventType} from "matrix-js-sdk/src/@types/event";
|
||||||
|
|
||||||
import {calculateRoomVia} from "../utils/permalinks/Permalinks";
|
import {calculateRoomVia} from "../utils/permalinks/Permalinks";
|
||||||
|
import Modal from "../Modal";
|
||||||
|
import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog";
|
||||||
|
|
||||||
export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => {
|
export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => {
|
||||||
const userId = cli.getUserId();
|
const userId = cli.getUserId();
|
||||||
|
@ -37,3 +39,10 @@ export const makeSpaceParentEvent = (room: Room, canonical = false) => ({
|
||||||
},
|
},
|
||||||
state_key: room.roomId,
|
state_key: room.roomId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const showSpaceSettings = (cli: MatrixClient, space: Room) => {
|
||||||
|
Modal.createTrackedDialog("Space Settings", "", SpaceSettingsDialog, {
|
||||||
|
matrixClient: cli,
|
||||||
|
space,
|
||||||
|
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue