From ab4b7b73ea7f8eeb18be6ab7491e2e1649b35969 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 10:34:28 +0000 Subject: [PATCH] Add a basic Space Settings view --- res/css/_components.scss | 1 + res/css/structures/_SpaceRoomView.scss | 10 ++ .../views/dialogs/_SpaceSettingsDialog.scss | 55 ++++++ src/components/structures/MatrixChat.tsx | 4 + src/components/structures/SpaceRoomView.tsx | 12 +- .../views/dialogs/SpaceSettingsDialog.tsx | 162 ++++++++++++++++++ src/i18n/strings/en_EN.json | 8 + src/utils/space.ts | 9 + 8 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 res/css/views/dialogs/_SpaceSettingsDialog.scss create mode 100644 src/components/views/dialogs/SpaceSettingsDialog.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index ca66aa60ec..db73eed3f2 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -91,6 +91,7 @@ @import "./views/dialogs/_SettingsDialog.scss"; @import "./views/dialogs/_ShareDialog.scss"; @import "./views/dialogs/_SlashCommandHelpDialog.scss"; +@import "./views/dialogs/_SpaceSettingsDialog.scss"; @import "./views/dialogs/_TabbedIntegrationManagerDialog.scss"; @import "./views/dialogs/_TermsDialog.scss"; @import "./views/dialogs/_UploadConfirmDialog.scss"; diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index 946856eed3..0a42db130a 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -187,6 +187,16 @@ $SpaceRoomViewInnerWidth: 428px; 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'); + } + } } } } diff --git a/res/css/views/dialogs/_SpaceSettingsDialog.scss b/res/css/views/dialogs/_SpaceSettingsDialog.scss new file mode 100644 index 0000000000..c1fa539e9b --- /dev/null +++ b/res/css/views/dialogs/_SpaceSettingsDialog.scss @@ -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; + } +} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index d9d8b659c9..83b3565738 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1118,6 +1118,10 @@ export default class MatrixChat extends React.PureComponent { const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner'); d.finally(() => modal.close()); + dis.dispatch({ + action: "after_leave_room", + room_id: roomId, + }); } }, }); diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 9e73b97d5a..49af14017e 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -46,7 +46,7 @@ import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload"; import {useStateArray} from "../../hooks/useStateArray"; import SpacePublicShare from "../views/spaces/SpacePublicShare"; -import {shouldShowSpaceSettings} from "../../utils/space"; +import {shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space"; import MemberAvatar from "../views/avatars/MemberAvatar"; interface IProps { @@ -119,6 +119,15 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => ); } + let settingsButton; + if (shouldShowSpaceSettings(cli, space)) { + settingsButton = { + showSpaceSettings(cli, space); + }}> + { _t("Settings") } + ; + } + return
@@ -180,6 +189,7 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => { joinButtons }
{ inviteButton } + { settingsButton }
; }; diff --git a/src/components/views/dialogs/SpaceSettingsDialog.tsx b/src/components/views/dialogs/SpaceSettingsDialog.tsx new file mode 100644 index 0000000000..f6bf5b87e6 --- /dev/null +++ b/src/components/views/dialogs/SpaceSettingsDialog.tsx @@ -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 = ({ 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(null); // undefined means to remove avatar + const canSetAvatar = space.currentState.maySendStateEvent(EventType.RoomAvatar, userId); + const avatarChanged = newAvatar !== null; + + const [name, setName] = useState(space.name); + const canSetName = space.currentState.maySendStateEvent(EventType.RoomName, userId); + const nameChanged = name !== space.name; + + const currentTopic = getTopic(space); + const [topic, setTopic] = useState(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 +
+
{ _t("Edit settings relating to your space.") }
+ + { error &&
{ error }
} + + + +
+ { _t("Make this space private") } + setJoinRule(checked ? "private" : "invite")} + disabled={!canSetJoinRule} + aria-label={_t("Make this space private")} + /> +
+ + { + defaultDispatcher.dispatch({ + action: "leave_room", + room_id: space.roomId, + }); + }} + /> + +
+ Modal.createDialog(DevtoolsDialog, {roomId: space.roomId})}> + { _t("View dev tools") } + + + { _t("Cancel") } + + +
+
+
; +}; + +export default SpaceSettingsDialog; + diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5f3d293571..cd2fcf1117 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2282,6 +2282,14 @@ "Link to selected message": "Link to selected message", "Copy": "Copy", "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 send us logs.": "To help us prevent this in future, please send us logs.", "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.", diff --git a/src/utils/space.ts b/src/utils/space.ts index 98801cabd0..2ee4d0071e 100644 --- a/src/utils/space.ts +++ b/src/utils/space.ts @@ -19,6 +19,8 @@ import {MatrixClient} from "matrix-js-sdk/src/client"; import {EventType} from "matrix-js-sdk/src/@types/event"; import {calculateRoomVia} from "../utils/permalinks/Permalinks"; +import Modal from "../Modal"; +import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog"; export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => { const userId = cli.getUserId(); @@ -37,3 +39,10 @@ export const makeSpaceParentEvent = (room: Room, canonical = false) => ({ }, 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); +};