From 7d22bbc00f49356cd5fec3565a19376ef8b0ef05 Mon Sep 17 00:00:00 2001 From: Bryan Kok <bryan.wyern1@gmail.com> Date: Sat, 17 Oct 2020 23:52:18 +0800 Subject: [PATCH 01/59] Trim spurious whitespace of nicknames --- src/components/views/settings/ProfileSettings.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js index 92851ccaa0..294f80acd1 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -77,10 +77,12 @@ export default class ProfileSettings extends React.Component { const client = MatrixClientPeg.get(); const newState = {}; + const displayName = this.state.displayName.trim(); try { if (this.state.originalDisplayName !== this.state.displayName) { - await client.setDisplayName(this.state.displayName); - newState.originalDisplayName = this.state.displayName; + await client.setDisplayName(displayName); + newState.originalDisplayName = displayName; + newState.displayName = displayName; } if (this.state.avatarFile) { From fcbaea640daf3a036d55cb1bda5d7fed552c2d4e Mon Sep 17 00:00:00 2001 From: Bryan Kok <bryan.wyern1@gmail.com> Date: Sun, 18 Oct 2020 14:36:50 +0800 Subject: [PATCH 02/59] Trim room names changed through the UI --- src/components/views/room_settings/RoomProfileSettings.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/room_settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js index ca09c3093a..b894663c16 100644 --- a/src/components/views/room_settings/RoomProfileSettings.js +++ b/src/components/views/room_settings/RoomProfileSettings.js @@ -95,10 +95,11 @@ export default class RoomProfileSettings extends React.Component { const newState = {}; // TODO: What do we do about errors? - + const displayName = this.state.displayName.trim(); if (this.state.originalDisplayName !== this.state.displayName) { - await client.setRoomName(this.props.roomId, this.state.displayName); - newState.originalDisplayName = this.state.displayName; + await client.setRoomName(this.props.roomId, displayName); + newState.originalDisplayName = displayName; + newState.displayName = displayName; } if (this.state.avatarFile) { From 89bc4435945bfb207355cf5e5e290925f7d7f7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Wed, 16 Dec 2020 16:02:27 +0100 Subject: [PATCH 03/59] Fix file drop UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_MainSplit.scss | 2 +- res/css/structures/_RoomView.scss | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index ad1656efbb..3de68b000d 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -22,7 +22,7 @@ limitations under the License. } .mx_MainSplit > .mx_RightPanel_ResizeWrapper { - padding: 5px; + padding: 0 5px 5px 5px; // margin left to not allow the handle to not encroach on the space for the scrollbar margin-left: 8px; height: calc(100vh - 51px); // height of .mx_RoomHeader.light-panel diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 572c7166d2..0a70b027ae 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -23,24 +23,21 @@ limitations under the License. .mx_RoomView_fileDropTarget { min-width: 0px; width: 100%; + height: 100%; + + margin-left: 6.25px; + font-size: $font-18px; text-align: center; pointer-events: none; - padding-left: 12px; - padding-right: 12px; - margin-left: -12px; - border-top-left-radius: 10px; border-top-right-radius: 10px; background-color: $droptarget-bg-color; - border: 2px #e1dddd solid; - border-bottom: none; + position: absolute; - top: 52px; - bottom: 0px; z-index: 3000; } From 41e2ffdf0df43104ef171b690b344a6e22b286f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Wed, 16 Dec 2020 19:51:49 +0100 Subject: [PATCH 04/59] Added background MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_RoomView.scss | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 0a70b027ae..9292a400bc 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -39,13 +39,19 @@ limitations under the License. position: absolute; z-index: 3000; + + display: flex; + justify-content: center; + align-items: center; } .mx_RoomView_fileDropTargetLabel { - top: 50%; - width: 100%; - margin-top: -50px; position: absolute; + + border-radius: 10px; + padding: 10px; + + background-color: $menu-bg-color; } .mx_RoomView_auxPanel { From da97d18332c5740499913506b0e059e5b4c7616c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Wed, 16 Dec 2020 21:33:05 +0100 Subject: [PATCH 05/59] Added a comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_RoomView.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 9292a400bc..dd63be3a11 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -25,6 +25,7 @@ limitations under the License. width: 100%; height: 100%; + // This is an ugly fix for centering this element margin-left: 6.25px; font-size: $font-18px; From dcb30b72b0ed1adc6fb075ee9cc26ca0338177bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 17 Dec 2020 13:25:22 +0100 Subject: [PATCH 06/59] Fix left panel resizer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_MainSplit.scss | 32 ++++++++++++++++---------- res/css/structures/_RoomView.scss | 12 ++++++---- src/components/structures/RoomView.tsx | 28 +++++++++++----------- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index 3de68b000d..6875ef12e0 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -22,22 +22,30 @@ limitations under the License. } .mx_MainSplit > .mx_RightPanel_ResizeWrapper { - padding: 0 5px 5px 5px; - // margin left to not allow the handle to not encroach on the space for the scrollbar - margin-left: 8px; + padding: 0 5px 5px 0px; height: calc(100vh - 51px); // height of .mx_RoomHeader.light-panel + + .mx_RightPanel_ResizeHandle { + width: 9px; + } &:hover .mx_RightPanel_ResizeHandle { - // Need to use important to override element style attributes - // set by re-resizable - top: 50% !important; - transform: translate(0, -50%); + &::before { + position: absolute; + left: 6px; + top: 50%; + transform: translate(0, -50%); - height: 64px !important; // to match width of the ones on roomlist - width: 4px !important; - border-radius: 4px !important; + height: 64px; + width: 4px; + border-radius: 4px; - background-color: $primary-fg-color; - opacity: 0.8; + content: ' '; + + background-color: $primary-fg-color; + opacity: 0.8; + + margin-left: -10px; + } } } diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index dd63be3a11..0a12a86c33 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -25,9 +25,6 @@ limitations under the License. width: 100%; height: 100%; - // This is an ugly fix for centering this element - margin-left: 6.25px; - font-size: $font-18px; text-align: center; @@ -120,16 +117,23 @@ limitations under the License. height: 50px; } -.mx_RoomView_body { +.mx_RoomView_container { position: relative; //for .mx_RoomView_auxPanel_fullHeight display: flex; flex-direction: column; +} + +.mx_RoomView_body { + display: flex; + flex-direction: column; flex: 1; min-width: 0; .mx_RoomView_messagePanel, .mx_RoomView_messagePanelSpinner, .mx_RoomView_messagePanelSearchSpinner { order: 2; } + + margin-right: 10px; } .mx_RoomView_body .mx_RoomView_timeline { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 0ee847fbc9..3d62c06e4b 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2003,22 +2003,24 @@ export default class RoomView extends React.Component<IProps, IState> { appsShown={this.state.showApps} /> <MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}> - <div className="mx_RoomView_body"> + <div className={"mx_RoomView_container"}> {auxPanel} - <div className={timelineClasses}> - {topUnreadMessagesBar} - {jumpToBottom} - {messagePanel} - {searchResultsPanel} - </div> - <div className={statusBarAreaClass}> - <div className="mx_RoomView_statusAreaBox"> - <div className="mx_RoomView_statusAreaBox_line" /> - {statusBar} + <div className="mx_RoomView_body"> + <div className={timelineClasses}> + {topUnreadMessagesBar} + {jumpToBottom} + {messagePanel} + {searchResultsPanel} </div> + <div className={statusBarAreaClass}> + <div className="mx_RoomView_statusAreaBox"> + <div className="mx_RoomView_statusAreaBox_line" /> + {statusBar} + </div> + </div> + {previewBar} + {messageComposer} </div> - {previewBar} - {messageComposer} </div> </MainSplit> </ErrorBoundary> From e70dee08d0ea7b303a51fb807929376b2dad79dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 17 Dec 2020 19:50:59 +0100 Subject: [PATCH 07/59] Fix flickering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/components/structures/RoomView.tsx | 42 ++++++++++++++++++++------ 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 3d62c06e4b..67f9663597 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -187,6 +187,7 @@ export interface IState { rejecting?: boolean; rejectError?: Error; hasPinnedWidgets?: boolean; + dragCounter: number; } export default class RoomView extends React.Component<IProps, IState> { @@ -237,6 +238,7 @@ export default class RoomView extends React.Component<IProps, IState> { canReply: false, useIRCLayout: SettingsStore.getValue("useIRCLayout"), matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), + dragCounter: 0, }; this.dispatcherRef = dis.register(this.onAction); @@ -525,8 +527,8 @@ export default class RoomView extends React.Component<IProps, IState> { if (!roomView.ondrop) { roomView.addEventListener('drop', this.onDrop); roomView.addEventListener('dragover', this.onDragOver); - roomView.addEventListener('dragleave', this.onDragLeaveOrEnd); - roomView.addEventListener('dragend', this.onDragLeaveOrEnd); + roomView.addEventListener('dragenter', this.onDragEnter); + roomView.addEventListener('dragleave', this.onDragLeave); } } @@ -1108,6 +1110,31 @@ export default class RoomView extends React.Component<IProps, IState> { this.updateTopUnreadMessagesBar(); }; + private onDragEnter = ev => { + ev.stopPropagation(); + ev.preventDefault(); + + this.setState({ + dragCounter: this.state.dragCounter + 1, + draggingFile: true, + }); + }; + + private onDragLeave = ev => { + ev.stopPropagation(); + ev.preventDefault(); + + this.setState({ + dragCounter: this.state.dragCounter - 1, + }); + + if (this.state.dragCounter == 0) { + this.setState({ + draggingFile: false, + }); + } + }; + private onDragOver = ev => { ev.stopPropagation(); ev.preventDefault(); @@ -1115,7 +1142,6 @@ export default class RoomView extends React.Component<IProps, IState> { ev.dataTransfer.dropEffect = 'none'; if (ev.dataTransfer.types.includes("Files") || ev.dataTransfer.types.includes("application/x-moz-file")) { - this.setState({ draggingFile: true }); ev.dataTransfer.dropEffect = 'copy'; } }; @@ -1126,14 +1152,12 @@ export default class RoomView extends React.Component<IProps, IState> { ContentMessages.sharedInstance().sendContentListToRoom( ev.dataTransfer.files, this.state.room.roomId, this.context, ); - this.setState({ draggingFile: false }); dis.fire(Action.FocusComposer); - }; - private onDragLeaveOrEnd = ev => { - ev.stopPropagation(); - ev.preventDefault(); - this.setState({ draggingFile: false }); + this.setState({ + draggingFile: false, + dragCounter: this.state.dragCounter - 1, + }); }; private injectSticker(url, info, text) { From 044e02b06ad46b417d3aa8fc33f24c1374fdcb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 17 Dec 2020 20:16:53 +0100 Subject: [PATCH 08/59] Remove spaces in empty line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_MainSplit.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index 6875ef12e0..f05f24d0d7 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -24,7 +24,7 @@ limitations under the License. .mx_MainSplit > .mx_RightPanel_ResizeWrapper { padding: 0 5px 5px 0px; height: calc(100vh - 51px); // height of .mx_RoomHeader.light-panel - + .mx_RightPanel_ResizeHandle { width: 9px; } From 365d252d3f0eb64755f502318c95f855a4404f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 17 Dec 2020 20:25:12 +0100 Subject: [PATCH 09/59] Fix removing event listeners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/components/structures/RoomView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 67f9663597..d910940a73 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -572,8 +572,8 @@ export default class RoomView extends React.Component<IProps, IState> { const roomView = this.roomView.current; roomView.removeEventListener('drop', this.onDrop); roomView.removeEventListener('dragover', this.onDragOver); - roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd); - roomView.removeEventListener('dragend', this.onDragLeaveOrEnd); + roomView.removeEventListener('dragenter', this.onDragEnter); + roomView.removeEventListener('dragleave', this.onDragLeave); } dis.unregister(this.dispatcherRef); if (this.context) { From 5d7e45e6cf85e14f4143923f7e29642f97965fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 17 Dec 2020 20:29:33 +0100 Subject: [PATCH 10/59] Added dragCounter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/contexts/RoomContext.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts index 082dcc4e6b..1b9097e337 100644 --- a/src/contexts/RoomContext.ts +++ b/src/contexts/RoomContext.ts @@ -42,6 +42,7 @@ const RoomContext = createContext<IState>({ canReply: false, useIRCLayout: false, matrixClientIsReady: false, + dragCounter: 0, }); RoomContext.displayName = "RoomContext"; export default RoomContext; From ad85764a8e3d1025c76db29e9f53ba690ac2eb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 25 Feb 2021 18:23:32 +0100 Subject: [PATCH 11/59] Fix timeline expansion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_MainSplit.scss | 1 + res/css/structures/_RoomView.scss | 1 + 2 files changed, 2 insertions(+) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index f05f24d0d7..9597083e9c 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -19,6 +19,7 @@ limitations under the License. flex-direction: row; min-width: 0; height: 100%; + justify-content: space-between; } .mx_MainSplit > .mx_RightPanel_ResizeWrapper { diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index cd8c640132..5240a0650f 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -121,6 +121,7 @@ limitations under the License. position: relative; //for .mx_RoomView_auxPanel_fullHeight display: flex; flex-direction: column; + width: 100%; } .mx_RoomView_body { From aa4ec9fca1a65202306ee77d705b15f1782de188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 25 Feb 2021 18:27:52 +0100 Subject: [PATCH 12/59] Make $droptarget-bg-color more opaque MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/themes/light/css/_light.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 1c89d83c01..ea7b0472e0 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -68,7 +68,7 @@ $groupFilterPanel-bg-color: rgba(232, 232, 232, 0.77); $plinth-bg-color: $secondary-accent-color; // used by RoomDropTarget -$droptarget-bg-color: rgba(255,255,255,0.5); +$droptarget-bg-color: rgba(255,255,255,0.95); // used by AddressSelector $selected-color: $secondary-accent-color; From 8551855a5626d06e6feda6f849f50b26af85a194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 25 Feb 2021 18:30:14 +0100 Subject: [PATCH 13/59] Add $droptarget-bg-color to the dark theme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/themes/dark/css/_dark.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index a878aa3cdd..f6f415ce70 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -42,6 +42,9 @@ $preview-bar-bg-color: $header-panel-bg-color; $groupFilterPanel-bg-color: rgba(38, 39, 43, 0.82); $inverted-bg-color: $base-color; +// used by RoomDropTarget +$droptarget-bg-color: rgba(21,25,30,0.95); + // used by AddressSelector $selected-color: $room-highlight-color; From a3001f77e46e7c3ac3479a70eb56db312c6f1361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 25 Feb 2021 18:30:39 +0100 Subject: [PATCH 14/59] Remove rounded corners of the drop area MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_RoomView.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 5240a0650f..e80ac062b6 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -30,9 +30,6 @@ limitations under the License. pointer-events: none; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - background-color: $droptarget-bg-color; position: absolute; From 26b70b62280b7f4fa21c6287faf20a309a399abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 25 Feb 2021 18:32:04 +0100 Subject: [PATCH 15/59] Remove label background MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_RoomView.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index e80ac062b6..8ba31fac20 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -42,11 +42,6 @@ limitations under the License. .mx_RoomView_fileDropTargetLabel { position: absolute; - - border-radius: 10px; - padding: 10px; - - background-color: $menu-bg-color; } .mx_RoomView_auxPanel { From 6a7340e8be3844881b1c114d74687983b9c0ba20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 25 Feb 2021 18:46:48 +0100 Subject: [PATCH 16/59] Use new upload icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/img/upload-big.svg | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/res/img/upload-big.svg b/res/img/upload-big.svg index 6099c2e976..9a6a265fdb 100644 --- a/res/img/upload-big.svg +++ b/res/img/upload-big.svg @@ -1,19 +1,3 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg width="45px" height="59px" viewBox="-1 -1 45 59" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"> - <!-- Generator: bin/sketchtool 1.4 (305) - http://www.bohemiancoding.com/sketch --> - <title>icons_upload_drop</title> - <desc>Created with bin/sketchtool.</desc> - <defs></defs> - <g id="03-Input" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage"> - <g id="03_05-File-drop" sketch:type="MSArtboardGroup" transform="translate(-570.000000, -368.000000)"> - <g id="icons_upload_drop" sketch:type="MSLayerGroup" transform="translate(570.000000, 368.000000)"> - <g id="Rectangle-5-+-Rectangle-6" sketch:type="MSShapeGroup"> - <path d="M0,4.00812931 C0,1.79450062 1.78537926,0 4.00241155,0 L24.8253683,0 C24.8253683,0 42.2466793,16.8210687 42.2466793,16.8210687 L42.2466793,53.000599 C42.2466793,55.2094072 40.4583762,57 38.2531894,57 L3.99348992,57 C1.78794634,57 0,55.1999609 0,52.9918707 L0,4.00812931 Z" id="Rectangle-5" stroke="#76CFA6"></path> - <path d="M40.5848017,19.419576 L29.8354335,19.419576 C26.7387692,19.419576 24.2284269,16.9063989 24.2284269,13.8067771 L24.2284269,4.88501382 L40.5848017,19.419576 Z" id="Rectangle-6-Copy" fill="#FFFFFF"></path> - <path d="M42.2466793,18.3870968 L29.539478,18.3870968 C26.4130381,18.3870968 23.8785579,15.8497544 23.8785579,12.7203286 L23.8785579,0" id="Rectangle-6" stroke="#76CFA6"></path> - </g> - <path d="M31.3419737,32.9284726 C31.701384,32.9284726 32.0607942,32.8000473 32.3359677,32.5414375 C32.8825707,32.0259772 32.8825707,31.1920926 32.3359677,30.6766323 L21.622922,20.6119619 C21.076319,20.0965016 20.187153,20.0982608 19.638678,20.6102026 L8.9125289,30.6607991 C8.36405391,31.1762594 8.36218198,32.0119032 8.90878504,32.5273635 C9.4553881,33.0445831 10.344554,33.0445831 10.893029,32.530882 L19.2399573,24.7092556 L19.2437012,46.487014 C19.2437012,47.2153435 19.874541,47.8064516 20.6476474,47.8064516 C21.4244976,47.8064516 22.0515936,47.2153435 22.0515936,46.487014 L22.0478497,24.7426814 L30.3498517,32.5414375 C30.6231533,32.8000473 30.9825635,32.9284726 31.3419737,32.9284726 L31.3419737,32.9284726 Z" id="Fill-75" fill="#76CFA6" sketch:type="MSShapeGroup"></path> - </g> - </g> - </g> +<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M16 0C7.16344 0 0 7.16344 0 16C0 24.8366 7.16344 32 16 32C24.8366 32 32 24.8366 32 16C32 7.16344 24.8366 0 16 0ZM17.2511 6.97409C16.9775 6.68236 16.5885 6.50012 16.157 6.50012C15.793 6.50012 15.4593 6.62973 15.1996 6.84532C15.1545 6.88267 15.1115 6.92281 15.0707 6.96564L8.79618 13.5539C8.22485 14.1538 8.24801 15.1032 8.8479 15.6746C9.4478 16.2459 10.3973 16.2227 10.9686 15.6228L14.657 11.7501V23.0589C14.657 23.8874 15.3285 24.5589 16.157 24.5589C16.9854 24.5589 17.657 23.8874 17.657 23.0589V11.7502L21.3452 15.6228C21.9165 16.2227 22.866 16.2459 23.4659 15.6746C24.0658 15.1032 24.0889 14.1538 23.5176 13.5539L17.2511 6.97409Z" fill="#0DBD8B"/> </svg> From 1c48804d96c3cdda150e38e2d520a2268dd5728e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 25 Feb 2021 19:28:08 +0100 Subject: [PATCH 17/59] Remove unnecessary class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_RoomView.scss | 4 ---- src/components/views/rooms/AuxPanel.tsx | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 8ba31fac20..28d8d1e196 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -40,10 +40,6 @@ limitations under the License. align-items: center; } -.mx_RoomView_fileDropTargetLabel { - position: absolute; -} - .mx_RoomView_auxPanel { min-width: 0px; width: 100%; diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 7966643084..543a50d59f 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -156,7 +156,7 @@ export default class AuxPanel extends React.Component<IProps, IState> { if (this.props.draggingFile) { fileDropTarget = ( <div className="mx_RoomView_fileDropTarget"> - <div className="mx_RoomView_fileDropTargetLabel" title={_t("Drop File Here")}> + <div title={_t("Drop File Here")}> <TintableSvg src={require("../../../../res/img/upload-big.svg")} width="45" height="59" /> <br /> { _t("Drop file here to upload") } From 43e1144ae7ca7eff08c1666b4a179ba828d41432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 25 Feb 2021 19:36:55 +0100 Subject: [PATCH 18/59] Don't use TintableSVG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This seemed to have caused a little lag and it was unnecessary Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/components/views/rooms/AuxPanel.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 543a50d59f..c9150d588f 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -150,14 +150,12 @@ export default class AuxPanel extends React.Component<IProps, IState> { } render() { - const TintableSvg = sdk.getComponent("elements.TintableSvg"); - let fileDropTarget = null; if (this.props.draggingFile) { fileDropTarget = ( <div className="mx_RoomView_fileDropTarget"> <div title={_t("Drop File Here")}> - <TintableSvg src={require("../../../../res/img/upload-big.svg")} width="45" height="59" /> + <img src={require( "../../../../res/img/upload-big.svg")}> </img> <br /> { _t("Drop file here to upload") } </div> From 7277c285a9326928555e05766ee3ce33603de18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 25 Feb 2021 20:10:38 +0100 Subject: [PATCH 19/59] Fix weird crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/components/views/rooms/AuxPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index c9150d588f..cc3408476c 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -155,7 +155,7 @@ export default class AuxPanel extends React.Component<IProps, IState> { fileDropTarget = ( <div className="mx_RoomView_fileDropTarget"> <div title={_t("Drop File Here")}> - <img src={require( "../../../../res/img/upload-big.svg")}> </img> + <img src={require( "../../../../res/img/upload-big.svg")} /> <br /> { _t("Drop file here to upload") } </div> From 49ea9a4788243346b20fcf5b4b79f46a7c3a80ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Thu, 25 Feb 2021 20:10:58 +0100 Subject: [PATCH 20/59] Remove sdk import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/components/views/rooms/AuxPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index cc3408476c..59ea8e237a 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import { Room } from 'matrix-js-sdk/src/models/room' -import * as sdk from '../../../index'; import dis from "../../../dispatcher/dispatcher"; import * as ObjectUtils from '../../../ObjectUtils'; import AppsDrawer from './AppsDrawer'; From 1a7f9091b4fc41eac6d5cc1b71d1dbe61f5d7f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 07:51:03 +0100 Subject: [PATCH 21/59] Animate icon size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_RoomView.scss | 12 ++++++++++++ src/components/views/rooms/AuxPanel.tsx | 10 +++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 28d8d1e196..d5caee5e8b 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -36,10 +36,22 @@ limitations under the License. z-index: 3000; display: flex; + flex-direction: column; justify-content: center; align-items: center; } +@keyframes mx_RoomView_fileDropTarget_image_animation { + from {width: 0px;} + to {width: 32px;} +} + +.mx_RoomView_fileDropTarget_image { + animation: mx_RoomView_fileDropTarget_image_animation; + animation-duration: 0.5s; + margin-bottom: 16px; +} + .mx_RoomView_auxPanel { min-width: 0px; width: 100%; diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 59ea8e237a..b3ef8c3cc8 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -153,11 +153,11 @@ export default class AuxPanel extends React.Component<IProps, IState> { if (this.props.draggingFile) { fileDropTarget = ( <div className="mx_RoomView_fileDropTarget"> - <div title={_t("Drop File Here")}> - <img src={require( "../../../../res/img/upload-big.svg")} /> - <br /> - { _t("Drop file here to upload") } - </div> + <img + src={require( "../../../../res/img/upload-big.svg")} + className="mx_RoomView_fileDropTarget_image" + /> + { _t("Drop file here to upload") } </div> ); } From f0c26846c75559e5be013eb25ebe5f54b4f6e264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 08:11:58 +0100 Subject: [PATCH 22/59] Fix formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_RoomView.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index d5caee5e8b..2c3fb2b32b 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -42,8 +42,8 @@ limitations under the License. } @keyframes mx_RoomView_fileDropTarget_image_animation { - from {width: 0px;} - to {width: 32px;} + from {width: 0px;} + to {width: 32px;} } .mx_RoomView_fileDropTarget_image { From 172cc01f7d3dcba08235a621cff118efe3e76d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 08:12:10 +0100 Subject: [PATCH 23/59] Add background animation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_RoomView.scss | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 2c3fb2b32b..5870e107c6 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -20,6 +20,12 @@ limitations under the License. flex-direction: column; } + +@keyframes mx_RoomView_fileDropTarget_animation { + from {opacity: 0;} + to {opacity: 0.95;} +} + .mx_RoomView_fileDropTarget { min-width: 0px; width: 100%; @@ -30,7 +36,8 @@ limitations under the License. pointer-events: none; - background-color: $droptarget-bg-color; + background-color: $primary-bg-color; + opacity: 0.95; position: absolute; z-index: 3000; @@ -39,6 +46,9 @@ limitations under the License. flex-direction: column; justify-content: center; align-items: center; + + animation: mx_RoomView_fileDropTarget_animation; + animation-duration: 0.5s; } @keyframes mx_RoomView_fileDropTarget_image_animation { From 3e0558f4d97bc618cc8e6d71f411370e894d642f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 08:12:38 +0100 Subject: [PATCH 24/59] Remove droptarget colors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/themes/dark/css/_dark.scss | 3 --- res/themes/light/css/_light.scss | 3 --- 2 files changed, 6 deletions(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index f6f415ce70..a878aa3cdd 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -42,9 +42,6 @@ $preview-bar-bg-color: $header-panel-bg-color; $groupFilterPanel-bg-color: rgba(38, 39, 43, 0.82); $inverted-bg-color: $base-color; -// used by RoomDropTarget -$droptarget-bg-color: rgba(21,25,30,0.95); - // used by AddressSelector $selected-color: $room-highlight-color; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index ea7b0472e0..c92e491ca2 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -67,9 +67,6 @@ $groupFilterPanel-bg-color: rgba(232, 232, 232, 0.77); // used by RoomDirectory permissions $plinth-bg-color: $secondary-accent-color; -// used by RoomDropTarget -$droptarget-bg-color: rgba(255,255,255,0.95); - // used by AddressSelector $selected-color: $secondary-accent-color; From 49ea83edb93dfa0e6f572aeac002adaaa0370fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 08:14:27 +0100 Subject: [PATCH 25/59] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5bbbdf60b5..9af8ccc172 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1381,7 +1381,6 @@ "Remove %(phone)s?": "Remove %(phone)s?", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.", "Phone Number": "Phone Number", - "Drop File Here": "Drop File Here", "Drop file here to upload": "Drop file here to upload", "This user has not verified all of their sessions.": "This user has not verified all of their sessions.", "You have not verified this user.": "You have not verified this user.", From e90ae2ea7596bff850cf4014c1109f93234132b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 08:18:05 +0100 Subject: [PATCH 26/59] Delint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_RoomView.scss | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 5870e107c6..5e8d84ff32 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -22,8 +22,12 @@ limitations under the License. @keyframes mx_RoomView_fileDropTarget_animation { - from {opacity: 0;} - to {opacity: 0.95;} + from { + opacity: 0; + } + to { + opacity: 0.95; + } } .mx_RoomView_fileDropTarget { @@ -52,8 +56,12 @@ limitations under the License. } @keyframes mx_RoomView_fileDropTarget_image_animation { - from {width: 0px;} - to {width: 32px;} + from { + width: 0px; + } + to { + width: 32px; + } } .mx_RoomView_fileDropTarget_image { From 819a0b013fda927bea3197e458741f4f4f85a271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 11:08:31 +0100 Subject: [PATCH 27/59] min-width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need to allow the container to be smaller Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_RoomView.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 5e8d84ff32..28591ad7a4 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -140,6 +140,7 @@ limitations under the License. display: flex; flex-direction: column; width: 100%; + min-width: 0; } .mx_RoomView_body { From 0d6a9fce67d24f441a62e140a2a73f669b959cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 11:12:14 +0100 Subject: [PATCH 28/59] Remove weird styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/components/structures/RoomView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index ff09af454e..42eafe5bdc 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2058,7 +2058,7 @@ export default class RoomView extends React.Component<IProps, IState> { appsShown={this.state.showApps} /> <MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}> - <div className={"mx_RoomView_container"}> + <div className="mx_RoomView_container"> {auxPanel} <div className="mx_RoomView_body"> <div className={timelineClasses}> From 11c5aa02d290739fff31bfa8365fe76562032594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 11:19:45 +0100 Subject: [PATCH 29/59] Remove mx_RoomView_container MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_MainSplit.scss | 1 + res/css/structures/_RoomView.scss | 8 ------- src/components/structures/RoomView.tsx | 30 ++++++++++++-------------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index 9597083e9c..5fa62e921d 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -20,6 +20,7 @@ limitations under the License. min-width: 0; height: 100%; justify-content: space-between; + min-height: 0; } .mx_MainSplit > .mx_RightPanel_ResizeWrapper { diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 28591ad7a4..b3dab5f992 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -135,14 +135,6 @@ limitations under the License. height: 50px; } -.mx_RoomView_container { - position: relative; //for .mx_RoomView_auxPanel_fullHeight - display: flex; - flex-direction: column; - width: 100%; - min-width: 0; -} - .mx_RoomView_body { display: flex; flex-direction: column; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 42eafe5bdc..be2f81a176 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2058,24 +2058,22 @@ export default class RoomView extends React.Component<IProps, IState> { appsShown={this.state.showApps} /> <MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}> - <div className="mx_RoomView_container"> + <div className="mx_RoomView_body"> {auxPanel} - <div className="mx_RoomView_body"> - <div className={timelineClasses}> - {topUnreadMessagesBar} - {jumpToBottom} - {messagePanel} - {searchResultsPanel} - </div> - <div className={statusBarAreaClass}> - <div className="mx_RoomView_statusAreaBox"> - <div className="mx_RoomView_statusAreaBox_line" /> - {statusBar} - </div> - </div> - {previewBar} - {messageComposer} + <div className={timelineClasses}> + {topUnreadMessagesBar} + {jumpToBottom} + {messagePanel} + {searchResultsPanel} </div> + <div className={statusBarAreaClass}> + <div className="mx_RoomView_statusAreaBox"> + <div className="mx_RoomView_statusAreaBox_line" /> + {statusBar} + </div> + </div> + {previewBar} + {messageComposer} </div> </MainSplit> </ErrorBoundary> From 9a5ba072ba0f551dc618cdf8bfb7afba004d01fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 11:23:58 +0100 Subject: [PATCH 30/59] Fix auxPanel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/components/structures/RoomView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index be2f81a176..5b79f23e0b 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2059,8 +2059,8 @@ export default class RoomView extends React.Component<IProps, IState> { /> <MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}> <div className="mx_RoomView_body"> - {auxPanel} <div className={timelineClasses}> + {auxPanel} {topUnreadMessagesBar} {jumpToBottom} {messagePanel} From 3bed37463fea1ff1a7c86ef5fe9a0a123e06008f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 11:38:05 +0100 Subject: [PATCH 31/59] Remove unnecessary code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_MainSplit.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index 5fa62e921d..2d9ea2729c 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -19,7 +19,6 @@ limitations under the License. flex-direction: row; min-width: 0; height: 100%; - justify-content: space-between; min-height: 0; } From 3a643e5b9df189916d2a3b8162636e2d05a5e2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 11:46:54 +0100 Subject: [PATCH 32/59] Remove unnecessary changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- res/css/structures/_MainSplit.scss | 34 ++++++++++++------------------ res/css/structures/_RoomView.scss | 2 -- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index 2d9ea2729c..8199121420 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -18,35 +18,27 @@ limitations under the License. display: flex; flex-direction: row; min-width: 0; - height: 100%; min-height: 0; + height: 100%; } .mx_MainSplit > .mx_RightPanel_ResizeWrapper { - padding: 0 5px 5px 0px; + padding: 5px; + // margin left to not allow the handle to not encroach on the space for the scrollbar + margin-left: 8px; height: calc(100vh - 51px); // height of .mx_RoomHeader.light-panel - .mx_RightPanel_ResizeHandle { - width: 9px; - } - &:hover .mx_RightPanel_ResizeHandle { - &::before { - position: absolute; - left: 6px; - top: 50%; - transform: translate(0, -50%); + // Need to use important to override element style attributes + // set by re-resizable + top: 50% !important; + transform: translate(0, -50%); - height: 64px; - width: 4px; - border-radius: 4px; + height: 64px !important; // to match width of the ones on roomlist + width: 4px !important; + border-radius: 4px !important; - content: ' '; - - background-color: $primary-fg-color; - opacity: 0.8; - - margin-left: -10px; - } + background-color: $primary-fg-color; + opacity: 0.8; } } diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index b3dab5f992..26382b55e8 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -144,8 +144,6 @@ limitations under the License. .mx_RoomView_messagePanel, .mx_RoomView_messagePanelSpinner, .mx_RoomView_messagePanelSearchSpinner { order: 2; } - - margin-right: 10px; } .mx_RoomView_body .mx_RoomView_timeline { From a0200de7b4a0afdecfdf5f0e06a68e0d990876f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 18:34:54 +0100 Subject: [PATCH 33/59] Add scrollToBottomOnMessageSent setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/settings/Settings.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index ca5e2f1d04..dd431f9b75 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -317,6 +317,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td('Show line numbers in code blocks'), default: true, }, + "scrollToBottomOnMessageSent": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td('Jump to the bottom of the timeline when you send a message'), + default: true, + }, "Pill.shouldShowPillAvatar": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Show avatars in user and room mentions'), From 1387c9f94db2815184f920c516a40b2809916fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 18:35:19 +0100 Subject: [PATCH 34/59] Display scrollToBottomOnMessageSent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 04fcea39dc..5e1c2e7288 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -48,6 +48,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showRedactions', 'enableSyntaxHighlightLanguageDetection', 'expandCodeByDefault', + `scrollToBottomOnMessageSent`, 'showCodeLineNumbers', 'showJoinLeaves', 'showAvatarChanges', From 361420bf6826dc309f6f46c4f9b4d0bed4790982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 18:35:45 +0100 Subject: [PATCH 35/59] Use scrollToBottomOnMessageSent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/components/views/rooms/SendMessageComposer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 62c474e417..c2baa2762c 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -403,7 +403,9 @@ export default class SendMessageComposer extends React.Component { this._editorRef.clearUndoHistory(); this._editorRef.focus(); this._clearStoredEditorState(); - dis.dispatch({action: "scroll_to_bottom"}); + if (SettingsStore.getValue("scrollToBottomOnMessageSent")) { + dis.dispatch({action: "scroll_to_bottom"}); + } } componentWillUnmount() { From 83df645dbbadcab2a003e14f946f11cf4daa59e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Fri, 26 Feb 2021 18:35:53 +0100 Subject: [PATCH 36/59] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5bbbdf60b5..4bd7131bfe 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -808,6 +808,7 @@ "Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting", "Expand code blocks by default": "Expand code blocks by default", "Show line numbers in code blocks": "Show line numbers in code blocks", + "Jump to the bottom of the timeline when you send a message": "Jump to the bottom of the timeline when you send a message", "Show avatars in user and room mentions": "Show avatars in user and room mentions", "Enable big emoji in chat": "Enable big emoji in chat", "Send typing notifications": "Send typing notifications", From 6fcb4c7cd2ddf0a5fc64ff1796d079389a3d8482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Tue, 2 Mar 2021 07:37:00 +0100 Subject: [PATCH 37/59] Fix quote Co-authored-by: Travis Ralston <travpc@gmail.com> --- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 5e1c2e7288..ae9cad4cfa 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -48,7 +48,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showRedactions', 'enableSyntaxHighlightLanguageDetection', 'expandCodeByDefault', - `scrollToBottomOnMessageSent`, + 'scrollToBottomOnMessageSent', 'showCodeLineNumbers', 'showJoinLeaves', 'showAvatarChanges', From ebedd3cbcbadaa8d945f430f5934ab7215e6b0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Tue, 2 Mar 2021 07:41:14 +0100 Subject: [PATCH 38/59] Remove space Co-authored-by: Travis Ralston <travpc@gmail.com> --- src/components/views/rooms/AuxPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index b3ef8c3cc8..9d19c212c4 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -154,7 +154,7 @@ export default class AuxPanel extends React.Component<IProps, IState> { fileDropTarget = ( <div className="mx_RoomView_fileDropTarget"> <img - src={require( "../../../../res/img/upload-big.svg")} + src={require("../../../../res/img/upload-big.svg")} className="mx_RoomView_fileDropTarget_image" /> { _t("Drop file here to upload") } From ff00683f321a0369bc41836fdc55007c38dfd75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Tue, 2 Mar 2021 07:42:07 +0100 Subject: [PATCH 39/59] Use === Co-authored-by: Travis Ralston <travpc@gmail.com> --- src/components/structures/RoomView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 5b79f23e0b..4a58e21820 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1159,7 +1159,7 @@ export default class RoomView extends React.Component<IProps, IState> { dragCounter: this.state.dragCounter - 1, }); - if (this.state.dragCounter == 0) { + if (this.state.dragCounter === 0) { this.setState({ draggingFile: false, }); From 8efe7dcaa11b2de16a4f77cb6d0cbaae0fb6d3bc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 09:51:11 +0000 Subject: [PATCH 40/59] Decorate Right Panel cards with Space header for when viewing it in that context --- res/css/structures/_RightPanel.scss | 17 ++++++ res/css/views/rooms/_MemberInfo.scss | 1 + res/css/views/rooms/_MemberList.scss | 4 ++ src/components/views/right_panel/UserInfo.tsx | 57 +++++++++++++------ src/components/views/rooms/MemberList.js | 22 ++++++- .../views/rooms/ThirdPartyMemberInfo.js | 19 +++++-- src/i18n/strings/en_EN.json | 3 +- 7 files changed, 98 insertions(+), 25 deletions(-) diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index 5bf0d953f3..5515fe4060 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -160,3 +160,20 @@ limitations under the License. mask-position: center; } } + +.mx_RightPanel_scopeHeader { + margin: 24px; + text-align: center; + font-weight: $font-semi-bold; + font-size: $font-18px; + line-height: $font-22px; + + .mx_BaseAvatar { + margin-right: 8px; + vertical-align: middle; + } + + .mx_BaseAvatar_image { + border-radius: 8px; + } +} diff --git a/res/css/views/rooms/_MemberInfo.scss b/res/css/views/rooms/_MemberInfo.scss index 182c280217..3f7f83d334 100644 --- a/res/css/views/rooms/_MemberInfo.scss +++ b/res/css/views/rooms/_MemberInfo.scss @@ -19,6 +19,7 @@ limitations under the License. flex-direction: column; flex: 1; overflow-y: auto; + margin-top: 8px; } .mx_MemberInfo_name { diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index 1e3506e371..631ddc484f 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -44,6 +44,10 @@ limitations under the License. .mx_AutoHideScrollbar { flex: 1 1 0; } + + .mx_RightPanel_scopeHeader { + margin-top: -8px; + } } .mx_GroupMemberList_query, diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index a4b5cd0fbb..eb47a56269 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -60,7 +60,9 @@ import QuestionDialog from "../dialogs/QuestionDialog"; import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog"; import InfoDialog from "../dialogs/InfoDialog"; import { EventType } from "matrix-js-sdk/src/@types/event"; -import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import RoomAvatar from "../avatars/RoomAvatar"; +import RoomName from "../elements/RoomName"; interface IDevice { deviceId: string; @@ -302,7 +304,8 @@ const UserOptionsSection: React.FC<{ member: RoomMember; isIgnored: boolean; canInvite: boolean; -}> = ({member, isIgnored, canInvite}) => { + isSpace?: boolean; +}> = ({member, isIgnored, canInvite, isSpace}) => { const cli = useContext(MatrixClientContext); let ignoreButton = null; @@ -342,7 +345,7 @@ const UserOptionsSection: React.FC<{ </AccessibleButton> ); - if (member.roomId) { + if (member.roomId && !isSpace) { const onReadReceiptButton = function() { const room = cli.getRoom(member.roomId); dis.dispatch({ @@ -434,14 +437,18 @@ const UserOptionsSection: React.FC<{ ); }; -const warnSelfDemote = async () => { +const warnSelfDemote = async (isSpace) => { const {finished} = Modal.createTrackedDialog('Demoting Self', '', QuestionDialog, { title: _t("Demote yourself?"), description: <div> - { _t("You will not be able to undo this change as you are demoting yourself, " + - "if you are the last privileged user in the room it will be impossible " + - "to regain privileges.") } + { isSpace + ? _t("You will not be able to undo this change as you are demoting yourself, " + + "if you are the last privileged user in the space it will be impossible " + + "to regain privileges.") + : _t("You will not be able to undo this change as you are demoting yourself, " + + "if you are the last privileged user in the room it will be impossible " + + "to regain privileges.") } </div>, button: _t("Demote"), }); @@ -717,7 +724,7 @@ const MuteToggleButton: React.FC<IBaseRoomProps> = ({member, room, powerLevels, // if muting self, warn as it may be irreversible if (target === cli.getUserId()) { try { - if (!(await warnSelfDemote())) return; + if (!(await warnSelfDemote(room?.isSpaceRoom()))) return; } catch (e) { console.error("Failed to warn about self demotion: ", e); return; @@ -806,7 +813,7 @@ const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({ if (canAffectUser && me.powerLevel >= kickPowerLevel) { kickButton = <RoomKickButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />; } - if (me.powerLevel >= redactPowerLevel) { + if (me.powerLevel >= redactPowerLevel && !room.isSpaceRoom()) { redactButton = ( <RedactMessagesButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} /> ); @@ -1085,7 +1092,7 @@ const PowerLevelEditor: React.FC<{ } else if (myUserId === target) { // If we are changing our own PL it can only ever be decreasing, which we cannot reverse. try { - if (!(await warnSelfDemote())) return; + if (!(await warnSelfDemote(room?.isSpaceRoom()))) return; } catch (e) { console.error("Failed to warn about self demotion: ", e); } @@ -1315,12 +1322,10 @@ const BasicUserInfo: React.FC<{ if (!isRoomEncrypted) { if (!cryptoEnabled) { text = _t("This client does not support end-to-end encryption."); - } else if (room) { + } else if (room && !room.isSpaceRoom()) { text = _t("Messages in this room are not end-to-end encrypted."); - } else { - // TODO what to render for GroupMember } - } else { + } else if (!room.isSpaceRoom()) { text = _t("Messages in this room are end-to-end encrypted."); } @@ -1381,7 +1386,9 @@ const BasicUserInfo: React.FC<{ <UserOptionsSection canInvite={roomPermissions.canInvite} isIgnored={isIgnored} - member={member} /> + member={member} + isSpace={room?.isSpaceRoom()} + /> { adminToolsContainer } @@ -1498,7 +1505,7 @@ interface IProps { user: Member; groupId?: string; room?: Room; - phase: RightPanelPhases.RoomMemberInfo | RightPanelPhases.GroupMemberInfo; + phase: RightPanelPhases.RoomMemberInfo | RightPanelPhases.GroupMemberInfo | RightPanelPhases.SpaceMemberInfo; onClose(): void; } @@ -1542,7 +1549,9 @@ const UserInfo: React.FC<Props> = ({ previousPhase = RightPanelPhases.RoomMemberInfo; refireParams = {member: member}; } else if (room) { - previousPhase = RightPanelPhases.RoomMemberList; + previousPhase = previousPhase = room.isSpaceRoom() + ? RightPanelPhases.SpaceMemberList + : RightPanelPhases.RoomMemberList; } const onEncryptionPanelClose = () => { @@ -1557,6 +1566,7 @@ const UserInfo: React.FC<Props> = ({ switch (phase) { case RightPanelPhases.RoomMemberInfo: case RightPanelPhases.GroupMemberInfo: + case RightPanelPhases.SpaceMemberInfo: content = ( <BasicUserInfo room={room} @@ -1587,7 +1597,18 @@ const UserInfo: React.FC<Props> = ({ } } - const header = <UserInfoHeader member={member} e2eStatus={e2eStatus} />; + let scopeHeader; + if (room?.isSpaceRoom()) { + scopeHeader = <div className="mx_RightPanel_scopeHeader"> + <RoomAvatar room={room} height={32} width={32} /> + <RoomName room={room} /> + </div>; + } + + const header = <React.Fragment> + { scopeHeader } + <UserInfoHeader member={member} e2eStatus={e2eStatus} /> + </React.Fragment>; return <BaseCard className={classes.join(" ")} header={header} diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 495a0f0d2c..d4d618c821 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -27,6 +27,8 @@ import * as sdk from "../../../index"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; import BaseCard from "../right_panel/BaseCard"; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; +import RoomAvatar from "../avatars/RoomAvatar"; +import RoomName from "../elements/RoomName"; const INITIAL_LOAD_NUM_MEMBERS = 30; const INITIAL_LOAD_NUM_INVITED = 5; @@ -456,6 +458,8 @@ export default class MemberList extends React.Component { const chat = CommunityPrototypeStore.instance.getSelectedCommunityGeneralChat(); if (chat && chat.roomId === this.props.roomId) { inviteButtonText = _t("Invite to this community"); + } else if (room.isSpaceRoom()) { + inviteButtonText = _t("Invite to this space"); } const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); @@ -483,12 +487,26 @@ export default class MemberList extends React.Component { onSearch={ this.onSearchQueryChanged } /> ); + let previousPhase = RightPanelPhases.RoomSummary; + // We have no previousPhase for when viewing a MemberList from a Space + let scopeHeader; + if (room?.isSpaceRoom()) { + previousPhase = undefined; + scopeHeader = <div className="mx_RightPanel_scopeHeader"> + <RoomAvatar room={room} height={32} width={32} /> + <RoomName room={room} /> + </div>; + } + return <BaseCard className="mx_MemberList" - header={inviteButton} + header={<React.Fragment> + { scopeHeader } + { inviteButton } + </React.Fragment>} footer={footer} onClose={this.props.onClose} - previousPhase={RightPanelPhases.RoomSummary} + previousPhase={previousPhase} > <div className="mx_MemberList_wrapper"> <TruncatedList className="mx_MemberList_section mx_MemberList_joined" truncateAt={this.state.truncateAtJoined} diff --git a/src/components/views/rooms/ThirdPartyMemberInfo.js b/src/components/views/rooms/ThirdPartyMemberInfo.js index 3a7042ebd2..73510c2b4f 100644 --- a/src/components/views/rooms/ThirdPartyMemberInfo.js +++ b/src/components/views/rooms/ThirdPartyMemberInfo.js @@ -23,6 +23,8 @@ import dis from "../../../dispatcher/dispatcher"; import * as sdk from "../../../index"; import Modal from "../../../Modal"; import {isValid3pidInvite} from "../../../RoomInvite"; +import RoomAvatar from "../avatars/RoomAvatar"; +import RoomName from "../elements/RoomName"; export default class ThirdPartyMemberInfo extends React.Component { static propTypes = { @@ -32,14 +34,14 @@ export default class ThirdPartyMemberInfo extends React.Component { constructor(props) { super(props); - const room = MatrixClientPeg.get().getRoom(this.props.event.getRoomId()); - const me = room.getMember(MatrixClientPeg.get().getUserId()); - const powerLevels = room.currentState.getStateEvents("m.room.power_levels", ""); + this.room = MatrixClientPeg.get().getRoom(this.props.event.getRoomId()); + const me = this.room.getMember(MatrixClientPeg.get().getUserId()); + const powerLevels = this.room.currentState.getStateEvents("m.room.power_levels", ""); let kickLevel = powerLevels ? powerLevels.getContent().kick : 50; if (typeof(kickLevel) !== 'number') kickLevel = 50; - const sender = room.getMember(this.props.event.getSender()); + const sender = this.room.getMember(this.props.event.getSender()); this.state = { stateKey: this.props.event.getStateKey(), @@ -119,9 +121,18 @@ export default class ThirdPartyMemberInfo extends React.Component { ); } + let scopeHeader; + if (this.room.isSpaceRoom()) { + scopeHeader = <div className="mx_RightPanel_scopeHeader"> + <RoomAvatar room={this.room} height={32} width={32} /> + <RoomName room={this.room} /> + </div>; + } + // We shamelessly rip off the MemberInfo styles here. return ( <div className="mx_MemberInfo" role="tabpanel"> + { scopeHeader } <div className="mx_MemberInfo_name"> <AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ae12b195a0..472fd9b1e7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1436,6 +1436,7 @@ "and %(count)s others...|one": "and one other...", "Invite to this room": "Invite to this room", "Invite to this community": "Invite to this community", + "Invite to this space": "Invite to this space", "Invited": "Invited", "Filter room members": "Filter room members", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", @@ -1698,6 +1699,7 @@ "Share Link to User": "Share Link to User", "Direct message": "Direct message", "Demote yourself?": "Demote yourself?", + "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the space it will be impossible to regain privileges.": "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the space it will be impossible to regain privileges.", "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.", "Demote": "Demote", "Disinvite": "Disinvite", @@ -2167,7 +2169,6 @@ "Start a conversation with someone using their name or username (like <userId/>).": "Start a conversation with someone using their name or username (like <userId/>).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>", "Go": "Go", - "Invite to this space": "Invite to this space", "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.", "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.", "Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.", From dfd0aaffe348e85c32b13b702e6a7cf0feb048c5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 09:55:55 +0000 Subject: [PATCH 41/59] Iterate copy for some global warning prompts for spaces --- src/components/structures/MatrixChat.tsx | 14 ++++++++++---- src/i18n/strings/en_EN.json | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 8e3d3e6b5f..d9d8b659c9 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1072,6 +1072,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> { private leaveRoomWarnings(roomId: string) { const roomToLeave = MatrixClientPeg.get().getRoom(roomId); + const isSpace = roomToLeave?.isSpaceRoom(); // Show a warning if there are additional complications. const joinRules = roomToLeave.currentState.getStateEvents('m.room.join_rules', ''); const warnings = []; @@ -1081,7 +1082,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> { warnings.push(( <span className="warning" key="non_public_warning"> {' '/* Whitespace, otherwise the sentences get smashed together */ } - { _t("This room is not public. You will not be able to rejoin without an invite.") } + { isSpace + ? _t("This space is not public. You will not be able to rejoin without an invite.") + : _t("This room is not public. You will not be able to rejoin without an invite.") } </span> )); } @@ -1094,11 +1097,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> { const roomToLeave = MatrixClientPeg.get().getRoom(roomId); const warnings = this.leaveRoomWarnings(roomId); - Modal.createTrackedDialog('Leave room', '', QuestionDialog, { - title: _t("Leave room"), + const isSpace = roomToLeave?.isSpaceRoom(); + Modal.createTrackedDialog(isSpace ? "Leave space" : "Leave room", '', QuestionDialog, { + title: isSpace ? _t("Leave space") : _t("Leave room"), description: ( <span> - { _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) } + { isSpace + ? _t("Are you sure you want to leave the space '%(spaceName)s'?", {spaceName: roomToLeave.name}) + : _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) } { warnings } </span> ), diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 472fd9b1e7..6603a83496 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2493,7 +2493,10 @@ "Failed to reject invitation": "Failed to reject invitation", "Cannot create rooms in this community": "Cannot create rooms in this community", "You do not have permission to create rooms in this community.": "You do not have permission to create rooms in this community.", + "This space is not public. You will not be able to rejoin without an invite.": "This space is not public. You will not be able to rejoin without an invite.", "This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.", + "Leave space": "Leave space", + "Are you sure you want to leave the space '%(spaceName)s'?": "Are you sure you want to leave the space '%(spaceName)s'?", "Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?", "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", "Signed Out": "Signed Out", From 926e226a784d5bd66dee9f788618c060cb693e46 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 10:07:43 +0000 Subject: [PATCH 42/59] Add Invite CTA to Space View --- res/css/structures/_SpaceRoomView.scss | 54 +++++++++++++++++++ src/RoomInvite.js | 14 ++--- src/components/structures/SpaceRoomView.tsx | 18 ++++++- .../views/spaces/SpacePublicShare.tsx | 4 +- src/i18n/strings/en_EN.json | 1 + 5 files changed, 78 insertions(+), 13 deletions(-) diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index 559f405e59..946856eed3 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -135,6 +135,60 @@ $SpaceRoomViewInnerWidth: 428px; padding: 8px 22px; } } + + .mx_SpaceRoomView_landing_adminButtons { + margin-top: 32px; + + .mx_AccessibleButton { + position: relative; + width: 160px; + height: 124px; + box-sizing: border-box; + padding: 72px 16px 0; + border-radius: 12px; + border: 1px solid $space-button-outline-color; + margin-right: 28px; + margin-bottom: 28px; + font-size: $font-14px; + display: inline-block; + vertical-align: bottom; + + &:last-child { + margin-right: 0; + } + + &:hover { + background-color: rgba(141, 151, 165, 0.1); + } + + &::before, &::after { + position: absolute; + content: ""; + left: 16px; + top: 16px; + height: 40px; + width: 40px; + border-radius: 20px; + } + + &::after { + mask-position: center; + mask-size: 30px; + mask-repeat: no-repeat; + background: #ffffff; // white icon fill + } + + &.mx_SpaceRoomView_landing_inviteButton { + &::before { + background-color: $accent-color; + } + + &::after { + mask-image: url('$(res)/img/element-icons/room/invite.svg'); + } + } + } + } } .mx_SpaceRoomView_privateScope { diff --git a/src/RoomInvite.js b/src/RoomInvite.js index 728ae11e79..503411d2b3 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -50,10 +50,13 @@ export function showStartChatInviteDialog(initialText) { } export function showRoomInviteDialog(roomId) { + const isSpace = MatrixClientPeg.get()?.getRoom(roomId)?.isSpaceRoom(); // This dialog handles the room creation internally - we don't need to worry about it. - const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); Modal.createTrackedDialog( - 'Invite Users', '', InviteDialog, {kind: KIND_INVITE, roomId}, + "Invite Users", isSpace ? "Space" : "Room", InviteDialog, { + kind: isSpace ? KIND_SPACE_INVITE : KIND_INVITE, + roomId, + }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, ); } @@ -75,13 +78,6 @@ export function showCommunityInviteDialog(communityId) { } } -export const showSpaceInviteDialog = (roomId) => { - Modal.createTrackedDialog("Invite Users", "Space", InviteDialog, { - kind: KIND_SPACE_INVITE, - roomId, - }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); -}; - /** * Checks if the given MatrixEvent is a valid 3rd party user invite. * @param {MatrixEvent} event The event to check diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 6c64df31eb..9e73b97d5a 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -25,7 +25,7 @@ import AccessibleButton from "../views/elements/AccessibleButton"; import RoomName from "../views/elements/RoomName"; import RoomTopic from "../views/elements/RoomTopic"; import FormButton from "../views/elements/FormButton"; -import {inviteMultipleToRoom, showSpaceInviteDialog} from "../../RoomInvite"; +import {inviteMultipleToRoom, showRoomInviteDialog} from "../../RoomInvite"; import {useRoomMembers} from "../../hooks/useRoomMembers"; import createRoom, {IOpts, Preset} from "../../createRoom"; import Field from "../views/elements/Field"; @@ -108,6 +108,17 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => </div>; } + let inviteButton; + if (myMembership === "join" && space.canInvite(userId)) { + inviteButton = ( + <AccessibleButton className="mx_SpaceRoomView_landing_inviteButton" onClick={() => { + showRoomInviteDialog(space.roomId); + }}> + { _t("Invite people") } + </AccessibleButton> + ); + } + return <div className="mx_SpaceRoomView_landing"> <RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} /> <div className="mx_SpaceRoomView_landing_name"> @@ -167,6 +178,9 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => <RoomTopic room={space} /> </div> { joinButtons } + <div className="mx_SpaceRoomView_landing_adminButtons"> + { inviteButton } + </div> </div>; }; @@ -361,7 +375,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => { <div className="mx_SpaceRoomView_inviteTeammates_buttons"> <AccessibleButton className="mx_SpaceRoomView_inviteTeammates_inviteDialogButton" - onClick={() => showSpaceInviteDialog(space.roomId)} + onClick={() => showRoomInviteDialog(space.roomId)} > { _t("Invite by username") } </AccessibleButton> diff --git a/src/components/views/spaces/SpacePublicShare.tsx b/src/components/views/spaces/SpacePublicShare.tsx index 064d1640a2..3930c1db16 100644 --- a/src/components/views/spaces/SpacePublicShare.tsx +++ b/src/components/views/spaces/SpacePublicShare.tsx @@ -22,7 +22,7 @@ import AccessibleButton from "../elements/AccessibleButton"; import {copyPlaintext} from "../../../utils/strings"; import {sleep} from "../../../utils/promise"; import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; -import {showSpaceInviteDialog} from "../../../RoomInvite"; +import {showRoomInviteDialog} from "../../../RoomInvite"; interface IProps { space: Room; @@ -53,7 +53,7 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => { <AccessibleButton className="mx_SpacePublicShare_inviteButton" onClick={() => { - showSpaceInviteDialog(space.roomId); + showRoomInviteDialog(space.roomId); onFinished(); }} > diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6603a83496..5f3d293571 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2561,6 +2561,7 @@ "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", "Accept Invite": "Accept Invite", + "Invite people": "Invite people", "%(count)s members|other": "%(count)s members", "%(count)s members|one": "%(count)s member", "<inviter/> invited you to <name/>": "<inviter/> invited you to <name/>", From 0a4c0b69b0fa250fa01c3ff1a88ba272dc5ef64e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Tue, 2 Mar 2021 12:07:33 +0100 Subject: [PATCH 43/59] Move fileDropTarget to RoomView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/components/structures/RoomView.tsx | 17 +++++++++++++++-- src/components/views/rooms/AuxPanel.tsx | 17 ----------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 4a58e21820..af7b8ee704 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1782,6 +1782,19 @@ export default class RoomView extends React.Component<IProps, IState> { } } + let fileDropTarget = null; + if (this.state.draggingFile) { + fileDropTarget = ( + <div className="mx_RoomView_fileDropTarget"> + <img + src={require("../../../res/img/upload-big.svg")} + className="mx_RoomView_fileDropTarget_image" + /> + { _t("Drop file here to upload") } + </div> + ); + } + // We have successfully loaded this room, and are not previewing. // Display the "normal" room view. @@ -1893,7 +1906,6 @@ export default class RoomView extends React.Component<IProps, IState> { room={this.state.room} fullHeight={false} userId={this.context.credentials.userId} - draggingFile={this.state.draggingFile} maxHeight={this.state.auxPanelMaxHeight} showApps={this.state.showApps} onResize={this.onResize} @@ -2059,8 +2071,9 @@ export default class RoomView extends React.Component<IProps, IState> { /> <MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}> <div className="mx_RoomView_body"> + {auxPanel} <div className={timelineClasses}> - {auxPanel} + {fileDropTarget} {topUnreadMessagesBar} {jumpToBottom} {messagePanel} diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 9d19c212c4..7aa7be42b6 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -35,9 +35,6 @@ interface IProps { userId: string, showApps: boolean, // Render apps - // set to true to show the file drop target - draggingFile: boolean, - // maxHeight attribute for the aux panel and the video // therein maxHeight: number, @@ -149,19 +146,6 @@ export default class AuxPanel extends React.Component<IProps, IState> { } render() { - let fileDropTarget = null; - if (this.props.draggingFile) { - fileDropTarget = ( - <div className="mx_RoomView_fileDropTarget"> - <img - src={require("../../../../res/img/upload-big.svg")} - className="mx_RoomView_fileDropTarget_image" - /> - { _t("Drop file here to upload") } - </div> - ); - } - const callView = ( <CallViewForRoom roomId={this.props.room.roomId} @@ -244,7 +228,6 @@ export default class AuxPanel extends React.Component<IProps, IState> { <AutoHideScrollbar className={classes} style={style} > { stateViews } { appsDrawer } - { fileDropTarget } { callView } { this.props.children } </AutoHideScrollbar> From 4476843264eeb61380662eb0040a30edfec42c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Tue, 2 Mar 2021 12:12:10 +0100 Subject: [PATCH 44/59] Remove unused _t MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/components/views/rooms/AuxPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 7aa7be42b6..c9821d51e3 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -20,7 +20,6 @@ import { Room } from 'matrix-js-sdk/src/models/room' import dis from "../../../dispatcher/dispatcher"; import * as ObjectUtils from '../../../ObjectUtils'; import AppsDrawer from './AppsDrawer'; -import { _t } from '../../../languageHandler'; import classNames from 'classnames'; import RateLimitedFunc from '../../../ratelimitedfunc'; import SettingsStore from "../../../settings/SettingsStore"; From 831cc7eaa0ecd57a25cff40daf582b02e3fd4e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com> Date: Tue, 2 Mar 2021 12:14:36 +0100 Subject: [PATCH 45/59] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9af8ccc172..5d2c70be03 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1381,7 +1381,6 @@ "Remove %(phone)s?": "Remove %(phone)s?", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.", "Phone Number": "Phone Number", - "Drop file here to upload": "Drop file here to upload", "This user has not verified all of their sessions.": "This user has not verified all of their sessions.", "You have not verified this user.": "You have not verified this user.", "You have verified this user. This user has verified all of their sessions.": "You have verified this user. This user has verified all of their sessions.", @@ -2513,6 +2512,7 @@ "No more results": "No more results", "Room": "Room", "Failed to reject invite": "Failed to reject invite", + "Drop file here to upload": "Drop file here to upload", "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", "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.", 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 46/59] 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<IProps, IState> { 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 = <AccessibleButton className="mx_SpaceRoomView_landing_settingsButton" onClick={() => { + showSpaceSettings(cli, space); + }}> + { _t("Settings") } + </AccessibleButton>; + } + return <div className="mx_SpaceRoomView_landing"> <RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} /> <div className="mx_SpaceRoomView_landing_name"> @@ -180,6 +189,7 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => { joinButtons } <div className="mx_SpaceRoomView_landing_adminButtons"> { inviteButton } + { settingsButton } </div> </div>; }; 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<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; + 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 <a>send us logs</a>.": "To help us prevent this in future, please <a>send us logs</a>.", "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); +}; From a687b9883ca42c927ec89d20ff066620a10b17ed Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 13:28:05 +0000 Subject: [PATCH 47/59] Add a create room in space CTA to Space View --- res/css/structures/_SpaceRoomView.scss | 10 ++++++++++ src/components/structures/SpaceRoomView.tsx | 16 +++++++++++++++- .../views/dialogs/CreateRoomDialog.js | 7 +++++++ src/i18n/strings/en_EN.json | 1 + src/utils/space.ts | 18 ++++++++++++++++++ 5 files changed, 51 insertions(+), 1 deletion(-) diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index 0a42db130a..eaaaa2f797 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -188,6 +188,16 @@ $SpaceRoomViewInnerWidth: 428px; } } + &.mx_SpaceRoomView_landing_createButton { + &::before { + background-color: #368bd6; + } + + &::after { + mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); + } + } + &.mx_SpaceRoomView_landing_settingsButton { &::before { background-color: #5c56f5; diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 49af14017e..4159a38cfe 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, showSpaceSettings} from "../../utils/space"; +import {showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space"; import MemberAvatar from "../views/avatars/MemberAvatar"; interface IProps { @@ -119,6 +119,19 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => ); } + const canAddRooms = myMembership === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId); + + let addRoomButtons; + if (canAddRooms) { + addRoomButtons = <React.Fragment> + <AccessibleButton className="mx_SpaceRoomView_landing_createButton" onClick={() => { + showCreateNewRoom(cli, space); + }}> + { _t("Create a new room") } + </AccessibleButton> + </React.Fragment>; + } + let settingsButton; if (shouldShowSpaceSettings(cli, space)) { settingsButton = <AccessibleButton className="mx_SpaceRoomView_landing_settingsButton" onClick={() => { @@ -189,6 +202,7 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => { joinButtons } <div className="mx_SpaceRoomView_landing_adminButtons"> { inviteButton } + { addRoomButtons } { settingsButton } </div> </div>; diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index 2b6bb5e187..0771b0ec45 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -17,6 +17,8 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; +import {Room} from "matrix-js-sdk/src/models/room"; + import * as sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; import withValidation from '../elements/Validation'; @@ -30,6 +32,7 @@ export default class CreateRoomDialog extends React.Component { static propTypes = { onFinished: PropTypes.func.isRequired, defaultPublic: PropTypes.bool, + parentSpace: PropTypes.instanceOf(Room), }; constructor(props) { @@ -85,6 +88,10 @@ export default class CreateRoomDialog extends React.Component { opts.associatedWithCommunity = CommunityPrototypeStore.instance.getSelectedCommunityId(); } + if (this.props.parentSpace) { + opts.parentSpace = this.props.parentSpace; + } + return opts; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index cd2fcf1117..aeef76bf22 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2570,6 +2570,7 @@ "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", "Accept Invite": "Accept Invite", "Invite people": "Invite people", + "Create a new room": "Create a new room", "%(count)s members|other": "%(count)s members", "%(count)s members|one": "%(count)s member", "<inviter/> invited you to <name/>": "<inviter/> invited you to <name/>", diff --git a/src/utils/space.ts b/src/utils/space.ts index 2ee4d0071e..c995b860ee 100644 --- a/src/utils/space.ts +++ b/src/utils/space.ts @@ -21,6 +21,8 @@ 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"; +import CreateRoomDialog from "../components/views/dialogs/CreateRoomDialog"; +import createRoom, {IOpts} from "../createRoom"; export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => { const userId = cli.getUserId(); @@ -46,3 +48,19 @@ export const showSpaceSettings = (cli: MatrixClient, space: Room) => { space, }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); }; + +export const showCreateNewRoom = async (cli: MatrixClient, space: Room) => { + const modal = Modal.createTrackedDialog<[boolean, IOpts]>( + "Space Landing", + "Create Room", + CreateRoomDialog, + { + defaultPublic: space.getJoinRule() === "public", + parentSpace: space, + }, + ); + const [shouldCreate, opts] = await modal.finished; + if (shouldCreate) { + await createRoom(opts); + } +}; From e479edd47a9849a5cf12523b82eba92f1c9fe59b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 13:32:24 +0000 Subject: [PATCH 48/59] Add an add existing room to space CTA to Space View --- res/css/_components.scss | 1 + res/css/structures/_SpaceRoomView.scss | 10 + .../dialogs/_AddExistingToSpaceDialog.scss | 185 ++++++++++++++++ src/components/structures/SpaceRoomView.tsx | 10 +- .../dialogs/AddExistingToSpaceDialog.tsx | 208 ++++++++++++++++++ src/i18n/strings/en_EN.json | 11 +- src/utils/space.ts | 15 ++ 7 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 res/css/views/dialogs/_AddExistingToSpaceDialog.scss create mode 100644 src/components/views/dialogs/AddExistingToSpaceDialog.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index db73eed3f2..8569f62de9 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -58,6 +58,7 @@ @import "./views/context_menus/_MessageContextMenu.scss"; @import "./views/context_menus/_StatusMessageContextMenu.scss"; @import "./views/context_menus/_TagTileContextMenu.scss"; +@import "./views/dialogs/_AddExistingToSpaceDialog.scss"; @import "./views/dialogs/_AddressPickerDialog.scss"; @import "./views/dialogs/_Analytics.scss"; @import "./views/dialogs/_BugReportDialog.scss"; diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index eaaaa2f797..ee60389c59 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -188,6 +188,16 @@ $SpaceRoomViewInnerWidth: 428px; } } + &.mx_SpaceRoomView_landing_addButton { + &::before { + background-color: #ac3ba8; + } + + &::after { + mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); + } + } + &.mx_SpaceRoomView_landing_createButton { &::before { background-color: #368bd6; diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss new file mode 100644 index 0000000000..0c9d8e3840 --- /dev/null +++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss @@ -0,0 +1,185 @@ +/* +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_AddExistingToSpaceDialog_wrapper { + .mx_Dialog { + display: flex; + flex-direction: column; + } +} + +.mx_AddExistingToSpaceDialog { + width: 480px; + color: $primary-fg-color; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + min-height: 0; + + .mx_Dialog_title { + display: flex; + + .mx_BaseAvatar { + display: inline-flex; + margin: 5px 16px 5px 5px; + vertical-align: middle; + } + + .mx_BaseAvatar_image { + border-radius: 8px; + margin: 0; + vertical-align: unset; + } + + > div { + > h1 { + font-weight: $font-semi-bold; + font-size: $font-18px; + line-height: $font-22px; + margin: 0; + } + + .mx_AddExistingToSpaceDialog_onlySpace { + color: $secondary-fg-color; + font-size: $font-15px; + line-height: $font-24px; + } + } + + .mx_Dropdown_input { + border: none; + + > .mx_Dropdown_option { + padding-left: 0; + flex: unset; + height: unset; + color: $secondary-fg-color; + font-size: $font-15px; + line-height: $font-24px; + + .mx_BaseAvatar { + display: none; + } + } + + .mx_Dropdown_menu { + .mx_AddExistingToSpaceDialog_dropdownOptionActive { + color: $accent-color; + padding-right: 32px; + position: relative; + + &::before { + content: ''; + width: 20px; + height: 20px; + top: 8px; + right: 0; + position: absolute; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background-color: $accent-color; + mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); + } + } + } + } + } + + .mx_SearchBox { + margin: 0; + } + + .mx_AddExistingToSpaceDialog_errorText { + font-weight: $font-semi-bold; + font-size: $font-12px; + line-height: $font-15px; + color: $notice-primary-color; + margin-bottom: 28px; + } + + .mx_AddExistingToSpaceDialog_content { + .mx_AddExistingToSpaceDialog_noResults { + margin-top: 24px; + } + } + + .mx_AddExistingToSpaceDialog_section { + margin-top: 24px; + + > h3 { + margin: 0; + color: $secondary-fg-color; + font-size: $font-12px; + font-weight: $font-semi-bold; + line-height: $font-15px; + } + + .mx_AddExistingToSpaceDialog_entry { + display: flex; + margin-top: 12px; + + .mx_BaseAvatar { + margin-right: 12px; + } + + .mx_AddExistingToSpaceDialog_entry_name { + font-size: $font-15px; + line-height: 30px; + flex-grow: 1; + } + + .mx_FormButton { + min-width: 92px; + font-weight: normal; + box-sizing: border-box; + } + } + } + + .mx_AddExistingToSpaceDialog_section_spaces { + .mx_BaseAvatar_image { + border-radius: 8px; + } + } + + .mx_AddExistingToSpaceDialog_footer { + display: flex; + margin-top: 32px; + + > span { + flex-grow: 1; + font-size: $font-12px; + line-height: $font-15px; + + > * { + vertical-align: middle; + } + } + + .mx_AccessibleButton { + display: inline-block; + } + + .mx_AccessibleButton_kind_link { + padding: 0; + } + } + + .mx_FormButton { + padding: 8px 22px; + } +} diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 4159a38cfe..f1a8a4d71b 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 {showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space"; +import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space"; import MemberAvatar from "../views/avatars/MemberAvatar"; interface IProps { @@ -124,6 +124,14 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => let addRoomButtons; if (canAddRooms) { addRoomButtons = <React.Fragment> + <AccessibleButton className="mx_SpaceRoomView_landing_addButton" onClick={async () => { + const [added] = await showAddExistingRooms(cli, space); + if (added) { + // TODO update rooms shown once we show hierarchy here + } + }}> + { _t("Add existing rooms & spaces") } + </AccessibleButton> <AccessibleButton className="mx_SpaceRoomView_landing_createButton" onClick={() => { showCreateNewRoom(cli, space); }}> diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx new file mode 100644 index 0000000000..66efaefd9d --- /dev/null +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -0,0 +1,208 @@ +/* +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 classNames from "classnames"; +import {Room} from "matrix-js-sdk/src/models/room"; +import {MatrixClient} from "matrix-js-sdk/src/client"; + +import {_t} from '../../../languageHandler'; +import {IDialogProps} from "./IDialogProps"; +import BaseDialog from "./BaseDialog"; +import FormButton from "../elements/FormButton"; +import Dropdown from "../elements/Dropdown"; +import SearchBox from "../../structures/SearchBox"; +import SpaceStore from "../../../stores/SpaceStore"; +import RoomAvatar from "../avatars/RoomAvatar"; +import {getDisplayAliasForRoom} from "../../../Rooms"; +import AccessibleButton from "../elements/AccessibleButton"; +import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; +import {allSettled} from "../../../utils/promise"; +import DMRoomMap from "../../../utils/DMRoomMap"; +import {calculateRoomVia} from "../../../utils/permalinks/Permalinks"; +import StyledCheckbox from "../elements/StyledCheckbox"; + +interface IProps extends IDialogProps { + matrixClient: MatrixClient; + space: Room; + onCreateRoomClick(cli: MatrixClient, space: Room): void; +} + +const Entry = ({ room, checked, onChange }) => { + return <div className="mx_AddExistingToSpaceDialog_entry"> + <RoomAvatar room={room} height={32} width={32} /> + <span className="mx_AddExistingToSpaceDialog_entry_name">{ room.name }</span> + <StyledCheckbox onChange={(e) => onChange(e.target.checked)} checked={checked} /> + </div>; +}; + +const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => { + const [query, setQuery] = useState(""); + const lcQuery = query.toLowerCase(); + + const [selectedSpace, setSelectedSpace] = useState(space); + const [selectedToAdd, setSelectedToAdd] = useState(new Set<Room>()); + + const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId); + const existingSubspacesSet = new Set(existingSubspaces); + const spaces = SpaceStore.instance.getSpaces().filter(s => { + return !existingSubspacesSet.has(s) // not already in space + && space !== s // not the top-level space + && selectedSpace !== s // not the selected space + && s.name.toLowerCase().includes(lcQuery); // contains query + }); + + const existingRooms = SpaceStore.instance.getChildRooms(space.roomId); + const existingRoomsSet = new Set(existingRooms); + const rooms = cli.getVisibleRooms().filter(room => { + return !existingRoomsSet.has(room) // not already in space + && room.name.toLowerCase().includes(lcQuery) // contains query + && !DMRoomMap.shared().getUserIdForRoomId(room.roomId); // not a DM + }); + + const [busy, setBusy] = useState(false); + const [error, setError] = useState(""); + + let spaceOptionSection; + if (existingSubspacesSet.size > 0) { + const options = [space, ...existingSubspaces].map((space) => { + const classes = classNames("mx_AddExistingToSpaceDialog_dropdownOption", { + mx_AddExistingToSpaceDialog_dropdownOptionActive: space === selectedSpace, + }); + return <div key={space.roomId} className={classes}> + <RoomAvatar room={space} width={24} height={24} /> + { space.name || getDisplayAliasForRoom(space) || space.roomId } + </div>; + }); + + spaceOptionSection = ( + <Dropdown + id="mx_SpaceSelectDropdown" + onOptionChange={(key: string) => { + setSelectedSpace(existingSubspaces.find(space => space.roomId === key) || space); + }} + value={selectedSpace.roomId} + label={_t("Space selection")} + > + { options } + </Dropdown> + ); + } else { + spaceOptionSection = <div className="mx_AddExistingToSpaceDialog_onlySpace"> + { space.name || getDisplayAliasForRoom(space) || space.roomId } + </div>; + } + + const title = <React.Fragment> + <RoomAvatar room={selectedSpace} height={40} width={40} /> + <div> + <h1>{ _t("Add existing spaces/rooms") }</h1> + { spaceOptionSection } + </div> + </React.Fragment>; + + return <BaseDialog + title={title} + className="mx_AddExistingToSpaceDialog" + contentId="mx_AddExistingToSpaceDialog" + onFinished={onFinished} + fixedWidth={false} + > + { error && <div className="mx_AddExistingToSpaceDialog_errorText">{ error }</div> } + + <SearchBox + className="mx_textinput_icon mx_textinput_search" + placeholder={ _t("Filter your rooms and spaces") } + onSearch={setQuery} + /> + <AutoHideScrollbar className="mx_AddExistingToSpaceDialog_content" id="mx_AddExistingToSpaceDialog"> + { spaces.length > 0 ? ( + <div className="mx_AddExistingToSpaceDialog_section mx_AddExistingToSpaceDialog_section_spaces"> + <h3>{ _t("Spaces") }</h3> + { spaces.map(space => { + return <Entry + key={space.roomId} + room={space} + checked={selectedToAdd.has(space)} + onChange={(checked) => { + if (checked) { + selectedToAdd.add(space); + } else { + selectedToAdd.delete(space); + } + setSelectedToAdd(new Set(selectedToAdd)); + }} + />; + }) } + </div> + ) : null } + + { rooms.length > 0 ? ( + <div className="mx_AddExistingToSpaceDialog_section"> + <h3>{ _t("Rooms") }</h3> + { rooms.map(room => { + return <Entry + key={room.roomId} + room={room} + checked={selectedToAdd.has(room)} + onChange={(checked) => { + if (checked) { + selectedToAdd.add(room); + } else { + selectedToAdd.delete(room); + } + setSelectedToAdd(new Set(selectedToAdd)); + }} + />; + }) } + </div> + ) : undefined } + + { spaces.length + rooms.length < 1 ? <span className="mx_AddExistingToSpaceDialog_noResults"> + { _t("No results") } + </span> : undefined } + </AutoHideScrollbar> + + <div className="mx_AddExistingToSpaceDialog_footer"> + <span> + <div>{ _t("Don't want to add an existing room?") }</div> + <AccessibleButton onClick={() => onCreateRoomClick(cli, space)} kind="link"> + { _t("Create a new room") } + </AccessibleButton> + </span> + + <FormButton + label={busy ? _t("Applying...") : _t("Apply")} + disabled={busy || selectedToAdd.size < 1} + onClick={async () => { + setBusy(true); + try { + await allSettled(Array.from(selectedToAdd).map((room) => + SpaceStore.instance.addRoomToSpace(space, room.roomId, calculateRoomVia(room)))); + onFinished(true); + } catch (e) { + console.error("Failed to add rooms to space", e); + setError(_t("Failed to add rooms to space")); + } + setBusy(false); + }} + /> + </div> + </BaseDialog>; +}; + +export default AddExistingToSpaceDialog; + diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index aeef76bf22..bb800b2af2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1978,6 +1978,15 @@ "Add a new server...": "Add a new server...", "%(networkName)s rooms": "%(networkName)s rooms", "Matrix rooms": "Matrix rooms", + "Space selection": "Space selection", + "Add existing spaces/rooms": "Add existing spaces/rooms", + "Filter your rooms and spaces": "Filter your rooms and spaces", + "Spaces": "Spaces", + "Don't want to add an existing room?": "Don't want to add an existing room?", + "Create a new room": "Create a new room", + "Applying...": "Applying...", + "Apply": "Apply", + "Failed to add rooms to space": "Failed to add rooms to space", "Matrix ID": "Matrix ID", "Matrix Room ID": "Matrix Room ID", "email address": "email address", @@ -2570,7 +2579,7 @@ "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", "Accept Invite": "Accept Invite", "Invite people": "Invite people", - "Create a new room": "Create a new room", + "Add existing rooms & spaces": "Add existing rooms & spaces", "%(count)s members|other": "%(count)s members", "%(count)s members|one": "%(count)s member", "<inviter/> invited you to <name/>": "<inviter/> invited you to <name/>", diff --git a/src/utils/space.ts b/src/utils/space.ts index c995b860ee..bc31829f45 100644 --- a/src/utils/space.ts +++ b/src/utils/space.ts @@ -21,6 +21,7 @@ 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"; +import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToSpaceDialog"; import CreateRoomDialog from "../components/views/dialogs/CreateRoomDialog"; import createRoom, {IOpts} from "../createRoom"; @@ -49,6 +50,20 @@ export const showSpaceSettings = (cli: MatrixClient, space: Room) => { }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); }; +export const showAddExistingRooms = async (cli: MatrixClient, space: Room) => { + return Modal.createTrackedDialog( + "Space Landing", + "Add Existing", + AddExistingToSpaceDialog, + { + matrixClient: cli, + onCreateRoomClick: showCreateNewRoom, + space, + }, + "mx_AddExistingToSpaceDialog_wrapper", + ).finished; +}; + export const showCreateNewRoom = async (cli: MatrixClient, space: Room) => { const modal = Modal.createTrackedDialog<[boolean, IOpts]>( "Space Landing", From 4e93452275ed7ed8224d14a62c8711a85fdc6e6f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 14:02:03 +0000 Subject: [PATCH 49/59] Tweak resizer collapse distributor behaviour to work with the expanding space panel --- src/components/structures/LoggedInView.tsx | 12 +++++++++++- src/resizer/distributors/collapse.ts | 9 ++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 1694b4bcf5..4e768bd9e5 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -223,7 +223,14 @@ class LoggedInView extends React.Component<IProps, IState> { let size; let collapsed; const collapseConfig: ICollapseConfig = { - toggleSize: 260 - 50, + // TODO: the space panel currently does not have a fixed width, + // just the headers at each level have a max-width of 150px + // Taking 222px for the space panel for now, + // so this will look slightly off for now, + // depending on the depth of your space tree. + // To fix this, we'll need to turn toggleSize + // into a callback so it can be measured when starting the resize operation + toggleSize: 222 + 68, onCollapsed: (_collapsed) => { collapsed = _collapsed; if (_collapsed) { @@ -244,6 +251,9 @@ class LoggedInView extends React.Component<IProps, IState> { if (!collapsed) window.localStorage.setItem("mx_lhs_size", '' + size); this.props.resizeNotifier.stopResizing(); }, + isItemCollapsed: domNode => { + return domNode.classList.contains("mx_LeftPanel_minimized"); + }, }; const resizer = new Resizer(this._resizeContainer.current, CollapseDistributor, collapseConfig); resizer.setClassNames({ diff --git a/src/resizer/distributors/collapse.ts b/src/resizer/distributors/collapse.ts index ddf3bd687e..f8db0be52c 100644 --- a/src/resizer/distributors/collapse.ts +++ b/src/resizer/distributors/collapse.ts @@ -22,6 +22,7 @@ import Sizer from "../sizer"; export interface ICollapseConfig extends IConfig { toggleSize: number; onCollapsed?(collapsed: boolean, id: string, element: HTMLElement): void; + isItemCollapsed(element: HTMLElement): boolean; } class CollapseItem extends ResizeItem<ICollapseConfig> { @@ -31,6 +32,11 @@ class CollapseItem extends ResizeItem<ICollapseConfig> { callback(collapsed, this.id, this.domNode); } } + + get isCollapsed() { + const isItemCollapsed = this.resizer.config.isItemCollapsed; + return isItemCollapsed(this.domNode); + } } export default class CollapseDistributor extends FixedDistributor<ICollapseConfig, CollapseItem> { @@ -39,11 +45,12 @@ export default class CollapseDistributor extends FixedDistributor<ICollapseConfi } private readonly toggleSize: number; - private isCollapsed = false; + private isCollapsed: boolean; constructor(item: CollapseItem) { super(item); this.toggleSize = item.resizer?.config?.toggleSize; + this.isCollapsed = item.isCollapsed; } public resize(newSize: number) { From faf7a4b8bce57d7d2654b49b9413762e1e876daf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 14:11:38 +0000 Subject: [PATCH 50/59] Initial Space room directory view --- res/css/_components.scss | 1 + res/css/structures/_SpaceRoomDirectory.scss | 231 +++++++ src/components/structures/MatrixChat.tsx | 17 +- .../structures/SpaceRoomDirectory.tsx | 572 ++++++++++++++++++ src/i18n/strings/en_EN.json | 10 + 5 files changed, 827 insertions(+), 4 deletions(-) create mode 100644 res/css/structures/_SpaceRoomDirectory.scss create mode 100644 src/components/structures/SpaceRoomDirectory.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 8569f62de9..daa7016623 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -28,6 +28,7 @@ @import "./structures/_ScrollPanel.scss"; @import "./structures/_SearchBox.scss"; @import "./structures/_SpacePanel.scss"; +@import "./structures/_SpaceRoomDirectory.scss"; @import "./structures/_SpaceRoomView.scss"; @import "./structures/_TabbedView.scss"; @import "./structures/_ToastContainer.scss"; diff --git a/res/css/structures/_SpaceRoomDirectory.scss b/res/css/structures/_SpaceRoomDirectory.scss new file mode 100644 index 0000000000..5cb91820cf --- /dev/null +++ b/res/css/structures/_SpaceRoomDirectory.scss @@ -0,0 +1,231 @@ +/* +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_SpaceRoomDirectory_dialogWrapper > .mx_Dialog { + max-width: 960px; + height: 100%; +} + +.mx_SpaceRoomDirectory { + height: 100%; + margin-bottom: 12px; + color: $primary-fg-color; + word-break: break-word; + display: flex; + flex-direction: column; + + .mx_Dialog_title { + display: flex; + + .mx_BaseAvatar { + margin-right: 16px; + } + + .mx_BaseAvatar_image { + border-radius: 8px; + } + + > div { + > h1 { + font-weight: $font-semi-bold; + font-size: $font-18px; + line-height: $font-22px; + margin: 0; + } + + > div { + color: $secondary-fg-color; + font-size: $font-15px; + line-height: $font-24px; + } + } + } + + .mx_Dialog_content { + // TODO fix scrollbar + //display: flex; + //flex-direction: column; + //height: calc(100% - 80px); + + .mx_AccessibleButton_kind_link { + padding: 0; + } + + .mx_SearchBox { + margin: 24px 0 28px; + } + + .mx_SpaceRoomDirectory_listHeader { + display: flex; + font-size: $font-12px; + line-height: $font-15px; + color: $secondary-fg-color; + + .mx_FormButton { + margin-bottom: 8px; + } + + > span { + margin: auto 0 0 auto; + } + } + } +} + +.mx_SpaceRoomDirectory_list { + margin-top: 8px; + + .mx_SpaceRoomDirectory_roomCount { + > h3 { + display: inline; + font-weight: $font-semi-bold; + font-size: $font-18px; + line-height: $font-22px; + color: $primary-fg-color; + } + + > span { + margin-left: 8px; + font-size: $font-15px; + line-height: $font-24px; + color: $secondary-fg-color; + } + } + + .mx_SpaceRoomDirectory_subspace { + margin-top: 8px; + + .mx_SpaceRoomDirectory_subspace_info { + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: 8px; + color: $secondary-fg-color; + font-weight: $font-semi-bold; + font-size: $font-12px; + line-height: $font-15px; + + .mx_BaseAvatar { + margin-right: 12px; + vertical-align: middle; + } + + .mx_BaseAvatar_image { + border-radius: 8px; + } + + .mx_SpaceRoomDirectory_actions { + text-align: right; + height: min-content; + margin-left: auto; + margin-right: 16px; + } + } + + .mx_SpaceRoomDirectory_subspace_children { + margin-left: 12px; + border-left: 2px solid $space-button-outline-color; + padding-left: 24px; + } + } + + .mx_SpaceRoomDirectory_roomTile { + padding: 16px; + border-radius: 8px; + border: 1px solid $space-button-outline-color; + margin: 8px 0 16px; + display: flex; + min-height: 76px; + box-sizing: border-box; + + &.mx_AccessibleButton:hover { + background-color: rgba(141, 151, 165, 0.1); + } + + .mx_BaseAvatar { + margin-right: 16px; + margin-top: 6px; + } + + .mx_SpaceRoomDirectory_roomTile_info { + display: inline-block; + font-size: $font-15px; + flex-grow: 1; + height: min-content; + margin: auto 0; + + .mx_SpaceRoomDirectory_roomTile_name { + font-weight: $font-semi-bold; + line-height: $font-18px; + } + .mx_SpaceRoomDirectory_roomTile_topic { + line-height: $font-24px; + color: $secondary-fg-color; + } + } + + .mx_SpaceRoomDirectory_roomTile_memberCount { + position: relative; + margin: auto 0 auto 24px; + padding: 0 0 0 28px; + line-height: $font-24px; + display: inline-block; + width: 32px; + + &::before { + position: absolute; + content: ''; + width: 24px; + height: 24px; + top: 0; + left: 0; + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + background-color: $secondary-fg-color; + mask-image: url('$(res)/img/element-icons/community-members.svg'); + } + } + + .mx_SpaceRoomDirectory_actions { + width: 180px; + text-align: right; + height: min-content; + margin: auto 0 auto 28px; + + .mx_AccessibleButton { + vertical-align: middle; + + & + .mx_AccessibleButton { + margin-left: 24px; + } + } + } + } + + .mx_SpaceRoomDirectory_actions { + .mx_SpaceRoomDirectory_actionsText { + font-weight: normal; + font-size: $font-12px; + line-height: $font-15px; + color: $secondary-fg-color; + } + + .mx_Checkbox { + display: inline-block; + } + } +} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 83b3565738..1700b627db 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -82,6 +82,8 @@ import {UIFeature} from "../../settings/UIFeature"; import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore"; import DialPadModal from "../views/voip/DialPadModal"; import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast'; +import SpaceStore from "../../stores/SpaceStore"; +import SpaceRoomDirectory from "./SpaceRoomDirectory"; /** constants for MatrixChat.state.view */ export enum Views { @@ -691,10 +693,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> { break; } case Action.ViewRoomDirectory: { - const RoomDirectory = sdk.getComponent("structures.RoomDirectory"); - Modal.createTrackedDialog('Room directory', '', RoomDirectory, { - initialText: payload.initialText, - }, 'mx_RoomDirectory_dialogWrapper', false, true); + if (SpaceStore.instance.activeSpace) { + Modal.createTrackedDialog("Space room directory", "", SpaceRoomDirectory, { + space: SpaceStore.instance.activeSpace, + initialText: payload.initialText, + }, "mx_SpaceRoomDirectory_dialogWrapper", false, true); + } else { + const RoomDirectory = sdk.getComponent("structures.RoomDirectory"); + Modal.createTrackedDialog('Room directory', '', RoomDirectory, { + initialText: payload.initialText, + }, 'mx_RoomDirectory_dialogWrapper', false, true); + } // View the welcome or home page if we need something to look at this.viewSomethingBehindModal(); diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx new file mode 100644 index 0000000000..7f7b9dbb99 --- /dev/null +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -0,0 +1,572 @@ +/* +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, {useMemo, useRef, useState} from "react"; +import Room from "matrix-js-sdk/src/models/room"; +import MatrixEvent from "matrix-js-sdk/src/models/event"; +import {EventType, RoomType} from "matrix-js-sdk/src/@types/event"; + +import {MatrixClientPeg} from "../../MatrixClientPeg"; +import dis from "../../dispatcher/dispatcher"; +import {_t} from "../../languageHandler"; +import AccessibleButton from "../views/elements/AccessibleButton"; +import BaseDialog from "../views/dialogs/BaseDialog"; +import FormButton from "../views/elements/FormButton"; +import SearchBox from "./SearchBox"; +import RoomAvatar from "../views/avatars/RoomAvatar"; +import RoomName from "../views/elements/RoomName"; +import {useAsyncMemo} from "../../hooks/useAsyncMemo"; +import {shouldShowSpaceSettings} from "../../utils/space"; +import {EnhancedMap} from "../../utils/maps"; +import StyledCheckbox from "../views/elements/StyledCheckbox"; +import AutoHideScrollbar from "./AutoHideScrollbar"; +import BaseAvatar from "../views/avatars/BaseAvatar"; + +interface IProps { + space: Room; + initialText?: string; + onFinished(): void; +} + +/* eslint-disable camelcase */ +export interface ISpaceSummaryRoom { + canonical_alias?: string; + aliases: string[]; + avatar_url?: string; + guest_can_join: boolean; + name?: string; + num_joined_members: number + room_id: string; + topic?: string; + world_readable: boolean; + num_refs: number; + room_type: string; +} + +export interface ISpaceSummaryEvent { + room_id: string; + event_id: string; + origin_server_ts: number; + type: string; + state_key: string; + content: { + order?: string; + auto_join?: boolean; + via?: string; + }; +} +/* eslint-enable camelcase */ + +interface ISubspaceProps { + space: ISpaceSummaryRoom; + event?: MatrixEvent; + editing?: boolean; + onPreviewClick?(): void; + queueAction?(action: IAction): void; + onJoinClick?(): void; +} + +const SubSpace: React.FC<ISubspaceProps> = ({ + space, + editing, + event, + queueAction, + onJoinClick, + onPreviewClick, + children, +}) => { + const name = space.name || space.canonical_alias || space.aliases?.[0] || _t("Unnamed Space"); + + const evContent = event?.getContent(); + const [autoJoin, _setAutoJoin] = useState(evContent?.auto_join); + const [removed, _setRemoved] = useState(!evContent?.via); + + const cli = MatrixClientPeg.get(); + const cliRoom = cli.getRoom(space.room_id); + const myMembership = cliRoom?.getMyMembership(); + + // TODO DRY code + let actions; + if (editing && queueAction) { + if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) { + const setAutoJoin = () => { + _setAutoJoin(v => { + queueAction({ + event, + removed, + autoJoin: !v, + }); + return !v; + }); + }; + + const setRemoved = () => { + _setRemoved(v => { + queueAction({ + event, + removed: !v, + autoJoin, + }); + return !v; + }); + }; + + if (removed) { + actions = <React.Fragment> + <FormButton kind="danger" onClick={setRemoved} label={_t("Undo")} /> + </React.Fragment>; + } else { + actions = <React.Fragment> + <FormButton kind="danger" onClick={setRemoved} label={_t("Remove from Space")} /> + <StyledCheckbox checked={autoJoin} onChange={setAutoJoin} /> + </React.Fragment>; + } + } else { + actions = <span className="mx_SpaceRoomDirectory_actionsText"> + { _t("No permissions")} + </span>; + } + // TODO confirm remove from space click behaviour here + } else { + if (myMembership === "join") { + actions = <span className="mx_SpaceRoomDirectory_actionsText"> + { _t("You're in this space")} + </span>; + } else if (onJoinClick) { + actions = <React.Fragment> + <AccessibleButton onClick={onPreviewClick} kind="link"> + { _t("Preview") } + </AccessibleButton> + <FormButton onClick={onJoinClick} label={_t("Join")} /> + </React.Fragment> + } + } + + let url: string; + if (space.avatar_url) { + url = MatrixClientPeg.get().mxcUrlToHttp(space.avatar_url, + Math.floor(24 * window.devicePixelRatio), + Math.floor(24 * window.devicePixelRatio), + "crop"); + } + + return <div className="mx_SpaceRoomDirectory_subspace"> + <div className="mx_SpaceRoomDirectory_subspace_info"> + <BaseAvatar name={name} idName={space.room_id} url={url} width={24} height={24} /> + { name } + + <div className="mx_SpaceRoomDirectory_actions"> + { actions } + </div> + </div> + <div className="mx_SpaceRoomDirectory_subspace_children"> + { children } + </div> + </div> +}; + +interface IAction { + event: MatrixEvent; + removed: boolean; + autoJoin: boolean; +} + +interface IRoomTileProps { + room: ISpaceSummaryRoom; + event?: MatrixEvent; + editing?: boolean; + onPreviewClick(): void; + queueAction?(action: IAction): void; + onJoinClick?(): void; +} + +const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinClick }: IRoomTileProps) => { + const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("Unnamed Room"); + + const evContent = event?.getContent(); + const [autoJoin, _setAutoJoin] = useState(evContent?.auto_join); + const [removed, _setRemoved] = useState(!evContent?.via); + + const cli = MatrixClientPeg.get(); + const cliRoom = cli.getRoom(room.room_id); + const myMembership = cliRoom?.getMyMembership(); + + let actions; + if (editing && queueAction) { + if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) { + const setAutoJoin = () => { + _setAutoJoin(v => { + queueAction({ + event, + removed, + autoJoin: !v, + }); + return !v; + }); + }; + + const setRemoved = () => { + _setRemoved(v => { + queueAction({ + event, + removed: !v, + autoJoin, + }); + return !v; + }); + }; + + if (removed) { + actions = <React.Fragment> + <FormButton kind="danger" onClick={setRemoved} label={_t("Undo")} /> + </React.Fragment>; + } else { + actions = <React.Fragment> + <FormButton kind="danger" onClick={setRemoved} label={_t("Remove from Space")} /> + <StyledCheckbox checked={autoJoin} onChange={setAutoJoin} /> + </React.Fragment>; + } + } else { + actions = <span className="mx_SpaceRoomDirectory_actionsText"> + { _t("No permissions")} + </span>; + } + // TODO confirm remove from space click behaviour here + } else { + if (myMembership === "join") { + actions = <span className="mx_SpaceRoomDirectory_actionsText"> + { _t("You're in this room")} + </span>; + } else if (onJoinClick) { + actions = <React.Fragment> + <AccessibleButton onClick={onPreviewClick} kind="link"> + { _t("Preview") } + </AccessibleButton> + <FormButton onClick={onJoinClick} label={_t("Join")} /> + </React.Fragment> + } + } + + let url: string; + if (room.avatar_url) { + url = cli.mxcUrlToHttp(room.avatar_url, + Math.floor(32 * window.devicePixelRatio), + Math.floor(32 * window.devicePixelRatio), + "crop"); + } + + const content = <React.Fragment> + <BaseAvatar name={name} idName={room.room_id} url={url} width={32} height={32} /> + + <div className="mx_SpaceRoomDirectory_roomTile_info"> + <div className="mx_SpaceRoomDirectory_roomTile_name"> + { name } + </div> + <div className="mx_SpaceRoomDirectory_roomTile_topic"> + { room.topic } + </div> + </div> + <div className="mx_SpaceRoomDirectory_roomTile_memberCount"> + { room.num_joined_members } + </div> + + <div className="mx_SpaceRoomDirectory_actions"> + { actions } + </div> + </React.Fragment>; + + if (editing) { + return <div className="mx_SpaceRoomDirectory_roomTile"> + { content } + </div> + } + + return <AccessibleButton className="mx_SpaceRoomDirectory_roomTile" onClick={onPreviewClick}> + { content } + </AccessibleButton>; +}; + +export const showRoom = (room: ISpaceSummaryRoom, viaServers?: string[], autoJoin = false) => { + // Don't let the user view a room they won't be able to either peek or join: + // fail earlier so they don't have to click back to the directory. + if (MatrixClientPeg.get().isGuest()) { + if (!room.world_readable && !room.guest_can_join) { + dis.dispatch({ action: "require_registration" }); + return; + } + } + + const roomAlias = getDisplayAliasForRoom(room) || undefined; + dis.dispatch({ + action: "view_room", + auto_join: autoJoin, + should_peek: true, + _type: "room_directory", // instrumentation + room_alias: roomAlias, + room_id: room.room_id, + via_servers: viaServers, + oob_data: { + avatarUrl: room.avatar_url, + // XXX: This logic is duplicated from the JS SDK which would normally decide what the name is. + name: room.name || roomAlias || _t("Unnamed room"), + }, + }); +}; + +interface IHierarchyLevelProps { + spaceId: string; + rooms: Map<string, ISpaceSummaryRoom>; + editing?: boolean; + relations: EnhancedMap<string, string[]>; + parents: Set<string>; + queueAction?(action: IAction): void; + onPreviewClick(roomId: string): void; + onRemoveFromSpaceClick?(roomId: string): void; + onJoinClick?(roomId: string): void; +} + +export const HierarchyLevel = ({ + spaceId, + rooms, + editing, + relations, + parents, + onPreviewClick, + onJoinClick, + queueAction, +}: IHierarchyLevelProps) => { + const cli = MatrixClientPeg.get(); + const space = cli.getRoom(spaceId); + // TODO respect order + const [subspaces, childRooms] = relations.get(spaceId)?.reduce((result, roomId: string) => { + if (!rooms.has(roomId)) return result; // TODO wat + result[rooms.get(roomId).room_type === RoomType.Space ? 0 : 1].push(roomId); + return result; + }, [[], []]) || [[], []]; + + // Don't render this subspace if it has no rooms we can show + // TODO this is broken - as a space may have subspaces we still need to show + // if (!childRooms.length) return null; + + const userId = cli.getUserId(); + + const newParents = new Set(parents).add(spaceId); + return <React.Fragment> + { + childRooms.map(roomId => ( + <RoomTile + key={roomId} + room={rooms.get(roomId)} + event={space?.currentState.maySendStateEvent(EventType.SpaceChild, userId) + ? space?.currentState.getStateEvents(EventType.SpaceChild, roomId) + : undefined} + editing={editing} + queueAction={queueAction} + onPreviewClick={() => { + onPreviewClick(roomId); + }} + onJoinClick={onJoinClick ? () => { + onJoinClick(roomId); + } : undefined} + /> + )) + } + + { + subspaces.filter(roomId => !newParents.has(roomId)).map(roomId => ( + <SubSpace + key={roomId} + space={rooms.get(roomId)} + event={space?.currentState.getStateEvents(EventType.SpaceChild, roomId)} + editing={editing} + queueAction={queueAction} + onPreviewClick={() => { + onPreviewClick(roomId); + }} + onJoinClick={() => { + onJoinClick(roomId); + }} + > + <HierarchyLevel + spaceId={roomId} + rooms={rooms} + editing={editing} + relations={relations} + parents={newParents} + onPreviewClick={onPreviewClick} + onJoinClick={onJoinClick} + queueAction={queueAction} + /> + </SubSpace> + )) + } + </React.Fragment> +}; + +const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinished }) => { + // TODO pagination + const cli = MatrixClientPeg.get(); + const [query, setQuery] = useState(initialText); + const [isEditing, setIsEditing] = useState(false); + + const onCreateRoomClick = () => { + dis.dispatch({ + action: 'view_create_room', + public: true, + }); + onFinished(); + }; + + // stored within a ref as we don't need to re-render when it changes + const pendingActions = useRef(new Map<string, IAction>()); + + let adminButton; + if (shouldShowSpaceSettings(cli, space)) { // TODO this is an imperfect test + const onManageButtonClicked = () => { + setIsEditing(true); + }; + + const onSaveButtonClicked = () => { + // TODO setBusy + pendingActions.current.forEach(({event, autoJoin, removed}) => { + const content = { + ...event.getContent(), + auto_join: autoJoin, + }; + + if (removed) { + delete content["via"]; + } + + cli.sendStateEvent(event.getRoomId(), event.getType(), content, event.getStateKey()); + }); + setIsEditing(false); + }; + + if (isEditing) { + adminButton = <React.Fragment> + <FormButton label={_t("Save changes")} onClick={onSaveButtonClicked} /> + <span>{ _t("All users join by default") }</span> + </React.Fragment>; + } else { + adminButton = <FormButton label={_t("Manage rooms")} onClick={onManageButtonClicked} />; + } + } + + const [rooms, relations, viaMap] = useAsyncMemo(async () => { + try { + const data = await cli.getSpaceSummary(space.roomId); + + const parentChildRelations = new EnhancedMap<string, string[]>(); + const viaMap = new EnhancedMap<string, Set<string>>(); + data.events.map((ev: ISpaceSummaryEvent) => { + if (ev.type === EventType.SpaceChild) { + parentChildRelations.getOrCreate(ev.room_id, []).push(ev.state_key); + } + if (Array.isArray(ev.content["via"])) { + const set = viaMap.getOrCreate(ev.state_key, new Set()); + ev.content["via"].forEach(via => set.add(via)); + } + }); + + return [data.rooms, parentChildRelations, viaMap]; + } catch (e) { + console.error(e); // TODO + } + + return []; + }, [space], []); + + const roomsMap = useMemo(() => { + if (!rooms) return null; + const lcQuery = query.toLowerCase(); + + const filteredRooms = rooms.filter(r => { + return r.room_type === RoomType.Space // always include spaces to allow filtering of sub-space rooms + || r.name?.toLowerCase().includes(lcQuery) + || r.topic?.toLowerCase().includes(lcQuery); + }); + + return new Map<string, ISpaceSummaryRoom>(filteredRooms.map(r => [r.room_id, r])); + // const root = rooms.get(space.roomId); + }, [rooms, query]); + + const title = <React.Fragment> + <RoomAvatar room={space} height={40} width={40} /> + <div> + <h1>{ _t("Explore rooms") }</h1> + <div><RoomName room={space} /></div> + </div> + </React.Fragment>; + const explanation = + _t("If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.", null, + {a: sub => { + return <AccessibleButton kind="link" onClick={onCreateRoomClick}>{sub}</AccessibleButton>; + }}, + ); + + let content; + if (roomsMap) { + content = <AutoHideScrollbar className="mx_SpaceRoomDirectory_list"> + <HierarchyLevel + spaceId={space.roomId} + rooms={roomsMap} + editing={isEditing} + relations={relations} + parents={new Set()} + queueAction={action => { + pendingActions.current.set(action.event.room_id, action); + }} + onPreviewClick={roomId => { + showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), false); + onFinished(); + }} + onJoinClick={(roomId) => { + showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), true); + onFinished(); + }} + /> + </AutoHideScrollbar>; + } + + // TODO loading state/error state + return ( + <BaseDialog className="mx_SpaceRoomDirectory" hasCancel={true} onFinished={onFinished} title={title}> + <div className="mx_Dialog_content"> + { explanation } + + <SearchBox + className="mx_textinput_icon mx_textinput_search" + placeholder={ _t("Find a room...") } + onSearch={setQuery} + /> + + <div className="mx_SpaceRoomDirectory_listHeader"> + { adminButton } + </div> + { content } + </div> + </BaseDialog> + ); +}; + +export default SpaceRoomDirectory; + +// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom +// but works with the objects we get from the public room list +function getDisplayAliasForRoom(room: ISpaceSummaryRoom) { + return room.canonical_alias || (room.aliases ? room.aliases[0] : ""); +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index bb800b2af2..04a6d63272 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2577,6 +2577,16 @@ "Failed to reject invite": "Failed to reject invite", "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", + "Unnamed Space": "Unnamed Space", + "Undo": "Undo", + "Remove from Space": "Remove from Space", + "No permissions": "No permissions", + "You're in this space": "You're in this space", + "You're in this room": "You're in this room", + "Save changes": "Save changes", + "All users join by default": "All users join by default", + "Manage rooms": "Manage rooms", + "Find a room...": "Find a room...", "Accept Invite": "Accept Invite", "Invite people": "Invite people", "Add existing rooms & spaces": "Add existing rooms & spaces", From ca1bd78921e0fb05bbaaae185f3a29402b61a38e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 14:19:40 +0000 Subject: [PATCH 51/59] Add space specific variant of the dropdown on "Rooms +" sublist --- res/css/views/rooms/_RoomList.scss | 5 +- .../element-icons/roomlist/hash-circle.svg | 7 +++ .../element-icons/roomlist/plus-circle.svg | 3 ++ src/components/views/rooms/RoomList.tsx | 47 +++++++++++++++++++ src/i18n/strings/en_EN.json | 4 ++ 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 res/img/element-icons/roomlist/hash-circle.svg create mode 100644 res/img/element-icons/roomlist/plus-circle.svg diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss index 66e1b827d0..d49ed4b736 100644 --- a/res/css/views/rooms/_RoomList.scss +++ b/res/css/views/rooms/_RoomList.scss @@ -19,7 +19,10 @@ limitations under the License. } .mx_RoomList_iconPlus::before { - mask-image: url('$(res)/img/element-icons/roomlist/plus.svg'); + mask-image: url('$(res)/img/element-icons/roomlist/plus-circle.svg'); +} +.mx_RoomList_iconHash::before { + mask-image: url('$(res)/img/element-icons/roomlist/hash-circle.svg'); } .mx_RoomList_iconExplore::before { mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); diff --git a/res/img/element-icons/roomlist/hash-circle.svg b/res/img/element-icons/roomlist/hash-circle.svg new file mode 100644 index 0000000000..924b22cf32 --- /dev/null +++ b/res/img/element-icons/roomlist/hash-circle.svg @@ -0,0 +1,7 @@ +<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> + <mask id="path-1-inside-1" fill="white"> + <path fill-rule="evenodd" clip-rule="evenodd" d="M9 17C13.4183 17 17 13.4183 17 9C17 4.58172 13.4183 1 9 1C4.58172 1 1 4.58172 1 9C1 13.4183 4.58172 17 9 17ZM8.33969 5.15616C8.38275 4.74419 8.0837 4.37532 7.67173 4.33225C7.25976 4.28918 6.89088 4.58824 6.84781 5.00021L6.68776 6.53131H5.65625C5.24204 6.53131 4.90625 6.8671 4.90625 7.28131C4.90625 7.69552 5.24204 8.03131 5.65625 8.03131H6.53095L6.32841 9.96881H5.39062C4.97641 9.96881 4.64062 10.3046 4.64062 10.7188C4.64062 11.133 4.97641 11.4688 5.39062 11.4688H6.17161L6.01969 12.9221C5.97662 13.3341 6.27568 13.7029 6.68765 13.746C7.09962 13.7891 7.46849 13.49 7.51156 13.078L7.67978 11.4688H9.49973L9.34781 12.9221C9.30475 13.3341 9.6038 13.7029 10.0158 13.746C10.4277 13.7891 10.7966 13.49 10.8397 13.078L11.0079 11.4688H12.2969C12.7111 11.4688 13.0469 11.133 13.0469 10.7188C13.0469 10.3046 12.7111 9.96881 12.2969 9.96881H11.1647L11.3673 8.03131H12.2969C12.7111 8.03131 13.0469 7.69552 13.0469 7.28131C13.0469 6.8671 12.7111 6.53131 12.2969 6.53131H11.5241L11.6678 5.15616C11.7109 4.74419 11.4118 4.37532 10.9999 4.33225C10.5879 4.28918 10.219 4.58824 10.1759 5.00021L10.0159 6.53131H8.19593L8.33969 5.15616ZM9.65654 9.96881H7.83659L8.03913 8.03131H9.85908L9.65654 9.96881Z"/> + </mask> + <path fill-rule="evenodd" clip-rule="evenodd" d="M9 17C13.4183 17 17 13.4183 17 9C17 4.58172 13.4183 1 9 1C4.58172 1 1 4.58172 1 9C1 13.4183 4.58172 17 9 17ZM8.33969 5.15616C8.38275 4.74419 8.0837 4.37532 7.67173 4.33225C7.25976 4.28918 6.89088 4.58824 6.84781 5.00021L6.68776 6.53131H5.65625C5.24204 6.53131 4.90625 6.8671 4.90625 7.28131C4.90625 7.69552 5.24204 8.03131 5.65625 8.03131H6.53095L6.32841 9.96881H5.39062C4.97641 9.96881 4.64062 10.3046 4.64062 10.7188C4.64062 11.133 4.97641 11.4688 5.39062 11.4688H6.17161L6.01969 12.9221C5.97662 13.3341 6.27568 13.7029 6.68765 13.746C7.09962 13.7891 7.46849 13.49 7.51156 13.078L7.67978 11.4688H9.49973L9.34781 12.9221C9.30475 13.3341 9.6038 13.7029 10.0158 13.746C10.4277 13.7891 10.7966 13.49 10.8397 13.078L11.0079 11.4688H12.2969C12.7111 11.4688 13.0469 11.133 13.0469 10.7188C13.0469 10.3046 12.7111 9.96881 12.2969 9.96881H11.1647L11.3673 8.03131H12.2969C12.7111 8.03131 13.0469 7.69552 13.0469 7.28131C13.0469 6.8671 12.7111 6.53131 12.2969 6.53131H11.5241L11.6678 5.15616C11.7109 4.74419 11.4118 4.37532 10.9999 4.33225C10.5879 4.28918 10.219 4.58824 10.1759 5.00021L10.0159 6.53131H8.19593L8.33969 5.15616ZM9.65654 9.96881H7.83659L8.03913 8.03131H9.85908L9.65654 9.96881Z" fill="black"/> + <path d="M7.67173 4.33225L7.5331 5.65836H7.5331L7.67173 4.33225ZM8.33969 5.15616L9.66579 5.29479V5.29479L8.33969 5.15616ZM6.84781 5.00021L5.52171 4.86158L5.52171 4.86158L6.84781 5.00021ZM6.68776 6.53131V7.86464H7.88898L8.01387 6.66994L6.68776 6.53131ZM6.53095 8.03131L7.85706 8.16994L8.01093 6.69798H6.53095V8.03131ZM6.32841 9.96881V11.3021H7.52963L7.65452 10.1074L6.32841 9.96881ZM6.17161 11.4688L7.49772 11.6074L7.65159 10.1355H6.17161V11.4688ZM6.01969 12.9221L4.69358 12.7835V12.7835L6.01969 12.9221ZM6.68765 13.746L6.82628 12.4199H6.82627L6.68765 13.746ZM7.51156 13.078L8.83767 13.2167L8.83767 13.2167L7.51156 13.078ZM7.67978 11.4688V10.1355H6.47857L6.35368 11.3302L7.67978 11.4688ZM9.49973 11.4688L10.8258 11.6074L10.9797 10.1355H9.49973V11.4688ZM9.34781 12.9221L10.6739 13.0607V13.0607L9.34781 12.9221ZM10.0158 13.746L10.1544 12.4199H10.1544L10.0158 13.746ZM10.8397 13.078L12.1658 13.2167V13.2167L10.8397 13.078ZM11.0079 11.4688V10.1355H9.80669L9.6818 11.3302L11.0079 11.4688ZM11.1647 9.96881L9.83861 9.83018L9.68473 11.3021H11.1647V9.96881ZM11.3673 8.03131V6.69798H10.166L10.0411 7.89268L11.3673 8.03131ZM11.5241 6.53131L10.198 6.39268L10.0441 7.86464H11.5241V6.53131ZM11.6678 5.15616L10.3417 5.01754V5.01754L11.6678 5.15616ZM10.9999 4.33225L11.1385 3.00614H11.1385L10.9999 4.33225ZM10.1759 5.00021L8.84983 4.86158L8.84983 4.86158L10.1759 5.00021ZM10.0159 6.53131V7.86464H11.2171L11.342 6.66994L10.0159 6.53131ZM8.19593 6.53131L6.86982 6.39268L6.71595 7.86464H8.19593V6.53131ZM7.83659 9.96881L6.51048 9.83018L6.35661 11.3021H7.83659V9.96881ZM9.65654 9.96881V11.3021H10.8578L10.9826 10.1074L9.65654 9.96881ZM8.03913 8.03131V6.69798H6.83791L6.71302 7.89268L8.03913 8.03131ZM9.85908 8.03131L11.1852 8.16994L11.3391 6.69798H9.85908V8.03131ZM15.6667 9C15.6667 12.6819 12.6819 15.6667 9 15.6667V18.3333C14.1547 18.3333 18.3333 14.1547 18.3333 9H15.6667ZM9 2.33333C12.6819 2.33333 15.6667 5.3181 15.6667 9H18.3333C18.3333 3.84534 14.1547 -0.333333 9 -0.333333V2.33333ZM2.33333 9C2.33333 5.3181 5.3181 2.33333 9 2.33333V-0.333333C3.84534 -0.333333 -0.333333 3.84534 -0.333333 9H2.33333ZM9 15.6667C5.3181 15.6667 2.33333 12.6819 2.33333 9H-0.333333C-0.333333 14.1547 3.84534 18.3333 9 18.3333V15.6667ZM7.5331 5.65836C7.21268 5.62486 6.98008 5.33796 7.01358 5.01754L9.66579 5.29479C9.78542 4.15043 8.95471 3.12577 7.81035 3.00614L7.5331 5.65836ZM8.17392 5.13884C8.14043 5.45925 7.85352 5.69185 7.5331 5.65836L7.81035 3.00614C6.666 2.88652 5.64134 3.71722 5.52171 4.86158L8.17392 5.13884ZM8.01387 6.66994L8.17392 5.13883L5.52171 4.86158L5.36165 6.39268L8.01387 6.66994ZM5.65625 7.86464H6.68776V5.19798H5.65625V7.86464ZM6.23958 7.28131C6.23958 7.60348 5.97842 7.86464 5.65625 7.86464V5.19798C4.50566 5.19798 3.57292 6.13072 3.57292 7.28131H6.23958ZM5.65625 6.69798C5.97842 6.69798 6.23958 6.95914 6.23958 7.28131H3.57292C3.57292 8.4319 4.50566 9.36464 5.65625 9.36464V6.69798ZM6.53095 6.69798H5.65625V9.36464H6.53095V6.69798ZM7.65452 10.1074L7.85706 8.16994L5.20485 7.89268L5.00231 9.83018L7.65452 10.1074ZM5.39062 11.3021H6.32841V8.63548H5.39062V11.3021ZM5.97396 10.7188C5.97396 11.041 5.71279 11.3021 5.39062 11.3021V8.63548C4.24003 8.63548 3.30729 9.56822 3.30729 10.7188H5.97396ZM5.39062 10.1355C5.71279 10.1355 5.97396 10.3966 5.97396 10.7188H3.30729C3.30729 11.8694 4.24003 12.8021 5.39062 12.8021V10.1355ZM6.17161 10.1355H5.39062V12.8021H6.17161V10.1355ZM7.3458 13.0607L7.49772 11.6074L4.8455 11.3302L4.69358 12.7835L7.3458 13.0607ZM6.82627 12.4199C7.14669 12.4534 7.37929 12.7403 7.3458 13.0607L4.69358 12.7835C4.57396 13.9278 5.40466 14.9525 6.54902 15.0721L6.82627 12.4199ZM6.18545 12.9394C6.21895 12.619 6.50585 12.3864 6.82628 12.4199L6.54902 15.0721C7.69338 15.1917 8.71804 14.361 8.83767 13.2167L6.18545 12.9394ZM6.35368 11.3302L6.18545 12.9394L8.83767 13.2167L9.00589 11.6074L6.35368 11.3302ZM9.49973 10.1355H7.67978V12.8021H9.49973V10.1355ZM10.6739 13.0607L10.8258 11.6074L8.17363 11.3302L8.02171 12.7835L10.6739 13.0607ZM10.1544 12.4199C10.4748 12.4534 10.7074 12.7403 10.6739 13.0607L8.02171 12.7835C7.90208 13.9278 8.73279 14.9525 9.87715 15.0721L10.1544 12.4199ZM9.51358 12.9394C9.54707 12.619 9.83398 12.3864 10.1544 12.4199L9.87714 15.0721C11.0215 15.1917 12.0462 14.361 12.1658 13.2167L9.51358 12.9394ZM9.6818 11.3302L9.51358 12.9394L12.1658 13.2167L12.334 11.6074L9.6818 11.3302ZM12.2969 10.1355H11.0079V12.8021H12.2969V10.1355ZM11.7135 10.7188C11.7135 10.3966 11.9747 10.1355 12.2969 10.1355V12.8021C13.4475 12.8021 14.3802 11.8694 14.3802 10.7188H11.7135ZM12.2969 11.3021C11.9747 11.3021 11.7135 11.041 11.7135 10.7188H14.3802C14.3802 9.56822 13.4475 8.63548 12.2969 8.63548V11.3021ZM11.1647 11.3021H12.2969V8.63548H11.1647V11.3021ZM10.0411 7.89268L9.83861 9.83018L12.4908 10.1074L12.6934 8.16994L10.0411 7.89268ZM12.2969 6.69798H11.3673V9.36464H12.2969V6.69798ZM11.7135 7.28131C11.7135 6.95914 11.9747 6.69798 12.2969 6.69798V9.36464C13.4475 9.36464 14.3802 8.4319 14.3802 7.28131H11.7135ZM12.2969 7.86464C11.9747 7.86464 11.7135 7.60348 11.7135 7.28131H14.3802C14.3802 6.13072 13.4475 5.19798 12.2969 5.19798V7.86464ZM11.5241 7.86464H12.2969V5.19798H11.5241V7.86464ZM10.3417 5.01754L10.198 6.39268L12.8502 6.66994L12.9939 5.29479L10.3417 5.01754ZM10.8612 5.65836C10.5408 5.62486 10.3082 5.33796 10.3417 5.01754L12.9939 5.29479C13.1135 4.15043 12.2828 3.12577 11.1385 3.00614L10.8612 5.65836ZM11.502 5.13884C11.4686 5.45925 11.1816 5.69185 10.8612 5.65836L11.1385 3.00614C9.99412 2.88652 8.96946 3.71722 8.84983 4.86158L11.502 5.13884ZM11.342 6.66994L11.502 5.13883L8.84983 4.86158L8.68978 6.39268L11.342 6.66994ZM8.19593 7.86464H10.0159V5.19798H8.19593V7.86464ZM7.01358 5.01754L6.86982 6.39268L9.52204 6.66994L9.66579 5.29479L7.01358 5.01754ZM7.83659 11.3021H9.65654V8.63548H7.83659V11.3021ZM6.71302 7.89268L6.51048 9.83018L9.16269 10.1074L9.36523 8.16994L6.71302 7.89268ZM9.85908 6.69798H8.03913V9.36464H9.85908V6.69798ZM10.9826 10.1074L11.1852 8.16994L8.53297 7.89268L8.33043 9.83018L10.9826 10.1074Z" fill="black" mask="url(#path-1-inside-1)"/> +</svg> diff --git a/res/img/element-icons/roomlist/plus-circle.svg b/res/img/element-icons/roomlist/plus-circle.svg new file mode 100644 index 0000000000..251ded225c --- /dev/null +++ b/res/img/element-icons/roomlist/plus-circle.svg @@ -0,0 +1,3 @@ +<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M17 9C17 13.4183 13.4183 17 9 17C4.58172 17 1 13.4183 1 9C1 4.58172 4.58172 1 9 1C13.4183 1 17 4.58172 17 9ZM5.25 9C5.25 8.58579 5.58579 8.25 6 8.25H8.25V6C8.25 5.58579 8.58579 5.25 9 5.25C9.41421 5.25 9.75 5.58579 9.75 6V8.25H12C12.4142 8.25 12.75 8.58579 12.75 9C12.75 9.41421 12.4142 9.75 12 9.75H9.75V12C9.75 12.4142 9.41421 12.75 9 12.75C8.58579 12.75 8.25 12.4142 8.25 12V9.75H6C5.58579 9.75 5.25 9.41421 5.25 9Z" fill="black"/> +</svg> diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 45db15df7c..f7da6571da 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -47,6 +47,9 @@ import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../con import AccessibleButton from "../elements/AccessibleButton"; import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; import CallHandler from "../../../CallHandler"; +import SpaceStore from "../../../stores/SpaceStore"; +import { showAddExistingRooms, showCreateNewRoom } from "../../../utils/space"; +import { EventType } from "matrix-js-sdk/src/@types/event"; interface IProps { onKeyDown: (ev: React.KeyboardEvent) => void; @@ -152,6 +155,50 @@ const TAG_AESTHETICS: ITagAestheticsMap = { defaultHidden: false, addRoomLabel: _td("Add room"), addRoomContextMenu: (onFinished: () => void) => { + if (SpaceStore.instance.activeSpace) { + const canAddRooms = SpaceStore.instance.activeSpace.currentState.maySendStateEvent(EventType.SpaceChild, + MatrixClientPeg.get().getUserId()); + + return <IconizedContextMenuOptionList first> + <IconizedContextMenuOption + label={_t("Create new room")} + iconClassName="mx_RoomList_iconPlus" + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + onFinished(); + showCreateNewRoom(MatrixClientPeg.get(), SpaceStore.instance.activeSpace); + }} + disabled={!canAddRooms} + tooltip={canAddRooms ? undefined + : _t("You do not have permissions to create new rooms in this space")} + /> + <IconizedContextMenuOption + label={_t("Add existing room")} + iconClassName="mx_RoomList_iconHash" + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + onFinished(); + showAddExistingRooms(MatrixClientPeg.get(), SpaceStore.instance.activeSpace); + }} + disabled={!canAddRooms} + tooltip={canAddRooms ? undefined + : _t("You do not have permissions to add rooms to this space")} + /> + <IconizedContextMenuOption + label={_t("Explore space rooms")} + iconClassName="mx_RoomList_iconExplore" + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + onFinished(); + defaultDispatcher.fire(Action.ViewRoomDirectory); + }} + /> + </IconizedContextMenuOptionList>; + } + return <IconizedContextMenuOptionList first> <IconizedContextMenuOption label={_t("Create new room")} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 04a6d63272..6d2f41ceae 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1509,6 +1509,10 @@ "Rooms": "Rooms", "Add room": "Add room", "Create new room": "Create new room", + "You do not have permissions to create new rooms in this space": "You do not have permissions to create new rooms in this space", + "Add existing room": "Add existing room", + "You do not have permissions to add rooms to this space": "You do not have permissions to add rooms to this space", + "Explore space rooms": "Explore space rooms", "Explore community rooms": "Explore community rooms", "Explore public rooms": "Explore public rooms", "Low priority": "Low priority", From 716268b2f953e54345389d08c2a95825e7590a7a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 14:34:47 +0000 Subject: [PATCH 52/59] Add context menu to spaces in the space panel --- res/css/structures/_SpacePanel.scss | 75 ++++++ src/accessibility/context_menu/MenuItem.tsx | 11 +- src/components/views/dialogs/InfoDialog.js | 6 +- .../views/spaces/SpaceTreeLevel.tsx | 216 ++++++++++++++++++ src/i18n/strings/en_EN.json | 16 +- 5 files changed, 314 insertions(+), 10 deletions(-) diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index 24d2243912..9937117086 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -212,6 +212,30 @@ $activeBorderColor: $secondary-fg-color; border-radius: 8px; } } + + .mx_SpaceButton_menuButton { + width: 20px; + min-width: 20px; // yay flex + height: 20px; + margin-top: auto; + margin-bottom: auto; + position: relative; + display: none; + + &::before { + top: 2px; + left: 2px; + content: ''; + width: 16px; + height: 16px; + position: absolute; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + mask-image: url('$(res)/img/element-icons/context-menu.svg'); + background: $primary-fg-color; + } + } } .mx_SpacePanel_badgeContainer { @@ -254,6 +278,10 @@ $activeBorderColor: $secondary-fg-color; height: 0; display: none; } + + .mx_SpaceButton_menuButton { + display: block; + } } } @@ -272,3 +300,50 @@ $activeBorderColor: $secondary-fg-color; } } } + +.mx_SpacePanel_contextMenu { + .mx_SpacePanel_contextMenu_header { + margin: 12px 16px 12px; + font-weight: $font-semi-bold; + font-size: $font-15px; + line-height: $font-18px; + } + + .mx_IconizedContextMenu_optionList .mx_AccessibleButton.mx_SpacePanel_contextMenu_inviteButton { + color: $accent-color; + + .mx_SpacePanel_iconInvite::before { + background-color: $accent-color; + mask-image: url('$(res)/img/element-icons/room/invite.svg'); + } + } + + .mx_SpacePanel_iconSettings::before { + mask-image: url('$(res)/img/element-icons/settings.svg'); + } + + .mx_SpacePanel_iconLeave::before { + mask-image: url('$(res)/img/element-icons/leave.svg'); + } + + .mx_SpacePanel_iconHome::before { + mask-image: url('$(res)/img/element-icons/roomlist/home.svg'); + } + + .mx_SpacePanel_iconMembers::before { + mask-image: url('$(res)/img/element-icons/room/members.svg'); + } + + .mx_SpacePanel_iconPlus::before { + mask-image: url('$(res)/img/element-icons/plus.svg'); + } + + .mx_SpacePanel_iconExplore::before { + mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); + } +} + + +.mx_SpacePanel_sharePublicSpace { + margin: 0; +} diff --git a/src/accessibility/context_menu/MenuItem.tsx b/src/accessibility/context_menu/MenuItem.tsx index 0bb169abf8..9a7c1d1f0a 100644 --- a/src/accessibility/context_menu/MenuItem.tsx +++ b/src/accessibility/context_menu/MenuItem.tsx @@ -19,14 +19,23 @@ limitations under the License. import React from "react"; import AccessibleButton from "../../components/views/elements/AccessibleButton"; +import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton"; interface IProps extends React.ComponentProps<typeof AccessibleButton> { label?: string; + tooltip?: string; } // Semantic component for representing a role=menuitem -export const MenuItem: React.FC<IProps> = ({children, label, ...props}) => { +export const MenuItem: React.FC<IProps> = ({children, label, tooltip, ...props}) => { const ariaLabel = props["aria-label"] || label; + + if (tooltip) { + return <AccessibleTooltipButton {...props} role="menuitem" tabIndex={-1} aria-label={ariaLabel} title={tooltip}> + { children } + </AccessibleTooltipButton>; + } + return ( <AccessibleButton {...props} role="menuitem" tabIndex={-1} aria-label={ariaLabel}> { children } diff --git a/src/components/views/dialogs/InfoDialog.js b/src/components/views/dialogs/InfoDialog.js index 97ae968ff3..6dc9fc01b0 100644 --- a/src/components/views/dialogs/InfoDialog.js +++ b/src/components/views/dialogs/InfoDialog.js @@ -27,7 +27,7 @@ export default class InfoDialog extends React.Component { className: PropTypes.string, title: PropTypes.string, description: PropTypes.node, - button: PropTypes.string, + button: PropTypes.oneOfType(PropTypes.string, PropTypes.bool), onFinished: PropTypes.func, hasCloseButton: PropTypes.bool, onKeyDown: PropTypes.func, @@ -60,11 +60,11 @@ export default class InfoDialog extends React.Component { <div className={classNames("mx_Dialog_content", this.props.className)} id="mx_Dialog_content"> { this.props.description } </div> - <DialogButtons primaryButton={this.props.button || _t('OK')} + { this.props.button !== false && <DialogButtons primaryButton={this.props.button || _t('OK')} onPrimaryButtonClick={this.onFinished} hasCancel={false} > - </DialogButtons> + </DialogButtons> } </BaseDialog> ); } diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index f94798433f..04d6c02208 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -23,7 +23,27 @@ import SpaceStore from "../../../stores/SpaceStore"; import NotificationBadge from "../rooms/NotificationBadge"; import {RovingAccessibleButton} from "../../../accessibility/roving/RovingAccessibleButton"; import {RovingAccessibleTooltipButton} from "../../../accessibility/roving/RovingAccessibleTooltipButton"; +import IconizedContextMenu, { + IconizedContextMenuOption, + IconizedContextMenuOptionList, +} from "../context_menus/IconizedContextMenu"; +import {_t} from "../../../languageHandler"; +import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton"; +import {toRightOf} from "../../structures/ContextMenu"; +import {shouldShowSpaceSettings, showCreateNewRoom, showSpaceSettings} from "../../../utils/space"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import {ButtonEvent} from "../elements/AccessibleButton"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import Modal from "../../../Modal"; +import SpacePublicShare from "./SpacePublicShare"; +import {Action} from "../../../dispatcher/actions"; +import RoomViewStore from "../../../stores/RoomViewStore"; +import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; +import {showRoomInviteDialog} from "../../../RoomInvite"; +import InfoDialog from "../dialogs/InfoDialog"; +import {EventType} from "matrix-js-sdk/src/@types/event"; +import SpaceRoomDirectory from "../../structures/SpaceRoomDirectory"; interface IItemProps { space?: Room; @@ -78,6 +98,200 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> { SpaceStore.instance.setActiveSpace(this.props.space); }; + private onMenuOpenClick = (ev: React.MouseEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + const target = ev.target as HTMLButtonElement; + this.setState({contextMenuPosition: target.getBoundingClientRect()}); + }; + + private onMenuClose = () => { + this.setState({contextMenuPosition: null}); + }; + + private onHomeClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + defaultDispatcher.dispatch({ + action: "view_room", + room_id: this.props.space.roomId, + }); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onInviteClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + if (this.props.space.getJoinRule() === "public") { + const modal = Modal.createTrackedDialog("Space Invite", "User Menu", InfoDialog, { + title: _t("Invite members"), + description: <React.Fragment> + <span>{ _t("Share your public space") }</span> + <SpacePublicShare space={this.props.space} onFinished={() => modal.close()} /> + </React.Fragment>, + fixedWidth: false, + button: false, + className: "mx_SpacePanel_sharePublicSpace", + hasCloseButton: true, + }); + } else { + showRoomInviteDialog(this.props.space.roomId); + } + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onSettingsClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + showSpaceSettings(this.context, this.props.space); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onLeaveClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + defaultDispatcher.dispatch({ + action: "leave_room", + room_id: this.props.space.roomId, + }); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onNewRoomClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + showCreateNewRoom(this.context, this.props.space); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onMembersClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + if (!RoomViewStore.getRoomId()) { + defaultDispatcher.dispatch({ + action: "view_room", + room_id: this.props.space.roomId, + }, true); + } + + defaultDispatcher.dispatch<SetRightPanelPhasePayload>({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.SpaceMemberList, + refireParams: { space: this.props.space }, + }); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onExploreRoomsClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + Modal.createTrackedDialog("Space room directory", "Space panel", SpaceRoomDirectory, { + space: this.props.space, + }, "mx_SpaceRoomDirectory_dialogWrapper", false, true); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private renderContextMenu(): React.ReactElement { + let contextMenu = null; + if (this.state.contextMenuPosition) { + const userId = this.context.getUserId(); + + let inviteOption; + if (this.props.space.canInvite(userId)) { + inviteOption = ( + <IconizedContextMenuOption + className="mx_SpacePanel_contextMenu_inviteButton" + iconClassName="mx_SpacePanel_iconInvite" + label={_t("Invite people")} + onClick={this.onInviteClick} + /> + ); + } + + let settingsOption; + let leaveSection; + if (shouldShowSpaceSettings(this.context, this.props.space)) { + settingsOption = ( + <IconizedContextMenuOption + iconClassName="mx_SpacePanel_iconSettings" + label={_t("Settings")} + onClick={this.onSettingsClick} + /> + ); + } else { + leaveSection = <IconizedContextMenuOptionList red first> + <IconizedContextMenuOption + iconClassName="mx_SpacePanel_iconLeave" + label={_t("Leave space")} + onClick={this.onLeaveClick} + /> + </IconizedContextMenuOptionList>; + } + + let newRoomOption; + if (this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) { + newRoomOption = ( + <IconizedContextMenuOption + iconClassName="mx_SpacePanel_iconPlus" + label={_t("New room")} + onClick={this.onNewRoomClick} + /> + ); + } + + contextMenu = <IconizedContextMenu + {...toRightOf(this.state.contextMenuPosition, 0)} + onFinished={this.onMenuClose} + className="mx_SpacePanel_contextMenu" + compact + > + <div className="mx_SpacePanel_contextMenu_header"> + { this.props.space.name } + </div> + <IconizedContextMenuOptionList first> + { inviteOption } + <IconizedContextMenuOption + iconClassName="mx_SpacePanel_iconHome" + label={_t("Space Home")} + onClick={this.onHomeClick} + /> + <IconizedContextMenuOption + iconClassName="mx_SpacePanel_iconMembers" + label={_t("Members")} + onClick={this.onMembersClick} + /> + { settingsOption } + <IconizedContextMenuOption + iconClassName="mx_SpacePanel_iconExplore" + label={_t("Explore rooms")} + onClick={this.onExploreRoomsClick} + /> + { newRoomOption } + </IconizedContextMenuOptionList> + { leaveSection } + </IconizedContextMenu>; + } + + return ( + <React.Fragment> + <ContextMenuTooltipButton + className="mx_SpaceButton_menuButton" + onClick={this.onMenuOpenClick} + title={_t("Space options")} + isExpanded={!!this.state.contextMenuPosition} + /> + { contextMenu } + </React.Fragment> + ); + } + render() { const {space, activeSpaces, isNested} = this.props; @@ -133,6 +347,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> { <div className="mx_SpaceButton_selectionWrapper"> <RoomAvatar width={avatarSize} height={avatarSize} room={space} /> { notifBadge } + { this.renderContextMenu() } </div> </RovingAccessibleTooltipButton> ); @@ -149,6 +364,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> { <RoomAvatar width={avatarSize} height={avatarSize} room={space} /> <span className="mx_SpaceButton_name">{ space.name }</span> { notifBadge } + { this.renderContextMenu() } </div> </RovingAccessibleButton> ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6d2f41ceae..19324e1540 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1003,6 +1003,16 @@ "Failed to copy": "Failed to copy", "Share invite link": "Share invite link", "Invite by email or username": "Invite by email or username", + "Invite members": "Invite members", + "Share your public space": "Share your public space", + "Invite people": "Invite people", + "Settings": "Settings", + "Leave space": "Leave space", + "New room": "New room", + "Space Home": "Space Home", + "Members": "Members", + "Explore rooms": "Explore rooms", + "Space options": "Space options", "Remove": "Remove", "This bridge was provisioned by <user />.": "This bridge was provisioned by <user />.", "This bridge is managed by <user />.": "This bridge is managed by <user />.", @@ -1583,7 +1593,6 @@ "Favourited": "Favourited", "Favourite": "Favourite", "Low Priority": "Low Priority", - "Settings": "Settings", "Leave Room": "Leave Room", "Room options": "Room options", "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", @@ -1672,7 +1681,6 @@ "The homeserver the user you’re verifying is connected to": "The homeserver the user you’re verifying is connected to", "Yours, or the other users’ internet connection": "Yours, or the other users’ internet connection", "Yours, or the other users’ session": "Yours, or the other users’ session", - "Members": "Members", "Room Info": "Room Info", "You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets", "Unpin": "Unpin", @@ -2510,13 +2518,11 @@ "Explore Public Rooms": "Explore Public Rooms", "Create a Group Chat": "Create a Group Chat", "Upgrade to %(hostSignupBrand)s": "Upgrade to %(hostSignupBrand)s", - "Explore rooms": "Explore rooms", "Failed to reject invitation": "Failed to reject invitation", "Cannot create rooms in this community": "Cannot create rooms in this community", "You do not have permission to create rooms in this community.": "You do not have permission to create rooms in this community.", "This space is not public. You will not be able to rejoin without an invite.": "This space is not public. You will not be able to rejoin without an invite.", "This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.", - "Leave space": "Leave space", "Are you sure you want to leave the space '%(spaceName)s'?": "Are you sure you want to leave the space '%(spaceName)s'?", "Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?", "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", @@ -2592,7 +2598,6 @@ "Manage rooms": "Manage rooms", "Find a room...": "Find a room...", "Accept Invite": "Accept Invite", - "Invite people": "Invite people", "Add existing rooms & spaces": "Add existing rooms & spaces", "%(count)s members|other": "%(count)s members", "%(count)s members|one": "%(count)s member", @@ -2607,7 +2612,6 @@ "Failed to create initial space rooms": "Failed to create initial space rooms", "Skip for now": "Skip for now", "Creating rooms...": "Creating rooms...", - "Share your public space": "Share your public space", "At the moment only you can see it.": "At the moment only you can see it.", "Finish": "Finish", "Who are you working with?": "Who are you working with?", From 43cc7deedae82893a96f22493a0e360399f12968 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 14:37:28 +0000 Subject: [PATCH 53/59] Show hierarchy of auto_join rooms in the space view --- res/css/structures/_SpaceRoomView.scss | 8 +++ src/components/structures/SpaceRoomView.tsx | 59 ++++++++++++++++++++- src/i18n/strings/en_EN.json | 2 + 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index ee60389c59..38310d39a9 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -219,6 +219,14 @@ $SpaceRoomViewInnerWidth: 428px; } } } + + .mx_SpaceRoomDirectory_list { + max-width: 600px; + + .mx_SpaceRoomDirectory_roomTile_actions { + display: none; + } + } } .mx_SpaceRoomView_privateScope { diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index f1a8a4d71b..5c91efc1c0 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React, {RefObject, useContext, useRef, useState} from "react"; -import {EventType} 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 MatrixClientContext from "../../contexts/MatrixClientContext"; @@ -24,6 +24,7 @@ import {_t} from "../../languageHandler"; import AccessibleButton from "../views/elements/AccessibleButton"; import RoomName from "../views/elements/RoomName"; import RoomTopic from "../views/elements/RoomTopic"; +import InlineSpinner from "../views/elements/InlineSpinner"; import FormButton from "../views/elements/FormButton"; import {inviteMultipleToRoom, showRoomInviteDialog} from "../../RoomInvite"; import {useRoomMembers} from "../../hooks/useRoomMembers"; @@ -47,7 +48,12 @@ import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanel import {useStateArray} from "../../hooks/useStateArray"; import SpacePublicShare from "../views/spaces/SpacePublicShare"; import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space"; +import {HierarchyLevel, ISpaceSummaryEvent, ISpaceSummaryRoom, showRoom} from "./SpaceRoomDirectory"; +import {useAsyncMemo} from "../../hooks/useAsyncMemo"; +import {EnhancedMap} from "../../utils/maps"; +import AutoHideScrollbar from "./AutoHideScrollbar"; import MemberAvatar from "../views/avatars/MemberAvatar"; +import {useStateToggle} from "../../hooks/useStateToggle"; interface IProps { space: Room; @@ -121,13 +127,15 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => const canAddRooms = myMembership === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId); + const [_, forceUpdate] = useStateToggle(false); // TODO + let addRoomButtons; if (canAddRooms) { addRoomButtons = <React.Fragment> <AccessibleButton className="mx_SpaceRoomView_landing_addButton" onClick={async () => { const [added] = await showAddExistingRooms(cli, space); if (added) { - // TODO update rooms shown once we show hierarchy here + forceUpdate(); } }}> { _t("Add existing rooms & spaces") } @@ -149,6 +157,51 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => </AccessibleButton>; } + const [loading, roomsMap, relations, numRooms] = useAsyncMemo(async () => { + try { + const data = await cli.getSpaceSummary(space.roomId, undefined, myMembership !== "join"); + + const parentChildRelations = new EnhancedMap<string, string[]>(); + data.events.map((ev: ISpaceSummaryEvent) => { + if (ev.type === EventType.SpaceChild) { + parentChildRelations.getOrCreate(ev.room_id, []).push(ev.state_key); + } + }); + + const roomsMap = new Map<string, ISpaceSummaryRoom>(data.rooms.map(r => [r.room_id, r])); + const numRooms = data.rooms.filter(r => r.room_type !== RoomType.Space).length; + return [false, roomsMap, parentChildRelations, numRooms]; + } catch (e) { + console.error(e); // TODO + } + + return [false]; + }, [space, _], [true]); + + let previewRooms; + if (roomsMap) { + previewRooms = <AutoHideScrollbar className="mx_SpaceRoomDirectory_list"> + <div className="mx_SpaceRoomDirectory_roomCount"> + <h3>{ myMembership === "join" ? _t("Rooms") : _t("Default Rooms")}</h3> + <span>{ numRooms }</span> + </div> + <HierarchyLevel + spaceId={space.roomId} + rooms={roomsMap} + editing={false} + relations={relations} + parents={new Set()} + onPreviewClick={roomId => { + showRoom(roomsMap.get(roomId), [], false); // TODO + }} + /> + </AutoHideScrollbar>; + } else if (loading) { + previewRooms = <InlineSpinner />; + } else { + previewRooms = <p>{_t("Your server does not support showing space hierarchies.")}</p>; + } + return <div className="mx_SpaceRoomView_landing"> <RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} /> <div className="mx_SpaceRoomView_landing_name"> @@ -213,6 +266,8 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => { addRoomButtons } { settingsButton } </div> + + { previewRooms } </div>; }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 19324e1540..8609af6a71 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2599,6 +2599,8 @@ "Find a room...": "Find a room...", "Accept Invite": "Accept Invite", "Add existing rooms & spaces": "Add existing rooms & spaces", + "Default Rooms": "Default Rooms", + "Your server does not support showing space hierarchies.": "Your server does not support showing space hierarchies.", "%(count)s members|other": "%(count)s members", "%(count)s members|one": "%(count)s member", "<inviter/> invited you to <name/>": "<inviter/> invited you to <name/>", From 85985db441da38bf3872f6483d15758af49f1b15 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 3 Mar 2021 11:50:41 +0000 Subject: [PATCH 54/59] add comment --- res/css/views/rooms/_MemberList.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index 631ddc484f..075e9ff585 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -46,6 +46,8 @@ limitations under the License. } .mx_RightPanel_scopeHeader { + // vertically align with position on other right panel cards + // to prevent it bouncing as user navigates right panel margin-top: -8px; } } From 73411fa53dd9b8a2f3b9300779ee1bfe45c8c0f9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 3 Mar 2021 13:42:44 +0000 Subject: [PATCH 55/59] tidy code style --- src/components/structures/SpaceRoomDirectory.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index 7f7b9dbb99..06df6a528e 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -157,10 +157,12 @@ const SubSpace: React.FC<ISubspaceProps> = ({ let url: string; if (space.avatar_url) { - url = MatrixClientPeg.get().mxcUrlToHttp(space.avatar_url, + url = MatrixClientPeg.get().mxcUrlToHttp( + space.avatar_url, Math.floor(24 * window.devicePixelRatio), Math.floor(24 * window.devicePixelRatio), - "crop"); + "crop", + ); } return <div className="mx_SpaceRoomDirectory_subspace"> @@ -262,10 +264,12 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli let url: string; if (room.avatar_url) { - url = cli.mxcUrlToHttp(room.avatar_url, + url = cli.mxcUrlToHttp( + room.avatar_url, Math.floor(32 * window.devicePixelRatio), Math.floor(32 * window.devicePixelRatio), - "crop"); + "crop", + ); } const content = <React.Fragment> From d8483ddf0de4a08074a3c59ed0eff71da1390e74 Mon Sep 17 00:00:00 2001 From: David Baker <dave@matrix.org> Date: Wed, 3 Mar 2021 20:23:21 +0000 Subject: [PATCH 56/59] Don't place another call if there's already one ongoing The 'call' button doesn't turn into a hangup button as soon as there's a call in the room, but we should have been doing this anyway. --- src/CallHandler.tsx | 8 ++++++++ src/i18n/strings/en_EN.json | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 42a38c7a54..8621f441de 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -706,6 +706,14 @@ export default class CallHandler { return; } + if (this.getCallForRoom(room.roomId)) { + Modal.createTrackedDialog('Call Handler', 'Existing Call with user', ErrorDialog, { + title: _t('Already in call'), + description: _t("You're already in a call with this person."), + }); + return; + } + const members = room.getJoinedMembers(); if (members.length <= 1) { Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fa7f446b7c..a1999acb3b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -58,6 +58,8 @@ "You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.", "Too Many Calls": "Too Many Calls", "You've reached the maximum number of simultaneous calls.": "You've reached the maximum number of simultaneous calls.", + "Already in call": "Already in call", + "You're already in a call with this person.": "You're already in a call with this person.", "You cannot place a call with yourself.": "You cannot place a call with yourself.", "Call in Progress": "Call in Progress", "A call is currently being placed!": "A call is currently being placed!", From 1cb19554eb3a151706b6b964f0c42f61b58c7a23 Mon Sep 17 00:00:00 2001 From: Ayush Kumar <2580ayush2580@gmail.com> Date: Thu, 4 Mar 2021 14:03:02 +0530 Subject: [PATCH 57/59] Fix Bottom border of state counters is white on the dark theme --- res/css/views/rooms/_AuxPanel.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_AuxPanel.scss b/res/css/views/rooms/_AuxPanel.scss index 34ef5e01d4..17a6294bf0 100644 --- a/res/css/views/rooms/_AuxPanel.scss +++ b/res/css/views/rooms/_AuxPanel.scss @@ -17,7 +17,7 @@ limitations under the License. .m_RoomView_auxPanel_stateViews { padding: 5px; padding-left: 19px; - border-bottom: 1px solid #e5e5e5; + border-bottom: 1px solid $primary-hairline-color; } .m_RoomView_auxPanel_stateViews_span a { From f2d2a048e184f597acd5d605c83944e63ecdecb1 Mon Sep 17 00:00:00 2001 From: Jason Robinson <mail@jasonrobinson.me> Date: Thu, 4 Mar 2021 11:07:14 +0200 Subject: [PATCH 58/59] Ensure HostSignupDialog border colour matches light theme Ensure dialog borders are always white as the HostSignupDialog does not yet support dark mode or theming in general. In the future we might want to pass the theme to the called iframe, should some hosting provider have that need. --- res/css/views/dialogs/_HostSignupDialog.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/dialogs/_HostSignupDialog.scss b/res/css/views/dialogs/_HostSignupDialog.scss index 1378ac9053..ac4bc41951 100644 --- a/res/css/views/dialogs/_HostSignupDialog.scss +++ b/res/css/views/dialogs/_HostSignupDialog.scss @@ -19,6 +19,11 @@ limitations under the License. max-width: 580px; height: 80vh; max-height: 600px; + // Ensure dialog borders are always white as the HostSignupDialog + // does not yet support dark mode or theming in general. + // In the future we might want to pass the theme to the called + // iframe, should some hosting provider have that need. + background-color: #ffffff; .mx_HostSignupDialog_info { text-align: center; From 1c1d239d5b0853e2b6ee56f823020655d2a68a2b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" <jryans@gmail.com> Date: Fri, 5 Mar 2021 13:19:06 +0000 Subject: [PATCH 59/59] Add Edge to the targets list Part of https://github.com/vector-im/element-web/issues/9175 --- babel.config.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/babel.config.js b/babel.config.js index d5a97d56ce..0a3a34a391 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,12 +3,15 @@ module.exports = { "presets": [ ["@babel/preset-env", { "targets": [ - "last 2 Chrome versions", "last 2 Firefox versions", "last 2 Safari versions" + "last 2 Chrome versions", + "last 2 Firefox versions", + "last 2 Safari versions", + "last 2 Edge versions", ], }], "@babel/preset-typescript", "@babel/preset-flow", - "@babel/preset-react" + "@babel/preset-react", ], "plugins": [ ["@babel/plugin-proposal-decorators", {legacy: true}], @@ -18,6 +21,6 @@ module.exports = { "@babel/plugin-proposal-object-rest-spread", "@babel/plugin-transform-flow-comments", "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-transform-runtime" - ] + "@babel/plugin-transform-runtime", + ], };