From fbe3eb772694fb14c1e6d60cc5f18bd5bf151a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 26 Jun 2021 09:00:42 +0200 Subject: [PATCH 01/17] Remove unused class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/dialogs/_DevtoolsDialog.scss | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss index 8fee740016..c197f8ac0a 100644 --- a/res/css/views/dialogs/_DevtoolsDialog.scss +++ b/res/css/views/dialogs/_DevtoolsDialog.scss @@ -55,22 +55,6 @@ limitations under the License. padding-right: 24px; } -.mx_DevTools_inputCell { - display: table-cell; - width: 240px; -} - -.mx_DevTools_inputCell input { - display: inline-block; - border: 0; - border-bottom: 1px solid $input-underline-color; - padding: 0; - width: 240px; - color: $input-fg-color; - font-family: $font-family; - font-size: $font-16px; -} - .mx_DevTools_textarea { font-size: $font-12px; max-width: 684px; From ffc6f96d45d1b8cbbfbcc51c3528baaae89f8bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 26 Jun 2021 09:01:26 +0200 Subject: [PATCH 02/17] Remove explicitly set font-family MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/dialogs/_AddressPickerDialog.scss | 1 - res/css/views/dialogs/_ConfirmUserActionDialog.scss | 1 - res/css/views/dialogs/_DevtoolsDialog.scss | 1 - res/css/views/elements/_Field.scss | 1 - res/css/views/settings/tabs/_SettingsTab.scss | 1 - 5 files changed, 5 deletions(-) diff --git a/res/css/views/dialogs/_AddressPickerDialog.scss b/res/css/views/dialogs/_AddressPickerDialog.scss index 136e497994..a1147e6fbc 100644 --- a/res/css/views/dialogs/_AddressPickerDialog.scss +++ b/res/css/views/dialogs/_AddressPickerDialog.scss @@ -29,7 +29,6 @@ limitations under the License. .mx_AddressPickerDialog_input:focus { height: 26px; font-size: $font-14px; - font-family: $font-family; padding-left: 12px; padding-right: 12px; margin: 0 !important; diff --git a/res/css/views/dialogs/_ConfirmUserActionDialog.scss b/res/css/views/dialogs/_ConfirmUserActionDialog.scss index 823f4d1e28..284c171f4e 100644 --- a/res/css/views/dialogs/_ConfirmUserActionDialog.scss +++ b/res/css/views/dialogs/_ConfirmUserActionDialog.scss @@ -34,7 +34,6 @@ limitations under the License. } .mx_ConfirmUserActionDialog_reasonField { - font-family: $font-family; font-size: $font-14px; color: $primary-fg-color; background-color: $primary-bg-color; diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss index c197f8ac0a..4d35e8d569 100644 --- a/res/css/views/dialogs/_DevtoolsDialog.scss +++ b/res/css/views/dialogs/_DevtoolsDialog.scss @@ -123,7 +123,6 @@ limitations under the License. + .mx_DevTools_tgl-btn { padding: 2px; transition: all .2s ease; - font-family: sans-serif; perspective: 100px; &::after, &::before { diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index f67da6477b..cae81dcc97 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -39,7 +39,6 @@ limitations under the License. .mx_Field select, .mx_Field textarea { font-weight: normal; - font-family: $font-family; font-size: $font-14px; border: none; // Even without a border here, we still need this avoid overlapping the rounded diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 892f5fe744..c69244a016 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -36,7 +36,6 @@ limitations under the License. .mx_SettingsTab_subheading { font-size: $font-16px; display: block; - font-family: $font-family; font-weight: 600; color: $primary-fg-color; margin-bottom: 10px; From ed78bd9096fb67b33089a78760fe00755b6be781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 26 Jun 2021 09:01:57 +0200 Subject: [PATCH 03/17] Remove explicitly set font-family in place where comments talk about FF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/_common.scss | 2 -- res/css/views/rooms/_MessageComposer.scss | 2 -- 2 files changed, 4 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index b128a82442..fd1e4c6d82 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -105,7 +105,6 @@ input[type=text], input[type=search], input[type=password] { padding: 9px; - font-family: $font-family; font-size: $font-14px; font-weight: 600; min-width: 0; @@ -146,7 +145,6 @@ input[type=text], input[type=password], textarea { /* Required by Firefox */ textarea { - font-family: $font-family; color: $primary-fg-color; } diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index e6c0cc3f46..5e2eff4047 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -165,8 +165,6 @@ limitations under the License. font-size: $font-14px; max-height: 120px; overflow: auto; - /* needed for FF */ - font-family: $font-family; } /* hack for FF as vertical alignment of custom placeholder text is broken */ From 43732e5c1248671c632b594bf848909f8c41053d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 26 Jun 2021 09:16:24 +0200 Subject: [PATCH 04/17] Inherit font-family MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/_common.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/_common.scss b/res/css/_common.scss index fd1e4c6d82..6b4e109b3a 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -104,6 +104,7 @@ a:visited { input[type=text], input[type=search], input[type=password] { + font-family: inherit; padding: 9px; font-size: $font-14px; font-weight: 600; From bb0be4ba726ec4529e999319752705dc6a859d11 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 22 Jul 2021 15:43:18 -0600 Subject: [PATCH 05/17] Add customisation point for declaring widget variables Fixes https://github.com/vector-im/element-web/issues/18035 (indirectly; issue is on a non-standard version of Element). This has an isReady() step because the variable usage code path cannot be made async, so instead we let the async loading state happen in the prepare() stage. This is required for the intended usecase. --- src/customisations/WidgetVariables.ts | 53 +++++++++++++++++++++++++++ src/stores/widgets/StopGapWidget.ts | 10 ++++- 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/customisations/WidgetVariables.ts diff --git a/src/customisations/WidgetVariables.ts b/src/customisations/WidgetVariables.ts new file mode 100644 index 0000000000..db3a56436d --- /dev/null +++ b/src/customisations/WidgetVariables.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Populate this class with the details of your customisations when copying it. +import { ITemplateParams } from "matrix-widget-api"; + +/** + * Provides a partial set of the variables needed to render any widget. If + * variables are missing or not provided then they will be filled with the + * application-determined defaults. + * + * This will not be called until after isReady() resolves. + * @returns {Partial>} The variables. + */ +function provideVariables(): Partial> { + return {}; +} + +/** + * Resolves to whether or not the customisation point is ready for variables + * to be provided. This will block widgets being rendered. + * @returns {Promise} Resolves when ready. + */ +async function isReady(): Promise { + return; // default no waiting +} + +// This interface summarises all available customisation points and also marks +// them all as optional. This allows customisers to only define and export the +// customisations they need while still maintaining type safety. +export interface IWidgetVariablesCustomisations { + provideVariables?: typeof provideVariables; + + // If not provided, the app will assume that the customisation is always ready. + isReady?: typeof isReady; +} + +// A real customisation module will define and export one or more of the +// customisation points that make up the interface above. +export const WidgetVariableCustomisations: IWidgetVariablesCustomisations = {}; diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 24869b5edc..daa1e0e787 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -54,6 +54,7 @@ import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { ELEMENT_CLIENT_ID } from "../../identifiers"; import { getUserLanguage } from "../../languageHandler"; +import { WidgetVariableCustomisations } from "../../customisations/WidgetVariables"; // TODO: Destroy all of this code @@ -191,7 +192,8 @@ export class StopGapWidget extends EventEmitter { } private runUrlTemplate(opts = { asPopout: false }): string { - const templated = this.mockWidget.getCompleteUrl({ + const fromCustomisation = WidgetVariableCustomisations?.provideVariables?.() ?? {}; + const defaults: ITemplateParams = { widgetRoomId: this.roomId, currentUserId: MatrixClientPeg.get().getUserId(), userDisplayName: OwnProfileStore.instance.displayName, @@ -199,7 +201,8 @@ export class StopGapWidget extends EventEmitter { clientId: ELEMENT_CLIENT_ID, clientTheme: SettingsStore.getValue("theme"), clientLanguage: getUserLanguage(), - }, opts?.asPopout); + }; + const templated = this.mockWidget.getCompleteUrl(Object.assign(defaults, fromCustomisation), opts?.asPopout); const parsed = new URL(templated); @@ -363,6 +366,9 @@ export class StopGapWidget extends EventEmitter { } public async prepare(): Promise { + // Ensure the variables are ready for us to be rendered before continuing + await (WidgetVariableCustomisations?.isReady?.() ?? Promise.resolve()); + if (this.scalarToken) return; const existingMessaging = WidgetMessagingStore.instance.getMessaging(this.mockWidget); if (existingMessaging) this.messaging = existingMessaging; From a8d27746b4de5a938fc44f94e19d0f8f81afefdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 17:07:18 +0200 Subject: [PATCH 06/17] Show dialpad only when PSTN supported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallView.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index a98c42526e..966d8e4a09 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -537,8 +537,6 @@ export default class CallView extends React.Component { } // The dial pad & 'more' button actions are only relevant in a connected call - // When not connected, we have to put something there to make the flexbox alignment correct - let dialpadButton; let contextMenuButton; if (this.state.callState === CallState.Connected) { contextMenuButton = ( @@ -549,6 +547,9 @@ export default class CallView extends React.Component { isExpanded={this.state.showMoreMenu} /> ); + } + let dialpadButton; + if (this.state.callState === CallState.Connected && CallHandler.sharedInstance().getSupportsPstnProtocol()) { dialpadButton = ( Date: Sun, 25 Jul 2021 17:24:18 +0200 Subject: [PATCH 07/17] Fix call type icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/voip/_CallView.scss | 11 +++++++++-- src/components/views/voip/CallView.tsx | 7 ++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 59298ef8e6..2bb5b8ec3f 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -279,7 +279,7 @@ limitations under the License. max-width: 240px; } -.mx_CallView_header_phoneIcon { +.mx_CallView_header_callTypeIcon { display: inline-block; margin-right: 6px; height: 16px; @@ -293,12 +293,19 @@ limitations under the License. height: 16px; width: 16px; - background-color: $warning-color; + background-color: $secondary-fg-color; mask-repeat: no-repeat; mask-size: contain; mask-position: center; + } + + &.mx_CallView_header_callTypeIcon_voice::before { mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); } + + &.mx_CallView_header_callTypeIcon_video::before { + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + } } .mx_CallView_callControls { diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 966d8e4a09..039f5f2dcf 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -822,10 +822,15 @@ export default class CallView extends React.Component { { expandButton } ; + const callTypeIconClassName = classNames("mx_CallView_header_callTypeIcon", { + "mx_CallView_header_callTypeIcon_voice": !isVideoCall, + "mx_CallView_header_callTypeIcon_video": isVideoCall, + }); + let header: React.ReactNode; if (!this.props.pipMode) { header =
-
+
{ callTypeText } { headerControls }
; From 17546026133259874c60fd29c02c65f2d415e9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 17:30:59 +0200 Subject: [PATCH 08/17] Increase CONTROLS_HIDE_DELAY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 039f5f2dcf..80db71d31f 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -102,7 +102,7 @@ function exitFullscreen() { if (exitMethod) exitMethod.call(document); } -const CONTROLS_HIDE_DELAY = 1000; +const CONTROLS_HIDE_DELAY = 2000; // Height of the header duplicated from CSS because we need to subtract it from our max // height to get the max height of the video const CONTEXT_MENU_VPADDING = 8; // How far the context menu sits above the button (px) From 3ef20174f30933f4333e0229a106f40a57dac6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 25 Jul 2021 18:11:29 +0200 Subject: [PATCH 09/17] Fix issues with the controls disappearing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/voip/_CallView.scss | 1 - src/components/views/voip/CallView.tsx | 59 +++++++++++--------------- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 2bb5b8ec3f..104e2993d8 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -313,7 +313,6 @@ limitations under the License. display: flex; justify-content: center; bottom: 5px; - width: 100%; opacity: 1; transition: opacity 0.5s; z-index: 200; // To be above _all_ feeds diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 80db71d31f..14025ce3f0 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -67,6 +67,7 @@ interface IState { screensharing: boolean; callState: CallState; controlsVisible: boolean; + hoveringControls: boolean; showMoreMenu: boolean; showDialpad: boolean; primaryFeed: CallFeed; @@ -128,6 +129,7 @@ export default class CallView extends React.Component { screensharing: this.props.call.isScreensharing(), callState: this.props.call.state, controlsVisible: true, + hoveringControls: false, showMoreMenu: false, showDialpad: false, primaryFeed: primary, @@ -244,6 +246,7 @@ export default class CallView extends React.Component { }; private onControlsHideTimer = () => { + if (this.state.hoveringControls || this.state.showDialpad || this.state.showMoreMenu) return; this.controlsHideTimer = null; this.setState({ controlsVisible: false, @@ -293,24 +296,10 @@ export default class CallView extends React.Component { private onDialpadClick = (): void => { if (!this.state.showDialpad) { - if (this.controlsHideTimer) { - clearTimeout(this.controlsHideTimer); - this.controlsHideTimer = null; - } - - this.setState({ - showDialpad: true, - controlsVisible: true, - }); + this.setState({ showDialpad: true }); + this.showControls(); } else { - if (this.controlsHideTimer !== null) { - clearTimeout(this.controlsHideTimer); - } - this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); - - this.setState({ - showDialpad: false, - }); + this.setState({ showDialpad: false }); } }; @@ -345,29 +334,16 @@ export default class CallView extends React.Component { }; private onMoreClick = (): void => { - if (this.controlsHideTimer) { - clearTimeout(this.controlsHideTimer); - this.controlsHideTimer = null; - } - - this.setState({ - showMoreMenu: true, - controlsVisible: true, - }); + this.setState({ showMoreMenu: true }); + this.showControls(); }; private closeDialpad = (): void => { - this.setState({ - showDialpad: false, - }); - this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); + this.setState({ showDialpad: false }); }; private closeContextMenu = (): void => { - this.setState({ - showMoreMenu: false, - }); - this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); + this.setState({ showMoreMenu: false }); }; // we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire @@ -403,6 +379,15 @@ export default class CallView extends React.Component { } }; + private onCallControlsMouseEnter = (): void => { + this.setState({ hoveringControls: true }); + this.showControls(); + }; + + private onCallControlsMouseLeave = (): void => { + this.setState({ hoveringControls: false }); + }; + private onRoomAvatarClick = (): void => { const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); dis.dispatch({ @@ -561,7 +546,11 @@ export default class CallView extends React.Component { } return ( -
+
{ dialpadButton } Date: Tue, 27 Jul 2021 14:39:14 +0100 Subject: [PATCH 10/17] Conclude labs flags and write more tests --- src/settings/Settings.tsx | 16 --- src/stores/SpaceStore.tsx | 27 ++--- .../notifications/SpaceNotificationState.ts | 2 +- test/stores/SpaceStore-setup.ts | 2 - test/stores/SpaceStore-test.ts | 108 ++++++++++++++++-- 5 files changed, 112 insertions(+), 43 deletions(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index f0bdb2e0e5..5aa49df8a1 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -181,8 +181,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { feedbackLabel: "spaces-feedback", extraSettings: [ "feature_spaces.all_rooms", - "feature_spaces.space_member_dms", - "feature_spaces.space_dm_badges", ], }, }, @@ -192,20 +190,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: true, controller: new ReloadOnChangeController(), }, - "feature_spaces.space_member_dms": { - displayName: _td("Show people in spaces"), - description: _td("If disabled, you can still add Direct Messages to Personal Spaces. " + - "If enabled, you'll automatically see everyone who is a member of the Space."), - supportedLevels: LEVELS_FEATURE, - default: true, - controller: new ReloadOnChangeController(), - }, - "feature_spaces.space_dm_badges": { - displayName: _td("Show notification badges for People in Spaces"), - supportedLevels: LEVELS_FEATURE, - default: false, - controller: new ReloadOnChangeController(), - }, "feature_dnd": { isFeature: true, displayName: _td("Show options to enable 'Do not disturb' mode"), diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index a338e71838..d064b01257 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -72,8 +72,6 @@ const MAX_SUGGESTED_ROOMS = 20; // All of these settings cause the page to reload and can be costly if read frequently, so read them here only const spacesEnabled = SettingsStore.getValue("feature_spaces"); const spacesTweakAllRoomsEnabled = SettingsStore.getValue("feature_spaces.all_rooms"); -const spacesTweakSpaceMemberDMsEnabled = SettingsStore.getValue("feature_spaces.space_member_dms"); -const spacesTweakSpaceDMBadgesEnabled = SettingsStore.getValue("feature_spaces.space_dm_badges"); const homeSpaceKey = spacesTweakAllRoomsEnabled ? "ALL_ROOMS" : "HOME_SPACE"; const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || homeSpaceKey}`; @@ -535,15 +533,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const roomIds = new Set(childRooms.map(r => r.roomId)); const space = this.matrixClient?.getRoom(spaceId); - if (spacesTweakSpaceMemberDMsEnabled) { - // Add relevant DMs - space?.getMembers().forEach(member => { - if (member.membership !== "join" && member.membership !== "invite") return; - DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => { - roomIds.add(roomId); - }); + // Add relevant DMs + space?.getMembers().forEach(member => { + if (member.membership !== "join" && member.membership !== "invite") return; + DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => { + roomIds.add(roomId); }); - } + }); const newPath = new Set(parentPath).add(spaceId); childSpaces.forEach(childSpace => { @@ -568,14 +564,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.spaceFilteredRooms.forEach((roomIds, s) => { // Update NotificationStates this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => { - if (roomIds.has(room.roomId)) { - if (s !== HOME_SPACE && spacesTweakSpaceDMBadgesEnabled) return true; + if (!roomIds.has(room.roomId)) return false; - return !DMRoomMap.shared().getUserIdForRoomId(room.roomId) - || RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite); + if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { + return s === HOME_SPACE; } - return false; + return true; })); }); }, 100, { trailing: true, leading: true }); @@ -878,8 +873,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient { export default class SpaceStore { public static spacesEnabled = spacesEnabled; public static spacesTweakAllRoomsEnabled = spacesTweakAllRoomsEnabled; - public static spacesTweakSpaceMemberDMsEnabled = spacesTweakSpaceMemberDMsEnabled; - public static spacesTweakSpaceDMBadgesEnabled = spacesTweakSpaceDMBadgesEnabled; private static internalInstance = new SpaceStoreClass(); diff --git a/src/stores/notifications/SpaceNotificationState.ts b/src/stores/notifications/SpaceNotificationState.ts index 4c0a582f3f..f8eb07251b 100644 --- a/src/stores/notifications/SpaceNotificationState.ts +++ b/src/stores/notifications/SpaceNotificationState.ts @@ -23,7 +23,7 @@ import { NOTIFICATION_STATE_UPDATE, NotificationState } from "./NotificationStat import { FetchRoomFn } from "./ListNotificationState"; export class SpaceNotificationState extends NotificationState { - private rooms: Room[] = []; + public rooms: Room[] = []; // exposed only for tests private states: { [spaceId: string]: RoomNotificationState } = {}; constructor(private spaceId: string | symbol, private getRoomFn: FetchRoomFn) { diff --git a/test/stores/SpaceStore-setup.ts b/test/stores/SpaceStore-setup.ts index 67d492255f..b9b865e89a 100644 --- a/test/stores/SpaceStore-setup.ts +++ b/test/stores/SpaceStore-setup.ts @@ -19,5 +19,3 @@ limitations under the License. localStorage.setItem("mx_labs_feature_feature_spaces", "true"); localStorage.setItem("mx_labs_feature_feature_spaces.all_rooms", "true"); -localStorage.setItem("mx_labs_feature_feature_spaces.space_member_dms", "true"); -localStorage.setItem("mx_labs_feature_feature_spaces.space_dm_badges", "false"); diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 8855f4e470..d772a7a658 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -17,6 +17,7 @@ limitations under the License. import { EventEmitter } from "events"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import "./SpaceStore-setup"; // enable space lab import "../skinned-sdk"; // Must be first for skinning to work @@ -53,18 +54,22 @@ const emitPromise = (e: EventEmitter, k: string | symbol) => new Promise(r => e. const testUserId = "@test:user"; const getUserIdForRoomId = jest.fn(); +const getDMRoomsForUserId = jest.fn(); // @ts-ignore -DMRoomMap.sharedInstance = { getUserIdForRoomId }; +DMRoomMap.sharedInstance = { getUserIdForRoomId, getDMRoomsForUserId }; const fav1 = "!fav1:server"; const fav2 = "!fav2:server"; const fav3 = "!fav3:server"; const dm1 = "!dm1:server"; -const dm1Partner = "@dm1Partner:server"; +const dm1Partner = new RoomMember(dm1, "@dm1Partner:server"); +dm1Partner.membership = "join"; const dm2 = "!dm2:server"; -const dm2Partner = "@dm2Partner:server"; +const dm2Partner = new RoomMember(dm2, "@dm2Partner:server"); +dm2Partner.membership = "join"; const dm3 = "!dm3:server"; -const dm3Partner = "@dm3Partner:server"; +const dm3Partner = new RoomMember(dm3, "@dm3Partner:server"); +dm3Partner.membership = "join"; const orphan1 = "!orphan1:server"; const orphan2 = "!orphan2:server"; const invite1 = "!invite1:server"; @@ -320,11 +325,40 @@ describe("SpaceStore", () => { getUserIdForRoomId.mockImplementation(roomId => { return { - [dm1]: dm1Partner, - [dm2]: dm2Partner, - [dm3]: dm3Partner, + [dm1]: dm1Partner.userId, + [dm2]: dm2Partner.userId, + [dm3]: dm3Partner.userId, }[roomId]; }); + getDMRoomsForUserId.mockImplementation(userId => { + switch (userId) { + case dm1Partner.userId: + return [dm1]; + case dm2Partner.userId: + return [dm2]; + case dm3Partner.userId: + return [dm3]; + default: + return []; + } + }); + + // have dmPartner1 be in space1 with you + const mySpace1Member = new RoomMember(space1, testUserId); + mySpace1Member.membership = "join"; + (rooms.find(r => r.roomId === space1).getMembers as jest.Mock).mockReturnValue([ + mySpace1Member, + dm1Partner, + ]); + // have dmPartner2 be in space2 with you + const mySpace2Member = new RoomMember(space2, testUserId); + mySpace2Member.membership = "join"; + (rooms.find(r => r.roomId === space2).getMembers as jest.Mock).mockReturnValue([ + mySpace2Member, + dm2Partner, + ]); + // dmPartner3 is not in any common spaces with you + await run(); }); @@ -375,6 +409,66 @@ describe("SpaceStore", () => { const space = client.getRoom(space3); expect(store.getSpaceFilteredRoomIds(space).has(invite2)).toBeTruthy(); }); + + it("spaces contain dms which you have with members of that space", () => { + expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(dm1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(dm1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(dm1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(dm2)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(dm2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(dm2)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(dm3)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(dm3)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(dm3)).toBeFalsy(); + }); + + it("dms are only added to Notification States for only the Home Space", () => { + // XXX: All rooms space is forcibly enabled, as part of a future PR test Home space better + // [dm1, dm2, dm3].forEach(d => { + // expect(store.getNotificationState(HOME_SPACE).rooms.map(r => r.roomId).includes(d)).toBeTruthy(); + // }); + [space1, space2, space3].forEach(s => { + [dm1, dm2, dm3].forEach(d => { + expect(store.getNotificationState(s).rooms.map(r => r.roomId).includes(d)).toBeFalsy(); + }); + }); + }); + + it("orphan rooms are added to Notification States for only the Home Space", () => { + // XXX: All rooms space is forcibly enabled, as part of a future PR test Home space better + // [orphan1, orphan2].forEach(d => { + // expect(store.getNotificationState(HOME_SPACE).rooms.map(r => r.roomId).includes(d)).toBeTruthy(); + // }); + [space1, space2, space3].forEach(s => { + [orphan1, orphan2].forEach(d => { + expect(store.getNotificationState(s).rooms.map(r => r.roomId).includes(d)).toBeFalsy(); + }); + }); + }); + + it("favourites are added to Notification States for all spaces containing the room inc Home", () => { + // XXX: All rooms space is forcibly enabled, as part of a future PR test Home space better + // [fav1, fav2, fav3].forEach(d => { + // expect(store.getNotificationState(HOME_SPACE).rooms.map(r => r.roomId).includes(d)).toBeTruthy(); + // }); + expect(store.getNotificationState(space1).rooms.map(r => r.roomId).includes(fav1)).toBeTruthy(); + expect(store.getNotificationState(space1).rooms.map(r => r.roomId).includes(fav2)).toBeFalsy(); + expect(store.getNotificationState(space1).rooms.map(r => r.roomId).includes(fav3)).toBeFalsy(); + expect(store.getNotificationState(space2).rooms.map(r => r.roomId).includes(fav1)).toBeTruthy(); + expect(store.getNotificationState(space2).rooms.map(r => r.roomId).includes(fav2)).toBeTruthy(); + expect(store.getNotificationState(space2).rooms.map(r => r.roomId).includes(fav3)).toBeTruthy(); + expect(store.getNotificationState(space3).rooms.map(r => r.roomId).includes(fav1)).toBeFalsy(); + expect(store.getNotificationState(space3).rooms.map(r => r.roomId).includes(fav2)).toBeFalsy(); + expect(store.getNotificationState(space3).rooms.map(r => r.roomId).includes(fav3)).toBeFalsy(); + }); + + it("other rooms are added to Notification States for all spaces containing the room exc Home", () => { + // XXX: All rooms space is forcibly enabled, as part of a future PR test Home space better + // expect(store.getNotificationState(HOME_SPACE).rooms.map(r => r.roomId).includes(room1)).toBeFalsy(); + expect(store.getNotificationState(space1).rooms.map(r => r.roomId).includes(room1)).toBeTruthy(); + expect(store.getNotificationState(space2).rooms.map(r => r.roomId).includes(room1)).toBeTruthy(); + expect(store.getNotificationState(space3).rooms.map(r => r.roomId).includes(room1)).toBeFalsy(); + }); }); }); From 353c70ad75ff79616c8cb9426b89885e7aad5470 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 27 Jul 2021 14:45:36 +0100 Subject: [PATCH 11/17] i18n --- src/i18n/strings/en_EN.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 102a481f52..de432d6177 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -798,9 +798,6 @@ "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.", "Your feedback will help make spaces better. The more detail you can go into, the better.": "Your feedback will help make spaces better. The more detail you can go into, the better.", "Show all rooms in Home": "Show all rooms in Home", - "Show people in spaces": "Show people in spaces", - "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.", - "Show notification badges for People in Spaces": "Show notification badges for People in Spaces", "Show options to enable 'Do not disturb' mode": "Show options to enable 'Do not disturb' mode", "Send and receive voice messages": "Send and receive voice messages", "Render LaTeX maths in messages": "Render LaTeX maths in messages", From 3037be11c4516cb03834b7c6edebea304f32008e Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 27 Jul 2021 16:24:05 +0200 Subject: [PATCH 12/17] Restore padding for single person state events --- res/css/views/rooms/_EventTile.scss | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 4a419244ff..808af30329 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -132,15 +132,6 @@ $hover-select-border: 4px; } } - &.mx_EventTile_info .mx_EventTile_line, - & ~ .mx_EventListSummary > :not(.mx_EventTile) .mx_EventTile_avatar ~ .mx_EventTile_line { - padding-left: calc($left-gutter + 18px); - } - - & ~ .mx_EventListSummary .mx_EventTile_line { - padding-left: calc($left-gutter); - } - &.mx_EventTile_selected.mx_EventTile_info .mx_EventTile_line { padding-left: calc($left-gutter + 18px - $hover-select-border); } @@ -280,6 +271,15 @@ $hover-select-border: 4px; } } +.mx_EventTile:not([data-layout=bubble]).mx_EventTile_info .mx_EventTile_line, +.mx_EventListSummary:not([data-layout=bubble]) > :not(.mx_EventTile) .mx_EventTile_avatar ~ .mx_EventTile_line { + padding-left: calc($left-gutter + 18px); +} + +.mx_EventListSummary:not([data-layout=bubble]) .mx_EventTile_line { + padding-left: calc($left-gutter); +} + /* all the overflow-y: hidden; are to trap Zalgos - but they introduce an implicit overflow-x: auto. so make that explicitly hidden too to avoid random From 6f77d3bbabf94fe6c90579a41868ad50cee5d1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 27 Jul 2021 17:52:13 +0200 Subject: [PATCH 13/17] Correctly hide the dialpad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/voip/CallView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 14025ce3f0..481d7b303c 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -534,7 +534,7 @@ export default class CallView extends React.Component { ); } let dialpadButton; - if (this.state.callState === CallState.Connected && CallHandler.sharedInstance().getSupportsPstnProtocol()) { + if (this.state.callState === CallState.Connected && this.props.call.opponentSupportsDTMF()) { dialpadButton = ( Date: Tue, 27 Jul 2021 17:57:46 +0200 Subject: [PATCH 14/17] Make link within replies clickable --- src/components/views/rooms/ReplyTile.tsx | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/components/views/rooms/ReplyTile.tsx b/src/components/views/rooms/ReplyTile.tsx index 18b30d33d5..e06cc90817 100644 --- a/src/components/views/rooms/ReplyTile.tsx +++ b/src/components/views/rooms/ReplyTile.tsx @@ -67,15 +67,21 @@ export default class ReplyTile extends React.PureComponent { }; private onClick = (e: React.MouseEvent): void => { - // This allows the permalink to be opened in a new tab/window or copied as - // matrix.to, but also for it to enable routing within Riot when clicked. - e.preventDefault(); - dis.dispatch({ - action: 'view_room', - event_id: this.props.mxEvent.getId(), - highlighted: true, - room_id: this.props.mxEvent.getRoomId(), - }); + const clickTarget = e.target as HTMLElement; + // Following a link within a reply should not dispatch the `view_room` action + // so that the browser can direct the user to the correct location + // The exceptionp being the link wrapping the reply + if (clickTarget.tagName.toLowerCase() !== "a" || clickTarget.closest("a") === null) { + // This allows the permalink to be opened in a new tab/window or copied as + // matrix.to, but also for it to enable routing within Riot when clicked. + e.preventDefault(); + dis.dispatch({ + action: 'view_room', + event_id: this.props.mxEvent.getId(), + highlighted: true, + room_id: this.props.mxEvent.getRoomId(), + }); + } }; render() { From 7e453d77450aa369618f32c1f072e19322c2e244 Mon Sep 17 00:00:00 2001 From: Germain Date: Tue, 27 Jul 2021 17:14:39 +0100 Subject: [PATCH 15/17] Typo Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/ReplyTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/ReplyTile.tsx b/src/components/views/rooms/ReplyTile.tsx index e06cc90817..16a7141bd7 100644 --- a/src/components/views/rooms/ReplyTile.tsx +++ b/src/components/views/rooms/ReplyTile.tsx @@ -70,7 +70,7 @@ export default class ReplyTile extends React.PureComponent { const clickTarget = e.target as HTMLElement; // Following a link within a reply should not dispatch the `view_room` action // so that the browser can direct the user to the correct location - // The exceptionp being the link wrapping the reply + // The exception being the link wrapping the reply if (clickTarget.tagName.toLowerCase() !== "a" || clickTarget.closest("a") === null) { // This allows the permalink to be opened in a new tab/window or copied as // matrix.to, but also for it to enable routing within Riot when clicked. From 3694006d0553628ae930578ccae516c166a61ac2 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 27 Jul 2021 23:48:51 -0400 Subject: [PATCH 16/17] Realign reactions row with messages in modern layout Signed-off-by: Robin Townsend --- res/css/views/rooms/_EventTile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 4a419244ff..2901410c95 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -276,7 +276,7 @@ $hover-select-border: 4px; .mx_ReactionsRow { margin: 0; - padding: 6px 60px; + padding: 4px 64px; } } From 52e50863f81cb5a3f0941f3d14374a20dd87c411 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 28 Jul 2021 08:39:04 +0100 Subject: [PATCH 17/17] Fix CreateRoomDialog exploding when making public room outside of a space --- src/components/views/dialogs/CreateRoomDialog.tsx | 8 +++++++- src/i18n/strings/en_EN.json | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index a06f508908..572212a96c 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -250,7 +250,7 @@ export default class CreateRoomDialog extends React.Component {   { _t("You can change this at any time from room settings.") }

; - } else if (this.state.joinRule === JoinRule.Public) { + } else if (this.state.joinRule === JoinRule.Public && this.props.parentSpace) { publicPrivateLabel =

{ _t( "Anyone will be able to find and join this room, not just members of .", {}, { @@ -260,6 +260,12 @@ export default class CreateRoomDialog extends React.Component {   { _t("You can change this at any time from room settings.") }

; + } else if (this.state.joinRule === JoinRule.Public) { + publicPrivateLabel =

+ { _t("Anyone will be able to find and join this room.") } +   + { _t("You can change this at any time from room settings.") } +

; } else if (this.state.joinRule === JoinRule.Invite) { publicPrivateLabel =

{ _t( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0ca7b9da9e..53b832d1b2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2200,6 +2200,7 @@ "Everyone in will be able to find and join this room.": "Everyone in will be able to find and join this room.", "You can change this at any time from room settings.": "You can change this at any time from room settings.", "Anyone will be able to find and join this room, not just members of .": "Anyone will be able to find and join this room, not just members of .", + "Anyone will be able to find and join this room.": "Anyone will be able to find and join this room.", "Only people invited will be able to find and join this room.": "Only people invited will be able to find and join this room.", "You can’t disable this later. Bridges & most bots won’t work yet.": "You can’t disable this later. Bridges & most bots won’t work yet.", "Your server requires encryption to be enabled in private rooms.": "Your server requires encryption to be enabled in private rooms.",