From 11e349d6c8cc8416dc329bc3d077cf0d12e6eb91 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Aug 2020 10:46:01 +0100 Subject: [PATCH 01/34] Update e2e iconography --- res/img/e2e/disabled.svg | 5 +++++ res/img/e2e/normal.svg | 4 ++-- res/img/e2e/verified.svg | 4 ++-- res/img/e2e/warning.svg | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 res/img/e2e/disabled.svg diff --git a/res/img/e2e/disabled.svg b/res/img/e2e/disabled.svg new file mode 100644 index 0000000000..2f6110a36a --- /dev/null +++ b/res/img/e2e/disabled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/e2e/normal.svg b/res/img/e2e/normal.svg index 23ca78e44d..83b544a326 100644 --- a/res/img/e2e/normal.svg +++ b/res/img/e2e/normal.svg @@ -1,3 +1,3 @@ - - + + diff --git a/res/img/e2e/verified.svg b/res/img/e2e/verified.svg index ac4827baed..f90d9db554 100644 --- a/res/img/e2e/verified.svg +++ b/res/img/e2e/verified.svg @@ -1,3 +1,3 @@ - - + + diff --git a/res/img/e2e/warning.svg b/res/img/e2e/warning.svg index d42922892a..58f5c3b7d1 100644 --- a/res/img/e2e/warning.svg +++ b/res/img/e2e/warning.svg @@ -1,3 +1,3 @@ - - + + From 25273442949ab1a6099b4ba5a574ba413959b8b8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Aug 2020 10:50:27 +0100 Subject: [PATCH 02/34] Create name/title Widget utils --- src/components/views/elements/PersistentApp.js | 2 +- src/components/views/rooms/AppsDrawer.js | 2 +- src/i18n/strings/en_EN.json | 1 + src/utils/WidgetUtils.js | 11 +++++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/PersistentApp.js b/src/components/views/elements/PersistentApp.js index bdf5f60234..686739a9f7 100644 --- a/src/components/views/elements/PersistentApp.js +++ b/src/components/views/elements/PersistentApp.js @@ -76,7 +76,7 @@ export default class PersistentApp extends React.Component { userId={MatrixClientPeg.get().credentials.userId} show={true} creatorUserId={app.creatorUserId} - widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''} + widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} waitForIframeLoad={app.waitForIframeLoad} whitelistCapabilities={capWhitelist} showDelete={false} diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index fca46b453f..1eca493e14 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -171,7 +171,7 @@ export default class AppsDrawer extends React.Component { userId={this.props.userId} show={this.props.showApps} creatorUserId={app.creatorUserId} - widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''} + widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} waitForIframeLoad={app.waitForIframeLoad} whitelistCapabilities={capWhitelist} />); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 47063bdae4..54a4bd6695 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -387,6 +387,7 @@ "Common names and surnames are easy to guess": "Common names and surnames are easy to guess", "Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess", "Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess", + "Unknown App": "Unknown App", "Help us improve %(brand)s": "Help us improve %(brand)s", "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.": "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.", "I want to help": "I want to help", diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index be176d042f..d4ed093a24 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -32,6 +32,7 @@ import {Capability} from "../widgets/WidgetApi"; import {Room} from "matrix-js-sdk/src/models/room"; import {WidgetType} from "../widgets/WidgetType"; import {objectClone} from "./objects"; +import {_t} from "../languageHandler"; export default class WidgetUtils { /* Returns true if user is able to send state events to modify widgets in this room @@ -486,4 +487,14 @@ export default class WidgetUtils { IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id); } } + + static getWidgetName(app) { + if (!app || !app.name) return ""; + return app.name.trim() || _t("Unknown App"); + } + + static getWidgetDataTitle(app) { + if (!app || !app.data || !app.data.title) return ""; + return app.data.title.trim(); + } } From 8f94a42dafcf30cf4776c2a6802ce94275034830 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Aug 2020 10:52:04 +0100 Subject: [PATCH 03/34] Update Right Panel phase recall behaviour --- src/components/structures/RightPanel.js | 6 +----- src/stores/RightPanelStore.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 11416b29fb..b2b1ceae00 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -47,10 +47,10 @@ export default class RightPanel extends React.Component { constructor(props, context) { super(props, context); this.state = { + ...RightPanelStore.getSharedInstance().roomPanelPhaseParams, phase: this._getPhaseFromProps(), isUserPrivilegedInGroup: null, member: this._getUserForPanel(), - verificationRequest: RightPanelStore.getSharedInstance().roomPanelPhaseParams.verificationRequest, }; this.onAction = this.onAction.bind(this); this.onRoomStateMember = this.onRoomStateMember.bind(this); @@ -102,10 +102,6 @@ export default class RightPanel extends React.Component { } return RightPanelPhases.RoomMemberInfo; } else { - if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.roomPanelPhase)) { - dis.dispatch({action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList}); - return RightPanelPhases.RoomMemberList; - } return rps.roomPanelPhase; } } diff --git a/src/stores/RightPanelStore.ts b/src/stores/RightPanelStore.ts index 34445d007b..c539fcdb40 100644 --- a/src/stores/RightPanelStore.ts +++ b/src/stores/RightPanelStore.ts @@ -33,6 +33,8 @@ interface RightPanelStoreState { lastRoomPhase: RightPanelPhases; lastGroupPhase: RightPanelPhases; + previousPhase?: RightPanelPhases; + // Extra information about the last phase lastRoomPhaseParams: {[key: string]: any}; } @@ -89,6 +91,10 @@ export default class RightPanelStore extends Store { return this.state.lastGroupPhase; } + get previousPhase(): RightPanelPhases | null { + return RIGHT_PANEL_PHASES_NO_ARGS.includes(this.state.previousPhase) ? this.state.previousPhase : null; + } + get visibleRoomPanelPhase(): RightPanelPhases { return this.isOpenForRoom ? this.roomPanelPhase : null; } @@ -176,23 +182,27 @@ export default class RightPanelStore extends Store { if (targetPhase === this.state.lastGroupPhase) { this.setState({ showGroupPanel: !this.state.showGroupPanel, + previousPhase: null, }); } else { this.setState({ lastGroupPhase: targetPhase, showGroupPanel: true, + previousPhase: this.state.lastGroupPhase, }); } } else { if (targetPhase === this.state.lastRoomPhase && !refireParams) { this.setState({ showRoomPanel: !this.state.showRoomPanel, + previousPhase: null, }); } else { this.setState({ lastRoomPhase: targetPhase, showRoomPanel: true, lastRoomPhaseParams: refireParams || {}, + previousPhase: this.state.lastRoomPhase, }); } } From 89a836100d62842c1af721749b83c0bfaa90d680 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 1 Sep 2020 14:32:21 +0100 Subject: [PATCH 04/34] small css tweaks to closer match the figma --- res/css/structures/_RightPanel.scss | 16 +++++++--------- res/css/structures/_UserMenu.scss | 3 +-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index c7c0d6fac4..7e5ab7cdbd 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -95,15 +95,7 @@ limitations under the License. mask-position: center; } -.mx_RightPanel_headerButton_highlight { - background: rgba($accent-color, 0.25); - // make the icon the accent color too - &::before { - background-color: $accent-color !important; - } -} - -.mx_RightPanel_headerButton:not(.mx_RightPanel_headerButton_highlight) { +.mx_RightPanel_headerButton { &:hover { background: rgba($accent-color, 0.1); @@ -113,6 +105,12 @@ limitations under the License. } } +.mx_RightPanel_headerButton_highlight { + &::before { + background-color: $accent-color !important; + } +} + .mx_RightPanel_headerButton_badge { font-size: $font-8px; border-radius: 8px; diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index 6fa2f2578e..fecac40e4e 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -15,7 +15,6 @@ limitations under the License. */ .mx_UserMenu { - // to make the menu button sort of aligned with the explore button below padding-right: 6px; @@ -59,7 +58,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $primary-fg-color; + background: $tertiary-fg-color; mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } } From eb7f6f4c4b5036e1c0e46babb9a3e3fafeb65963 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Sep 2020 16:19:42 +0100 Subject: [PATCH 05/34] Create new WidgetStore to track all widgets stuff --- src/@types/global.d.ts | 2 + src/stores/WidgetStore.ts | 191 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 src/stores/WidgetStore.ts diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 1a361e7b55..de3eb5e767 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -29,6 +29,7 @@ import {ActiveRoomObserver} from "../ActiveRoomObserver"; import {Notifier} from "../Notifier"; import type {Renderer} from "react-dom"; import RightPanelStore from "../stores/RightPanelStore"; +import {WidgetStore} from "../stores/WidgetStore"; declare global { interface Window { @@ -51,6 +52,7 @@ declare global { mxSettingsStore: SettingsStore; mxNotifier: typeof Notifier; mxRightPanelStore: RightPanelStore; + mxWidgetStore: WidgetStore; } interface Document { diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts new file mode 100644 index 0000000000..b31fc99515 --- /dev/null +++ b/src/stores/WidgetStore.ts @@ -0,0 +1,191 @@ +/* +Copyright 2020 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 { Room } from "matrix-js-sdk/src/models/room"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +import { ActionPayload } from "../dispatcher/payloads"; +import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; +import defaultDispatcher from "../dispatcher/dispatcher"; +import SettingsStore from "../settings/SettingsStore"; +import WidgetEchoStore from "../stores/WidgetEchoStore"; +import WidgetUtils from "../utils/WidgetUtils"; +import {IRRELEVANT_ROOM} from "../settings/WatchManager"; +import {SettingLevel} from "../settings/SettingLevel"; + +interface IState {} + +export interface IApp { + id: string; + type: string; + roomId: string; + eventId: string; + creatorUserId: string; + waitForIframeLoad?: boolean; + // eslint-disable-next-line camelcase + avatar_url: string; // MSC2765 https://github.com/matrix-org/matrix-doc/pull/2765 +} + +interface IRoomWidgets { + widgets: IApp[]; + pinned: Set; +} + +// TODO consolidate WidgetEchoStore into this +// TODO consolidate ActiveWidgetStore into this +export class WidgetStore extends AsyncStoreWithClient { + private static internalInstance = new WidgetStore(); + + private widgetMap = new Map(); + private roomMap = new Map(); + + private constructor() { + super(defaultDispatcher, {}); + + SettingsStore.watchSetting("Widgets.pinned", IRRELEVANT_ROOM, this.onPinnedWidgetsChange); + WidgetEchoStore.on("update", this.onWidgetEchoStoreUpdate); + } + + public static get instance(): WidgetStore { + return WidgetStore.internalInstance; + } + + private initRoom(roomId: string) { + if (!this.roomMap.has(roomId)) { + this.roomMap.set(roomId, { + pinned: new Set(), + widgets: [], + }); + } + } + + protected async onReady(): Promise { + this.matrixClient.on("RoomState.events", this.onRoomStateEvents); + this.matrixClient.getRooms().forEach((room: Room) => { + const pinned = SettingsStore.getValue("Widgets.pinned", room.roomId); + + if (pinned || WidgetUtils.getRoomWidgets(room).length) { + this.initRoom(room.roomId); + } + + if (pinned) { + this.getRoom(room.roomId).pinned = new Set(pinned); + } + + this.loadRoomWidgets(room); + }); + this.emit("update"); + } + + protected async onNotReady(): Promise { + this.matrixClient.off("RoomState.events", this.onRoomStateEvents); + this.widgetMap = new Map(); + this.roomMap = new Map(); + await this.reset({}); + } + + // We don't need this, but our contract says we do. + protected async onAction(payload: ActionPayload) { + return; + } + + private onWidgetEchoStoreUpdate(roomId: string, widgetId: string) { + this.initRoom(roomId); + this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); + this.emit("update"); + } + + private generateApps(room: Room): IApp[] { + return WidgetEchoStore.getEchoedRoomWidgets(room.roomId, WidgetUtils.getRoomWidgets(room)).map((ev) => { + return WidgetUtils.makeAppConfig( + ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(), + ); + }); + } + + private loadRoomWidgets(room: Room) { + const roomInfo = this.roomMap.get(room.roomId); + roomInfo.widgets = []; + this.generateApps(room).forEach(app => { + this.widgetMap.set(app.id, app); + roomInfo.widgets.push(app); + }); + this.emit(room.roomId); + } + + private onRoomStateEvents(ev: MatrixEvent) { + if (ev.getType() !== "im.vector.modular.widgets") return; + const roomId = ev.getRoomId(); + this.initRoom(roomId); + this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); + this.emit("update"); + } + + public getRoomId = (widgetId: string) => { + const app = this.widgetMap.get(widgetId); + if (!app) return null; + return app.roomId; + } + + public getRoom = (roomId: string) => { + return this.roomMap.get(roomId); + }; + + private onPinnedWidgetsChange = (settingName: string, roomId: string) => { + const pinned = SettingsStore.getValue(settingName, roomId); + this.initRoom(roomId); + this.getRoom(roomId).pinned = new Set(pinned); + this.emit(roomId); + this.emit("update"); + }; + + public isPinned(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + // TODO heuristic for Jitsi etc + return roomInfo ? roomInfo.pinned.has(widgetId) : false; + } + + public pinWidget(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + if (!roomInfo) return; + roomInfo.pinned.add(widgetId); + SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); + this.emit(roomId); + this.emit("update"); + } + + public unpinWidget(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + if (!roomInfo) return; + roomInfo.pinned.delete(widgetId); + SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); + this.emit(roomId); + this.emit("update"); + } + + public getApps(room: Room, pinned?: boolean): IApp[] { + const apps = this.getRoom(room.roomId).widgets; + if (pinned) { + return apps.filter(app => this.isPinned(app.id)); + } + return apps + } +} + +window.mxWidgetStore = WidgetStore.instance; From 31cca5e0f2a4b8249eb3cc5b48409781b087b2ca Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 08:48:03 +0100 Subject: [PATCH 06/34] Create new right panel cards --- res/css/_components.scss | 3 + res/css/views/right_panel/_BaseCard.scss | 164 +++++++++++++ .../views/right_panel/_RoomSummaryCard.scss | 157 ++++++++++++ res/css/views/right_panel/_WidgetCard.scss | 25 ++ res/css/views/rooms/_RoomHeader.scss | 12 - src/components/structures/RightPanel.js | 41 +++- src/components/views/right_panel/BaseCard.tsx | 93 +++++++ .../views/right_panel/HeaderButtons.tsx | 3 +- .../views/right_panel/RoomHeaderButtons.tsx | 43 ++-- .../views/right_panel/RoomSummaryCard.tsx | 231 ++++++++++++++++++ .../views/right_panel/WidgetCard.tsx | 107 ++++++++ .../payloads/SetRightPanelPhasePayload.ts | 1 + src/settings/Settings.ts | 4 + src/stores/RightPanelStorePhases.ts | 3 + 14 files changed, 844 insertions(+), 43 deletions(-) create mode 100644 res/css/views/right_panel/_BaseCard.scss create mode 100644 res/css/views/right_panel/_RoomSummaryCard.scss create mode 100644 res/css/views/right_panel/_WidgetCard.scss create mode 100644 src/components/views/right_panel/BaseCard.tsx create mode 100644 src/components/views/right_panel/RoomSummaryCard.tsx create mode 100644 src/components/views/right_panel/WidgetCard.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 45ed6b3300..27ec1088c3 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -155,9 +155,12 @@ @import "./views/messages/_UnknownBody.scss"; @import "./views/messages/_ViewSourceEvent.scss"; @import "./views/messages/_common_CryptoEvent.scss"; +@import "./views/right_panel/_BaseCard.scss"; @import "./views/right_panel/_EncryptionInfo.scss"; +@import "./views/right_panel/_RoomSummaryCard.scss"; @import "./views/right_panel/_UserInfo.scss"; @import "./views/right_panel/_VerificationPanel.scss"; +@import "./views/right_panel/_WidgetCard.scss"; @import "./views/room_settings/_AliasSettings.scss"; @import "./views/rooms/_AppsDrawer.scss"; @import "./views/rooms/_Autocomplete.scss"; diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss new file mode 100644 index 0000000000..c66b7261e3 --- /dev/null +++ b/res/css/views/right_panel/_BaseCard.scss @@ -0,0 +1,164 @@ +/* +Copyright 2020 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_BaseCard { + padding: 0 8px; + overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; + + .mx_BaseCard_header { + margin: 8px 0; + + h2 { + margin: 0 44px; + font-size: $font-18px; + font-weight: $font-semi-bold; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .mx_BaseCard_back, .mx_BaseCard_close { + position: absolute; + background-color: rgba(141, 151, 165, 0.2); // TODO + height: 20px; + width: 20px; + margin: 12px; + top: 0; + + &::before { + content: ""; + position: absolute; + height: 16px; + width: 16px; + top: 2px; + left: 2px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $rightpanel-button-color; + } + } + + .mx_BaseCard_back { + border-radius: 4px; + left: 0; + + &::before { + transform: rotate(90deg); + mask-size: 20px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); // TODO + } + } + + .mx_BaseCard_close { + border-radius: 10px; + right: 0; + + &::before { + mask-image: url('$(res)/img/icons-close.svg'); // TODO + } + } + } + + .mx_AutoHideScrollbar { + // collapse the margin into a padding to move the scrollbar into the right gutter + margin-right: -8px; + padding-right: 8px; + min-height: 0; + width: 100%; + height: 100%; + } + + .mx_BaseCard_Group { + margin: 20px 0 16px; + + & > * { + margin-left: 10px; + margin-right: 10px; + } + + h1 { + color: $tertiary-fg-color; + font-size: $font-12px; + font-weight: 500; + } + + .mx_BaseCard_Button { + padding: 10px 38px 10px 12px; + margin: 0; + position: relative; + font-size: $font-13px; + height: 20px; + line-height: 20px; + border-radius: 8px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + &:hover { + background-color: rgba(141, 151, 165, 0.1); + } + + &::after { + content: ''; + position: absolute; + top: 10px; + right: 6px; + height: 20px; + width: 20px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $icon-button-color; + transform: rotate(270deg); + mask-size: 20px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); // TODO + } + } + } + + .mx_BaseCard_footer { + padding-top: 4px; + text-align: center; + + .mx_AccessibleButton_kind_secondary { + color: $secondary-fg-color; + background-color: rgba(141, 151, 165, 0.2); // TODO + font-weight: $font-semi-bold; + font-size: $font-14px; + min-width: 70px; + + & + .mx_AccessibleButton_kind_secondary { + margin-left: 16px; + } + } + } +} + +.mx_FilePanel, +.mx_UserInfo, +.mx_NotificationPanel, +.mx_MemberList { + &.mx_BaseCard { + padding: 32px 0 0; + + .mx_AutoHideScrollbar { + margin-right: unset; + padding-right: unset; + } + } +} diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss new file mode 100644 index 0000000000..3c46727348 --- /dev/null +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -0,0 +1,157 @@ +/* +Copyright 2020 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_RoomSummaryCard { + // shrink left gutter by 12 and instead add it as margin to all things except the buttons + // as their hover effect should go into the gutter + & > * { // TODO consolidate this as the standard effect + margin-left: 10px; + margin-right: 10px; + } + .mx_AutoHideScrollbar { + margin-left: 0; + } + + .mx_BaseCard_header { + text-align: center; + margin-top: 20px; + + h2 { + font-weight: $font-semi-bold; + font-size: $font-18px; + margin: 12px 0 4px; + } + + .mx_RoomSummaryCard_alias { + font-size: $font-13px; + color: $secondary-fg-color; + } + + h2, .mx_RoomSummaryCard_alias { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + + .mx_RoomSummaryCard_avatar { + display: inline-flex; + + .mx_RoomSummaryCard_e2ee { + display: inline-block; + position: relative; + width: 54px; + height: 54px; + border-radius: 50%; + background-color: #737D8C; + margin-top: -3px; // alignment + margin-left: -10px; // overlap + border: 3px solid $dark-panel-bg-color; + + &::before { + content: ''; + position: absolute; + top: 13px; + left: 13px; + height: 28px; + width: 28px; + mask-size: cover; + mask-repeat: no-repeat; + mask-position: center; + mask-image: url('$(res)/img/e2e/disabled.svg'); + background-color: #ffffff; + } + } + + .mx_RoomSummaryCard_e2ee_secure{ + background-color: #5ABFF2; + &::before { + mask-image: url('$(res)/img/e2e/normal.svg'); + } + } + } + } + + .mx_RoomSummaryCard_aboutGroup { + .mx_RoomSummaryCard_Button { + padding-left: 48px; + + &::before { + content: ''; + position: absolute; + top: 8px; + left: 8px; + height: 24px; + width: 24px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $icon-button-color; + } + } + } + + .mx_RoomSummaryCard_appsGroup { + .mx_RoomSummaryCard_Button { + padding-left: 10px; + color: $tertiary-fg-color; + + span { + color: $primary-fg-color; + } + + img { + vertical-align: top; + margin-right: 18px; + border-radius: 4px; + } + + &::before { + content: unset; + } + } + + .mx_RoomSummaryCard_icon_app_pinned::after { + mask-image: url('$(res)/img/element-icons/room/pin2.svg'); + background-color: $accent-color; + transform: unset; + } + } + + .mx_AccessibleButton_kind_link { + padding: 0; + margin-top: 12px; + margin-bottom: 12px; + font-size: $font-13px; + font-weight: $font-semi-bold; + } +} + +.mx_RoomSummaryCard_icon_people::before { + mask-image: url("$(res)/img/element-icons/room/members.svg"); +} + +.mx_RoomSummaryCard_icon_files::before { + mask-image: url('$(res)/img/element-icons/room/files.svg'); +} + +.mx_RoomSummaryCard_icon_share::before { + mask-image: url('$(res)/img/element-icons/room/share.svg'); +} + +.mx_RoomSummaryCard_icon_settings::before { + mask-image: url('$(res)/img/element-icons/settings.svg'); +} diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss new file mode 100644 index 0000000000..b863c53c33 --- /dev/null +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -0,0 +1,25 @@ +/* +Copyright 2020 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_WidgetCard { + height: 100%; + display: contents; + + .mx_AppTileFullWidth { + height: 100%; + border: 0; + } +} diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index a880a7bee2..d240877507 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -236,10 +236,6 @@ limitations under the License. } } -.mx_RoomHeader_settingsButton::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); -} - .mx_RoomHeader_forgetButton::before { mask-image: url('$(res)/img/element-icons/leave.svg'); width: 26px; @@ -249,14 +245,6 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/search-inset.svg'); } -.mx_RoomHeader_shareButton::before { - mask-image: url('$(res)/img/element-icons/room/share.svg'); -} - -.mx_RoomHeader_manageIntegsButton::before { - mask-image: url('$(res)/img/element-icons/room/integrations.svg'); -} - .mx_RoomHeader_showPanel { height: 16px; } diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index b2b1ceae00..57c0f46fad 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -32,6 +32,9 @@ import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPa import RightPanelStore from "../../stores/RightPanelStore"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import {Action} from "../../dispatcher/actions"; +import RoomSummaryCard from "../views/right_panel/RoomSummaryCard"; +import WidgetCard from "../views/right_panel/WidgetCard"; +import defaultDispatcher from "../../dispatcher/dispatcher"; export default class RightPanel extends React.Component { static get propTypes() { @@ -182,6 +185,7 @@ export default class RightPanel extends React.Component { event: payload.event, verificationRequest: payload.verificationRequest, verificationRequestPromise: payload.verificationRequestPromise, + widgetId: payload.widgetId, }); } } @@ -209,6 +213,14 @@ export default class RightPanel extends React.Component { } }; + onClose = () => { + // the RightPanelStore has no way of knowing which mode room/group it is in, so we handle closing here + defaultDispatcher.dispatch({ + action: Action.ToggleRightPanel, + type: this.props.groupId ? "group" : "room", + }); + }; + render() { const MemberList = sdk.getComponent('rooms.MemberList'); const UserInfo = sdk.getComponent('right_panel.UserInfo'); @@ -225,17 +237,24 @@ export default class RightPanel extends React.Component { switch (this.state.phase) { case RightPanelPhases.RoomMemberList: if (this.props.room.roomId) { - panel = ; + panel = ; } break; + case RightPanelPhases.GroupMemberList: if (this.props.groupId) { panel = ; } break; + case RightPanelPhases.GroupRoomList: panel = ; break; + case RightPanelPhases.RoomMemberInfo: case RightPanelPhases.EncryptionPanel: panel = ; break; + case RightPanelPhases.Room3pidMemberInfo: panel = ; break; + case RightPanelPhases.GroupMemberInfo: panel = ; break; + case RightPanelPhases.GroupRoomInfo: panel = ; break; + case RightPanelPhases.NotificationPanel: - panel = ; + panel = ; break; + case RightPanelPhases.FilePanel: - panel = ; + panel = ; + break; + + case RightPanelPhases.RoomSummary: + panel = ; + break; + + case RightPanelPhases.Widget: + panel = ; break; } diff --git a/src/components/views/right_panel/BaseCard.tsx b/src/components/views/right_panel/BaseCard.tsx new file mode 100644 index 0000000000..3e95da1bc1 --- /dev/null +++ b/src/components/views/right_panel/BaseCard.tsx @@ -0,0 +1,93 @@ +/* +Copyright 2020 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, {ReactNode} from 'react'; +import classNames from 'classnames'; + +import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; +import {_t} from "../../../languageHandler"; +import AccessibleButton from "../elements/AccessibleButton"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import {Action} from "../../../dispatcher/actions"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; + +interface IProps { + header?: ReactNode; + footer?: ReactNode; + className?: string; + withoutScrollContainer?: boolean; + previousPhase?: RightPanelPhases; + onClose?(): void; +} + +interface IGroupProps { + className?: string; + title: string; +} + +export const Group: React.FC = ({ className, title, children }) => { + return
+

{title}

+ {children} +
; +}; + +const BaseCard: React.FC = ({ + onClose, + className, + header, + footer, + withoutScrollContainer, + previousPhase, + children, +}) => { + let backButton; + if (previousPhase) { + const onBackClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: previousPhase, + }); + }; + backButton = ; + } + + let closeButton; + if (onClose) { + closeButton = ; + } + + if (!withoutScrollContainer) { + children = + { children } + ; + } + + return ( +
+
+ { backButton } + { closeButton } + { header } +
+ { children } + { footer &&
{ footer }
} +
+ ); +}; + +export default BaseCard; diff --git a/src/components/views/right_panel/HeaderButtons.tsx b/src/components/views/right_panel/HeaderButtons.tsx index e922959bbb..543c7c067f 100644 --- a/src/components/views/right_panel/HeaderButtons.tsx +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -96,8 +96,7 @@ export default abstract class HeaderButtons extends React.Component + return
{this.renderButtons()}
; } diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 7d732b8ae3..e19a440005 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -26,7 +26,10 @@ import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import {Action} from "../../../dispatcher/actions"; import {ActionPayload} from "../../../dispatcher/payloads"; -const MEMBER_PHASES = [ +const ROOM_INFO_PHASES = [ + RightPanelPhases.RoomSummary, + RightPanelPhases.Widget, + RightPanelPhases.FilePanel, RightPanelPhases.RoomMemberList, RightPanelPhases.RoomMemberInfo, RightPanelPhases.EncryptionPanel, @@ -54,20 +57,10 @@ export default class RoomHeaderButtons extends HeaderButtons { } } - private onMembersClicked = () => { - if (this.state.phase === RightPanelPhases.RoomMemberInfo) { - // send the active phase to trigger a toggle - // XXX: we should pass refireParams here but then it won't collapse as we desire it to - this.setPhase(RightPanelPhases.RoomMemberInfo); - } else { - // This toggles for us, if needed - this.setPhase(RightPanelPhases.RoomMemberList); - } - }; - - private onFilesClicked = () => { + // TODO make it restore whatever widget they were on last + private onRoomSummaryClicked = () => { // This toggles for us, if needed - this.setPhase(RightPanelPhases.FilePanel); + this.setPhase(RightPanelPhases.RoomSummary); }; private onNotificationsClicked = () => { @@ -77,19 +70,17 @@ export default class RoomHeaderButtons extends HeaderButtons { public renderButtons() { return [ - , - , - = ({ children, className, onClick }) => { + return + { children } + ; +}; + +export const useWidgets = (room: Room) => { + const [apps, setApps] = useState(WidgetStore.instance.getApps(room)); + + const updateApps = useCallback(() => { + // Copy the array so that we always trigger a re-render, as some updates mutate the array of apps/settings + setApps([...WidgetStore.instance.getApps(room)]); + }, [room]); + + useEffect(updateApps, [room]); + useEventEmitter(WidgetEchoStore, "update", updateApps); + useEventEmitter(WidgetStore.instance, room.roomId, updateApps); + + return apps; +}; + +const AppsSection: React.FC = ({ room }) => { + const cli = useContext(MatrixClientContext); + const apps = useWidgets(room); + + const onManageIntegrations = () => { + const managers = IntegrationManagers.sharedInstance(); + if (!managers.hasManager()) { + managers.openNoManagerDialog(); + } else { + if (SettingsStore.getValue("feature_many_integration_managers")) { + managers.openAll(room); + } else { + managers.getPrimaryManager().open(room); + } + } + }; + + return + { apps.map(app => { + const name = WidgetUtils.getWidgetName(app); + const dataTitle = WidgetUtils.getWidgetDataTitle(app); + const subtitle = dataTitle && " - " + dataTitle; + + let iconUrls = [require("../../../../res/img/element-icons/room/default_app.svg")]; + // heuristics for some better icons until Widgets support their own icons + if (app.type.includes("meeting") || app.type.includes("calendar")) { + iconUrls = [require("../../../../res/img/element-icons/room/default_cal.svg")]; + } else if (app.type.includes("pad") || app.type.includes("doc") || app.type.includes("calc")) { + iconUrls = [require("../../../../res/img/element-icons/room/default_doc.svg")]; + } else if (app.type.includes("clock")) { + iconUrls = [require("../../../../res/img/element-icons/room/default_clock.svg")]; + } + + if (app.avatar_url) { // MSC2765 + iconUrls.unshift(getHttpUriForMxc(cli.getHomeserverUrl(), app.avatar_url, 20, 20, "crop")); + } + + const isPinned = WidgetStore.instance.isPinned(app.id); + const classes = classNames("mx_RoomSummaryCard_icon_app", { + mx_RoomSummaryCard_icon_app_pinned: isPinned, + }); + + if (isPinned) { + const onClick = () => { + WidgetStore.instance.unpinWidget(app.id); + }; + + return + + {name} + { subtitle } + + } + + const onOpenWidgetClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.Widget, + refireParams: { + widgetId: app.id, + }, + }); + }; + + return ; + }) } + + + { apps.length > 0 ? _t("Edit apps") : _t("Add applications") } + + ; +}; + +const onRoomMembersClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomMemberList, + }); +}; + +const onRoomFilesClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.FilePanel, + }); +}; + +const onRoomSettingsClick = () => { + defaultDispatcher.dispatch({ action: "open_room_settings" }); +}; + +const RoomSummaryCard: React.FC = ({ room, onClose }) => { + const cli = useContext(MatrixClientContext); + + const onShareRoomClick = () => { + Modal.createTrackedDialog('share room dialog', '', ShareDialog, { + target: room, + }); + }; + + const isRoomEncrypted = useIsEncrypted(cli, room); + + const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || ""; + const header = +
+ + +
+ +

{ room.name }

+
+ { alias } +
+
; + + return + + + + + + + + + ; +}; + +export default RoomSummaryCard; diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx new file mode 100644 index 0000000000..c447bb2952 --- /dev/null +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -0,0 +1,107 @@ +/* +Copyright 2020 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, {useContext, useEffect} from "react"; +import {Room} from "matrix-js-sdk/src/models/room"; + +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import BaseCard from "./BaseCard"; +import WidgetUtils from "../../../utils/WidgetUtils"; +import AccessibleButton from "../elements/AccessibleButton"; +import AppTile from "../elements/AppTile"; +import {_t} from "../../../languageHandler"; +import {useWidgets} from "./RoomSummaryCard"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import {Action} from "../../../dispatcher/actions"; +import {WidgetStore} from "../../../stores/WidgetStore"; + +interface IProps { + room: Room; + widgetId: string; + onClose(): void; +} + +const onFinished = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); +} + +const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { + const cli = useContext(MatrixClientContext); + + const apps = useWidgets(room); + const app = apps.find(a => a.id === widgetId); + const isPinned = app && WidgetStore.instance.isPinned(app.id); + + useEffect(() => { + if (!app || isPinned) { + // TODO maybe we should do this in the ActiveWidgetStore instead + onFinished(); + } + }, [app, isPinned]); + + // Don't render anything as we are about to transition + if (!app || isPinned) return null; + + const header = +

{ WidgetUtils.getWidgetName(app) }

+
; + + const onPinClick = () => { + WidgetStore.instance.pinWidget(app.id); + }; + + const onEditClick = () => { + WidgetUtils.editWidget(room, app); + }; + + const footer = + + { _t("Pin to room") } + + + { _t("Edit") } + + ; + + return + + ; +}; + +export default WidgetCard; diff --git a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts index 75dea9f3df..4126e8a669 100644 --- a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts +++ b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts @@ -34,4 +34,5 @@ export interface SetRightPanelPhaseRefireParams { groupRoomId?: string; // XXX: The type for event should 'view_3pid_invite' action's payload event?: any; + widgetId?: string; } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 95861e11df..43bb989d1f 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -607,4 +607,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Enable experimental, compact IRC style layout"), default: false, }, + "Widgets.pinned": { + supportedLevels: LEVELS_ROOM_OR_ACCOUNT, + default: [], + }, }; diff --git a/src/stores/RightPanelStorePhases.ts b/src/stores/RightPanelStorePhases.ts index 9045b17193..11b51dfc2d 100644 --- a/src/stores/RightPanelStorePhases.ts +++ b/src/stores/RightPanelStorePhases.ts @@ -22,6 +22,8 @@ export enum RightPanelPhases { NotificationPanel = 'NotificationPanel', RoomMemberInfo = 'RoomMemberInfo', EncryptionPanel = 'EncryptionPanel', + RoomSummary = 'RoomSummary', + Widget = 'Widget', Room3pidMemberInfo = 'Room3pidMemberInfo', // Group stuff @@ -34,6 +36,7 @@ export enum RightPanelPhases { // These are the phases that are safe to persist (the ones that don't require additional // arguments). export const RIGHT_PANEL_PHASES_NO_ARGS = [ + RightPanelPhases.RoomSummary, RightPanelPhases.NotificationPanel, RightPanelPhases.FilePanel, RightPanelPhases.RoomMemberList, From 98b59fb217b705c934355093fe71ec0a63f407d1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 10:19:51 +0100 Subject: [PATCH 07/34] Consolidate all the work thus far --- res/css/structures/_HeaderButtons.scss | 8 ++ res/css/structures/_MainSplit.scss | 1 + res/css/structures/_RightPanel.scss | 17 ++-- .../context_menus/_IconizedContextMenu.scss | 5 +- res/css/views/right_panel/_BaseCard.scss | 11 +-- res/css/views/right_panel/_UserInfo.scss | 4 +- src/@types/global.d.ts | 2 +- src/components/structures/ContextMenu.tsx | 6 +- src/components/structures/FilePanel.js | 35 ++++++-- .../structures/NotificationPanel.js | 38 +++++---- src/components/structures/RoomView.js | 6 +- .../context_menus/IconizedContextMenu.tsx | 4 +- .../views/context_menus/WidgetContextMenu.js | 11 +++ src/components/views/elements/AppTile.js | 39 +++++---- .../views/elements/ManageIntegsButton.js | 63 -------------- .../views/right_panel/RoomSummaryCard.tsx | 2 +- src/components/views/right_panel/UserInfo.js | 30 +++---- .../views/right_panel/WidgetCard.tsx | 84 +++++++++++++++++-- src/components/views/rooms/AppsDrawer.js | 52 ++---------- src/components/views/rooms/MemberList.js | 51 ++++++----- src/components/views/rooms/RoomHeader.js | 36 -------- src/dispatcher/actions.ts | 10 +++ .../payloads/AppTileActionPayload.ts | 23 +++++ src/i18n/strings/en_EN.json | 33 +++++--- src/stores/WidgetStore.ts | 10 ++- src/utils/WidgetUtils.js | 30 +++++-- 26 files changed, 337 insertions(+), 274 deletions(-) delete mode 100644 src/components/views/elements/ManageIntegsButton.js create mode 100644 src/dispatcher/payloads/AppTileActionPayload.ts diff --git a/res/css/structures/_HeaderButtons.scss b/res/css/structures/_HeaderButtons.scss index 9ef40e9d6a..72b663ef0e 100644 --- a/res/css/structures/_HeaderButtons.scss +++ b/res/css/structures/_HeaderButtons.scss @@ -18,6 +18,14 @@ limitations under the License. display: flex; } +.mx_RoomHeader_buttons + .mx_HeaderButtons { + // remove the | separator line for when next to RoomHeaderButtons + // TODO: remove this once when we redo communities and make the right panel similar to the new rooms one + &::before { + content: unset; + } +} + .mx_HeaderButtons::before { content: ""; background-color: $header-divider-color; diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index dc62cb8218..ad1656efbb 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -25,6 +25,7 @@ limitations under the License. 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 &:hover .mx_RightPanel_ResizeHandle { // Need to use important to override element style attributes diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index 7e5ab7cdbd..b9b765db56 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -70,21 +70,16 @@ limitations under the License. } } -.mx_RightPanel_membersButton::before { - mask-image: url('$(res)/img/element-icons/room/members.svg'); - mask-position: center; -} - -.mx_RightPanel_filesButton::before { - mask-image: url('$(res)/img/element-icons/room/files.svg'); - mask-position: center; -} - .mx_RightPanel_notifsButton::before { mask-image: url('$(res)/img/element-icons/notifications.svg'); mask-position: center; } +.mx_RightPanel_roomSummaryButton::before { + mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); + mask-position: center; +} + .mx_RightPanel_groupMembersButton::before { mask-image: url('$(res)/img/element-icons/community-members.svg'); mask-position: center; @@ -144,7 +139,7 @@ limitations under the License. } .mx_RightPanel_empty { - margin-right: -42px; + margin-right: -28px; h2 { font-weight: 700; diff --git a/res/css/views/context_menus/_IconizedContextMenu.scss b/res/css/views/context_menus/_IconizedContextMenu.scss index 7913058995..d911ac6dfe 100644 --- a/res/css/views/context_menus/_IconizedContextMenu.scss +++ b/res/css/views/context_menus/_IconizedContextMenu.scss @@ -82,7 +82,6 @@ limitations under the License. } span.mx_IconizedContextMenu_label { // labels - padding-left: 14px; width: 100%; flex: 1; @@ -91,6 +90,10 @@ limitations under the License. overflow: hidden; white-space: nowrap; } + + .mx_IconizedContextMenu_icon + .mx_IconizedContextMenu_label { + padding-left: 14px; + } } } diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index c66b7261e3..661f496e8d 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -134,17 +134,18 @@ limitations under the License. .mx_BaseCard_footer { padding-top: 4px; text-align: center; + display: flex; + justify-content: space-around; .mx_AccessibleButton_kind_secondary { color: $secondary-fg-color; background-color: rgba(141, 151, 165, 0.2); // TODO font-weight: $font-semi-bold; font-size: $font-14px; - min-width: 70px; - - & + .mx_AccessibleButton_kind_secondary { - margin-left: 16px; - } + } + + .mx_AccessibleButton_disabled { + cursor: not-allowed; } } } diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index 6f86d1ad18..9fcf06e5d0 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -15,7 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_UserInfo { +.mx_UserInfo.mx_BaseCard { + // UserInfo has a circular image at the top so it fits between the back & close buttons + padding-top: 0; display: flex; flex-direction: column; flex: 1; diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index de3eb5e767..e1111a8a94 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -29,7 +29,7 @@ import {ActiveRoomObserver} from "../ActiveRoomObserver"; import {Notifier} from "../Notifier"; import type {Renderer} from "react-dom"; import RightPanelStore from "../stores/RightPanelStore"; -import {WidgetStore} from "../stores/WidgetStore"; +import WidgetStore from "../stores/WidgetStore"; declare global { interface Window { diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 64e0160d83..884f77aba5 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {CSSProperties, useRef, useState} from "react"; +import React, {CSSProperties, RefObject, useRef, useState} from "react"; import ReactDOM from "react-dom"; import classNames from "classnames"; @@ -416,8 +416,8 @@ export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None return menuOptions; }; -export const useContextMenu = () => { - const button = useRef(null); +export const useContextMenu = (): [boolean, RefObject, () => void, () => void, (val: boolean) => void] => { + const button = useRef(null); const [isOpen, setIsOpen] = useState(false); const open = () => { setIsOpen(true); diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 8aa1192458..8812ba4302 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -23,6 +23,8 @@ import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import EventIndexPeg from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; +import BaseCard from "../views/right_panel/BaseCard"; +import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; /* * Component which shows the filtered file using a TimelinePanel @@ -30,6 +32,7 @@ import { _t } from '../../languageHandler'; class FilePanel extends React.Component { static propTypes = { roomId: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired, }; // This is used to track if a decrypted event was a live event and should be @@ -188,18 +191,26 @@ class FilePanel extends React.Component { render() { if (MatrixClientPeg.get().isGuest()) { - return
+ return
{ _t("You must register to use this functionality", {}, { 'a': (sub) => { sub } }) }
-
; + ; } else if (this.noRoom) { - return
+ return
{ _t("You must join the room to see its files") }
-
; + ; } // wrap a TimelinePanel with the jump-to-event bits turned off. @@ -215,7 +226,11 @@ class FilePanel extends React.Component { // console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " + // "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId); return ( -
+ -
+ ); } else { return ( -
+ -
+ ); } } diff --git a/src/components/structures/NotificationPanel.js b/src/components/structures/NotificationPanel.js index 6ae7f91142..a561ade799 100644 --- a/src/components/structures/NotificationPanel.js +++ b/src/components/structures/NotificationPanel.js @@ -17,14 +17,21 @@ limitations under the License. */ import React from 'react'; +import PropTypes from "prop-types"; + import { _t } from '../../languageHandler'; import {MatrixClientPeg} from "../../MatrixClientPeg"; import * as sdk from "../../index"; +import BaseCard from "../views/right_panel/BaseCard"; /* * Component which shows the global notification list using a TimelinePanel */ class NotificationPanel extends React.Component { + static propTypes = { + onClose: PropTypes.func.isRequired, + }; + render() { // wrap a TimelinePanel with the jump-to-event bits turned off. const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); @@ -35,28 +42,27 @@ class NotificationPanel extends React.Component {

{_t('You have no visible notifications in this room.')}

); + let content; const timelineSet = MatrixClientPeg.get().getNotifTimelineSet(); if (timelineSet) { - return ( -
- -
+ content = ( + ); } else { console.error("No notifTimelineSet available!"); - return ( -
- -
- ); + content = ; } + + return + { content } + ; } } diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index d98a19ebe8..be75f56e1d 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -56,6 +56,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import { shieldStatusForRoom } from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; +import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; const DEBUG = false; let debuglog = function() {}; @@ -1356,7 +1357,10 @@ export default class RoomView extends React.Component { }; onSettingsClick = () => { - dis.dispatch({ action: 'open_room_settings' }); + dis.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); }; onCancelClick = () => { diff --git a/src/components/views/context_menus/IconizedContextMenu.tsx b/src/components/views/context_menus/IconizedContextMenu.tsx index b3ca9fde6f..a3fb00a9f4 100644 --- a/src/components/views/context_menus/IconizedContextMenu.tsx +++ b/src/components/views/context_menus/IconizedContextMenu.tsx @@ -37,7 +37,7 @@ interface IOptionListProps { } interface IOptionProps extends React.ComponentProps { - iconClassName: string; + iconClassName?: string; } interface ICheckboxProps extends React.ComponentProps { @@ -92,7 +92,7 @@ export const IconizedContextMenuCheckbox: React.FC = ({ export const IconizedContextMenuOption: React.FC = ({label, iconClassName, ...props}) => { return - + { iconClassName && } {label} ; }; diff --git a/src/components/views/context_menus/WidgetContextMenu.js b/src/components/views/context_menus/WidgetContextMenu.js index 1ec74b2e6c..9182b92c8c 100644 --- a/src/components/views/context_menus/WidgetContextMenu.js +++ b/src/components/views/context_menus/WidgetContextMenu.js @@ -26,6 +26,9 @@ export default class WidgetContextMenu extends React.Component { // Callback for when the revoke button is clicked. Required. onRevokeClicked: PropTypes.func.isRequired, + // Callback for when the unpin button is clicked. Required. + onUnpinClicked: PropTypes.func.isRequired, + // Callback for when the snapshot button is clicked. Button not shown // without a callback. onSnapshotClicked: PropTypes.func, @@ -70,6 +73,8 @@ export default class WidgetContextMenu extends React.Component { this.proxyClick(this.props.onRevokeClicked); }; + onUnpinClicked = () => this.proxyClick(this.props.onUnpinClicked); + render() { const options = []; @@ -81,6 +86,12 @@ export default class WidgetContextMenu extends React.Component { ); } + options.push( + + {_t("Unpin")} + , + ); + if (this.props.onReloadClicked) { options.push( diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 1c93841afb..812539ec0d 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -42,6 +42,8 @@ import {WidgetType} from "../../../widgets/WidgetType"; import {Capability} from "../../../widgets/WidgetApi"; import {sleep} from "../../../utils/promise"; import {SettingLevel} from "../../../settings/SettingLevel"; +import WidgetStore from "../../../stores/WidgetStore"; +import {Action} from "../../../dispatcher/actions"; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const ENABLE_REACT_PERF = false; @@ -315,17 +317,7 @@ export default class AppTile extends React.Component { } _onSnapshotClick() { - console.log("Requesting widget snapshot"); - ActiveWidgetStore.getWidgetMessaging(this.props.app.id).getScreenshot() - .catch((err) => { - console.error("Failed to get screenshot", err); - }) - .then((screenshot) => { - dis.dispatch({ - action: 'picture_snapshot', - file: screenshot, - }, true); - }); + WidgetUtils.snapshotWidget(this.props.app); } /** @@ -406,6 +398,10 @@ export default class AppTile extends React.Component { } } + _onUnpinClicked = () => { + WidgetStore.instance.unpinWidget(this.props.app.id); + } + _onRevokeClicked() { console.info("Revoke widget permissions - %s", this.props.app.id); this._revokeWidgetPermission(); @@ -477,12 +473,20 @@ export default class AppTile extends React.Component { if (payload.widgetId === this.props.app.id) { switch (payload.action) { case 'm.sticker': - if (this._hasCapability('m.sticker')) { - dis.dispatch({action: 'post_sticker_message', data: payload.data}); - } else { - console.warn('Ignoring sticker message. Invalid capability'); - } - break; + if (this._hasCapability('m.sticker')) { + dis.dispatch({action: 'post_sticker_message', data: payload.data}); + } else { + console.warn('Ignoring sticker message. Invalid capability'); + } + break; + + case Action.AppTileDelete: + this._onDeleteClick(); + break; + + case Action.AppTileRevoke: + this._onRevokeClicked(); + break; } } } @@ -826,6 +830,7 @@ export default class AppTile extends React.Component { contextMenu = ( { - ev.preventDefault(); - - const managers = IntegrationManagers.sharedInstance(); - if (!managers.hasManager()) { - managers.openNoManagerDialog(); - } else { - if (SettingsStore.getValue("feature_many_integration_managers")) { - managers.openAll(this.props.room); - } else { - managers.getPrimaryManager().open(this.props.room); - } - } - }; - - render() { - let integrationsButton =
; - if (IntegrationManagers.sharedInstance().hasManager()) { - integrationsButton = ( - - ); - } - - return integrationsButton; - } -} - -ManageIntegsButton.propTypes = { - room: PropTypes.object.isRequired, -}; diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 018b02c879..64743e1bb8 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -39,7 +39,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import TextWithTooltip from "../elements/TextWithTooltip"; import BaseAvatar from "../avatars/BaseAvatar"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import {IApp, WidgetStore} from "../../../stores/WidgetStore"; +import WidgetStore, {IApp} from "../../../stores/WidgetStore"; interface IProps { room: Room; diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 518bb133ce..a79e65cb8b 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -46,6 +46,7 @@ import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; import { verifyUser, legacyVerifyUser, verifyDevice } from '../../../verification'; import {Action} from "../../../dispatcher/actions"; import {useIsEncrypted} from "../../../hooks/useIsEncrypted"; +import BaseCard from "./BaseCard"; const _disambiguateDevices = (devices) => { const names = Object.create(null); @@ -451,7 +452,7 @@ const _isMuted = (member, powerLevelContent) => { return member.powerLevel < levelToSend; }; -const useRoomPowerLevels = (cli, room) => { +export const useRoomPowerLevels = (cli, room) => { const [powerLevels, setPowerLevels] = useState({}); const update = useCallback(() => { @@ -1364,16 +1365,9 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { ; }; -const UserInfoHeader = ({onClose, member, e2eStatus}) => { +const UserInfoHeader = ({member, e2eStatus}) => { const cli = useContext(MatrixClientContext); - let closeButton; - if (onClose) { - closeButton = -
- ; - } - const onMemberAvatarClick = useCallback(() => { const avatarUrl = member.getMxcAvatarUrl ? member.getMxcAvatarUrl() : member.avatarUrl; if (!avatarUrl) return; @@ -1448,7 +1442,6 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => { const displayName = member.name || member.displayname; return - { closeButton } { avatarElement }
@@ -1510,15 +1503,16 @@ const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMe break; } - return ( -
- - + let previousPhase: RightPanelPhases; + // We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time + if (room) { + previousPhase = RightPanelPhases.RoomMemberList; + } - { content } - -
- ); + const header = ; + return + { content } + ; }; UserInfo.propTypes = { diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index c447bb2952..8abff36c31 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -28,7 +28,15 @@ import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; import {Action} from "../../../dispatcher/actions"; -import {WidgetStore} from "../../../stores/WidgetStore"; +import WidgetStore from "../../../stores/WidgetStore"; +import ActiveWidgetStore from "../../../stores/ActiveWidgetStore"; +import {ChevronFace, ContextMenuButton, useContextMenu} from "../../structures/ContextMenu"; +import IconizedContextMenu, { + IconizedContextMenuOption, + IconizedContextMenuOptionList, +} from "../context_menus/IconizedContextMenu"; +import {AppTileActionPayload} from "../../../dispatcher/payloads/AppTileActionPayload"; +import {Capability} from "../../../widgets/WidgetApi"; interface IProps { room: Room; @@ -50,6 +58,8 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { const app = apps.find(a => a.id === widgetId); const isPinned = app && WidgetStore.instance.isPinned(app.id); + const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); + useEffect(() => { if (!app || isPinned) { // TODO maybe we should do this in the ActiveWidgetStore instead @@ -64,6 +74,58 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => {

{ WidgetUtils.getWidgetName(app) }

; + const canModify = WidgetUtils.canUserModifyWidgets(room.roomId); + + let contextMenu; + if (menuDisplayed) { + let snapshotButton; + if (ActiveWidgetStore.widgetHasCapability(app.id, Capability.Screenshot)) { + const onSnapshotClick = () => { + WidgetUtils.snapshotWidget(app); + closeMenu(); + }; + + snapshotButton = ; + } + + let deleteButton; + if (canModify) { + const onDeleteClick = () => { + defaultDispatcher.dispatch({ + action: Action.AppTileDelete, + widgetId: app.id, + }); + closeMenu(); + }; + + deleteButton = ; + } + + const onRevokeClick = () => { + defaultDispatcher.dispatch({ + action: Action.AppTileRevoke, + widgetId: app.id, + }); + closeMenu(); + }; + + const rect = handle.current.getBoundingClientRect(); + contextMenu = ( + + + { snapshotButton } + { deleteButton } + + + + ); + } + const onPinClick = () => { WidgetStore.instance.pinWidget(app.id); }; @@ -73,12 +135,24 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { }; const footer = - - { _t("Pin to room") } - - + { _t("Edit") } + + { _t("Pin to room") } + + + ... + + + { contextMenu } ; return { - if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') { - return; - } - this._updateApps(); - }; - - _getApps() { - const widgets = WidgetEchoStore.getEchoedRoomWidgets( - this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room), - ); - return widgets.map((ev) => { - return WidgetUtils.makeAppConfig( - ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(), - ); - }); - } + _getApps = () => WidgetStore.instance.getApps(this.props.room, true); _updateApps = () => { - const apps = this._getApps(); this.setState({ - apps: apps, + apps: this._getApps(), }); }; @@ -144,18 +120,6 @@ export default class AppsDrawer extends React.Component { onClickAddWidget = (e) => { e.preventDefault(); - // Display a warning dialog if the max number of widgets have already been added to the room - const apps = this._getApps(); - if (apps && apps.length >= MAX_WIDGETS) { - const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); - const errorMsg = `The maximum number of ${MAX_WIDGETS} widgets have already been added to this room.`; - console.error(errorMsg); - Modal.createDialog(ErrorDialog, { - title: _t('Cannot add any more widgets'), - description: _t('The maximum permitted number of widgets have already been added to this room.'), - }); - return; - } this._launchManageIntegrations(); }; diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 1fdc67eee7..40b3b042b1 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -20,13 +20,14 @@ import React from 'react'; import { _t } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; import dis from '../../../dispatcher/dispatcher'; -import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import {isValid3pidInvite} from "../../../RoomInvite"; import rate_limited_func from "../../../ratelimitedfunc"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import * as sdk from "../../../index"; import CallHandler from "../../../CallHandler"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; +import BaseCard from "../right_panel/BaseCard"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; const INITIAL_LOAD_NUM_MEMBERS = 30; const INITIAL_LOAD_NUM_INVITED = 5; @@ -438,7 +439,13 @@ export default class MemberList extends React.Component { render() { if (this.state.loading) { const Spinner = sdk.getComponent("elements.Spinner"); - return
; + return + + ; } const SearchBox = sdk.getComponent('structures.SearchBox'); @@ -485,25 +492,29 @@ export default class MemberList extends React.Component { />; } - return ( -
- { inviteButton } - -
- - { invitedHeader } - { invitedSection } -
-
- - -
+ const footer = ( + ); + + return +
+ + { invitedHeader } + { invitedSection } +
+
; } onInviteButtonClick = () => { diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 2a44f53d21..1a116838ac 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -18,14 +18,11 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import Modal from "../../../Modal"; import RateLimitedFunc from '../../../ratelimitedfunc'; import { linkifyElement } from '../../../HtmlUtils'; -import ManageIntegsButton from '../elements/ManageIntegsButton'; import {CancelButton} from './SimpleRoomHeader'; import SettingsStore from "../../../settings/SettingsStore"; import RoomHeaderButtons from '../right_panel/RoomHeaderButtons'; @@ -114,13 +111,6 @@ export default class RoomHeader extends React.Component { this.forceUpdate(); }; - onShareRoomClick = (ev) => { - const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); - Modal.createTrackedDialog('share room dialog', '', ShareDialog, { - target: this.props.room, - }); - }; - _hasUnreadPins() { const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); if (!currentPinEvent) return false; @@ -150,7 +140,6 @@ export default class RoomHeader extends React.Component { render() { let searchStatus = null; let cancelButton = null; - let settingsButton = null; let pinnedEventsButton = null; if (this.props.onCancelClick) { @@ -214,14 +203,6 @@ export default class RoomHeader extends React.Component { />; } - if (this.props.onSettingsClick) { - settingsButton = - ; - } - if (this.props.onPinnedClick && SettingsStore.getValue('feature_pinning')) { let pinsIndicator = null; if (this._hasUnreadPins()) { @@ -258,26 +239,9 @@ export default class RoomHeader extends React.Component { title={_t("Search")} />; } - let shareRoomButton; - if (this.props.inRoom) { - shareRoomButton = - ; - } - - let manageIntegsButton; - if (this.props.room && this.props.room.roomId && this.props.inRoom) { - manageIntegsButton = ; - } - const rightRow =
- { settingsButton } { pinnedEventsButton } - { shareRoomButton } - { manageIntegsButton } { forgetButton } { searchButton }
; diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 6fb71df30d..26d585b76e 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -94,4 +94,14 @@ export enum Action { * Trigged after the phase of the right panel is set. Should be used with AfterRightPanelPhaseChangePayload. */ AfterRightPanelPhaseChange = "after_right_panel_phase_change", + + /** + * Requests that the AppTile deletes the widget. Should be used with the AppTileActionPayload. + */ + AppTileDelete = "appTile_delete", + + /** + * Requests that the AppTile revokes the widget. Should be used with the AppTileActionPayload. + */ + AppTileRevoke = "appTile_revoke", } diff --git a/src/dispatcher/payloads/AppTileActionPayload.ts b/src/dispatcher/payloads/AppTileActionPayload.ts new file mode 100644 index 0000000000..3cdb0f8c1f --- /dev/null +++ b/src/dispatcher/payloads/AppTileActionPayload.ts @@ -0,0 +1,23 @@ +/* +Copyright 2020 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 { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface AppTileActionPayload extends ActionPayload { + action: Action.AppTileDelete | Action.AppTileRevoke; + widgetId: string; +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 54a4bd6695..9abebfaf60 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1029,8 +1029,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", - "Cannot add any more widgets": "Cannot add any more widgets", - "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.", "Add a widget": "Add a widget", "Drop File Here": "Drop File Here", "Drop file here to upload": "Drop file here to upload", @@ -1115,10 +1113,8 @@ "(~%(count)s results)|other": "(~%(count)s results)", "(~%(count)s results)|one": "(~%(count)s result)", "Join Room": "Join Room", - "Settings": "Settings", "Forget room": "Forget room", "Search": "Search", - "Share room": "Share room", "Invites": "Invites", "Favourites": "Favourites", "People": "People", @@ -1135,6 +1131,7 @@ "Can't see what you’re looking for?": "Can't see what you’re looking for?", "Explore all public rooms": "Explore all public rooms", "%(count)s results|other": "%(count)s results", + "%(count)s results|one": "%(count)s result", "This room": "This room", "Joining room …": "Joining room …", "Loading …": "Loading …", @@ -1197,6 +1194,7 @@ "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.", @@ -1267,6 +1265,7 @@ "URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.", "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.", "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.", + "Back": "Back", "Waiting for you to accept on your other session…": "Waiting for you to accept on your other session…", "Waiting for %(displayName)s to accept…": "Waiting for %(displayName)s to accept…", "Accepting…": "Accepting…", @@ -1284,7 +1283,18 @@ "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", - "Files": "Files", + "Room Info": "Room Info", + "Apps": "Apps", + "Unpin app": "Unpin app", + "Edit apps": "Edit apps", + "Add applications": "Add applications", + "Not encrypted": "Not encrypted", + "About": "About", + "%(count)s people|other": "%(count)s people", + "%(count)s people|one": "%(count)s person", + "Show files": "Show files", + "Share room": "Share room", + "Room settings": "Room settings", "Trusted": "Trusted", "Not trusted": "Not trusted", "%(count)s verified sessions|other": "%(count)s verified sessions", @@ -1362,6 +1372,12 @@ "You cancelled verification.": "You cancelled verification.", "Verification cancelled": "Verification cancelled", "Compare emoji": "Compare emoji", + "Reload": "Reload", + "Take a picture": "Take a picture", + "Remove for everyone": "Remove for everyone", + "Remove for me": "Remove for me", + "Edit": "Edit", + "Pin to room": "Pin to room", "Sunday": "Sunday", "Monday": "Monday", "Tuesday": "Tuesday", @@ -1379,7 +1395,6 @@ "Error decrypting audio": "Error decrypting audio", "React": "React", "Reply": "Reply", - "Edit": "Edit", "Message Actions": "Message Actions", "Attachment": "Attachment", "Error decrypting attachment": "Error decrypting attachment", @@ -1490,7 +1505,6 @@ "Download this file": "Download this file", "Information": "Information", "Language Dropdown": "Language Dropdown", - "Manage Integrations": "Manage Integrations", "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times", "%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sjoined", @@ -1670,7 +1684,6 @@ "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.", "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.", "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)", - "Back": "Back", "Send": "Send", "Send Custom Event": "Send Custom Event", "You must specify an event type!": "You must specify an event type!", @@ -1911,10 +1924,8 @@ "Set status": "Set status", "Set a new status...": "Set a new status...", "View Community": "View Community", - "Reload": "Reload", + "Unpin": "Unpin", "Take picture": "Take picture", - "Remove for everyone": "Remove for everyone", - "Remove for me": "Remove for me", "This room is public": "This room is public", "Away": "Away", "User Status": "User Status", diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index b31fc99515..f6a838344a 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -46,7 +46,7 @@ interface IRoomWidgets { // TODO consolidate WidgetEchoStore into this // TODO consolidate ActiveWidgetStore into this -export class WidgetStore extends AsyncStoreWithClient { +export default class WidgetStore extends AsyncStoreWithClient { private static internalInstance = new WidgetStore(); private widgetMap = new Map(); @@ -159,6 +159,14 @@ export class WidgetStore extends AsyncStoreWithClient { return roomInfo ? roomInfo.pinned.has(widgetId) : false; } + public canPin(widgetId: string) { + // only allow pinning up to a max of two as we do not yet have grid splits + // the only case it will go to three is if you have two and then a Jitsi gets added + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + return roomInfo && roomInfo.pinned.size < 2; + } + public pinWidget(widgetId: string) { const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index d4ed093a24..771bc0887a 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -479,15 +479,6 @@ export default class WidgetUtils { return url.href; } - static editWidget(room, app) { - // TODO: Open the right manager for the widget - if (SettingsStore.getValue("feature_many_integration_managers")) { - IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id); - } else { - IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id); - } - } - static getWidgetName(app) { if (!app || !app.name) return ""; return app.name.trim() || _t("Unknown App"); @@ -497,4 +488,25 @@ export default class WidgetUtils { if (!app || !app.data || !app.data.title) return ""; return app.data.title.trim(); } + + static editWidget(room, app) { + // TODO: Open the right manager for the widget + if (SettingsStore.getValue("feature_many_integration_managers")) { + IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id); + } else { + IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id); + } + } + + static snapshotWidget(app) { + console.log("Requesting widget snapshot"); + ActiveWidgetStore.getWidgetMessaging(app.id).getScreenshot().catch((err) => { + console.error("Failed to get screenshot", err); + }).then((screenshot) => { + dis.dispatch({ + action: 'picture_snapshot', + file: screenshot, + }, true); + }); + } } From 0fe6ce18424da21cf8a77045b013af1dfb84bf12 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 11:18:41 +0100 Subject: [PATCH 08/34] Stage svgs --- res/css/views/right_panel/_RoomSummaryCard.scss | 2 +- res/img/element-icons/room/default_app.svg | 11 +++++++++++ res/img/element-icons/room/default_cal.svg | 6 ++++++ res/img/element-icons/room/default_clock.svg | 5 +++++ res/img/element-icons/room/default_doc.svg | 4 ++++ res/img/element-icons/room/pin-upright.svg | 7 +++++++ res/img/element-icons/room/room-summary.svg | 3 +++ 7 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 res/img/element-icons/room/default_app.svg create mode 100644 res/img/element-icons/room/default_cal.svg create mode 100644 res/img/element-icons/room/default_clock.svg create mode 100644 res/img/element-icons/room/default_doc.svg create mode 100644 res/img/element-icons/room/pin-upright.svg create mode 100644 res/img/element-icons/room/room-summary.svg diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index 3c46727348..41df1ca2eb 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -125,7 +125,7 @@ limitations under the License. } .mx_RoomSummaryCard_icon_app_pinned::after { - mask-image: url('$(res)/img/element-icons/room/pin2.svg'); + mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); background-color: $accent-color; transform: unset; } diff --git a/res/img/element-icons/room/default_app.svg b/res/img/element-icons/room/default_app.svg new file mode 100644 index 0000000000..08734170df --- /dev/null +++ b/res/img/element-icons/room/default_app.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/element-icons/room/default_cal.svg b/res/img/element-icons/room/default_cal.svg new file mode 100644 index 0000000000..5bced115cf --- /dev/null +++ b/res/img/element-icons/room/default_cal.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/element-icons/room/default_clock.svg b/res/img/element-icons/room/default_clock.svg new file mode 100644 index 0000000000..cc21716d15 --- /dev/null +++ b/res/img/element-icons/room/default_clock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/room/default_doc.svg b/res/img/element-icons/room/default_doc.svg new file mode 100644 index 0000000000..93e7507be3 --- /dev/null +++ b/res/img/element-icons/room/default_doc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/room/pin-upright.svg b/res/img/element-icons/room/pin-upright.svg new file mode 100644 index 0000000000..9297f62a02 --- /dev/null +++ b/res/img/element-icons/room/pin-upright.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/room/room-summary.svg b/res/img/element-icons/room/room-summary.svg new file mode 100644 index 0000000000..b6ac258b18 --- /dev/null +++ b/res/img/element-icons/room/room-summary.svg @@ -0,0 +1,3 @@ + + + From ef0843d4ad00bfd9c3572469351c5d771f858836 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 12:01:44 +0100 Subject: [PATCH 09/34] Iterate to match design --- res/css/views/right_panel/_WidgetCard.scss | 16 +++++++ .../views/right_panel/WidgetCard.tsx | 42 ++++++++++++++++--- src/stores/WidgetStore.ts | 3 +- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss index b863c53c33..0f859738b1 100644 --- a/res/css/views/right_panel/_WidgetCard.scss +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -22,4 +22,20 @@ limitations under the License. height: 100%; border: 0; } + + &.mx_WidgetCard_noEdit { + .mx_AccessibleButton_kind_secondary { + margin: 0 12px; + + &:first-child { + // expand the Pin to room primary action + flex-grow: 1; + } + } + } +} + +.mx_WidgetCard_maxPinnedTooltip { + background-color: $notice-primary-color; + color: #ffffff; } diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index 8abff36c31..bbd02c18fb 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -37,6 +37,8 @@ import IconizedContextMenu, { } from "../context_menus/IconizedContextMenu"; import {AppTileActionPayload} from "../../../dispatcher/payloads/AppTileActionPayload"; import {Capability} from "../../../widgets/WidgetApi"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; +import classNames from "classnames"; interface IProps { room: Room; @@ -134,13 +136,39 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { WidgetUtils.editWidget(room, app); }; - const footer = - + let editButton; + if (canModify) { + editButton = { _t("Edit") } - - + ; + } + + const pinButtonClasses = canModify ? "" : "mx_WidgetCard_widePinButton"; + + let pinButton; + if (WidgetStore.instance.canPin(app.id)) { + pinButton = { _t("Pin to room") } - + ; + } else { + pinButton = + { _t("Pin to room") } + ; + } + + const footer = + { editButton } + { pinButton } = ({ room, widgetId, onClose }) => { return { private constructor() { super(defaultDispatcher, {}); - SettingsStore.watchSetting("Widgets.pinned", IRRELEVANT_ROOM, this.onPinnedWidgetsChange); + SettingsStore.watchSetting("Widgets.pinned", null, this.onPinnedWidgetsChange); WidgetEchoStore.on("update", this.onWidgetEchoStoreUpdate); } From 73dcba1425b3b8d87d9a3bef9c843d52be8fbb15 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 12:02:48 +0100 Subject: [PATCH 10/34] i18n --- src/i18n/strings/en_EN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9abebfaf60..b3da8cbbb1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1372,12 +1372,12 @@ "You cancelled verification.": "You cancelled verification.", "Verification cancelled": "Verification cancelled", "Compare emoji": "Compare emoji", - "Reload": "Reload", "Take a picture": "Take a picture", "Remove for everyone": "Remove for everyone", "Remove for me": "Remove for me", "Edit": "Edit", "Pin to room": "Pin to room", + "You can only pin 2 apps at a time": "You can only pin 2 apps at a time", "Sunday": "Sunday", "Monday": "Monday", "Tuesday": "Tuesday", @@ -1925,6 +1925,7 @@ "Set a new status...": "Set a new status...", "View Community": "View Community", "Unpin": "Unpin", + "Reload": "Reload", "Take picture": "Take picture", "This room is public": "This room is public", "Away": "Away", From 3a993434334655a17509b4d45c3c5827484c626b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 15:04:03 +0100 Subject: [PATCH 11/34] iterate styling --- res/css/views/right_panel/_BaseCard.scss | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index 661f496e8d..ee267d93a5 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -35,7 +35,7 @@ limitations under the License. .mx_BaseCard_back, .mx_BaseCard_close { position: absolute; - background-color: rgba(141, 151, 165, 0.2); // TODO + background-color: rgba(141, 151, 165, 0.2); height: 20px; width: 20px; margin: 12px; @@ -44,10 +44,10 @@ limitations under the License. &::before { content: ""; position: absolute; - height: 16px; - width: 16px; - top: 2px; - left: 2px; + height: 20px; + width: 20px; + top: 0; + left: 0; mask-repeat: no-repeat; mask-position: center; background-color: $rightpanel-button-color; @@ -60,8 +60,8 @@ limitations under the License. &::before { transform: rotate(90deg); - mask-size: 20px; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); // TODO + mask-size: 22px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } } @@ -70,7 +70,8 @@ limitations under the License. right: 0; &::before { - mask-image: url('$(res)/img/icons-close.svg'); // TODO + mask-image: url('$(res)/img/icons-close.svg'); + mask-size: 8px; } } } @@ -126,7 +127,7 @@ limitations under the License. background-color: $icon-button-color; transform: rotate(270deg); mask-size: 20px; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); // TODO + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } } } @@ -139,11 +140,11 @@ limitations under the License. .mx_AccessibleButton_kind_secondary { color: $secondary-fg-color; - background-color: rgba(141, 151, 165, 0.2); // TODO + background-color: rgba(141, 151, 165, 0.2); font-weight: $font-semi-bold; font-size: $font-14px; } - + .mx_AccessibleButton_disabled { cursor: not-allowed; } From 4a4a8cd6110f4af5eb4795d2efd42a71a4e63b8e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 15:39:58 +0100 Subject: [PATCH 12/34] styling and fix `i` button behaviour --- res/css/structures/_RightPanel.scss | 18 ++++++++---------- .../views/right_panel/RoomHeaderButtons.tsx | 11 ++++++++--- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index b9b765db56..5bf0d953f3 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -68,6 +68,14 @@ limitations under the License. mask-repeat: no-repeat; mask-size: contain; } + + &:hover { + background: rgba($accent-color, 0.1); + + &::before { + background-color: $accent-color; + } + } } .mx_RightPanel_notifsButton::before { @@ -90,16 +98,6 @@ limitations under the License. mask-position: center; } -.mx_RightPanel_headerButton { - &:hover { - background: rgba($accent-color, 0.1); - - &::before { - background-color: $accent-color; - } - } -} - .mx_RightPanel_headerButton_highlight { &::before { background-color: $accent-color !important; diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index e19a440005..fa97568fba 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -19,7 +19,7 @@ limitations under the License. */ import React from 'react'; -import { _t } from '../../../languageHandler'; +import {_t} from '../../../languageHandler'; import HeaderButton from './HeaderButton'; import HeaderButtons, {HeaderKind} from './HeaderButtons'; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; @@ -59,8 +59,13 @@ export default class RoomHeaderButtons extends HeaderButtons { // TODO make it restore whatever widget they were on last private onRoomSummaryClicked = () => { - // This toggles for us, if needed - this.setPhase(RightPanelPhases.RoomSummary); + if (this.state.phase === RightPanelPhases.Widget) { + // Close the panel + this.setPhase(RightPanelPhases.Widget); + } else { + // This toggles for us, if needed + this.setPhase(RightPanelPhases.RoomSummary); + } }; private onNotificationsClicked = () => { From 8d14d26e2b6841cbb610fa039602df614bcff212 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:27:09 +0100 Subject: [PATCH 13/34] do the todos --- .../views/right_panel/_RoomSummaryCard.scss | 8 ++-- .../views/right_panel/RoomHeaderButtons.tsx | 13 ++++-- .../views/right_panel/WidgetCard.tsx | 14 +++--- src/settings/Settings.ts | 2 +- src/stores/WidgetStore.ts | 43 +++++++++++-------- 5 files changed, 45 insertions(+), 35 deletions(-) diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index 41df1ca2eb..84e02af7bc 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_RoomSummaryCard { // shrink left gutter by 12 and instead add it as margin to all things except the buttons // as their hover effect should go into the gutter - & > * { // TODO consolidate this as the standard effect + & > * { margin-left: 10px; margin-right: 10px; } @@ -57,7 +57,7 @@ limitations under the License. width: 54px; height: 54px; border-radius: 50%; - background-color: #737D8C; + background-color: #737D8C; // TODO margin-top: -3px; // alignment margin-left: -10px; // overlap border: 3px solid $dark-panel-bg-color; @@ -77,8 +77,8 @@ limitations under the License. } } - .mx_RoomSummaryCard_e2ee_secure{ - background-color: #5ABFF2; + .mx_RoomSummaryCard_e2ee_secure { + background-color: #5ABFF2; // TODO &::before { mask-image: url('$(res)/img/e2e/normal.svg'); } diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index fa97568fba..338290b7c9 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -25,6 +25,7 @@ import HeaderButtons, {HeaderKind} from './HeaderButtons'; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import {Action} from "../../../dispatcher/actions"; import {ActionPayload} from "../../../dispatcher/payloads"; +import RightPanelStore from "../../../stores/RightPanelStore"; const ROOM_INFO_PHASES = [ RightPanelPhases.RoomSummary, @@ -57,11 +58,15 @@ export default class RoomHeaderButtons extends HeaderButtons { } } - // TODO make it restore whatever widget they were on last private onRoomSummaryClicked = () => { - if (this.state.phase === RightPanelPhases.Widget) { - // Close the panel - this.setPhase(RightPanelPhases.Widget); + // use roomPanelPhase rather than this.state.phase as it remembers the latest one if we close + const lastPhase = RightPanelStore.getSharedInstance().roomPanelPhase; + if (ROOM_INFO_PHASES.includes(lastPhase)) { + if (this.state.phase === lastPhase) { + this.setPhase(lastPhase); + } else { + this.setPhase(lastPhase, RightPanelStore.getSharedInstance().roomPanelPhaseParams); + } } else { // This toggles for us, if needed this.setPhase(RightPanelPhases.RoomSummary); diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index bbd02c18fb..621b1aa1c7 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -46,13 +46,6 @@ interface IProps { onClose(): void; } -const onFinished = () => { - defaultDispatcher.dispatch({ - action: Action.SetRightPanelPhase, - phase: RightPanelPhases.RoomSummary, - }); -} - const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { const cli = useContext(MatrixClientContext); @@ -64,8 +57,11 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { useEffect(() => { if (!app || isPinned) { - // TODO maybe we should do this in the ActiveWidgetStore instead - onFinished(); + // stop showing this card + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); } }, [app, isPinned]); diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 43bb989d1f..e15cb46145 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -609,6 +609,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "Widgets.pinned": { supportedLevels: LEVELS_ROOM_OR_ACCOUNT, - default: [], + default: {}, }, }; diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index cf74081633..a43a6bfc30 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -24,6 +24,7 @@ import SettingsStore from "../settings/SettingsStore"; import WidgetEchoStore from "../stores/WidgetEchoStore"; import WidgetUtils from "../utils/WidgetUtils"; import {SettingLevel} from "../settings/SettingLevel"; +import {WidgetType} from "../widgets/WidgetType"; interface IState {} @@ -40,7 +41,7 @@ export interface IApp { interface IRoomWidgets { widgets: IApp[]; - pinned: Set; + pinned: Record; } // TODO consolidate WidgetEchoStore into this @@ -65,7 +66,7 @@ export default class WidgetStore extends AsyncStoreWithClient { private initRoom(roomId: string) { if (!this.roomMap.has(roomId)) { this.roomMap.set(roomId, { - pinned: new Set(), + pinned: {}, widgets: [], }); } @@ -81,7 +82,7 @@ export default class WidgetStore extends AsyncStoreWithClient { } if (pinned) { - this.getRoom(room.roomId).pinned = new Set(pinned); + this.getRoom(room.roomId).pinned = pinned; } this.loadRoomWidgets(room); @@ -144,9 +145,8 @@ export default class WidgetStore extends AsyncStoreWithClient { }; private onPinnedWidgetsChange = (settingName: string, roomId: string) => { - const pinned = SettingsStore.getValue(settingName, roomId); this.initRoom(roomId); - this.getRoom(roomId).pinned = new Set(pinned); + this.getRoom(roomId).pinned = SettingsStore.getValue(settingName, roomId); this.emit(roomId); this.emit("update"); }; @@ -154,8 +154,11 @@ export default class WidgetStore extends AsyncStoreWithClient { public isPinned(widgetId: string) { const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); - // TODO heuristic for Jitsi etc - return roomInfo ? roomInfo.pinned.has(widgetId) : false; + + let pinned = roomInfo && roomInfo.pinned[widgetId]; + // Jitsi widgets should be pinned by default + if (pinned === undefined && WidgetType.JITSI.matches(this.widgetMap.get(widgetId).type)) pinned = true; + return pinned; } public canPin(widgetId: string) { @@ -163,25 +166,31 @@ export default class WidgetStore extends AsyncStoreWithClient { // the only case it will go to three is if you have two and then a Jitsi gets added const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); - return roomInfo && roomInfo.pinned.size < 2; + return roomInfo && Object.keys(roomInfo.pinned).length < 2; } public pinWidget(widgetId: string) { - const roomId = this.getRoomId(widgetId); - const roomInfo = this.getRoom(roomId); - if (!roomInfo) return; - roomInfo.pinned.add(widgetId); - SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); - this.emit(roomId); - this.emit("update"); + this.setPinned(widgetId, true); } public unpinWidget(widgetId: string) { + this.setPinned(widgetId, false); + } + + private setPinned(widgetId: string, value: boolean) { const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); if (!roomInfo) return; - roomInfo.pinned.delete(widgetId); - SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); + roomInfo.pinned[widgetId] = value; + + // Clean up the pinned record + Object.keys(roomInfo).forEach(wId => { + if (!roomInfo.widgets.some(w => w.id === wId)) { + delete roomInfo.pinned[wId]; + } + }); + + SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, roomInfo.pinned); this.emit(roomId); this.emit("update"); } From 6c7cb473dc46104687f3cf8ef5356462dea20bf3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:33:26 +0100 Subject: [PATCH 14/34] finalise colours from Figma --- res/css/views/right_panel/_RoomSummaryCard.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index 84e02af7bc..c8c2cceec7 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -57,7 +57,7 @@ limitations under the License. width: 54px; height: 54px; border-radius: 50%; - background-color: #737D8C; // TODO + background-color: #737d8c; margin-top: -3px; // alignment margin-left: -10px; // overlap border: 3px solid $dark-panel-bg-color; @@ -78,7 +78,7 @@ limitations under the License. } .mx_RoomSummaryCard_e2ee_secure { - background-color: #5ABFF2; // TODO + background-color: #5abff2; &::before { mask-image: url('$(res)/img/e2e/normal.svg'); } From b982bf87b5a22fbe8c6ea5df48173e9f41a3db6e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:33:55 +0100 Subject: [PATCH 15/34] delint --- src/components/views/right_panel/UserInfo.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index a79e65cb8b..84995aeb73 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -31,7 +31,6 @@ import AccessibleButton from '../elements/AccessibleButton'; import SdkConfig from '../../../SdkConfig'; import SettingsStore from "../../../settings/SettingsStore"; import {EventTimeline} from "matrix-js-sdk"; -import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import RoomViewStore from "../../../stores/RoomViewStore"; import MultiInviter from "../../../utils/MultiInviter"; import GroupStore from "../../../stores/GroupStore"; From 1532048f33a02ae88055c0452926b5c78d3decfb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:49:50 +0100 Subject: [PATCH 16/34] Fix behaviour WidgetStore for new/unknown rooms --- src/stores/WidgetStore.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index a43a6bfc30..d29ee81e13 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -196,11 +196,12 @@ export default class WidgetStore extends AsyncStoreWithClient { } public getApps(room: Room, pinned?: boolean): IApp[] { - const apps = this.getRoom(room.roomId).widgets; + const roomInfo = this.getRoom(room.roomId); + if (!roomInfo) return []; if (pinned) { - return apps.filter(app => this.isPinned(app.id)); + return roomInfo.widgets.filter(app => this.isPinned(app.id)); } - return apps + return roomInfo.widgets; } } From a17b2ba1e559bb9f35764be8ec6097ff27f3da47 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 17:38:13 +0100 Subject: [PATCH 17/34] use constant --- src/stores/WidgetStore.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index d29ee81e13..c01d77855c 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -25,6 +25,7 @@ import WidgetEchoStore from "../stores/WidgetEchoStore"; import WidgetUtils from "../utils/WidgetUtils"; import {SettingLevel} from "../settings/SettingLevel"; import {WidgetType} from "../widgets/WidgetType"; +import {UPDATE_EVENT} from "./AsyncStore"; interface IState {} @@ -87,7 +88,7 @@ export default class WidgetStore extends AsyncStoreWithClient { this.loadRoomWidgets(room); }); - this.emit("update"); + this.emit(UPDATE_EVENT); } protected async onNotReady(): Promise { @@ -105,7 +106,7 @@ export default class WidgetStore extends AsyncStoreWithClient { private onWidgetEchoStoreUpdate(roomId: string, widgetId: string) { this.initRoom(roomId); this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); - this.emit("update"); + this.emit(UPDATE_EVENT); } private generateApps(room: Room): IApp[] { @@ -131,7 +132,7 @@ export default class WidgetStore extends AsyncStoreWithClient { const roomId = ev.getRoomId(); this.initRoom(roomId); this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); - this.emit("update"); + this.emit(UPDATE_EVENT); } public getRoomId = (widgetId: string) => { @@ -148,7 +149,7 @@ export default class WidgetStore extends AsyncStoreWithClient { this.initRoom(roomId); this.getRoom(roomId).pinned = SettingsStore.getValue(settingName, roomId); this.emit(roomId); - this.emit("update"); + this.emit(UPDATE_EVENT); }; public isPinned(widgetId: string) { @@ -192,7 +193,7 @@ export default class WidgetStore extends AsyncStoreWithClient { SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, roomInfo.pinned); this.emit(roomId); - this.emit("update"); + this.emit(UPDATE_EVENT); } public getApps(room: Room, pinned?: boolean): IApp[] { From 01a8ac25c98d792da0e41601cad64e28411a2538 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 17:40:57 +0100 Subject: [PATCH 18/34] Use null coalescing operator --- src/utils/WidgetUtils.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 771bc0887a..d1daba7ca5 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -480,13 +480,11 @@ export default class WidgetUtils { } static getWidgetName(app) { - if (!app || !app.name) return ""; - return app.name.trim() || _t("Unknown App"); + return app?.name?.trim() || _t("Unknown App"); } static getWidgetDataTitle(app) { - if (!app || !app.data || !app.data.title) return ""; - return app.data.title.trim(); + return app?.data?.title?.trim() || ""; } static editWidget(room, app) { From 596060c5069b76556bbe1b68d9f60af854bd7530 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 17:57:08 +0100 Subject: [PATCH 19/34] fix alignments and iterate PR --- res/css/views/right_panel/_BaseCard.scss | 8 ++++---- .../views/right_panel/_RoomSummaryCard.scss | 18 ++++-------------- .../views/right_panel/RoomSummaryCard.tsx | 12 +++++++----- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index ee267d93a5..26f846fe0a 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -24,7 +24,7 @@ limitations under the License. .mx_BaseCard_header { margin: 8px 0; - h2 { + > h2 { margin: 0 44px; font-size: $font-18px; font-weight: $font-semi-bold; @@ -89,11 +89,11 @@ limitations under the License. margin: 20px 0 16px; & > * { - margin-left: 10px; - margin-right: 10px; + margin-left: 12px; + margin-right: 12px; } - h1 { + > h1 { color: $tertiary-fg-color; font-size: $font-12px; font-weight: 500; diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index c8c2cceec7..78324c5e89 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -15,16 +15,6 @@ limitations under the License. */ .mx_RoomSummaryCard { - // shrink left gutter by 12 and instead add it as margin to all things except the buttons - // as their hover effect should go into the gutter - & > * { - margin-left: 10px; - margin-right: 10px; - } - .mx_AutoHideScrollbar { - margin-left: 0; - } - .mx_BaseCard_header { text-align: center; margin-top: 20px; @@ -88,13 +78,13 @@ limitations under the License. .mx_RoomSummaryCard_aboutGroup { .mx_RoomSummaryCard_Button { - padding-left: 48px; + padding-left: 44px; &::before { content: ''; position: absolute; top: 8px; - left: 8px; + left: 10px; height: 24px; width: 24px; mask-repeat: no-repeat; @@ -106,7 +96,7 @@ limitations under the License. .mx_RoomSummaryCard_appsGroup { .mx_RoomSummaryCard_Button { - padding-left: 10px; + padding-left: 12px; color: $tertiary-fg-color; span { @@ -115,7 +105,7 @@ limitations under the License. img { vertical-align: top; - margin-right: 18px; + margin-right: 12px; border-radius: 4px; } diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 64743e1bb8..83d249b482 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -148,11 +148,13 @@ const AppsSection: React.FC = ({ room }) => { }); }; - return ; + return ( + + ); }) } From 12a6bc8231017b40b3543477b35fc37a87362ec0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 18:00:21 +0100 Subject: [PATCH 20/34] update copy --- src/components/views/right_panel/RoomSummaryCard.tsx | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 83d249b482..544582d206 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -158,7 +158,7 @@ const AppsSection: React.FC = ({ room }) => { }) } - { apps.length > 0 ? _t("Edit apps") : _t("Add applications") } + { apps.length > 0 ? _t("Edit apps") : _t("Add apps") } ; }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b3da8cbbb1..41d908b4fb 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1287,7 +1287,7 @@ "Apps": "Apps", "Unpin app": "Unpin app", "Edit apps": "Edit apps", - "Add applications": "Add applications", + "Add apps": "Add apps", "Not encrypted": "Not encrypted", "About": "About", "%(count)s people|other": "%(count)s people", From 644ff56ace9c1f05a5fbeb47e6a8116388e11674 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 09:38:13 +0100 Subject: [PATCH 21/34] Fix e2e tests --- test/end-to-end-tests/src/usecases/room-settings.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index 11e2f52c6e..f5fb37167c 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -45,7 +45,9 @@ async function findTabs(session) { /// XXX delay is needed here, possibly because the header is being rerendered /// click doesn't do anything otherwise await session.delay(1000); - const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[aria-label=Settings]"); + const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); + await roomSummaryButton.click(); + const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); await settingsButton.click(); //find tabs From 29c2a0ef35d7de332f61d9396cbf11087139557a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 09:50:08 +0100 Subject: [PATCH 22/34] Fix FilePanel and NotificationPanel overscroll issues --- src/components/structures/FilePanel.js | 1 + src/components/structures/NotificationPanel.js | 2 +- src/components/structures/TimelinePanel.js | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 8812ba4302..6d618d0b9d 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -230,6 +230,7 @@ class FilePanel extends React.Component { className="mx_FilePanel" onClose={this.props.onClose} previousPhase={RightPanelPhases.RoomSummary} + withoutScrollContainer > ; } - return + return { content } ; } diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index daa18bb290..97f9ba48ed 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -104,8 +104,8 @@ class TimelinePanel extends React.Component { // shape property to be passed to EventTiles tileShape: PropTypes.string, - // placeholder text to use if the timeline is empty - empty: PropTypes.string, + // placeholder to use if the timeline is empty + empty: PropTypes.node, // whether to show reactions for an event showReactions: PropTypes.bool, From c8bc80a3b1493d92349c3fd78b09cd5e384c844c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 10:07:43 +0100 Subject: [PATCH 23/34] test with delay --- res/css/views/right_panel/_WidgetCard.scss | 1 + test/end-to-end-tests/src/usecases/room-settings.js | 1 + 2 files changed, 2 insertions(+) diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss index 0f859738b1..28f09bf319 100644 --- a/res/css/views/right_panel/_WidgetCard.scss +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -19,6 +19,7 @@ limitations under the License. display: contents; .mx_AppTileFullWidth { + max-width: unset; height: 100%; border: 0; } diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index f5fb37167c..c7f4495a86 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -47,6 +47,7 @@ async function findTabs(session) { await session.delay(1000); const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); await roomSummaryButton.click(); + await session.delay(1000); const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); await settingsButton.click(); From fb0b784369ba2daf66bbe61580bf88bb70a2d408 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 10:56:14 +0100 Subject: [PATCH 24/34] test CI --- test/end-to-end-tests/src/usecases/room-settings.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index c7f4495a86..01894ee794 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -47,9 +47,8 @@ async function findTabs(session) { await session.delay(1000); const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); await roomSummaryButton.click(); - await session.delay(1000); - const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); - await settingsButton.click(); + // const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); + // await settingsButton.click(); //find tabs const tabButtons = await session.queryAll(".mx_RoomSettingsDialog .mx_TabbedView_tabLabel"); From 37c0d524bcbce5e48da581230c1fad806a9e5baf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 11:06:15 +0100 Subject: [PATCH 25/34] re-order top right buttons --- .../views/right_panel/RoomHeaderButtons.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 338290b7c9..c2364546fd 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -80,14 +80,6 @@ export default class RoomHeaderButtons extends HeaderButtons { public renderButtons() { return [ - , , + , ]; } } From bb9858714306f4b8976c0f84459bd447a257f5a3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 11:42:41 +0100 Subject: [PATCH 26/34] fix e2e tests. Change the default Room Tab to RoomSummary --- src/settings/Settings.ts | 2 +- test/end-to-end-tests/src/usecases/room-settings.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index e15cb46145..9e0f36b1ba 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -566,7 +566,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "lastRightPanelPhaseForRoom": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, - default: RightPanelPhases.RoomMemberInfo, + default: RightPanelPhases.RoomSummary, }, "lastRightPanelPhaseForGroup": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index 01894ee794..f5fb37167c 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -47,8 +47,8 @@ async function findTabs(session) { await session.delay(1000); const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); await roomSummaryButton.click(); - // const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); - // await settingsButton.click(); + const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); + await settingsButton.click(); //find tabs const tabButtons = await session.queryAll(".mx_RoomSettingsDialog .mx_TabbedView_tabLabel"); From 8dcb2d47199ff26e975424c65c2d100b9d8833d0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 12:06:16 +0100 Subject: [PATCH 27/34] attempt to fix CI tests --- test/end-to-end-tests/src/usecases/memberlist.js | 5 +++++ test/end-to-end-tests/src/usecases/verify.js | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/end-to-end-tests/src/usecases/memberlist.js b/test/end-to-end-tests/src/usecases/memberlist.js index e974eea95b..cc641ae4c9 100644 --- a/test/end-to-end-tests/src/usecases/memberlist.js +++ b/test/end-to-end-tests/src/usecases/memberlist.js @@ -17,6 +17,11 @@ limitations under the License. const assert = require('assert'); +module.exports.openMemberList = async function(session) { + const peopleButton = await session.query(".mx_RoomSummaryCard_icon_people"); + await peopleButton.click(); +}; + async function openMemberInfo(session, name) { const membersAndNames = await getMembersInMemberlist(session); const matchingLabel = membersAndNames.filter((m) => { diff --git a/test/end-to-end-tests/src/usecases/verify.js b/test/end-to-end-tests/src/usecases/verify.js index 98e73ad6b7..4021c97dc9 100644 --- a/test/end-to-end-tests/src/usecases/verify.js +++ b/test/end-to-end-tests/src/usecases/verify.js @@ -16,10 +16,11 @@ limitations under the License. */ const assert = require('assert'); -const {openMemberInfo} = require("./memberlist"); +const {openMemberList, openMemberInfo} = require("./memberlist"); async function startVerification(session, name) { session.log.step("opens their opponent's profile and starts verification"); + await openMemberList(session); await openMemberInfo(session, name); // click verify in member info const firstVerifyButton = await session.query(".mx_UserInfo_verifyButton"); From b635598bc3266190b238e6aa8708ef4ab41c78f8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 12:28:12 +0100 Subject: [PATCH 28/34] Attempt to fix tests and fix RoomSummaryCard having wrong member count --- .../views/right_panel/RoomSummaryCard.tsx | 12 ++++++- .../src/usecases/memberlist.js | 31 +++++++++++-------- test/end-to-end-tests/src/usecases/verify.js | 3 +- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 544582d206..c782654637 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -181,6 +181,14 @@ const onRoomSettingsClick = () => { defaultDispatcher.dispatch({ action: "open_room_settings" }); }; +const useMemberCount = (room: Room) => { + const [count, setCount] = useState(room.getJoinedMembers().length); + useEventEmitter(room.currentState, "RoomState.members", () => { + setCount(room.getJoinedMembers().length); + }); + return count; +}; + const RoomSummaryCard: React.FC = ({ room, onClose }) => { const cli = useContext(MatrixClientContext); @@ -210,10 +218,12 @@ const RoomSummaryCard: React.FC = ({ room, onClose }) => {
; + const memberCount = useMemberCount(room); + return