From 431b69ee49dc3ee1e54e6d39b4b04ec832d6e72e Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 10 Nov 2021 15:44:57 +0000 Subject: [PATCH 001/118] Display started polls in timeline (without votes) (behind labs setting) (#7088) * Display started polls in timeline (without votes) * Update i18n info * Keep original background colour of poll options, even on hover * Show full avatar above a poll message --- res/css/_components.scss | 1 + res/css/views/messages/_MPollBody.scss | 109 ++++++++++++++++++ res/img/element-icons/check-white.svg | 3 + src/components/views/messages/MPollBody.tsx | 93 +++++++++++++++ .../views/messages/MessageEvent.tsx | 10 ++ src/components/views/rooms/EventTile.tsx | 2 + src/i18n/strings/en_EN.json | 2 + src/polls/consts.ts | 10 +- src/utils/EventUtils.ts | 4 +- 9 files changed, 229 insertions(+), 5 deletions(-) create mode 100644 res/css/views/messages/_MPollBody.scss create mode 100644 res/img/element-icons/check-white.svg create mode 100644 src/components/views/messages/MPollBody.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 116189d64cc..d4c383b1fe6 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -182,6 +182,7 @@ @import "./views/messages/_MImageReplyBody.scss"; @import "./views/messages/_MJitsiWidgetEvent.scss"; @import "./views/messages/_MNoticeBody.scss"; +@import "./views/messages/_MPollBody.scss"; @import "./views/messages/_MStickerBody.scss"; @import "./views/messages/_MTextBody.scss"; @import "./views/messages/_MVideoBody.scss"; diff --git a/res/css/views/messages/_MPollBody.scss b/res/css/views/messages/_MPollBody.scss new file mode 100644 index 00000000000..46051821a0a --- /dev/null +++ b/res/css/views/messages/_MPollBody.scss @@ -0,0 +1,109 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_MPollBody { + margin-top: 8px; + + h2 { + font-weight: 600; + font-size: $font-15px; + line-height: $font-24px; + margin-top: 0; + margin-bottom: 8px; + } + + h2::before { + content: ''; + position: relative; + display: inline-block; + margin-right: 12px; + top: 3px; + left: 3px; + height: 20px; + width: 20px; + background-color: $secondary-content; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + mask-image: url('$(res)/img/element-icons/room/composer/poll.svg'); + } + + .mx_MPollBody_option { + border: 1px solid $quinary-content; + border-radius: 8px; + margin-bottom: 16px; + padding: 6px; + max-width: 550px; + background-color: $background; + + .mx_StyledRadioButton { + margin-bottom: 8px; + } + + .mx_StyledRadioButton_content { + padding-top: 2px; + } + + .mx_MPollBody_optionVoteCount { + position: absolute; + color: $secondary-content; + right: 4px; + font-size: $font-12px; + } + + .mx_MPollBody_popularityBackground { + width: calc(100% - 4px); + height: 8px; + margin-right: 12px; + border-radius: 8px; + background-color: $system; + + .mx_MPollBody_popularityAmount { + width: 0%; + height: 8px; + border-radius: 8px; + background-color: $quaternary-content; + } + } + } + + .mx_MPollBody_option:last-child { + margin-bottom: 8px; + } + + .mx_MPollBody_option_checked { + border-color: $accent-color; + } + + .mx_StyledRadioButton_checked input[type="radio"] + div { + border-width: 2px; + border-color: $accent-color; + background-color: $accent-color; + background-image: url('$(res)/img/element-icons/check-white.svg'); + background-size: 12px; + background-repeat: no-repeat; + background-position: center; + + div { + visibility: hidden; + } + } + + .mx_MPollBody_totalVotes { + color: $secondary-content; + font-size: $font-12px; + } +} diff --git a/res/img/element-icons/check-white.svg b/res/img/element-icons/check-white.svg new file mode 100644 index 00000000000..018c8b33d96 --- /dev/null +++ b/res/img/element-icons/check-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/views/messages/MPollBody.tsx b/src/components/views/messages/MPollBody.tsx new file mode 100644 index 00000000000..93fbe974d1f --- /dev/null +++ b/src/components/views/messages/MPollBody.tsx @@ -0,0 +1,93 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { _t } from '../../../languageHandler'; +import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { IBodyProps } from "./IBodyProps"; +import { IPollAnswer, IPollContent, POLL_START_EVENT_TYPE } from '../../../polls/consts'; +import StyledRadioButton from '../elements/StyledRadioButton'; + +// TODO: [andyb] Use extensible events library when ready +const TEXT_NODE_TYPE = "org.matrix.msc1767.text"; + +interface IState { + selected?: string; +} + +@replaceableComponent("views.messages.MPollBody") +export default class MPollBody extends React.Component { + constructor(props: IBodyProps) { + super(props); + + this.state = { + selected: null, + }; + } + + private selectOption(answerId: string) { + this.setState({ selected: answerId }); + } + + private onOptionSelected = (e: React.FormEvent): void => { + this.selectOption(e.currentTarget.value); + }; + + render() { + const pollStart: IPollContent = + this.props.mxEvent.getContent()[POLL_START_EVENT_TYPE.name]; + const pollId = this.props.mxEvent.getId(); + + return
+

{ pollStart.question[TEXT_NODE_TYPE] }

+
+ { + pollStart.answers.map((answer: IPollAnswer) => { + const checked = this.state.selected === answer.id; + const classNames = `mx_MPollBody_option${ + checked ? " mx_MPollBody_option_checked": "" + }`; + return
this.selectOption(answer.id)} + > + +
+ { _t("%(number)s votes", { number: 0 }) } +
+
+ { answer[TEXT_NODE_TYPE] } +
+
+
+
+
+
; + }) + } +
+
+ { _t( "Based on %(total)s votes", { total: 0 } ) } +
+
; + } +} diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index b72e40d194b..9590cd1ed76 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -27,6 +27,7 @@ import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import { ReactAnyComponent } from "../../../@types/common"; import { EventType, MsgType } from "matrix-js-sdk/src/@types/event"; import { IBodyProps } from "./IBodyProps"; +import { POLL_START_EVENT_TYPE } from '../../../polls/consts'; // onMessageAllowed is handled internally interface IProps extends Omit { @@ -111,6 +112,15 @@ export default class MessageEvent extends React.Component implements IMe // Fallback to UnknownBody otherwise if not redacted BodyType = UnknownBody; } + + if (type && type === POLL_START_EVENT_TYPE.name) { + // TODO: this can all disappear when Polls comes out of labs - + // instead, add something like this into this.evTypes: + // [EventType.Poll]: "messages.MPollBody" + if (SettingsStore.getValue("feature_polls")) { + BodyType = sdk.getComponent('messages.MPollBody'); + } + } } if (SettingsStore.getValue("feature_mjolnir")) { diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 341870f92b6..f6cfa4c3525 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -65,12 +65,14 @@ import { logger } from "matrix-js-sdk/src/logger"; import { TimelineRenderingType } from "../../../contexts/RoomContext"; import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import Toolbar from '../../../accessibility/Toolbar'; +import { POLL_START_EVENT_TYPE } from '../../../polls/consts'; import { RovingAccessibleTooltipButton } from '../../../accessibility/roving/RovingAccessibleTooltipButton'; import { ThreadListContextMenu } from '../context_menus/ThreadListContextMenu'; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', [EventType.Sticker]: 'messages.MessageEvent', + [POLL_START_EVENT_TYPE.name]: 'messages.MessageEvent', [EventType.KeyVerificationCancel]: 'messages.MKeyVerificationConclusion', [EventType.KeyVerificationDone]: 'messages.MKeyVerificationConclusion', [EventType.CallInvite]: 'messages.CallEvent', diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7b9e050e596..b0db4d44def 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2021,6 +2021,8 @@ "Declining …": "Declining …", "%(name)s wants to verify": "%(name)s wants to verify", "You sent a verification request": "You sent a verification request", + "%(number)s votes": "%(number)s votes", + "Based on %(total)s votes": "Based on %(total)s votes", "Error decrypting video": "Error decrypting video", "Error processing voice message": "Error processing voice message", "Add reaction": "Add reaction", diff --git a/src/polls/consts.ts b/src/polls/consts.ts index 6dc196f5ecb..be9eb97b5e6 100644 --- a/src/polls/consts.ts +++ b/src/polls/consts.ts @@ -24,16 +24,18 @@ export const POLL_KIND_UNDISCLOSED = new UnstableValue("m.poll.undisclosed", "or // TODO: [TravisR] Use extensible events library when ready const TEXT_NODE_TYPE = "org.matrix.msc1767.text"; +export interface IPollAnswer extends IContent { + id: string; + [TEXT_NODE_TYPE]: string; +} + export interface IPollContent extends IContent { [POLL_START_EVENT_TYPE.name]: { kind: string; // disclosed or undisclosed (untypeable for now) question: { [TEXT_NODE_TYPE]: string; }; - answers: { - id: string; - [TEXT_NODE_TYPE]: string; - }[]; + answers: IPollAnswer[]; }; [TEXT_NODE_TYPE]: string; } diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index 894dcb39559..1fdc3a51569 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -24,6 +24,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event"; import { MatrixClient } from 'matrix-js-sdk/src/client'; import { Thread } from 'matrix-js-sdk/src/models/thread'; import { logger } from 'matrix-js-sdk/src/logger'; +import { POLL_START_EVENT_TYPE } from '../polls/consts'; /** * Returns whether an event should allow actions like reply, reactions, edit, etc. @@ -136,7 +137,8 @@ export function getEventDisplayInfo(mxEvent: MatrixEvent): { !isLeftAlignedBubbleMessage && eventType !== EventType.RoomMessage && eventType !== EventType.Sticker && - eventType !== EventType.RoomCreate + eventType !== EventType.RoomCreate && + eventType !== POLL_START_EVENT_TYPE.name ); // If we're showing hidden events in the timeline, we should use the From 5226edba4b5501579cf616a5c5485de57bd16858 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 10 Nov 2021 16:35:43 +0000 Subject: [PATCH 002/118] Use en_GB spelling of maximise / minimise in labels (#7111) --- src/components/structures/LeftPanelWidget.tsx | 2 +- src/components/views/dialogs/HostSignupDialog.tsx | 8 ++++---- .../settings/tabs/user/PreferencesUserSettingsTab.tsx | 2 +- src/i18n/strings/en_EN.json | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/structures/LeftPanelWidget.tsx b/src/components/structures/LeftPanelWidget.tsx index 6b91acb5f80..470b4e733d1 100644 --- a/src/components/structures/LeftPanelWidget.tsx +++ b/src/components/structures/LeftPanelWidget.tsx @@ -131,7 +131,7 @@ const LeftPanelWidget: React.FC = () => { }} className="mx_LeftPanelWidget_maximizeButton" tooltipClassName="mx_LeftPanelWidget_maximizeButtonTooltip" - title={_t("Maximize")} + title={_t("Maximise")} />*/ }
diff --git a/src/components/views/dialogs/HostSignupDialog.tsx b/src/components/views/dialogs/HostSignupDialog.tsx index 12284957476..71183cc9634 100644 --- a/src/components/views/dialogs/HostSignupDialog.tsx +++ b/src/components/views/dialogs/HostSignupDialog.tsx @@ -253,8 +253,8 @@ export default class HostSignupDialog extends React.PureComponent } @@ -263,8 +263,8 @@ export default class HostSignupDialog extends React.PureComponent ; + label={_t('Show tray icon and minimise window to it on close')} />; } return ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b0db4d44def..86d77489cba 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1394,7 +1394,7 @@ "Start automatically after system login": "Start automatically after system login", "Warn before quitting": "Warn before quitting", "Always show the window menu bar": "Always show the window menu bar", - "Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close", + "Show tray icon and minimise window to it on close": "Show tray icon and minimise window to it on close", "Preferences": "Preferences", "Room list": "Room list", "Communities": "Communities", @@ -2447,8 +2447,8 @@ "Terms of Service": "Terms of Service", "You should know": "You should know", "%(hostSignupBrand)s Setup": "%(hostSignupBrand)s Setup", - "Maximize dialog": "Maximize dialog", - "Minimize dialog": "Minimize dialog", + "Maximise dialog": "Maximise dialog", + "Minimise dialog": "Minimise dialog", "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.", "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.", "Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.": "Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.", From b8edebecc958ec9681b64a368320887aa8ec558b Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Wed, 10 Nov 2021 18:07:31 +0000 Subject: [PATCH 003/118] Add PassphraseConfirmField component for registration and password recovery screens (#7044) --- .../views/auth/PassphraseConfirmField.tsx | 83 +++++++++++++++++++ .../views/auth/RegistrationForm.tsx | 29 ++----- 2 files changed, 88 insertions(+), 24 deletions(-) create mode 100644 src/components/views/auth/PassphraseConfirmField.tsx diff --git a/src/components/views/auth/PassphraseConfirmField.tsx b/src/components/views/auth/PassphraseConfirmField.tsx new file mode 100644 index 00000000000..74ce711470c --- /dev/null +++ b/src/components/views/auth/PassphraseConfirmField.tsx @@ -0,0 +1,83 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { PureComponent, RefCallback, RefObject } from "react"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; +import Field, { IInputProps } from "../elements/Field"; +import withValidation, { IFieldState, IValidationResult } from "../elements/Validation"; +import { _t, _td } from "../../../languageHandler"; + +interface IProps extends Omit { + id?: string; + fieldRef?: RefCallback | RefObject; + autoComplete?: string; + value: string; + password: string; // The password we're confirming + + labelRequired?: string; + labelInvalid?: string; + + onChange(ev: React.FormEvent); + onValidate?(result: IValidationResult); +} + +@replaceableComponent("views.auth.EmailField") +class PassphraseConfirmField extends PureComponent { + static defaultProps = { + label: _td("Confirm password"), + labelRequired: _td("Confirm password"), + labelInvalid: _td("Passwords don't match"), + }; + + private validate = withValidation({ + rules: [ + { + key: "required", + test: ({ value, allowEmpty }) => allowEmpty || !!value, + invalid: () => _t(this.props.labelRequired), + }, + { + key: "match", + test: ({ value }) => !value || value === this.props.password, + invalid: () => _t(this.props.labelInvalid), + }, + ], + }); + + private onValidate = async (fieldState: IFieldState) => { + const result = await this.validate(fieldState); + if (this.props.onValidate) { + this.props.onValidate(result); + } + + return result; + }; + + render() { + return ; + } +} + +export default PassphraseConfirmField; diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index b593853f343..06ab1852181 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -35,6 +35,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; import CountryDropdown from "./CountryDropdown"; import { logger } from "matrix-js-sdk/src/logger"; +import PassphraseConfirmField from "./PassphraseConfirmField"; enum RegistrationField { Email = "field_email", @@ -295,29 +296,10 @@ export default class RegistrationForm extends React.PureComponent { - const result = await this.validatePasswordConfirmRules(fieldState); + private onPasswordConfirmValidate = (result: IValidationResult) => { this.markFieldValid(RegistrationField.PasswordConfirm, result.valid); - return result; }; - private validatePasswordConfirmRules = withValidation({ - rules: [ - { - key: "required", - test: ({ value, allowEmpty }) => allowEmpty || !!value, - invalid: () => _t("Confirm password"), - }, - { - key: "match", - test(this: RegistrationForm, { value }) { - return !value || value === this.state.password; - }, - invalid: () => _t("Passwords don't match"), - }, - ], - }); - private onPhoneCountryChange = newVal => { this.setState({ phoneCountry: newVal.iso2, @@ -477,13 +459,12 @@ export default class RegistrationForm extends React.PureComponent this[RegistrationField.PasswordConfirm] = field} - type="password" + fieldRef={field => this[RegistrationField.PasswordConfirm] = field} autoComplete="new-password" - label={_t("Confirm password")} value={this.state.passwordConfirm} + password={this.state.password} onChange={this.onPasswordConfirmChange} onValidate={this.onPasswordConfirmValidate} onFocus={() => CountlyAnalytics.instance.track("onboarding_registration_passwordConfirm_focus")} From 1de9630e44c694693ee7d039c95e8e697c13c881 Mon Sep 17 00:00:00 2001 From: Germain Date: Thu, 11 Nov 2021 11:00:18 +0000 Subject: [PATCH 004/118] Fixes following threads design implementation review (#7100) --- res/css/structures/_ContextualMenu.scss | 1 - res/css/structures/_RightPanel.scss | 2 +- res/css/views/right_panel/_BaseCard.scss | 8 +- res/css/views/right_panel/_ThreadPanel.scss | 132 ++++++++++++++---- res/css/views/rooms/_EventTile.scss | 74 ++++++++-- .../element-icons/message/overflow-large.svg | 5 + src/components/structures/RightPanel.tsx | 4 +- src/components/structures/ThreadPanel.tsx | 19 ++- src/components/structures/ThreadView.tsx | 3 +- .../context_menus/ThreadListContextMenu.tsx | 13 +- .../views/messages/MessageActionBar.tsx | 4 +- src/components/views/right_panel/BaseCard.tsx | 5 +- .../views/right_panel/RoomHeaderButtons.tsx | 24 +++- .../views/right_panel/RoomSummaryCard.tsx | 6 - src/components/views/rooms/EventTile.tsx | 91 +++++++----- src/i18n/strings/en_EN.json | 4 +- 16 files changed, 280 insertions(+), 115 deletions(-) create mode 100644 res/img/element-icons/message/overflow-large.svg diff --git a/res/css/structures/_ContextualMenu.scss b/res/css/structures/_ContextualMenu.scss index 88a01f220fb..42e8b403d19 100644 --- a/res/css/structures/_ContextualMenu.scss +++ b/res/css/structures/_ContextualMenu.scss @@ -38,7 +38,6 @@ limitations under the License. position: absolute; font-size: $font-14px; z-index: 5001; - contain: content; } .mx_ContextualMenu_right { diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index 51d7693cff8..91194024902 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -22,7 +22,7 @@ limitations under the License. display: flex; flex-direction: column; border-radius: 8px; - padding: 4px 0; + padding: 8px 0; box-sizing: border-box; height: 100%; contain: strict; diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index 8c1a55fe053..4c5594ce6d6 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -22,7 +22,7 @@ limitations under the License. flex: 1; .mx_BaseCard_header { - margin: 8px 0; + margin: 4px 0; > h2 { margin: 0 44px; @@ -40,13 +40,13 @@ limitations under the License. width: 20px; margin: 12px; top: 0; - border-radius: 10px; + border-radius: 50%; &::before { content: ""; position: absolute; - height: 20px; - width: 20px; + height: inherit; + width: inherit; top: 0; left: 0; mask-repeat: no-repeat; diff --git a/res/css/views/right_panel/_ThreadPanel.scss b/res/css/views/right_panel/_ThreadPanel.scss index 9fd3cb36356..b890658bded 100644 --- a/res/css/views/right_panel/_ThreadPanel.scss +++ b/res/css/views/right_panel/_ThreadPanel.scss @@ -18,21 +18,29 @@ limitations under the License. display: flex; flex-direction: column; - padding-right: 0; - .mx_BaseCard_header { + margin-bottom: 12px; .mx_BaseCard_close, .mx_BaseCard_back { - margin-top: 15px; + width: 24px; + height: 24px; + } + .mx_BaseCard_back { + left: -4px; } .mx_BaseCard_close { - right: -8px; + right: -4px; } } - .mx_ThreadPanel__header { + .mx_BaseCard_back ~ .mx_ThreadPanel__header { width: calc(100% - 60px); margin-left: 30px; + } + + .mx_ThreadPanel__header { + width: calc(100% - 30px); + height: 24px; display: flex; flex: 1; justify-content: space-between; @@ -47,13 +55,23 @@ limitations under the License. .mx_AccessibleButton { font-size: 12px; - color: $primary-content; + color: $secondary-content; } .mx_MessageActionBar_optionsButton { position: relative; } + .mx_MessageActionBar_maskButton { + --size: 24px; + width: var(--size); + height: var(--size); + &::after { + mask-size: var(--size); + mask-image: url("$(res)/img/element-icons/message/overflow-large.svg"); + } + } + .mx_ContextualMenu_wrapper { // It's added here due to some weird error if I pass it directly in the style, even though it's a numeric value, so it's being passed 0 instead. // The error: react_devtools_backend.js:2526 Warning: `NaN` is an invalid value for the `top` css style property. @@ -70,6 +88,25 @@ limitations under the License. font-size: 12px; color: $secondary-content; + padding-top: 10px; + padding-bottom: 10px; + + border: 1px solid $quinary-content; + box-shadow: 0px 1px 3px rgba(23, 25, 28, 0.05); + } + + .mx_ContextualMenu_chevron_top { + left: auto; + right: 22px; + border-bottom-color: $quinary-content; + &::after { + content: ""; + border: inherit; + border-bottom-color: $background; + position: absolute; + top: 1px; + left: -8px; + } } .mx_ThreadPanel_Header_FilterOptionItem { @@ -77,31 +114,33 @@ limitations under the License. flex-grow: 1; justify-content: space-between; flex-direction: column; - overflow: visible; - width: 100%; - padding: 20px; - padding-left: 30px; + padding: 10px 20px 10px 30px; position: relative; &:hover { background-color: $event-selected-color; } &[aria-selected="true"] { - &::before { + :first-child { + margin-left: -20px; + } + :first-child::before { content: ""; width: 12px; height: 12px; - grid-column: 1; - grid-row: 1; + margin-right: 8px; mask-image: url("$(res)/img/feather-customised/check.svg"); mask-size: 100%; mask-repeat: no-repeat; - position: absolute; - top: 22px; - left: 10px; background-color: $primary-content; + display: inline-block; + vertical-align: middle; } } + + :last-child { + color: $secondary-content; + } } } @@ -131,24 +170,20 @@ limitations under the License. } .mx_AutoHideScrollbar { - border-radius: 8px; - } - - .mx_RoomView_messageListWrapper { + background: #fff; background-color: $background; - padding: 8px; - border-radius: inherit; + border-radius: 8px; + width: calc(100% - 16px); + padding-right: 16px; } - .mx_ScrollPanel { - .mx_RoomView_MessageList { - padding: 0; - } + .mx_RoomView_MessageList { + padding-left: 12px; + padding-right: 0; } .mx_EventTile, .mx_EventListSummary { // Account for scrollbar when hovering - width: calc(100% - 3px); margin: 0 2px; padding-top: 0; @@ -170,19 +205,28 @@ limitations under the License. .mx_DateSeparator { display: none; } + + &.mx_EventTile_clamp:hover { + cursor: pointer; + } + } + + .mx_EventTile:not([data-layout=bubble]) { + .mx_EventTile_e2eIcon { + left: 8px; + } } .mx_MessageComposer { background-color: $background; border-radius: 8px; margin-top: 8px; - width: calc(100% - 8px); padding: 0 8px; box-sizing: border-box; } .mx_ThreadPanel_dropdown { - padding: 4px 8px; + padding: 3px 8px; border-radius: 4px; line-height: 1.5; user-select: none; @@ -207,6 +251,36 @@ limitations under the License. .mx_ThreadPanel_dropdown[aria-expanded=true]::before { transform: rotate(180deg); } + + .mx_MessageTimestamp { + font-size: $font-12px; + } +} + +.mx_ThreadPanel_replies { + margin-top: 8px; +} + +.mx_ThreadPanel_repliesSummary { + &::before { + content: ""; + mask-image: url('$(res)/img/element-icons/thread-summary.svg'); + mask-position: center; + display: inline-block; + height: 18px; + min-width: 18px; + background-color: currentColor; + mask-repeat: no-repeat; + mask-size: contain; + margin-right: 8px; + vertical-align: middle; + } + + color: $secondary-content; + font-weight: 600; + float: left; + margin-right: 12px; + font-size: $font-12px; } .mx_ThreadPanel_viewInRoom::before { diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 002ff1540c4..2ca72d6a436 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -460,6 +460,16 @@ $left-gutter: 64px; } } +.mx_EventTile_clamp { + .mx_EventTile_body { + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + } +} + .mx_EventTile_content .markdown-body { font-family: inherit !important; white-space: normal !important; @@ -663,7 +673,10 @@ $left-gutter: 64px; } .mx_ThreadInfo { - height: 35px; + min-width: 267px; + max-width: min(calc(100% - 64px), 600px); + width: auto; + height: 40px; position: relative; background-color: $system; padding-left: 12px; @@ -671,13 +684,13 @@ $left-gutter: 64px; align-items: center; border-radius: 8px; padding-right: 16px; - padding-top: 8px; - padding-bottom: 8px; + margin-top: 8px; font-size: $font-12px; color: $secondary-content; box-sizing: border-box; justify-content: flex-start; clear: both; + overflow: hidden; &:hover { cursor: pointer; @@ -687,6 +700,44 @@ $left-gutter: 64px; padding-left: 11px; padding-right: 15px; } + + &::before { + content: ""; + mask-image: url('$(res)/img/element-icons/thread-summary.svg'); + mask-position: center; + height: 18px; + min-width: 18px; + background-color: $secondary-content; + mask-repeat: no-repeat; + mask-size: contain; + } + + &::after { + content: "›"; + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 60px; + padding: 0 10px; + font-size: 15px; + line-height: 39px; + box-sizing: border-box; + + text-align: right; + font-weight: 600; + + background: linear-gradient(270deg, $system 52.6%, transparent 100%); + + opacity: 0; + transform: translateX(20px); + transition: all .1s ease-in-out; + } + + &:hover::after { + opacity: 1; + transform: translateX(0); + } } .mx_ThreadInfo_content { @@ -703,15 +754,6 @@ $left-gutter: 64px; float: left; } -.mx_ThreadInfo_thread-icon { - mask-image: url('$(res)/img/element-icons/thread-summary.svg'); - mask-position: center; - height: 16px; - min-width: 16px; - background-color: $secondary-content; - mask-repeat: no-repeat; - mask-size: contain; -} .mx_ThreadInfo_threads-amount { font-weight: 600; position: relative; @@ -720,10 +762,10 @@ $left-gutter: 64px; } .mx_EventTile[data-shape=thread_list] { - --topOffset: 24px; + --topOffset: 20px; --leftOffset: 46px; - margin: var(--topOffset) 0; + margin: var(--topOffset) 16px var(--topOffset) 0; border-radius: 8px; &:hover { @@ -819,6 +861,7 @@ $left-gutter: 64px; left: auto; right: 2px !important; top: 1px !important; + font-size: 1rem; } .mx_ReactionsRow { @@ -830,7 +873,8 @@ $left-gutter: 64px; } } - .mx_EventTile_content { + .mx_EventTile_content, + .mx_RedactedBody { margin-left: 36px; margin-right: 50px; } diff --git a/res/img/element-icons/message/overflow-large.svg b/res/img/element-icons/message/overflow-large.svg new file mode 100644 index 00000000000..65a52e4aa23 --- /dev/null +++ b/res/img/element-icons/message/overflow-large.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 16ef8918505..570865731ff 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -355,7 +355,9 @@ export default class RightPanel extends React.Component { panel = ; + onClose={this.onClose} + permalinkCreator={this.props.permalinkCreator} + />; break; case RightPanelPhases.RoomSummary: diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index f55a479c3f3..3754e0d364d 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -20,24 +20,24 @@ import { EventTimelineSet } from 'matrix-js-sdk/src/models/event-timeline-set'; import { Room } from 'matrix-js-sdk/src/models/room'; import BaseCard from "../views/right_panel/BaseCard"; -import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; - import ResizeNotifier from '../../utils/ResizeNotifier'; import MatrixClientContext from '../../contexts/MatrixClientContext'; import { _t } from '../../languageHandler'; import { ContextMenuButton } from '../../accessibility/context_menu/ContextMenuButton'; -import ContextMenu, { useContextMenu } from './ContextMenu'; +import ContextMenu, { ChevronFace, useContextMenu } from './ContextMenu'; import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext'; import TimelinePanel from './TimelinePanel'; import { Layout } from '../../settings/Layout'; import { useEventEmitter } from '../../hooks/useEventEmitter'; import AccessibleButton from '../views/elements/AccessibleButton'; import { TileShape } from '../views/rooms/EventTile'; +import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; interface IProps { roomId: string; onClose: () => void; resizeNotifier: ResizeNotifier; + permalinkCreator: RoomPermalinkCreator; } export enum ThreadFilterType { @@ -162,7 +162,13 @@ export const ThreadPanelHeader = ({ filterOption, setFilterOption }: { }} isSelected={opt === value} />); - const contextMenu = menuDisplayed ? + const contextMenu = menuDisplayed ? { contextMenuOptions } : null; return
@@ -174,7 +180,7 @@ export const ThreadPanelHeader = ({ filterOption, setFilterOption }: {
; }; -const ThreadPanel: React.FC = ({ roomId, onClose }) => { +const ThreadPanel: React.FC = ({ roomId, onClose, permalinkCreator }) => { const mxClient = useContext(MatrixClientContext); const roomContext = useContext(RoomContext); const room = mxClient.getRoom(roomId); @@ -200,7 +206,7 @@ const ThreadPanel: React.FC = ({ roomId, onClose }) => { header={} className="mx_ThreadPanel" onClose={onClose} - previousPhase={RightPanelPhases.RoomSummary} + withoutScrollContainer={true} > = ({ roomId, onClose }) => { showReactions={true} className="mx_RoomView_messagePanel mx_GroupLayout" membersLoaded={true} + permalinkCreator={permalinkCreator} tileShape={TileShape.ThreadPanel} /> diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 04c7ff576c6..c07741510ad 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -40,7 +40,7 @@ import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext'; import ContentMessages from '../../ContentMessages'; import UploadBar from './UploadBar'; import { _t } from '../../languageHandler'; -import { ThreadListContextMenu } from '../views/context_menus/ThreadListContextMenu'; +import ThreadListContextMenu from '../views/context_menus/ThreadListContextMenu'; interface IProps { room: Room; @@ -214,6 +214,7 @@ export default class ThreadView extends React.Component { className="mx_ThreadView mx_ThreadPanel" onClose={this.props.onClose} previousPhase={RightPanelPhases.ThreadPanel} + previousPhaseLabel={_t("All threads")} withoutScrollContainer={true} header={this.renderThreadViewHeader()} > diff --git a/src/components/views/context_menus/ThreadListContextMenu.tsx b/src/components/views/context_menus/ThreadListContextMenu.tsx index 38fbcda5fc5..e01834b03a3 100644 --- a/src/components/views/context_menus/ThreadListContextMenu.tsx +++ b/src/components/views/context_menus/ThreadListContextMenu.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { MatrixEvent } from "matrix-js-sdk/src"; import { ButtonEvent } from "../elements/AccessibleButton"; import dis from '../../../dispatcher/dispatcher'; @@ -27,17 +27,18 @@ import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOpti interface IProps { mxEvent: MatrixEvent; permalinkCreator: RoomPermalinkCreator; + onMenuToggle?: (open: boolean) => void; } const contextMenuBelow = (elementRect: DOMRect) => { // align the context menu's icons with the icon which opened the context menu const left = elementRect.left + window.pageXOffset + elementRect.width; - const top = elementRect.bottom + window.pageYOffset + 17; + const top = elementRect.bottom + window.pageYOffset; const chevronFace = ChevronFace.None; return { left, top, chevronFace }; }; -export const ThreadListContextMenu: React.FC = ({ mxEvent, permalinkCreator }) => { +const ThreadListContextMenu: React.FC = ({ mxEvent, permalinkCreator, onMenuToggle }) => { const [optionsPosition, setOptionsPosition] = useState(null); const closeThreadOptions = useCallback(() => { setOptionsPosition(null); @@ -72,6 +73,12 @@ export const ThreadListContextMenu: React.FC = ({ mxEvent, permalinkCrea } }, [closeThreadOptions, optionsPosition]); + useEffect(() => { + if (onMenuToggle) { + onMenuToggle(!!optionsPosition); + } + }, [optionsPosition, onMenuToggle]); + return @@ -327,7 +327,7 @@ export default class MessageActionBar extends React.PureComponent); diff --git a/src/components/views/right_panel/BaseCard.tsx b/src/components/views/right_panel/BaseCard.tsx index 54bf6e769c0..4b18c63e120 100644 --- a/src/components/views/right_panel/BaseCard.tsx +++ b/src/components/views/right_panel/BaseCard.tsx @@ -31,6 +31,7 @@ interface IProps { className?: string; withoutScrollContainer?: boolean; previousPhase?: RightPanelPhases; + previousPhaseLabel?: string; closeLabel?: string; onClose?(): void; refireParams?; @@ -56,6 +57,7 @@ const BaseCard: React.FC = ({ footer, withoutScrollContainer, previousPhase, + previousPhaseLabel, children, refireParams, }) => { @@ -68,7 +70,8 @@ const BaseCard: React.FC = ({ refireParams: refireParams, }); }; - backButton = ; + const label = previousPhaseLabel ?? _t("Back"); + backButton = ; } let closeButton; diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index fabe46c1154..3261cacc812 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -33,6 +33,7 @@ import { useSettingValue } from "../../../hooks/useSettings"; import { useReadPinnedEvents, usePinnedEvents } from './PinnedMessagesCard'; import { dispatchShowThreadsPanelEvent } from "../../../dispatcher/dispatch-actions/threads"; import SettingsStore from "../../../settings/SettingsStore"; +import dis from "../../../dispatcher/dispatcher"; const ROOM_INFO_PHASES = [ RightPanelPhases.RoomSummary, @@ -72,6 +73,11 @@ interface IProps { @replaceableComponent("views.right_panel.RoomHeaderButtons") export default class RoomHeaderButtons extends HeaderButtons { + private static readonly THREAD_PHASES = [ + RightPanelPhases.ThreadPanel, + RightPanelPhases.ThreadView, + ]; + constructor(props: IProps) { super(props, HeaderKind.Room); } @@ -117,6 +123,17 @@ export default class RoomHeaderButtons extends HeaderButtons { this.setPhase(RightPanelPhases.PinnedMessages); }; + private onThreadsPanelClicked = () => { + if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) { + dis.dispatch({ + action: Action.ToggleRightPanel, + type: "room", + }); + } else { + dispatchShowThreadsPanelEvent(); + } + }; + public renderButtons() { return <> { { SettingsStore.getValue("feature_thread") && } = ({ room, onClose }) => { - { SettingsStore.getValue("feature_thread") && ( - - ) } diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index f6cfa4c3525..9a163aebc1b 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -67,7 +67,7 @@ import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import Toolbar from '../../../accessibility/Toolbar'; import { POLL_START_EVENT_TYPE } from '../../../polls/consts'; import { RovingAccessibleTooltipButton } from '../../../accessibility/roving/RovingAccessibleTooltipButton'; -import { ThreadListContextMenu } from '../context_menus/ThreadListContextMenu'; +import ThreadListContextMenu from '../context_menus/ThreadListContextMenu'; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', @@ -552,7 +552,7 @@ export default class EventTile extends React.Component { } }; - private renderThreadLastMessagePreview(): JSX.Element | null { + private get thread(): Thread | null { if (!SettingsStore.getValue("feature_thread")) { return null; } @@ -570,7 +570,28 @@ export default class EventTile extends React.Component { return null; } - const [lastEvent] = thread.events + return thread; + } + + private renderThreadPanelSummary(): JSX.Element | null { + if (!this.thread) { + return null; + } + + return
+ + { this.thread.length } + + { this.renderThreadLastMessagePreview() } +
; + } + + private renderThreadLastMessagePreview(): JSX.Element | null { + if (!this.thread) { + return null; + } + + const [lastEvent] = this.thread.events .filter(event => event.isThreadRelation) .slice(-1); const threadMessagePreview = MessagePreviewStore.instance.generatePreviewForEvent(lastEvent); @@ -590,24 +611,7 @@ export default class EventTile extends React.Component { } private renderThreadInfo(): React.ReactNode { - if (!SettingsStore.getValue("feature_thread")) { - return null; - } - - /** - * Accessing the threads value through the room due to a race condition - * that will be solved when there are proper backend support for threads - * We currently have no reliable way to discover than an event is a thread - * when we are at the sync stage - */ - const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); - const thread = room?.threads.get(this.props.mxEvent.getId()); - - if (thread && !thread.ready) { - thread.addEvent(this.props.mxEvent, true); - } - - if (!thread || this.props.showThreadInfo === false || thread.length === 0) { + if (!this.thread) { return null; } @@ -620,10 +624,9 @@ export default class EventTile extends React.Component { ); }} > - { _t("%(count)s reply", { - count: thread.length, + count: this.thread.length, }) } { this.renderThreadLastMessagePreview() } @@ -1063,6 +1066,7 @@ export default class EventTile extends React.Component { mx_EventTile_bad: isEncryptionFailure, mx_EventTile_emote: msgtype === 'm.emote', mx_EventTile_noSender: this.props.hideSender, + mx_EventTile_clamp: this.props.tileShape === TileShape.ThreadPanel, }); // If the tile is in the Sending state, don't speak the message. @@ -1161,11 +1165,16 @@ export default class EventTile extends React.Component { || this.state.hover || this.state.actionBarFocused); + // Thread panel shows the timestamp of the last reply in that thread + const ts = this.props.tileShape !== TileShape.ThreadPanel + ? this.props.mxEvent.getTs() + : this.props.mxEvent.getThread().lastReply.getTs(); + const timestamp = showTimestamp ? : null; const keyRequestHelpText = @@ -1337,11 +1346,15 @@ export default class EventTile extends React.Component { "data-has-reply": !!replyChain, "onMouseEnter": () => this.setState({ hover: true }), "onMouseLeave": () => this.setState({ hover: false }), - "onClick": () => dispatchShowThreadEvent(this.props.mxEvent), + }, <> { sender } { avatar } -
+
dispatchShowThreadEvent(this.props.mxEvent)} + key="mx_EventTile_line" + > { linkedTimestamp } { this.renderE2EPadlock() } { replyChain } @@ -1359,19 +1372,21 @@ export default class EventTile extends React.Component { tileShape={this.props.tileShape} /> { keyRequestInfo } - - dispatchShowThreadEvent(this.props.mxEvent)} - key="thread" - /> - - - { this.renderThreadLastMessagePreview() } + { this.renderThreadPanelSummary() }
+ + dispatchShowThreadEvent(this.props.mxEvent)} + key="thread" + /> + + { msgOption } ) ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 86d77489cba..a25ee37386f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1573,7 +1573,7 @@ "Key request sent.": "Key request sent.", "Re-request encryption keys from your other sessions.": "Re-request encryption keys from your other sessions.", "Message Actions": "Message Actions", - "Thread": "Thread", + "Reply in thread": "Reply in thread", "This message cannot be decrypted": "This message cannot be decrypted", "Encrypted by an unverified session": "Encrypted by an unverified session", "Unencrypted": "Unencrypted", @@ -1864,7 +1864,6 @@ "%(count)s people|one": "%(count)s person", "Show files": "Show files", "Export chat": "Export chat", - "Show threads": "Show threads", "Share room": "Share room", "Room settings": "Room settings", "Trusted": "Trusted", @@ -3012,6 +3011,7 @@ "All threads": "All threads", "Shows all threads from current room": "Shows all threads from current room", "Show:": "Show:", + "Thread": "Thread", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", "Failed to load timeline position": "Failed to load timeline position", From dadac386fedec56021e598998b4d1476ccf0e6d4 Mon Sep 17 00:00:00 2001 From: Germain Date: Thu, 11 Nov 2021 12:28:56 +0000 Subject: [PATCH 005/118] Display threads relation as replies when labs is disabled (#7109) --- src/components/views/elements/ReplyChain.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/elements/ReplyChain.tsx b/src/components/views/elements/ReplyChain.tsx index 50efdc92fb5..481a8c870bf 100644 --- a/src/components/views/elements/ReplyChain.tsx +++ b/src/components/views/elements/ReplyChain.tsx @@ -110,6 +110,8 @@ export default class ReplyChain extends React.Component { if (mRelatesTo && mRelatesTo['m.in_reply_to']) { const mInReplyTo = mRelatesTo['m.in_reply_to']; if (mInReplyTo && mInReplyTo['event_id']) return mInReplyTo['event_id']; + } else if (!SettingsStore.getValue("feature_thread") && ev.isThreadRelation) { + return ev.threadRootId; } } From 5ad3261cb210c862a7d9aecd387bd37fa99f74fa Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 11 Nov 2021 13:07:41 +0000 Subject: [PATCH 006/118] Implement more meta-spaces (#7077) --- res/css/_components.scss | 1 + res/css/structures/_SpacePanel.scss | 32 +- .../views/dialogs/_UserSettingsDialog.scss | 4 + .../tabs/user/_SidebarUserSettingsTab.scss | 87 ++++++ res/img/element-icons/settings/sidebar.svg | 7 + src/@types/global.d.ts | 2 +- src/Avatar.ts | 2 +- src/autocomplete/Autocompleter.ts | 2 +- src/autocomplete/RoomProvider.tsx | 2 +- src/components/structures/LeftPanel.tsx | 17 +- src/components/structures/LoggedInView.tsx | 2 +- src/components/structures/MatrixChat.tsx | 6 +- src/components/structures/RightPanel.tsx | 2 +- src/components/structures/RoomSearch.tsx | 3 +- src/components/structures/RoomView.tsx | 2 +- src/components/structures/SpaceHierarchy.tsx | 2 +- src/components/structures/SpaceRoomView.tsx | 2 +- src/components/structures/UserMenu.tsx | 10 +- .../dialogs/AddExistingToSpaceDialog.tsx | 2 +- .../dialogs/ConfirmSpaceUserActionDialog.tsx | 2 +- .../views/dialogs/CreateRoomDialog.tsx | 2 +- .../CreateSpaceFromCommunityDialog.tsx | 2 +- .../views/dialogs/CreateSubspaceDialog.tsx | 2 +- .../views/dialogs/ForwardDialog.tsx | 2 +- src/components/views/dialogs/InviteDialog.tsx | 2 +- .../views/dialogs/LeaveSpaceDialog.tsx | 2 +- .../ManageRestrictedJoinRuleDialog.tsx | 4 +- .../views/dialogs/UserSettingsDialog.tsx | 11 + src/components/views/right_panel/UserInfo.tsx | 2 +- src/components/views/rooms/MemberList.tsx | 2 +- src/components/views/rooms/NewRoomIntro.tsx | 8 +- src/components/views/rooms/RoomList.tsx | 55 ++-- .../views/rooms/RoomListNumResults.tsx | 2 +- .../views/rooms/ThirdPartyMemberInfo.tsx | 2 +- .../views/settings/JoinRuleSettings.tsx | 10 +- .../tabs/user/SidebarUserSettingsTab.tsx | 123 ++++++++ .../views/spaces/SpaceCreateMenu.tsx | 1 + src/components/views/spaces/SpacePanel.tsx | 115 +++++-- .../views/spaces/SpaceTreeLevel.tsx | 27 +- src/createRoom.ts | 2 +- src/i18n/strings/en_EN.json | 16 +- src/settings/Settings.tsx | 20 ++ src/stores/BreadcrumbsStore.ts | 2 +- src/stores/room-list/RoomListStore.ts | 2 +- src/stores/room-list/SpaceWatcher.ts | 30 +- src/stores/room-list/algorithms/Algorithm.ts | 2 +- .../room-list/filters/SpaceFilterCondition.ts | 15 +- .../room-list/filters/VisibilityProvider.ts | 2 +- src/stores/{ => spaces}/SpaceStore.ts | 291 +++++++++++------- .../{ => spaces}/SpaceTreeLevelLayoutStore.ts | 0 src/stores/spaces/index.ts | 40 +++ src/utils/RoomUpgrade.ts | 2 +- test/stores/SpaceStore-test.ts | 217 +++++++------ test/stores/enable-metaspaces-labs.ts | 17 + test/stores/room-list/SpaceWatcher-test.ts | 104 ++++++- 55 files changed, 971 insertions(+), 354 deletions(-) create mode 100644 res/css/views/settings/tabs/user/_SidebarUserSettingsTab.scss create mode 100644 res/img/element-icons/settings/sidebar.svg create mode 100644 src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx rename src/stores/{ => spaces}/SpaceStore.ts (76%) rename src/stores/{ => spaces}/SpaceTreeLevelLayoutStore.ts (100%) create mode 100644 src/stores/spaces/index.ts create mode 100644 test/stores/enable-metaspaces-labs.ts diff --git a/res/css/_components.scss b/res/css/_components.scss index d4c383b1fe6..f59da633d71 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -276,6 +276,7 @@ @import "./views/settings/tabs/user/_NotificationUserSettingsTab.scss"; @import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss"; @import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss"; +@import "./views/settings/tabs/user/_SidebarUserSettingsTab.scss"; @import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss"; @import "./views/spaces/_SpaceBasicSettings.scss"; @import "./views/spaces/_SpaceChildrenPicker.scss"; diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index bf79426c9d4..d822b9baf23 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -189,15 +189,35 @@ $activeBorderColor: $secondary-content; } } - &.mx_SpaceButton_home .mx_SpaceButton_icon { - background-color: #ffffff; - - &::before { - background-color: #3f3d3d; - mask-image: url('$(res)/img/element-icons/home.svg'); + &.mx_SpaceButton_home, + &.mx_SpaceButton_favourites, + &.mx_SpaceButton_people, + &.mx_SpaceButton_orphans { + .mx_SpaceButton_icon { + background-color: #ffffff; + + &::before { + background-color: #3f3d3d; + } } } + &.mx_SpaceButton_home .mx_SpaceButton_icon::before { + mask-image: url('$(res)/img/element-icons/home.svg'); + } + + &.mx_SpaceButton_favourites .mx_SpaceButton_icon::before { + mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg'); + } + + &.mx_SpaceButton_people .mx_SpaceButton_icon::before { + mask-image: url('$(res)/img/element-icons/room/members.svg'); + } + + &.mx_SpaceButton_orphans .mx_SpaceButton_icon::before { + mask-image: url('$(res)/img/element-icons/roomlist/hash-circle.svg'); + } + &.mx_SpaceButton_new .mx_SpaceButton_icon { background-color: $roomlist-button-bg-color; diff --git a/res/css/views/dialogs/_UserSettingsDialog.scss b/res/css/views/dialogs/_UserSettingsDialog.scss index bd472710eaa..f7728eb69b7 100644 --- a/res/css/views/dialogs/_UserSettingsDialog.scss +++ b/res/css/views/dialogs/_UserSettingsDialog.scss @@ -37,6 +37,10 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/settings/preference.svg'); } +.mx_UserSettingsDialog_sidebarIcon::before { + mask-image: url('$(res)/img/element-icons/settings/sidebar.svg'); +} + .mx_UserSettingsDialog_securityIcon::before { mask-image: url('$(res)/img/element-icons/security.svg'); } diff --git a/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.scss b/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.scss new file mode 100644 index 00000000000..91869f4e028 --- /dev/null +++ b/res/css/views/settings/tabs/user/_SidebarUserSettingsTab.scss @@ -0,0 +1,87 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_SidebarUserSettingsTab { + .mx_SidebarUserSettingsTab_subheading { + font-size: $font-15px; + line-height: $font-24px; + color: $primary-content; + margin-bottom: 4px; + } + + .mx_Checkbox { + margin-top: 12px; + font-size: $font-15px; + line-height: $font-24px; + color: $secondary-content; + } + + .mx_SidebarUserSettingsTab_checkboxMicrocopy { + margin-bottom: 12px; + margin-left: 24px; + font-size: $font-15px; + line-height: $font-24px; + color: $secondary-content; + } + + .mx_SidebarUserSettingsTab_homeAllRoomsCheckbox { + margin-left: 24px; + + & + div { + margin-left: 48px; + } + } + + .mx_SidebarUserSettingsTab_homeCheckbox, + .mx_SidebarUserSettingsTab_favouritesCheckbox, + .mx_SidebarUserSettingsTab_peopleCheckbox, + .mx_SidebarUserSettingsTab_orphansCheckbox { + .mx_Checkbox_background + div { + padding-left: 20px; + position: relative; + + &::before { + background-color: $secondary-content; + content: ""; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + width: 16px; + height: 16px; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + } + } + } + + .mx_SidebarUserSettingsTab_homeCheckbox .mx_Checkbox_background + div::before { + mask-image: url('$(res)/img/element-icons/home.svg'); + } + + .mx_SidebarUserSettingsTab_favouritesCheckbox .mx_Checkbox_background + div::before { + mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg'); + } + + .mx_SidebarUserSettingsTab_peopleCheckbox .mx_Checkbox_background + div::before { + mask-image: url('$(res)/img/element-icons/room/members.svg'); + } + + .mx_SidebarUserSettingsTab_orphansCheckbox .mx_Checkbox_background + div::before { + mask-image: url('$(res)/img/element-icons/roomlist/hash-circle.svg'); + } +} diff --git a/res/img/element-icons/settings/sidebar.svg b/res/img/element-icons/settings/sidebar.svg new file mode 100644 index 00000000000..24dc9562bc5 --- /dev/null +++ b/res/img/element-icons/settings/sidebar.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index a9d8e9547fe..43c30c94b8c 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -41,7 +41,7 @@ import UserActivity from "../UserActivity"; import { ModalWidgetStore } from "../stores/ModalWidgetStore"; import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore"; import VoipUserMapper from "../VoipUserMapper"; -import { SpaceStoreClass } from "../stores/SpaceStore"; +import { SpaceStoreClass } from "../stores/spaces/SpaceStore"; import TypingStore from "../stores/TypingStore"; import { EventIndexPeg } from "../indexing/EventIndexPeg"; import { VoiceRecordingStore } from "../stores/VoiceRecordingStore"; diff --git a/src/Avatar.ts b/src/Avatar.ts index 93109a470e4..310fec5f4c9 100644 --- a/src/Avatar.ts +++ b/src/Avatar.ts @@ -22,7 +22,7 @@ import { split } from "lodash"; import DMRoomMap from './utils/DMRoomMap'; import { mediaFromMxc } from "./customisations/Media"; -import SpaceStore from "./stores/SpaceStore"; +import SpaceStore from "./stores/spaces/SpaceStore"; // Not to be used for BaseAvatar urls as that has similar default avatar fallback already export function avatarUrlForMember( diff --git a/src/autocomplete/Autocompleter.ts b/src/autocomplete/Autocompleter.ts index 4c9e82f2900..555429e75fc 100644 --- a/src/autocomplete/Autocompleter.ts +++ b/src/autocomplete/Autocompleter.ts @@ -27,7 +27,7 @@ import NotifProvider from './NotifProvider'; import { timeout } from "../utils/promise"; import AutocompleteProvider, { ICommand } from "./AutocompleteProvider"; import SpaceProvider from "./SpaceProvider"; -import SpaceStore from "../stores/SpaceStore"; +import SpaceStore from "../stores/spaces/SpaceStore"; export interface ISelectionRange { beginning?: boolean; // whether the selection is in the first block of the editor or not diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx index 00bfe6be5cf..ced0e7ad177 100644 --- a/src/autocomplete/RoomProvider.tsx +++ b/src/autocomplete/RoomProvider.tsx @@ -28,7 +28,7 @@ import { PillCompletion } from './Components'; import { makeRoomPermalink } from "../utils/permalinks/Permalinks"; import { ICompletion, ISelectionRange } from "./Autocompleter"; import RoomAvatar from '../components/views/avatars/RoomAvatar'; -import SpaceStore from "../stores/SpaceStore"; +import SpaceStore from "../stores/spaces/SpaceStore"; const ROOM_REGEX = /\B#\S*/g; diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index f12b4cbcf5c..98a92f46248 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -17,7 +17,6 @@ limitations under the License. import * as React from "react"; import { createRef } from "react"; import classNames from "classnames"; -import { Room } from "matrix-js-sdk/src/models/room"; import dis from "../../dispatcher/dispatcher"; import { _t } from "../../languageHandler"; @@ -37,10 +36,12 @@ import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; import RoomListNumResults from "../views/rooms/RoomListNumResults"; import LeftPanelWidget from "./LeftPanelWidget"; import { replaceableComponent } from "../../utils/replaceableComponent"; -import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; +import { SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces"; import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; import UIStore from "../../stores/UIStore"; import { findSiblingElement, IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex"; +import MatrixClientContext from "../../contexts/MatrixClientContext"; interface IProps { isMinimized: boolean; @@ -49,7 +50,7 @@ interface IProps { interface IState { showBreadcrumbs: boolean; - activeSpace?: Room; + activeSpace: SpaceKey; } @replaceableComponent("structures.LeftPanel") @@ -61,6 +62,9 @@ export default class LeftPanel extends React.Component { private focusedElement = null; private isDoingStickyHeaders = false; + static contextType = MatrixClientContext; + public context!: React.ContextType; + constructor(props: IProps) { super(props); @@ -98,7 +102,7 @@ export default class LeftPanel extends React.Component { } } - private updateActiveSpace = (activeSpace: Room) => { + private updateActiveSpace = (activeSpace: SpaceKey) => { this.setState({ activeSpace }); }; @@ -343,6 +347,7 @@ export default class LeftPanel extends React.Component { />; } + const space = this.state.activeSpace[0] === "!" ? this.context.getRoom(this.state.activeSpace) : null; return (
{ mx_LeftPanel_exploreButton_space: !!this.state.activeSpace, })} onClick={this.onExplore} - title={this.state.activeSpace - ? _t("Explore %(spaceName)s", { spaceName: this.state.activeSpace.name }) - : _t("Explore rooms")} + title={space ? _t("Explore %(spaceName)s", { spaceName: space.name }) : _t("Explore rooms")} />
); diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 566e14e6337..88756a334bf 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -64,7 +64,7 @@ import MyGroups from "./MyGroups"; import UserView from "./UserView"; import GroupView from "./GroupView"; import BackdropPanel from "./BackdropPanel"; -import SpaceStore from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; import classNames from 'classnames'; import GroupFilterPanel from './GroupFilterPanel'; import CustomRoomTagPanel from './CustomRoomTagPanel'; diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index eb60506589c..c82bfd8bd6f 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -78,7 +78,7 @@ import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore"; import DialPadModal from "../views/voip/DialPadModal"; import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast'; import { shouldUseLoginForWelcome } from "../../utils/pages"; -import SpaceStore from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; import { replaceableComponent } from "../../utils/replaceableComponent"; import RoomListStore from "../../stores/room-list/RoomListStore"; import { RoomUpdateCause } from "../../stores/room-list/models"; @@ -712,10 +712,10 @@ export default class MatrixChat extends React.PureComponent { break; } case Action.ViewRoomDirectory: { - if (SpaceStore.instance.activeSpace) { + if (SpaceStore.instance.activeSpace[0] === "!") { defaultDispatcher.dispatch({ action: "view_room", - room_id: SpaceStore.instance.activeSpace.roomId, + room_id: SpaceStore.instance.activeSpace, }); } else { Modal.createTrackedDialog('Room directory', '', RoomDirectory, { diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 570865731ff..440ef65cebc 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -50,7 +50,7 @@ import NotificationPanel from "./NotificationPanel"; import ResizeNotifier from "../../utils/ResizeNotifier"; import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard"; import { throttle } from 'lodash'; -import SpaceStore from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; import { E2EStatus } from '../../utils/ShieldUtils'; import { dispatchShowThreadsPanelEvent } from '../../dispatcher/dispatch-actions/threads'; diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 1a1cf460238..23862ec3c6f 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -28,7 +28,8 @@ import RoomListStore from "../../stores/room-list/RoomListStore"; import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition"; import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; import { replaceableComponent } from "../../utils/replaceableComponent"; -import SpaceStore, { UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES } from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; +import { UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES } from "../../stores/spaces"; interface IProps { isMinimized: boolean; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 03cca683aee..3cf87e1bea1 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -88,7 +88,7 @@ import RoomStatusBar from "./RoomStatusBar"; import MessageComposer from '../views/rooms/MessageComposer'; import JumpToBottomButton from "../views/rooms/JumpToBottomButton"; import TopUnreadMessagesBar from "../views/rooms/TopUnreadMessagesBar"; -import SpaceStore from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; import { logger } from "matrix-js-sdk/src/logger"; import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline'; diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx index 698f24d6594..a944ee7f675 100644 --- a/src/components/structures/SpaceHierarchy.tsx +++ b/src/components/structures/SpaceHierarchy.tsx @@ -49,7 +49,7 @@ import { mediaFromMxc } from "../../customisations/Media"; import InfoTooltip from "../views/elements/InfoTooltip"; import TextWithTooltip from "../views/elements/TextWithTooltip"; import { useStateToggle } from "../../hooks/useStateToggle"; -import { getChildOrder } from "../../stores/SpaceStore"; +import { getChildOrder } from "../../stores/spaces/SpaceStore"; import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; import { linkifyElement } from "../../HtmlUtils"; import { useDispatcher } from "../../hooks/useDispatcher"; diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 25128dd4f08..ddf8f9225b5 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -57,7 +57,7 @@ import { } from "../../utils/space"; import SpaceHierarchy, { joinRoom, showRoom } from "./SpaceHierarchy"; import MemberAvatar from "../views/avatars/MemberAvatar"; -import SpaceStore from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; import FacePile from "../views/elements/FacePile"; import { AddExistingToSpace, diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 4a533f1f8e7..5ffaab7746e 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -54,7 +54,8 @@ import EditCommunityPrototypeDialog from "../views/dialogs/EditCommunityPrototyp import { UIFeature } from "../../settings/UIFeature"; import HostSignupAction from "./HostSignupAction"; import { IHostSignupConfig } from "../views/dialogs/HostSignupDialogTypes"; -import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore"; +import SpaceStore from "../../stores/spaces/SpaceStore"; +import { UPDATE_SELECTED_SPACE } from "../../stores/spaces"; import RoomName from "../views/elements/RoomName"; import { replaceableComponent } from "../../utils/replaceableComponent"; import InlineSpinner from "../views/elements/InlineSpinner"; @@ -90,6 +91,7 @@ export default class UserMenu extends React.Component { isDarkTheme: this.isUserOnDarkTheme(), isHighContrast: this.isUserOnHighContrastTheme(), pendingRoomJoin: new Set(), + selectedSpace: SpaceStore.instance.activeSpaceRoom, }; OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); @@ -162,8 +164,10 @@ export default class UserMenu extends React.Component { this.forceUpdate(); }; - private onSelectedSpaceUpdate = async (selectedSpace?: Room) => { - this.setState({ selectedSpace }); + private onSelectedSpaceUpdate = async () => { + this.setState({ + selectedSpace: SpaceStore.instance.activeSpaceRoom, + }); }; private onThemeChanged = () => { diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index 337941ce5fd..7e2c7be0c30 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -24,7 +24,7 @@ import { _t } from '../../../languageHandler'; import BaseDialog from "./BaseDialog"; import Dropdown from "../elements/Dropdown"; import SearchBox from "../../structures/SearchBox"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import RoomAvatar from "../avatars/RoomAvatar"; import { getDisplayAliasForRoom } from "../../../Rooms"; import AccessibleButton from "../elements/AccessibleButton"; diff --git a/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx b/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx index 1c5dd3fafa3..2c3d570eae4 100644 --- a/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx +++ b/src/components/views/dialogs/ConfirmSpaceUserActionDialog.tsx @@ -17,7 +17,7 @@ limitations under the License. import React, { ComponentProps, useMemo, useState } from 'react'; import ConfirmUserActionDialog from "./ConfirmUserActionDialog"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { Room } from "matrix-js-sdk/src/models/room"; import SpaceChildrenPicker from "../spaces/SpaceChildrenPicker"; diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index c61d6382041..e4c2f4f8730 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -32,7 +32,7 @@ import RoomAliasField from "../elements/RoomAliasField"; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import DialogButtons from "../elements/DialogButtons"; import BaseDialog from "../dialogs/BaseDialog"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import JoinRuleDropdown from "../elements/JoinRuleDropdown"; interface IProps { diff --git a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx index b19c8d64961..f7417ce7dbf 100644 --- a/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx +++ b/src/components/views/dialogs/CreateSpaceFromCommunityDialog.tsx @@ -32,7 +32,7 @@ import { calculateRoomVia, makeRoomPermalink } from "../../../utils/permalinks/P import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; import Spinner from "../elements/Spinner"; import { mediaFromMxc } from "../../../customisations/Media"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import Modal from "../../../Modal"; import InfoDialog from "./InfoDialog"; import dis from "../../../dispatcher/dispatcher"; diff --git a/src/components/views/dialogs/CreateSubspaceDialog.tsx b/src/components/views/dialogs/CreateSubspaceDialog.tsx index 44ffd2afdd2..c128808a3e8 100644 --- a/src/components/views/dialogs/CreateSubspaceDialog.tsx +++ b/src/components/views/dialogs/CreateSubspaceDialog.tsx @@ -25,7 +25,7 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { BetaPill } from "../beta/BetaCard"; import Field from "../elements/Field"; import RoomAliasField from "../elements/RoomAliasField"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { createSpace, SpaceCreateForm } from "../spaces/SpaceCreateMenu"; import { SubspaceSelector } from "./AddExistingToSpaceDialog"; import JoinRuleDropdown from "../elements/JoinRuleDropdown"; diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 7f08a3eb589..93149021044 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -43,7 +43,7 @@ import QueryMatcher from "../../../autocomplete/QueryMatcher"; import TruncatedList from "../elements/TruncatedList"; import EntityTile from "../rooms/EntityTile"; import BaseAvatar from "../avatars/BaseAvatar"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; const AVATAR_SIZE = 30; diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index d37b8c7d6b7..d22c891b4ba 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -71,7 +71,7 @@ import QuestionDialog from "./QuestionDialog"; import Spinner from "../elements/Spinner"; import BaseDialog from "./BaseDialog"; import DialPadBackspaceButton from "../elements/DialPadBackspaceButton"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/components/views/dialogs/LeaveSpaceDialog.tsx b/src/components/views/dialogs/LeaveSpaceDialog.tsx index 3793deee634..74fec7eae27 100644 --- a/src/components/views/dialogs/LeaveSpaceDialog.tsx +++ b/src/components/views/dialogs/LeaveSpaceDialog.tsx @@ -21,7 +21,7 @@ import { JoinRule } from "matrix-js-sdk/src/@types/partials"; import { _t } from '../../../languageHandler'; import DialogButtons from "../elements/DialogButtons"; import BaseDialog from "../dialogs/BaseDialog"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import SpaceChildrenPicker from "../spaces/SpaceChildrenPicker"; interface IProps { diff --git a/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx b/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx index dd5c549bbe2..e4b01526fcf 100644 --- a/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx +++ b/src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx @@ -21,7 +21,7 @@ import { _t } from '../../../languageHandler'; import { IDialogProps } from "./IDialogProps"; import BaseDialog from "./BaseDialog"; import SearchBox from "../../structures/SearchBox"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import RoomAvatar from "../avatars/RoomAvatar"; import AccessibleButton from "../elements/AccessibleButton"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; @@ -75,7 +75,7 @@ const ManageRestrictedJoinRuleDialog: React.FC = ({ room, selected = [], const [spacesContainingRoom, otherEntries] = useMemo(() => { const spaces = cli.getVisibleRooms().filter(r => r.getMyMembership() === "join" && r.isSpaceRoom()); return [ - spaces.filter(r => SpaceStore.instance.getSpaceFilteredRoomIds(r).has(room.roomId)), + spaces.filter(r => SpaceStore.instance.getSpaceFilteredRoomIds(r.roomId).has(room.roomId)), selected.map(roomId => { const room = cli.getRoom(roomId); if (!room) { diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx index a848bf2773f..a2699e99821 100644 --- a/src/components/views/dialogs/UserSettingsDialog.tsx +++ b/src/components/views/dialogs/UserSettingsDialog.tsx @@ -34,6 +34,7 @@ import { UIFeature } from "../../../settings/UIFeature"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import BaseDialog from "./BaseDialog"; import { IDialogProps } from "./IDialogProps"; +import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab"; export enum UserTab { General = "USER_GENERAL_TAB", @@ -41,6 +42,7 @@ export enum UserTab { Flair = "USER_FLAIR_TAB", Notifications = "USER_NOTIFICATIONS_TAB", Preferences = "USER_PREFERENCES_TAB", + Sidebar = "USER_SIDEBAR_TAB", Voice = "USER_VOICE_TAB", Security = "USER_SECURITY_TAB", Labs = "USER_LABS_TAB", @@ -117,6 +119,15 @@ export default class UserSettingsDialog extends React.Component , )); + if (SettingsStore.getValue("feature_spaces_metaspaces")) { + tabs.push(new Tab( + UserTab.Sidebar, + _td("Sidebar"), + "mx_UserSettingsDialog_sidebarIcon", + , + )); + } + if (SettingsStore.getValue(UIFeature.Voip)) { tabs.push(new Tab( UserTab.Voice, diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index 2a355964ff5..cb3e9949a8d 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -69,7 +69,7 @@ import RoomName from "../elements/RoomName"; import { mediaFromMxc } from "../../../customisations/Media"; import UIStore from "../../../stores/UIStore"; import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import ConfirmSpaceUserActionDialog from "../dialogs/ConfirmSpaceUserActionDialog"; import { bulkSpaceBehaviour } from "../../../utils/space"; diff --git a/src/components/views/rooms/MemberList.tsx b/src/components/views/rooms/MemberList.tsx index 80214ca8909..224bb03ff64 100644 --- a/src/components/views/rooms/MemberList.tsx +++ b/src/components/views/rooms/MemberList.tsx @@ -43,7 +43,7 @@ import EntityTile from "./EntityTile"; import MemberTile from "./MemberTile"; import BaseAvatar from '../avatars/BaseAvatar'; import { throttle } from 'lodash'; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { UIComponent } from "../../../settings/UIFeature"; import { JoinRule } from "matrix-js-sdk/src/@types/partials"; diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index 3d92a9cced5..100b1ca4350 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -31,7 +31,7 @@ import defaultDispatcher from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher"; import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload"; import { Action } from "../../../dispatcher/actions"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { showSpaceInvite } from "../../../utils/space"; import { privateShouldBeEncrypted } from "../../../createRoom"; import EventTileBubble from "../messages/EventTileBubble"; @@ -126,12 +126,12 @@ const NewRoomIntro = () => { }); } - let parentSpace; + let parentSpace: Room; if ( - SpaceStore.instance.activeSpace?.canInvite(cli.getUserId()) && + SpaceStore.instance.activeSpaceRoom?.canInvite(cli.getUserId()) && SpaceStore.instance.getSpaceFilteredRoomIds(SpaceStore.instance.activeSpace).has(room.roomId) ) { - parentSpace = SpaceStore.instance.activeSpace; + parentSpace = SpaceStore.instance.activeSpaceRoom; } let buttons; diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 704af3f009c..1be7eb07472 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -21,7 +21,7 @@ import * as fbEmitter from "fbemitter"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { _t, _td } from "../../../languageHandler"; -import { RovingTabIndexProvider, IState as IRovingTabIndexState } from "../../../accessibility/RovingTabIndex"; +import { IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex"; import ResizeNotifier from "../../../utils/ResizeNotifier"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; import RoomViewStore from "../../../stores/RoomViewStore"; @@ -44,7 +44,8 @@ import { objectShallowClone, objectWithOnly } from "../../../utils/objects"; import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu"; import AccessibleButton from "../elements/AccessibleButton"; import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; -import SpaceStore, { ISuggestedRoom, SUGGESTED_ROOMS } from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; +import { ISuggestedRoom, MetaSpace, SpaceKey, UPDATE_SUGGESTED_ROOMS } from "../../../stores/spaces"; import { showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../../utils/space"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import RoomAvatar from "../avatars/RoomAvatar"; @@ -52,6 +53,7 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { UIComponent } from "../../../settings/UIFeature"; import { JoinRule } from "matrix-js-sdk/src/@types/partials"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; interface IProps { onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void; @@ -61,7 +63,7 @@ interface IProps { onListCollapse?: (isExpanded: boolean) => void; resizeNotifier: ResizeNotifier; isMinimized: boolean; - activeSpace: Room; + activeSpace: SpaceKey; } interface IState { @@ -131,9 +133,10 @@ const TAG_AESTHETICS: ITagAestheticsMap = { defaultHidden: false, addRoomLabel: _td("Add room"), addRoomContextMenu: (onFinished: () => void) => { - if (SpaceStore.instance.activeSpace) { - const canAddRooms = SpaceStore.instance.activeSpace.currentState.maySendStateEvent(EventType.SpaceChild, - MatrixClientPeg.get().getUserId()); + if (SpaceStore.instance.activeSpaceRoom) { + const userId = MatrixClientPeg.get().getUserId(); + const space = SpaceStore.instance.activeSpaceRoom; + const canAddRooms = space.currentState.maySendStateEvent(EventType.SpaceChild, userId); return { @@ -146,7 +149,7 @@ const TAG_AESTHETICS: ITagAestheticsMap = { e.preventDefault(); e.stopPropagation(); onFinished(); - showCreateNewRoom(SpaceStore.instance.activeSpace); + showCreateNewRoom(space); }} disabled={!canAddRooms} tooltip={canAddRooms ? undefined @@ -159,7 +162,7 @@ const TAG_AESTHETICS: ITagAestheticsMap = { e.preventDefault(); e.stopPropagation(); onFinished(); - showAddExistingRooms(SpaceStore.instance.activeSpace); + showAddExistingRooms(space); }} disabled={!canAddRooms} tooltip={canAddRooms ? undefined @@ -251,6 +254,9 @@ export default class RoomList extends React.PureComponent { private roomStoreToken: fbEmitter.EventSubscription; private treeRef = createRef(); + static contextType = MatrixClientContext; + public context!: React.ContextType; + constructor(props: IProps) { super(props); @@ -264,14 +270,14 @@ export default class RoomList extends React.PureComponent { public componentDidMount(): void { this.dispatcherRef = defaultDispatcher.register(this.onAction); this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); - SpaceStore.instance.on(SUGGESTED_ROOMS, this.updateSuggestedRooms); + SpaceStore.instance.on(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms); RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists); this.customTagStoreRef = CustomRoomTagStore.addListener(this.updateLists); this.updateLists(); // trigger the first update } public componentWillUnmount() { - SpaceStore.instance.off(SUGGESTED_ROOMS, this.updateSuggestedRooms); + SpaceStore.instance.off(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists); defaultDispatcher.unregister(this.dispatcherRef); if (this.customTagStoreRef) this.customTagStoreRef.remove(); @@ -379,7 +385,7 @@ export default class RoomList extends React.PureComponent { private onSpaceInviteClick = () => { const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search; - showSpaceInvite(this.props.activeSpace, initialText); + showSpaceInvite(this.context.getRoom(this.props.activeSpace), initialText); }; private renderSuggestedRooms(): ReactComponentElement[] { @@ -485,6 +491,15 @@ export default class RoomList extends React.PureComponent { : TAG_AESTHETICS[orderedTagId]; if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`); + let alwaysVisible = ALWAYS_VISIBLE_TAGS.includes(orderedTagId); + if ( + (this.props.activeSpace === MetaSpace.Favourites && orderedTagId !== DefaultTagID.Favourite) || + (this.props.activeSpace === MetaSpace.People && orderedTagId !== DefaultTagID.DM) || + (this.props.activeSpace === MetaSpace.Orphans && orderedTagId === DefaultTagID.DM) + ) { + alwaysVisible = false; + } + // The cost of mounting/unmounting this component offsets the cost // of keeping it in the DOM and hiding it when it is not required return { showSkeleton={showSkeleton} extraTiles={extraTiles} resizeNotifier={this.props.resizeNotifier} - alwaysVisible={ALWAYS_VISIBLE_TAGS.includes(orderedTagId)} + alwaysVisible={alwaysVisible} onListCollapse={this.props.onListCollapse} />; }); @@ -515,6 +530,7 @@ export default class RoomList extends React.PureComponent { public render() { const cli = MatrixClientPeg.get(); const userId = cli.getUserId(); + const activeSpace = this.props.activeSpace[0] === "!" ? cli.getRoom(this.props.activeSpace) : null; let explorePrompt: JSX.Element; if (!this.props.isMinimized) { @@ -533,17 +549,16 @@ export default class RoomList extends React.PureComponent { kind="link" onClick={this.onExplore} > - { this.props.activeSpace ? _t("Explore rooms") : _t("Explore all public rooms") } + { activeSpace ? _t("Explore rooms") : _t("Explore all public rooms") }
; } else if ( - this.props.activeSpace?.canInvite(userId) || - this.props.activeSpace?.getMyMembership() === "join" || - this.props.activeSpace?.getJoinRule() === JoinRule.Public + activeSpace?.canInvite(userId) || + activeSpace?.getMyMembership() === "join" || + activeSpace?.getJoinRule() === JoinRule.Public ) { - const spaceName = this.props.activeSpace.name; - const canInvite = this.props.activeSpace?.canInvite(userId) || - this.props.activeSpace?.getJoinRule() === JoinRule.Public; + const spaceName = activeSpace.name; + const canInvite = activeSpace?.canInvite(userId) || activeSpace?.getJoinRule() === JoinRule.Public; explorePrompt =
{ _t("Quick actions") }
{ canInvite && { > { _t("Invite people") } } - { this.props.activeSpace?.getMyMembership() === "join" && void; diff --git a/src/components/views/rooms/ThirdPartyMemberInfo.tsx b/src/components/views/rooms/ThirdPartyMemberInfo.tsx index c29c558655d..ef1902fcf3e 100644 --- a/src/components/views/rooms/ThirdPartyMemberInfo.tsx +++ b/src/components/views/rooms/ThirdPartyMemberInfo.tsx @@ -27,7 +27,7 @@ import RoomName from "../elements/RoomName"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import ErrorDialog from '../dialogs/ErrorDialog'; import AccessibleButton from '../elements/AccessibleButton'; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/components/views/settings/JoinRuleSettings.tsx b/src/components/views/settings/JoinRuleSettings.tsx index e93bac7fa7a..152578d4995 100644 --- a/src/components/views/settings/JoinRuleSettings.tsx +++ b/src/components/views/settings/JoinRuleSettings.tsx @@ -23,7 +23,7 @@ import StyledRadioGroup, { IDefinition } from "../elements/StyledRadioGroup"; import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; import RoomAvatar from "../avatars/RoomAvatar"; -import SpaceStore from "../../../stores/SpaceStore"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import Modal from "../../../Modal"; import ManageRestrictedJoinRuleDialog from "../dialogs/ManageRestrictedJoinRuleDialog"; @@ -67,8 +67,8 @@ const JoinRuleSettings = ({ room, promptUpgrade, onError, beforeChange, closeSet const editRestrictedRoomIds = async (): Promise => { let selected = restrictedAllowRoomIds; - if (!selected?.length && SpaceStore.instance.activeSpace) { - selected = [SpaceStore.instance.activeSpace.roomId]; + if (!selected?.length && SpaceStore.instance.activeSpaceRoom) { + selected = [SpaceStore.instance.activeSpaceRoom.roomId]; } const matrixClient = MatrixClientPeg.get(); @@ -176,9 +176,9 @@ const JoinRuleSettings = ({ room, promptUpgrade, onError, beforeChange, closeSet { moreText && { moreText } }
; - } else if (SpaceStore.instance.activeSpace) { + } else if (SpaceStore.instance.activeSpaceRoom) { description = _t("Anyone in can find and join. You can select other spaces too.", {}, { - spaceName: () => { SpaceStore.instance.activeSpace.name }, + spaceName: () => { SpaceStore.instance.activeSpaceRoom.name }, }); } else { description = _t("Anyone in a space can find and join. You can select multiple spaces."); diff --git a/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx new file mode 100644 index 00000000000..8b1d0d8f852 --- /dev/null +++ b/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx @@ -0,0 +1,123 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { ChangeEvent } from 'react'; + +import { _t } from "../../../../../languageHandler"; +import SettingsStore from "../../../../../settings/SettingsStore"; +import { SettingLevel } from "../../../../../settings/SettingLevel"; +import StyledCheckbox from "../../../elements/StyledCheckbox"; +import { useSettingValue } from "../../../../../hooks/useSettings"; +import { MetaSpace } from "../../../../../stores/spaces"; + +const onMetaSpaceChangeFactory = (metaSpace: MetaSpace) => (e: ChangeEvent) => { + const currentValue = SettingsStore.getValue("Spaces.enabledMetaSpaces"); + SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.ACCOUNT, { + ...currentValue, + [metaSpace]: e.target.checked, + }); +}; + +const SidebarUserSettingsTab = () => { + const { + [MetaSpace.Home]: homeEnabled, + [MetaSpace.Favourites]: favouritesEnabled, + [MetaSpace.People]: peopleEnabled, + [MetaSpace.Orphans]: orphansEnabled, + } = useSettingValue>("Spaces.enabledMetaSpaces"); + const allRoomsInHome = useSettingValue("Spaces.allRoomsInHome"); + + return ( +
+
{ _t("Sidebar") }
+ +
+ { _t("Spaces") } +
{ _t("Spaces are ways to group rooms and people.") }
+ +
{ _t("Spaces to show") }
+
+ { _t("Along with the spaces you're in, you can use some pre-built ones too.") } +
+ + + { _t("Home") } + +
+ { _t("Home is useful for getting an overview of everything.") } +
+ + { + SettingsStore.setValue( + "Spaces.allRoomsInHome", + null, + SettingLevel.ACCOUNT, + e.target.checked, + ); + }} + className="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox" + > + { _t("Show all rooms") } + +
+ { _t("Show all your rooms in Home, even if they're in a space.") } +
+ + + { _t("Favourites") } + +
+ { _t("Automatically group all your favourite rooms and people together in one place.") } +
+ + + { _t("People") } + +
+ { _t("Automatically group all your people together in one place.") } +
+ + + { _t("Rooms outside of a space") } + +
+ { _t("Automatically group all your rooms that aren't part of a space in one place.") } +
+
+
+ ); +}; + +export default SidebarUserSettingsTab; diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index 5ec44e970b4..c5e15ad855b 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -119,6 +119,7 @@ export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => { rageshakeLabel: "spaces-feedback", rageshakeData: Object.fromEntries([ "Spaces.allRoomsInHome", + "Spaces.enabledMetaSpaces", ].map(k => [k, SettingsStore.getValue(k)])), }); }} diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index f61ebdf0f7c..9c572c9fe50 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -34,13 +34,15 @@ import SpaceCreateMenu from "./SpaceCreateMenu"; import { SpaceButton, SpaceItem } from "./SpaceTreeLevel"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { useEventEmitterState } from "../../../hooks/useEventEmitter"; -import SpaceStore, { - HOME_SPACE, +import SpaceStore from "../../../stores/spaces/SpaceStore"; +import { + MetaSpace, + SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_INVITED_SPACES, UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES, -} from "../../../stores/SpaceStore"; +} from "../../../stores/spaces"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; @@ -53,17 +55,21 @@ import SettingsStore from "../../../settings/SettingsStore"; import { SettingLevel } from "../../../settings/SettingLevel"; import UIStore from "../../../stores/UIStore"; -const useSpaces = (): [Room[], Room[], Room | null] => { +const useSpaces = (): [Room[], MetaSpace[], Room[], SpaceKey] => { const invites = useEventEmitterState(SpaceStore.instance, UPDATE_INVITED_SPACES, () => { return SpaceStore.instance.invitedSpaces; }); - const spaces = useEventEmitterState(SpaceStore.instance, UPDATE_TOP_LEVEL_SPACES, () => { - return SpaceStore.instance.spacePanelSpaces; - }); - const activeSpace = useEventEmitterState(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => { + const [metaSpaces, actualSpaces] = useEventEmitterState<[MetaSpace[], Room[]]>( + SpaceStore.instance, UPDATE_TOP_LEVEL_SPACES, + () => [ + SpaceStore.instance.enabledMetaSpaces, + SpaceStore.instance.spacePanelSpaces, + ], + ); + const activeSpace = useEventEmitterState(SpaceStore.instance, UPDATE_SELECTED_SPACE, () => { return SpaceStore.instance.activeSpace; }); - return [invites, spaces, activeSpace]; + return [invites, metaSpaces, actualSpaces, activeSpace]; }; interface IInnerSpacePanelProps { @@ -99,37 +105,76 @@ const HomeButtonContextMenu = ({ onFinished, ...props }: ComponentProps; }; -interface IHomeButtonProps { +interface IMetaSpaceButtonProps extends ComponentProps { selected: boolean; isPanelCollapsed: boolean; } -const HomeButton = ({ selected, isPanelCollapsed }: IHomeButtonProps) => { - const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => { - return SpaceStore.instance.allRoomsInHome; - }); +type MetaSpaceButtonProps = Pick; +const MetaSpaceButton = ({ selected, isPanelCollapsed, ...props }: IMetaSpaceButtonProps) => { return
  • - SpaceStore.instance.setActiveSpace(null)} - selected={selected} - label={allRoomsInHome ? _t("All rooms") : _t("Home")} - notificationState={allRoomsInHome - ? RoomNotificationStateStore.instance.globalState - : SpaceStore.instance.getNotificationState(HOME_SPACE)} - isNarrow={isPanelCollapsed} - ContextMenuComponent={HomeButtonContextMenu} - contextMenuTooltip={_t("Options")} - /> +
  • ; }; +const HomeButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => { + const allRoomsInHome = useEventEmitterState(SpaceStore.instance, UPDATE_HOME_BEHAVIOUR, () => { + return SpaceStore.instance.allRoomsInHome; + }); + + return ; +}; + +const FavouritesButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => { + return ; +}; + +const PeopleButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => { + return ; +}; + +const OrphansButton = ({ selected, isPanelCollapsed }: MetaSpaceButtonProps) => { + return ; +}; + const CreateSpaceButton = ({ isPanelCollapsed, setPanelCollapsed, @@ -181,13 +226,25 @@ const CreateSpaceButton = ({ ; }; +const metaSpaceComponentMap: Record = { + [MetaSpace.Home]: HomeButton, + [MetaSpace.Favourites]: FavouritesButton, + [MetaSpace.People]: PeopleButton, + [MetaSpace.Orphans]: OrphansButton, +}; + // Optimisation based on https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md#recommended-droppable--performance-optimisation const InnerSpacePanel = React.memo(({ children, isPanelCollapsed, setPanelCollapsed }) => { - const [invites, spaces, activeSpace] = useSpaces(); + const [invites, metaSpaces, actualSpaces, activeSpace] = useSpaces(); const activeSpaces = activeSpace ? [activeSpace] : []; + const metaSpacesSection = metaSpaces.map(key => { + const Component = metaSpaceComponentMap[key]; + return ; + }); + return
    - + { metaSpacesSection } { invites.map(s => ( (({ children, isPanelCo onExpand={() => setPanelCollapsed(false)} /> )) } - { spaces.map((s, i) => ( + { actualSpaces.map((s, i) => ( { (provided, snapshot) => ( , "title"> { +interface IButtonProps extends Omit, "title" | "onClick"> { space?: Room; + spaceKey?: SpaceKey; className?: string; selected?: boolean; label: string; @@ -53,14 +54,14 @@ interface IButtonProps extends Omit>; - onClick(ev: MouseEvent): void; + onClick?(ev?: ButtonEvent): void; } export const SpaceButton: React.FC = ({ space, + spaceKey, className, selected, - onClick, label, contextMenuTooltip, notificationState, @@ -88,7 +89,7 @@ export const SpaceButton: React.FC = ({ notifBadge =
    SpaceStore.instance.setActiveRoomInSpace(space || null)} + onClick={() => SpaceStore.instance.setActiveRoomInSpace(spaceKey ?? space.roomId)} forceCount={false} notification={notificationState} aria-label={ariaLabel} @@ -116,7 +117,7 @@ export const SpaceButton: React.FC = ({ mx_SpaceButton_narrow: isNarrow, })} title={label} - onClick={onClick} + onClick={spaceKey ? () => SpaceStore.instance.setActiveSpace(spaceKey) : props.onClick} onContextMenu={openMenu} forceHide={!isNarrow || menuDisplayed} inputRef={handle} @@ -146,7 +147,7 @@ export const SpaceButton: React.FC = ({ interface IItemProps extends InputHTMLAttributes { space?: Room; - activeSpaces: Room[]; + activeSpaces: SpaceKey[]; isNested?: boolean; isPanelCollapsed?: boolean; onExpand?: Function; @@ -258,7 +259,7 @@ export class SpaceItem extends React.PureComponent { private onClick = (ev: React.MouseEvent) => { ev.preventDefault(); ev.stopPropagation(); - SpaceStore.instance.setActiveSpace(this.props.space); + SpaceStore.instance.setActiveSpace(this.props.space.roomId); }; render() { @@ -316,7 +317,7 @@ export class SpaceItem extends React.PureComponent { {...restDragHandleProps} space={space} className={isInvite ? "mx_SpaceButton_invite" : undefined} - selected={activeSpaces.includes(space)} + selected={activeSpaces.includes(space.roomId)} label={space.name} contextMenuTooltip={_t("Space options")} notificationState={notificationState} @@ -337,7 +338,7 @@ export class SpaceItem extends React.PureComponent { interface ITreeLevelProps { spaces: Room[]; - activeSpaces: Room[]; + activeSpaces: SpaceKey[]; isNested?: boolean; parents: Set; } diff --git a/src/createRoom.ts b/src/createRoom.ts index 6394cb6849d..f4d796cb4b5 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -40,7 +40,7 @@ import GroupStore from "./stores/GroupStore"; import CountlyAnalytics from "./CountlyAnalytics"; import { isJoinedOrNearlyJoined } from "./utils/membership"; import { VIRTUAL_ROOM_EVENT_TYPE } from "./CallHandler"; -import SpaceStore from "./stores/SpaceStore"; +import SpaceStore from "./stores/spaces/SpaceStore"; import { makeSpaceParentEvent } from "./utils/space"; import { Action } from "./dispatcher/actions"; import ErrorDialog from "./components/views/dialogs/ErrorDialog"; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a25ee37386f..1fab6eda59c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -830,6 +830,7 @@ "Polls (under active development)": "Polls (under active development)", "Show info about bridges in room settings": "Show info about bridges in room settings", "New layout switcher (with message bubbles)": "New layout switcher (with message bubbles)", + "Meta Spaces": "Meta Spaces", "Don't send read receipts": "Don't send read receipts", "Font size": "Font size", "Use custom size": "Use custom size", @@ -1064,6 +1065,9 @@ "Show all rooms": "Show all rooms", "All rooms": "All rooms", "Options": "Options", + "Favourites": "Favourites", + "People": "People", + "Other rooms": "Other rooms", "Spaces": "Spaces", "Expand space panel": "Expand space panel", "Collapse space panel": "Collapse space panel", @@ -1426,6 +1430,16 @@ "Learn more about how we use analytics.": "Learn more about how we use analytics.", "Where you're signed in": "Where you're signed in", "Manage your signed-in devices below. A device's name is visible to people you communicate with.": "Manage your signed-in devices below. A device's name is visible to people you communicate with.", + "Sidebar": "Sidebar", + "Spaces are ways to group rooms and people.": "Spaces are ways to group rooms and people.", + "Spaces to show": "Spaces to show", + "Along with the spaces you're in, you can use some pre-built ones too.": "Along with the spaces you're in, you can use some pre-built ones too.", + "Home is useful for getting an overview of everything.": "Home is useful for getting an overview of everything.", + "Show all your rooms in Home, even if they're in a space.": "Show all your rooms in Home, even if they're in a space.", + "Automatically group all your favourite rooms and people together in one place.": "Automatically group all your favourite rooms and people together in one place.", + "Automatically group all your people together in one place.": "Automatically group all your people together in one place.", + "Rooms outside of a space": "Rooms outside of a space", + "Automatically group all your rooms that aren't part of a space in one place.": "Automatically group all your rooms that aren't part of a space in one place.", "Default Device": "Default Device", "No media permissions": "No media permissions", "You may need to manually permit %(brand)s to access your microphone/webcam": "You may need to manually permit %(brand)s to access your microphone/webcam", @@ -1670,8 +1684,6 @@ "Show Widgets": "Show Widgets", "Search": "Search", "Invites": "Invites", - "Favourites": "Favourites", - "People": "People", "Start chat": "Start chat", "Rooms": "Rooms", "Add room": "Add room", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 8b253091208..c263317dc49 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -42,6 +42,7 @@ import ReducedMotionController from './controllers/ReducedMotionController'; import IncompatibleController from "./controllers/IncompatibleController"; import PseudonymousAnalyticsController from './controllers/PseudonymousAnalyticsController'; import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController'; +import { MetaSpace } from "../stores/spaces"; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times const LEVELS_ROOM_SETTINGS = [ @@ -283,6 +284,16 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: false, controller: new NewLayoutSwitcherController(), }, + "feature_spaces_metaspaces": { + isFeature: true, + supportedLevels: LEVELS_FEATURE, + displayName: _td("Meta Spaces"), + default: false, + controller: new OrderedMultiController([ + new IncompatibleController("showCommunitiesInsteadOfSpaces"), + new ReloadOnChangeController(), + ]), + }, "RoomList.backgroundImage": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, default: null, @@ -755,6 +766,15 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: false, controller: new IncompatibleController("showCommunitiesInsteadOfSpaces", null), }, + "Spaces.enabledMetaSpaces": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + default: { + [MetaSpace.Home]: true, + }, + controller: new IncompatibleController("feature_spaces_metaspaces", { + [MetaSpace.Home]: true, + }, false), + }, "showCommunitiesInsteadOfSpaces": { displayName: _td("Display Communities instead of Spaces"), description: _td("Temporarily show communities instead of Spaces for this session. " + diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index 8a85ca354fd..7c33901ae4f 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -22,7 +22,7 @@ import defaultDispatcher from "../dispatcher/dispatcher"; import { arrayHasDiff } from "../utils/arrays"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; import { SettingLevel } from "../settings/SettingLevel"; -import SpaceStore from "./SpaceStore"; +import SpaceStore from "./spaces/SpaceStore"; import { Action } from "../dispatcher/actions"; import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload"; diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index c4b1f012b1b..9fbfcb32e25 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -35,7 +35,7 @@ import { NameFilterCondition } from "./filters/NameFilterCondition"; import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore"; import { VisibilityProvider } from "./filters/VisibilityProvider"; import { SpaceWatcher } from "./SpaceWatcher"; -import SpaceStore from "../SpaceStore"; +import SpaceStore from "../spaces/SpaceStore"; import { Action } from "../../dispatcher/actions"; import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload"; diff --git a/src/stores/room-list/SpaceWatcher.ts b/src/stores/room-list/SpaceWatcher.ts index fe2eb1e881b..e7d6e78206a 100644 --- a/src/stores/room-list/SpaceWatcher.ts +++ b/src/stores/room-list/SpaceWatcher.ts @@ -14,11 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Room } from "matrix-js-sdk/src/models/room"; - import { RoomListStoreClass } from "./RoomListStore"; import { SpaceFilterCondition } from "./filters/SpaceFilterCondition"; -import SpaceStore, { UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../SpaceStore"; +import SpaceStore from "../spaces/SpaceStore"; +import { MetaSpace, SpaceKey, UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../spaces"; /** * Watches for changes in spaces to manage the filter on the provided RoomListStore @@ -26,11 +25,11 @@ import SpaceStore, { UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../Spa export class SpaceWatcher { private readonly filter = new SpaceFilterCondition(); // we track these separately to the SpaceStore as we need to observe transitions - private activeSpace: Room = SpaceStore.instance.activeSpace; + private activeSpace: SpaceKey = SpaceStore.instance.activeSpace; private allRoomsInHome: boolean = SpaceStore.instance.allRoomsInHome; constructor(private store: RoomListStoreClass) { - if (!this.allRoomsInHome || this.activeSpace) { + if (SpaceWatcher.needsFilter(this.activeSpace, this.allRoomsInHome)) { this.updateFilter(); store.addFilter(this.filter); } @@ -38,21 +37,26 @@ export class SpaceWatcher { SpaceStore.instance.on(UPDATE_HOME_BEHAVIOUR, this.onHomeBehaviourUpdated); } - private onSelectedSpaceUpdated = (activeSpace?: Room, allRoomsInHome = this.allRoomsInHome) => { + private static needsFilter(spaceKey: SpaceKey, allRoomsInHome: boolean): boolean { + return !(spaceKey === MetaSpace.Home && allRoomsInHome); + } + + private onSelectedSpaceUpdated = (activeSpace: SpaceKey, allRoomsInHome = this.allRoomsInHome) => { if (activeSpace === this.activeSpace && allRoomsInHome === this.allRoomsInHome) return; // nop - const oldActiveSpace = this.activeSpace; - const oldAllRoomsInHome = this.allRoomsInHome; + const neededFilter = SpaceWatcher.needsFilter(this.activeSpace, this.allRoomsInHome); + const needsFilter = SpaceWatcher.needsFilter(activeSpace, allRoomsInHome); + this.activeSpace = activeSpace; this.allRoomsInHome = allRoomsInHome; - if (activeSpace || !allRoomsInHome) { + if (needsFilter) { this.updateFilter(); } - if (oldAllRoomsInHome && !oldActiveSpace) { + if (!neededFilter && needsFilter) { this.store.addFilter(this.filter); - } else if (allRoomsInHome && !activeSpace) { + } else if (neededFilter && !needsFilter) { this.store.removeFilter(this.filter); } }; @@ -62,8 +66,8 @@ export class SpaceWatcher { }; private updateFilter = () => { - if (this.activeSpace) { - SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => { + if (this.activeSpace[0] === "!") { + SpaceStore.instance.traverseSpace(this.activeSpace, roomId => { this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded(); }); } diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 754e1c1d946..c812edee489 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -34,7 +34,7 @@ import { EffectiveMembership, getEffectiveMembership, splitRoomsByMembership } f import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm"; import { getListAlgorithmInstance } from "./list-ordering"; import { VisibilityProvider } from "../filters/VisibilityProvider"; -import SpaceStore from "../../SpaceStore"; +import SpaceStore from "../../spaces/SpaceStore"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts index 0e6965d843a..fd815bf86fc 100644 --- a/src/stores/room-list/filters/SpaceFilterCondition.ts +++ b/src/stores/room-list/filters/SpaceFilterCondition.ts @@ -19,7 +19,8 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition"; import { IDestroyable } from "../../../utils/IDestroyable"; -import SpaceStore, { HOME_SPACE } from "../../SpaceStore"; +import SpaceStore from "../../spaces/SpaceStore"; +import { MetaSpace, SpaceKey } from "../../spaces"; import { setHasDiff } from "../../../utils/sets"; /** @@ -30,7 +31,7 @@ import { setHasDiff } from "../../../utils/sets"; */ export class SpaceFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable { private roomIds = new Set(); - private space: Room = null; + private space: SpaceKey = MetaSpace.Home; public get kind(): FilterKind { return FilterKind.Prefilter; @@ -55,15 +56,13 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi } }; - private getSpaceEventKey = (space: Room | null) => space ? space.roomId : HOME_SPACE; - - public updateSpace(space: Room) { - SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate); - SpaceStore.instance.on(this.getSpaceEventKey(this.space = space), this.onStoreUpdate); + public updateSpace(space: SpaceKey) { + SpaceStore.instance.off(this.space, this.onStoreUpdate); + SpaceStore.instance.on(this.space = space, this.onStoreUpdate); this.onStoreUpdate(); // initial update from the change to the space } public destroy(): void { - SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate); + SpaceStore.instance.off(this.space, this.onStoreUpdate); } } diff --git a/src/stores/room-list/filters/VisibilityProvider.ts b/src/stores/room-list/filters/VisibilityProvider.ts index f63b622053e..18b68da301f 100644 --- a/src/stores/room-list/filters/VisibilityProvider.ts +++ b/src/stores/room-list/filters/VisibilityProvider.ts @@ -18,7 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room"; import CallHandler from "../../../CallHandler"; import { RoomListCustomisations } from "../../../customisations/RoomList"; import VoipUserMapper from "../../../VoipUserMapper"; -import SpaceStore from "../../SpaceStore"; +import SpaceStore from "../../spaces/SpaceStore"; export class VisibilityProvider { private static internalInstance: VisibilityProvider; diff --git a/src/stores/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts similarity index 76% rename from src/stores/SpaceStore.ts rename to src/stores/spaces/SpaceStore.ts index ea5ff56aea1..5cea148b780 100644 --- a/src/stores/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -18,56 +18,51 @@ import { ListIteratee, Many, sortBy, throttle } from "lodash"; import { EventType, RoomType } from "matrix-js-sdk/src/@types/event"; import { Room } from "matrix-js-sdk/src/models/room"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces"; import { IRoomCapability } from "matrix-js-sdk/src/client"; - -import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; -import defaultDispatcher from "../dispatcher/dispatcher"; -import { ActionPayload } from "../dispatcher/payloads"; -import RoomListStore from "./room-list/RoomListStore"; -import SettingsStore from "../settings/SettingsStore"; -import DMRoomMap from "../utils/DMRoomMap"; -import { FetchRoomFn } from "./notifications/ListNotificationState"; -import { SpaceNotificationState } from "./notifications/SpaceNotificationState"; -import { RoomNotificationStateStore } from "./notifications/RoomNotificationStateStore"; -import { DefaultTagID } from "./room-list/models"; -import { EnhancedMap, mapDiff } from "../utils/maps"; -import { setHasDiff } from "../utils/sets"; -import RoomViewStore from "./RoomViewStore"; -import { Action } from "../dispatcher/actions"; -import { arrayHasDiff, arrayHasOrderChange } from "../utils/arrays"; -import { objectDiff } from "../utils/objects"; -import { reorderLexicographically } from "../utils/stringOrderField"; -import { TAG_ORDER } from "../components/views/rooms/RoomList"; -import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload"; - import { logger } from "matrix-js-sdk/src/logger"; -type SpaceKey = string | symbol; +import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; +import defaultDispatcher from "../../dispatcher/dispatcher"; +import { ActionPayload } from "../../dispatcher/payloads"; +import RoomListStore from "../room-list/RoomListStore"; +import SettingsStore from "../../settings/SettingsStore"; +import DMRoomMap from "../../utils/DMRoomMap"; +import { FetchRoomFn } from "../notifications/ListNotificationState"; +import { SpaceNotificationState } from "../notifications/SpaceNotificationState"; +import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore"; +import { DefaultTagID } from "../room-list/models"; +import { EnhancedMap, mapDiff } from "../../utils/maps"; +import { setHasDiff } from "../../utils/sets"; +import RoomViewStore from "../RoomViewStore"; +import { Action } from "../../dispatcher/actions"; +import { arrayHasDiff, arrayHasOrderChange } from "../../utils/arrays"; +import { objectDiff } from "../../utils/objects"; +import { reorderLexicographically } from "../../utils/stringOrderField"; +import { TAG_ORDER } from "../../components/views/rooms/RoomList"; +import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload"; +import { + ISuggestedRoom, + MetaSpace, + SpaceKey, + UPDATE_HOME_BEHAVIOUR, + UPDATE_INVITED_SPACES, + UPDATE_SELECTED_SPACE, + UPDATE_SUGGESTED_ROOMS, + UPDATE_TOP_LEVEL_SPACES, +} from "."; interface IState {} const ACTIVE_SPACE_LS_KEY = "mx_active_space"; -export const HOME_SPACE = Symbol("home-space"); -export const SUGGESTED_ROOMS = Symbol("suggested-rooms"); - -export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces"); -export const UPDATE_INVITED_SPACES = Symbol("invited-spaces"); -export const UPDATE_SELECTED_SPACE = Symbol("selected-space"); -export const UPDATE_HOME_BEHAVIOUR = Symbol("home-behaviour"); -// Space Room ID/HOME_SPACE will be emitted when a Space's children change - -export interface ISuggestedRoom extends IHierarchyRoom { - viaServers: string[]; -} +const metaSpaceOrder: MetaSpace[] = [MetaSpace.Home, MetaSpace.Favourites, MetaSpace.People, MetaSpace.Orphans]; const MAX_SUGGESTED_ROOMS = 20; // This setting causes the page to reload and can be costly if read frequently, so read it here only const spacesEnabled = !SettingsStore.getValue("showCommunitiesInsteadOfSpaces"); -const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "HOME_SPACE"}`; +const getSpaceContextKey = (space: SpaceKey) => `mx_space_context_${space}`; const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms] return arr.reduce((result, room: Room) => { @@ -105,30 +100,41 @@ export class SpaceStoreClass extends AsyncStoreWithClient { private notificationStateMap = new Map(); // Map from space key to Set of room IDs that should be shown as part of that space's filter private spaceFilteredRooms = new Map>(); - // The space currently selected in the Space Panel - if null then Home is selected - private _activeSpace?: Room = null; + // The space currently selected in the Space Panel + private _activeSpace?: SpaceKey = MetaSpace.Home; // set properly by onReady private _suggestedRooms: ISuggestedRoom[] = []; private _invitedSpaces = new Set(); private spaceOrderLocalEchoMap = new Map(); private _restrictedJoinRuleSupport?: IRoomCapability; private _allRoomsInHome: boolean = SettingsStore.getValue("Spaces.allRoomsInHome"); + private _enabledMetaSpaces: MetaSpace[] = []; // set by onReady constructor() { super(defaultDispatcher, {}); SettingsStore.monitorSetting("Spaces.allRoomsInHome", null); + SettingsStore.monitorSetting("Spaces.enabledMetaSpaces", null); } public get invitedSpaces(): Room[] { return Array.from(this._invitedSpaces); } + public get enabledMetaSpaces(): MetaSpace[] { + return this._enabledMetaSpaces; + } + public get spacePanelSpaces(): Room[] { return this.rootSpaces; } - public get activeSpace(): Room | null { - return this._activeSpace || null; + public get activeSpace(): SpaceKey { + return this._activeSpace; + } + + public get activeSpaceRoom(): Room | null { + if (this._activeSpace[0] !== "!") return null; + return this.matrixClient?.getRoom(this._activeSpace); } public get suggestedRooms(): ISuggestedRoom[] { @@ -139,12 +145,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return this._allRoomsInHome; } - public setActiveRoomInSpace(space: Room | null): void { - if (space && !space.isSpaceRoom()) return; + public setActiveRoomInSpace(space: SpaceKey): void { + if (space[0] === "!" && !this.matrixClient?.getRoom(space)?.isSpaceRoom()) return; if (space !== this.activeSpace) this.setActiveSpace(space); if (space) { - const roomId = this.getNotificationState(space.roomId).getFirstRoomWithNotifications(); + const roomId = this.getNotificationState(space).getFirstRoomWithNotifications(); defaultDispatcher.dispatch({ action: "view_room", room_id: roomId, @@ -184,12 +190,20 @@ export class SpaceStoreClass extends AsyncStoreWithClient { * @param contextSwitch whether to switch the user's context, * should not be done when the space switch is done implicitly due to another event like switching room. */ - public setActiveSpace(space: Room | null, contextSwitch = true) { - if (!this.matrixClient || space === this.activeSpace || (space && !space.isSpaceRoom())) return; + public setActiveSpace(space: SpaceKey, contextSwitch = true) { + if (!space || !this.matrixClient || space === this.activeSpace) return; + + let cliSpace: Room; + if (space[0] === "!") { + cliSpace = this.matrixClient.getRoom(space); + if (!cliSpace?.isSpaceRoom()) return; + } else if (!this.enabledMetaSpaces.includes(space as MetaSpace)) { + return; + } this._activeSpace = space; this.emit(UPDATE_SELECTED_SPACE, this.activeSpace); - this.emit(SUGGESTED_ROOMS, this._suggestedRooms = []); + this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms = []); if (contextSwitch) { // view last selected room from space @@ -198,7 +212,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { // if the space being selected is an invite then always view that invite // else if the last viewed room in this space is joined then view that // else view space home or home depending on what is being clicked on - if (space?.getMyMembership() !== "invite" && + if (cliSpace?.getMyMembership() !== "invite" && this.matrixClient.getRoom(roomId)?.getMyMembership() === "join" && this.getSpaceFilteredRoomIds(space).has(roomId) ) { @@ -207,10 +221,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { room_id: roomId, context_switch: true, }); - } else if (space) { + } else if (cliSpace) { defaultDispatcher.dispatch({ action: "view_room", - room_id: space.roomId, + room_id: space, context_switch: true, }); } else { @@ -221,22 +235,18 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } // persist space selected - if (space) { - window.localStorage.setItem(ACTIVE_SPACE_LS_KEY, space.roomId); - } else { - window.localStorage.removeItem(ACTIVE_SPACE_LS_KEY); - } + window.localStorage.setItem(ACTIVE_SPACE_LS_KEY, space); - if (space) { - this.loadSuggestedRooms(space); + if (cliSpace) { + this.loadSuggestedRooms(cliSpace); } } private async loadSuggestedRooms(space: Room): Promise { const suggestedRooms = await this.fetchSuggestedRooms(space); - if (this._activeSpace === space) { + if (this._activeSpace === space.roomId) { this._suggestedRooms = suggestedRooms; - this.emit(SUGGESTED_ROOMS, this._suggestedRooms); + this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms); } } @@ -337,11 +347,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient { return this.parentMap.get(roomId) || new Set(); } - public getSpaceFilteredRoomIds = (space: Room | null): Set => { - if (!space && this.allRoomsInHome) { + public getSpaceFilteredRoomIds = (space: SpaceKey): Set => { + if (space === MetaSpace.Home && this.allRoomsInHome) { return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId)); } - return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set(); + return this.spaceFilteredRooms.get(space) || new Set(); }; private rebuild = throttle(() => { @@ -420,12 +430,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.parentMap = backrefs; // if the currently selected space no longer exists, remove its selection - if (this._activeSpace && detachedNodes.has(this._activeSpace)) { - this.setActiveSpace(null, false); + if (this._activeSpace[0] === "!" && detachedNodes.has(this.matrixClient.getRoom(this._activeSpace))) { + this.goToFirstSpace(); } this.onRoomsUpdate(); // TODO only do this if a change has happened - this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces); + this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces, this.enabledMetaSpaces); // build initial state of invited spaces as we would have missed the emitted events about the room at launch this._invitedSpaces = new Set(this.sortRootSpaces(invitedSpaces)); @@ -440,19 +450,22 @@ export class SpaceStoreClass extends AsyncStoreWithClient { if (this.allRoomsInHome) return true; if (room.isSpaceRoom()) return false; return !this.parentMap.get(room.roomId)?.size // put all orphaned rooms in the Home Space - || DMRoomMap.shared().getUserIdForRoomId(room.roomId) // put all DMs in the Home Space - || RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite); // show all favourites + || DMRoomMap.shared().getUserIdForRoomId(room.roomId); // put all DMs in the Home Space }; // Update a given room due to its tag changing (e.g DM-ness or Fav-ness) // This can only change whether it shows up in the HOME_SPACE or not private onRoomUpdate = (room: Room) => { - if (this.showInHomeSpace(room)) { - this.spaceFilteredRooms.get(HOME_SPACE)?.add(room.roomId); - this.emit(HOME_SPACE); - } else if (!this.orphanedRooms.has(room.roomId)) { - this.spaceFilteredRooms.get(HOME_SPACE)?.delete(room.roomId); - this.emit(HOME_SPACE); + const enabledMetaSpaces = new Set(this.enabledMetaSpaces); + // TODO more metaspace stuffs + if (enabledMetaSpaces.has(MetaSpace.Home)) { + if (this.showInHomeSpace(room)) { + this.spaceFilteredRooms.get(MetaSpace.Home)?.add(room.roomId); + this.emit(MetaSpace.Home); + } else if (!this.orphanedRooms.has(room.roomId)) { + this.spaceFilteredRooms.get(MetaSpace.Home)?.delete(room.roomId); + this.emit(MetaSpace.Home); + } } }; @@ -469,18 +482,41 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const oldFilteredRooms = this.spaceFilteredRooms; this.spaceFilteredRooms = new Map(); - if (!this.allRoomsInHome) { + const enabledMetaSpaces = new Set(this.enabledMetaSpaces); + // populate the Home metaspace if it is enabled and is not set to all rooms + if (enabledMetaSpaces.has(MetaSpace.Home) && !this.allRoomsInHome) { // put all room invites in the Home Space const invites = visibleRooms.filter(r => !r.isSpaceRoom() && r.getMyMembership() === "invite"); - this.spaceFilteredRooms.set(HOME_SPACE, new Set(invites.map(room => room.roomId))); + this.spaceFilteredRooms.set(MetaSpace.Home, new Set(invites.map(r => r.roomId))); visibleRooms.forEach(room => { if (this.showInHomeSpace(room)) { - this.spaceFilteredRooms.get(HOME_SPACE).add(room.roomId); + this.spaceFilteredRooms.get(MetaSpace.Home).add(room.roomId); } }); } + // populate the Favourites metaspace if it is enabled + if (enabledMetaSpaces.has(MetaSpace.Favourites)) { + const favourites = visibleRooms.filter(r => r.tags[DefaultTagID.Favourite]); + this.spaceFilteredRooms.set(MetaSpace.Favourites, new Set(favourites.map(r => r.roomId))); + } + + // populate the People metaspace if it is enabled + if (enabledMetaSpaces.has(MetaSpace.People)) { + const people = visibleRooms.filter(r => DMRoomMap.shared().getUserIdForRoomId(r.roomId)); + this.spaceFilteredRooms.set(MetaSpace.People, new Set(people.map(r => r.roomId))); + } + + // populate the Orphans metaspace if it is enabled + if (enabledMetaSpaces.has(MetaSpace.Orphans)) { + const orphans = visibleRooms.filter(r => { + // filter out DMs and rooms with >0 parents + return !this.parentMap.get(r.roomId)?.size && !DMRoomMap.shared().getUserIdForRoomId(r.roomId); + }); + this.spaceFilteredRooms.set(MetaSpace.Orphans, new Set(orphans.map(r => r.roomId))); + } + const hiddenChildren = new EnhancedMap>(); visibleRooms.forEach(room => { if (room.getMyMembership() !== "join") return; @@ -540,15 +576,23 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.emit(k); }); + let dmBadgeSpace: MetaSpace; + // only show badges on dms on the most relevant space if such exists + if (enabledMetaSpaces.has(MetaSpace.People)) { + dmBadgeSpace = MetaSpace.People; + } else if (enabledMetaSpaces.has(MetaSpace.Home)) { + dmBadgeSpace = MetaSpace.Home; + } + this.spaceFilteredRooms.forEach((roomIds, s) => { - if (this.allRoomsInHome && s === HOME_SPACE) return; // we'll be using the global notification state, skip + if (this.allRoomsInHome && s === MetaSpace.Home) return; // we'll be using the global notification state, skip // Update NotificationStates this.getNotificationState(s).setRooms(visibleRooms.filter(room => { if (!roomIds.has(room.roomId) || room.isSpaceRoom()) return false; - if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { - return s === HOME_SPACE; + if (dmBadgeSpace && DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { + return s === dmBadgeSpace; } return true; @@ -575,7 +619,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } // don't trigger a context switch when we are switching a space to match the chosen room - this.setActiveSpace(parent || null, false); + this.setActiveSpace(parent?.roomId ?? MetaSpace.Home, false); // TODO }; private onRoom = (room: Room, newMembership?: string, oldMembership?: string) => { @@ -597,7 +641,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const numSuggestedRooms = this._suggestedRooms.length; this._suggestedRooms = this._suggestedRooms.filter(r => r.room_id !== room.roomId); if (numSuggestedRooms !== this._suggestedRooms.length) { - this.emit(SUGGESTED_ROOMS, this._suggestedRooms); + this.emit(UPDATE_SUGGESTED_ROOMS, this._suggestedRooms); } // if the room currently being viewed was just joined then switch to its related space @@ -622,10 +666,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { if (membership === "join" && room.roomId === RoomViewStore.getRoomId()) { // if the user was looking at the space and then joined: select that space - this.setActiveSpace(room, false); - } else if (membership === "leave" && room.roomId === this.activeSpace?.roomId) { + this.setActiveSpace(room.roomId, false); + } else if (membership === "leave" && room.roomId === this.activeSpace) { // user's active space has gone away, go back to home - this.setActiveSpace(null, true); + this.goToFirstSpace(true); } }; @@ -633,7 +677,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { const rootSpaces = this.sortRootSpaces(this.rootSpaces); if (arrayHasOrderChange(this.rootSpaces, rootSpaces)) { this.rootSpaces = rootSpaces; - this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces); + this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces, this.enabledMetaSpaces); } } @@ -648,7 +692,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.emit(room.roomId); } - if (room === this.activeSpace && // current space + if (room.roomId === this.activeSpace && // current space this.matrixClient.getRoom(ev.getStateKey())?.getMyMembership() !== "join" && // target not joined ev.getPrevContent().suggested !== ev.getContent().suggested // suggested flag changed ) { @@ -694,7 +738,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { if (order !== lastOrder) { this.notifyIfOrderChanged(); } - } else if (ev.getType() === EventType.Tag && !this.allRoomsInHome) { + } else if (ev.getType() === EventType.Tag) { // If the room was in favourites and now isn't or the opposite then update its position in the trees const oldTags = lastEv?.getContent()?.tags || {}; const newTags = ev.getContent()?.tags || {}; @@ -728,9 +772,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { this.parentMap = new EnhancedMap(); this.notificationStateMap = new Map(); this.spaceFilteredRooms = new Map(); - this._activeSpace = null; + this._activeSpace = MetaSpace.Home; // set properly by onReady this._suggestedRooms = []; this._invitedSpaces = new Set(); + this._enabledMetaSpaces = []; } protected async onNotReady() { @@ -760,16 +805,27 @@ export class SpaceStoreClass extends AsyncStoreWithClient { ?.["m.room_versions"]?.["org.matrix.msc3244.room_capabilities"]?.["restricted"]; }); + const enabledMetaSpaces = SettingsStore.getValue("Spaces.enabledMetaSpaces"); + this._enabledMetaSpaces = metaSpaceOrder.filter(k => enabledMetaSpaces[k]) as MetaSpace[]; + await this.onSpaceUpdate(); // trigger an initial update // restore selected state from last session if any and still valid const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY); - if (lastSpaceId) { + if (lastSpaceId && ( + lastSpaceId[0] === "!" ? this.matrixClient.getRoom(lastSpaceId) : enabledMetaSpaces[lastSpaceId] + )) { // don't context switch here as it may break permalinks - this.setActiveSpace(this.matrixClient.getRoom(lastSpaceId), false); + this.setActiveSpace(lastSpaceId, false); + } else { + this.goToFirstSpace(); } } + private goToFirstSpace(contextSwitch = false) { + this.setActiveSpace(this.enabledMetaSpaces[0] ?? this.spacePanelSpaces[0]?.roomId, contextSwitch); + } + protected async onAction(payload: ActionPayload) { if (!spacesEnabled) return; switch (payload.action) { @@ -783,9 +839,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient { if (room?.isSpaceRoom()) { // Don't context switch when navigating to the space room // as it will cause you to end up in the wrong room - this.setActiveSpace(room, false); + this.setActiveSpace(room.roomId, false); } else if ( - (!this.allRoomsInHome || this.activeSpace) && + (!this.allRoomsInHome || this.activeSpace[0] === "!") && !this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId) ) { this.switchToRelatedSpace(roomId); @@ -799,31 +855,54 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } case "after_leave_room": - if (this._activeSpace && payload.room_id === this._activeSpace.roomId) { - this.setActiveSpace(null, false); + if (this._activeSpace[0] === "!" && payload.room_id === this._activeSpace) { + // User has left the current space, go to first space + this.goToFirstSpace(); } break; - case Action.SwitchSpace: - // 1 is Home, 2-9 are the spaces after Home - if (payload.num === 1) { - this.setActiveSpace(null); - } else if (payload.num > 0 && this.spacePanelSpaces.length > payload.num - 2) { - this.setActiveSpace(this.spacePanelSpaces[payload.num - 2]); + case Action.SwitchSpace: { + // Metaspaces start at 1, Spaces follow + if (payload.num < 1 || payload.num > 9) break; + const numMetaSpaces = this.enabledMetaSpaces.length; + if (payload.num <= numMetaSpaces) { + this.setActiveSpace(this.enabledMetaSpaces[payload.num - 1]); + } else if (this.spacePanelSpaces.length > payload.num - numMetaSpaces - 1) { + this.setActiveSpace(this.spacePanelSpaces[payload.num - numMetaSpaces - 1].roomId); } break; + } case Action.SettingUpdated: { const settingUpdatedPayload = payload as SettingUpdatedPayload; - if (settingUpdatedPayload.settingName === "Spaces.allRoomsInHome") { - const newValue = SettingsStore.getValue("Spaces.allRoomsInHome"); - if (this.allRoomsInHome !== newValue) { - this._allRoomsInHome = newValue; - this.emit(UPDATE_HOME_BEHAVIOUR, this.allRoomsInHome); - this.rebuild(); // rebuild everything + switch (settingUpdatedPayload.settingName) { + case "Spaces.allRoomsInHome": { + const newValue = SettingsStore.getValue("Spaces.allRoomsInHome"); + if (this.allRoomsInHome !== newValue) { + this._allRoomsInHome = newValue; + this.emit(UPDATE_HOME_BEHAVIOUR, this.allRoomsInHome); + this.rebuild(); // rebuild everything + } + break; + } + + case "Spaces.enabledMetaSpaces": { + const newValue = SettingsStore.getValue("Spaces.enabledMetaSpaces"); + const enabledMetaSpaces = metaSpaceOrder.filter(k => newValue[k]) as MetaSpace[]; + if (arrayHasDiff(this._enabledMetaSpaces, enabledMetaSpaces)) { + this._enabledMetaSpaces = enabledMetaSpaces; + // if a metaspace currently being viewed was remove, go to another one + if (this.activeSpace[0] !== "!" && + !enabledMetaSpaces.includes(this.activeSpace as MetaSpace) + ) { + this.goToFirstSpace(); + } + this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces, this.enabledMetaSpaces); + this.rebuild(); // rebuild everything + } + break; } } - break; } } } diff --git a/src/stores/SpaceTreeLevelLayoutStore.ts b/src/stores/spaces/SpaceTreeLevelLayoutStore.ts similarity index 100% rename from src/stores/SpaceTreeLevelLayoutStore.ts rename to src/stores/spaces/SpaceTreeLevelLayoutStore.ts diff --git a/src/stores/spaces/index.ts b/src/stores/spaces/index.ts new file mode 100644 index 00000000000..7816932b050 --- /dev/null +++ b/src/stores/spaces/index.ts @@ -0,0 +1,40 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { Room } from "matrix-js-sdk/src/models/room"; +import { IHierarchyRoom } from "matrix-js-sdk/src/@types/spaces"; + +// The consts & types are moved out here to prevent cyclical imports + +export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces"); +export const UPDATE_INVITED_SPACES = Symbol("invited-spaces"); +export const UPDATE_SELECTED_SPACE = Symbol("selected-space"); +export const UPDATE_HOME_BEHAVIOUR = Symbol("home-behaviour"); +export const UPDATE_SUGGESTED_ROOMS = Symbol("suggested-rooms"); +// Space Key will be emitted when a Space's children change + +export enum MetaSpace { + Home = "home-space", + Favourites = "favourites-space", + People = "people-space", + Orphans = "orphans-space", +} + +export type SpaceKey = MetaSpace | Room["roomId"]; + +export interface ISuggestedRoom extends IHierarchyRoom { + viaServers: string[]; +} diff --git a/src/utils/RoomUpgrade.ts b/src/utils/RoomUpgrade.ts index b9ea93d7fc6..902c5d00ca8 100644 --- a/src/utils/RoomUpgrade.ts +++ b/src/utils/RoomUpgrade.ts @@ -21,7 +21,7 @@ import { inviteUsersToRoom } from "../RoomInvite"; import Modal, { IHandle } from "../Modal"; import { _t } from "../languageHandler"; import ErrorDialog from "../components/views/dialogs/ErrorDialog"; -import SpaceStore from "../stores/SpaceStore"; +import SpaceStore from "../stores/spaces/SpaceStore"; import Spinner from "../components/views/elements/Spinner"; import { logger } from "matrix-js-sdk/src/logger"; diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index ccbf0af4024..d6d05668113 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -18,13 +18,16 @@ import { EventEmitter } from "events"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import "./enable-metaspaces-labs"; import "../skinned-sdk"; // Must be first for skinning to work -import SpaceStore, { +import SpaceStore from "../../src/stores/spaces/SpaceStore"; +import { + MetaSpace, UPDATE_HOME_BEHAVIOUR, UPDATE_INVITED_SPACES, UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES, -} from "../../src/stores/SpaceStore"; +} from "../../src/stores/spaces"; import * as testUtils from "../utils/test-utils"; import { mkEvent, stubClient } from "../test-utils"; import DMRoomMap from "../../src/utils/DMRoomMap"; @@ -90,10 +93,18 @@ describe("SpaceStore", () => { await emitProm; }; - beforeEach(() => { + beforeEach(async () => { jest.runAllTimers(); // run async dispatch client.getVisibleRooms.mockReturnValue(rooms = []); + + await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, { + [MetaSpace.Home]: true, + [MetaSpace.Favourites]: true, + [MetaSpace.People]: true, + [MetaSpace.Orphans]: true, + }); }); + afterEach(async () => { await testUtils.resetAsyncStoreWithClient(store); }); @@ -377,69 +388,84 @@ describe("SpaceStore", () => { }); it("home space contains orphaned rooms", () => { - expect(store.getSpaceFilteredRoomIds(null).has(orphan1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(null).has(orphan2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(orphan1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(orphan2)).toBeTruthy(); }); - it("home space contains favourites", () => { - expect(store.getSpaceFilteredRoomIds(null).has(fav1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(null).has(fav2)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(null).has(fav3)).toBeTruthy(); + it("home space does not contain all favourites", () => { + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(fav1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(fav2)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(fav3)).toBeFalsy(); }); it("home space contains dm rooms", () => { - expect(store.getSpaceFilteredRoomIds(null).has(dm1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(null).has(dm2)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(null).has(dm3)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(dm1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(dm2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(dm3)).toBeTruthy(); }); it("home space contains invites", () => { - expect(store.getSpaceFilteredRoomIds(null).has(invite1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(invite1)).toBeTruthy(); }); it("home space contains invites even if they are also shown in a space", () => { - expect(store.getSpaceFilteredRoomIds(null).has(invite2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(invite2)).toBeTruthy(); }); it("all rooms space does contain rooms/low priority even if they are also shown in a space", async () => { await setShowAllRooms(true); - expect(store.getSpaceFilteredRoomIds(null).has(room1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(room1)).toBeTruthy(); + }); + + it("favourites space does contain favourites even if they are also shown in a space", async () => { + expect(store.getSpaceFilteredRoomIds(MetaSpace.Favourites).has(fav1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Favourites).has(fav2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Favourites).has(fav3)).toBeTruthy(); + }); + + it("people space does contain people even if they are also shown in a space", async () => { + expect(store.getSpaceFilteredRoomIds(MetaSpace.People).has(dm1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.People).has(dm2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.People).has(dm3)).toBeTruthy(); + }); + + it("orphans space does contain orphans even if they are also shown in all rooms", async () => { + await setShowAllRooms(true); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Orphans).has(orphan1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Orphans).has(orphan2)).toBeTruthy(); }); it("home space doesn't contain rooms/low priority if they are also shown in a space", async () => { await setShowAllRooms(false); - expect(store.getSpaceFilteredRoomIds(null).has(room1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(room1)).toBeFalsy(); }); it("space contains child rooms", () => { - const space = client.getRoom(space1); - expect(store.getSpaceFilteredRoomIds(space).has(fav1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(space).has(room1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space1).has(fav1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space1).has(room1)).toBeTruthy(); }); it("space contains child favourites", () => { - const space = client.getRoom(space2); - expect(store.getSpaceFilteredRoomIds(space).has(fav1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(space).has(fav2)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(space).has(fav3)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(space).has(room1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space2).has(fav1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space2).has(fav2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space2).has(fav3)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space2).has(room1)).toBeTruthy(); }); it("space contains child invites", () => { - const space = client.getRoom(space3); - expect(store.getSpaceFilteredRoomIds(space).has(invite2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space3).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(); + expect(store.getSpaceFilteredRoomIds(space1).has(dm1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space2).has(dm1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space3).has(dm1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space1).has(dm2)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space2).has(dm2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space3).has(dm2)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space1).has(dm3)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space2).has(dm3)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space3).has(dm3)).toBeFalsy(); }); it("dms are only added to Notification States for only the Home Space", () => { @@ -491,11 +517,11 @@ describe("SpaceStore", () => { }); it("honours m.space.parent if sender has permission in parent space", () => { - expect(store.getSpaceFilteredRoomIds(client.getRoom(space2)).has(room2)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space2).has(room2)).toBeTruthy(); }); it("does not honour m.space.parent if sender does not have permission in parent space", () => { - expect(store.getSpaceFilteredRoomIds(client.getRoom(space3)).has(room3)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space3).has(room3)).toBeFalsy(); }); }); }); @@ -586,8 +612,8 @@ describe("SpaceStore", () => { expect(store.invitedSpaces).toStrictEqual([]); expect(store.getChildSpaces(space1)).toStrictEqual([]); expect(store.getChildRooms(space1)).toStrictEqual([]); - expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(invite1)).toBeFalsy(); - expect(store.getSpaceFilteredRoomIds(null).has(invite1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(space1).has(invite1)).toBeFalsy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(invite1)).toBeFalsy(); const invite = mkRoom(invite1); invite.getMyMembership.mockReturnValue("invite"); @@ -599,8 +625,8 @@ describe("SpaceStore", () => { expect(store.invitedSpaces).toStrictEqual([]); expect(store.getChildSpaces(space1)).toStrictEqual([]); expect(store.getChildRooms(space1)).toStrictEqual([invite]); - expect(store.getSpaceFilteredRoomIds(client.getRoom(space1)).has(invite1)).toBeTruthy(); - expect(store.getSpaceFilteredRoomIds(null).has(invite1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(space1).has(invite1)).toBeTruthy(); + expect(store.getSpaceFilteredRoomIds(MetaSpace.Home).has(invite1)).toBeTruthy(); }); }); @@ -614,49 +640,46 @@ describe("SpaceStore", () => { ]); mkSpace(space3).getMyMembership.mockReturnValue("invite"); await run(); - store.setActiveSpace(null); - expect(store.activeSpace).toBe(null); + store.setActiveSpace(MetaSpace.Home); + expect(store.activeSpace).toBe(MetaSpace.Home); }); afterEach(() => { fn.mockClear(); }); it("switch to home space", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); fn.mockClear(); - store.setActiveSpace(null); - expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, null); - expect(store.activeSpace).toBe(null); + store.setActiveSpace(MetaSpace.Home); + expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, MetaSpace.Home); + expect(store.activeSpace).toBe(MetaSpace.Home); }); it("switch to invited space", async () => { - const space = client.getRoom(space3); - store.setActiveSpace(space); - expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); - expect(store.activeSpace).toBe(space); + store.setActiveSpace(space3); + expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space3); + expect(store.activeSpace).toBe(space3); }); it("switch to top level space", async () => { - const space = client.getRoom(space1); - store.setActiveSpace(space); - expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); - expect(store.activeSpace).toBe(space); + store.setActiveSpace(space1); + expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space1); + expect(store.activeSpace).toBe(space1); }); it("switch to subspace", async () => { - const space = client.getRoom(space2); - store.setActiveSpace(space); - expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); - expect(store.activeSpace).toBe(space); + store.setActiveSpace(space2); + expect(fn).toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space2); + expect(store.activeSpace).toBe(space2); }); it("switch to unknown space is a nop", async () => { - expect(store.activeSpace).toBe(null); + expect(store.activeSpace).toBe(MetaSpace.Home); const space = client.getRoom(room1); // not a space - store.setActiveSpace(space); - expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space); - expect(store.activeSpace).toBe(null); + store.setActiveSpace(space.roomId); + expect(fn).not.toHaveBeenCalledWith(UPDATE_SELECTED_SPACE, space.roomId); + expect(store.activeSpace).toBe(MetaSpace.Home); }); }); @@ -678,6 +701,7 @@ describe("SpaceStore", () => { }); afterEach(() => { localStorage.clear(); + localStorage.setItem("mx_labs_feature_feature_spaces_metaspaces", "true"); defaultDispatcher.unregister(dispatcherRef); }); @@ -687,59 +711,59 @@ describe("SpaceStore", () => { }; it("last viewed room in target space is the current viewed and in both spaces", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room2); - store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(space2); viewRoom(room2); - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); expect(getCurrentRoom()).toBe(room2); }); it("last viewed room in target space is in the current space", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room2); - store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(space2); expect(getCurrentRoom()).toBe(space2); - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); expect(getCurrentRoom()).toBe(room2); }); it("last viewed room in target space is not in the current space", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room1); - store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(space2); viewRoom(room2); - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); expect(getCurrentRoom()).toBe(room1); }); it("last viewed room is target space is not known", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room1); localStorage.setItem(`mx_space_context_${space2}`, orphan2); - store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(space2); expect(getCurrentRoom()).toBe(space2); }); it("last viewed room is target space is no longer in that space", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room1); localStorage.setItem(`mx_space_context_${space2}`, room1); - store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(space2); expect(getCurrentRoom()).toBe(space2); // Space home instead of room1 }); it("no last viewed room in target space", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room1); - store.setActiveSpace(client.getRoom(space2)); + store.setActiveSpace(space2); expect(getCurrentRoom()).toBe(space2); }); it("no last viewed room in home space", async () => { - store.setActiveSpace(client.getRoom(space1)); + store.setActiveSpace(space1); viewRoom(room1); - store.setActiveSpace(null); + store.setActiveSpace(MetaSpace.Home); expect(getCurrentRoom()).toBeNull(); // Home }); }); @@ -767,38 +791,51 @@ describe("SpaceStore", () => { it("no switch required, room is in current space", async () => { viewRoom(room1); - store.setActiveSpace(client.getRoom(space1), false); + store.setActiveSpace(space1, false); viewRoom(room2); - expect(store.activeSpace).toBe(client.getRoom(space1)); + expect(store.activeSpace).toBe(space1); }); it("switch to canonical parent space for room", async () => { viewRoom(room1); - store.setActiveSpace(client.getRoom(space2), false); + store.setActiveSpace(space2, false); viewRoom(room2); - expect(store.activeSpace).toBe(client.getRoom(space2)); + expect(store.activeSpace).toBe(space2); }); it("switch to first containing space for room", async () => { viewRoom(room2); - store.setActiveSpace(client.getRoom(space2), false); + store.setActiveSpace(space2, false); viewRoom(room3); - expect(store.activeSpace).toBe(client.getRoom(space1)); + expect(store.activeSpace).toBe(space1); }); it("switch to home for orphaned room", async () => { viewRoom(room1); - store.setActiveSpace(client.getRoom(space1), false); + store.setActiveSpace(space1, false); viewRoom(orphan1); - expect(store.activeSpace).toBeNull(); + expect(store.activeSpace).toBe(MetaSpace.Home); + }); + + it("switch to first space when selected metaspace is disabled", async () => { + store.setActiveSpace(MetaSpace.People, false); + expect(store.activeSpace).toBe(MetaSpace.People); + await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, { + [MetaSpace.Home]: false, + [MetaSpace.Favourites]: true, + [MetaSpace.People]: false, + [MetaSpace.Orphans]: true, + }); + jest.runAllTimers(); + expect(store.activeSpace).toBe(MetaSpace.Favourites); }); it("when switching rooms in the all rooms home space don't switch to related space", async () => { await setShowAllRooms(true); viewRoom(room2); - store.setActiveSpace(null, false); + store.setActiveSpace(MetaSpace.Home, false); viewRoom(room1); - expect(store.activeSpace).toBeNull(); + expect(store.activeSpace).toBe(MetaSpace.Home); }); }); diff --git a/test/stores/enable-metaspaces-labs.ts b/test/stores/enable-metaspaces-labs.ts new file mode 100644 index 00000000000..f22132a0d66 --- /dev/null +++ b/test/stores/enable-metaspaces-labs.ts @@ -0,0 +1,17 @@ +/* +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. +*/ + +localStorage.setItem("mx_labs_feature_feature_spaces_metaspaces", "true"); diff --git a/test/stores/room-list/SpaceWatcher-test.ts b/test/stores/room-list/SpaceWatcher-test.ts index cb2394349a2..42ffbe53332 100644 --- a/test/stores/room-list/SpaceWatcher-test.ts +++ b/test/stores/room-list/SpaceWatcher-test.ts @@ -14,17 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ +import "../enable-metaspaces-labs"; import "../../skinned-sdk"; // Must be first for skinning to work import { SpaceWatcher } from "../../../src/stores/room-list/SpaceWatcher"; import type { RoomListStoreClass } from "../../../src/stores/room-list/RoomListStore"; import SettingsStore from "../../../src/settings/SettingsStore"; -import SpaceStore, { UPDATE_HOME_BEHAVIOUR } from "../../../src/stores/SpaceStore"; +import SpaceStore from "../../../src/stores/spaces/SpaceStore"; +import { MetaSpace, UPDATE_HOME_BEHAVIOUR } from "../../../src/stores/spaces"; import { stubClient } from "../../test-utils"; import { SettingLevel } from "../../../src/settings/SettingLevel"; +import * as testUtils from "../../utils/test-utils"; import { setupAsyncStoreWithClient } from "../../utils/test-utils"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; -import * as testUtils from "../../utils/test-utils"; import { SpaceFilterCondition } from "../../../src/stores/room-list/filters/SpaceFilterCondition"; +import DMRoomMap from "../../../src/utils/DMRoomMap"; let filter: SpaceFilterCondition = null; @@ -33,8 +36,13 @@ const mockRoomListStore = { removeFilter: () => filter = null, } as unknown as RoomListStoreClass; -const space1Id = "!space1:server"; -const space2Id = "!space2:server"; +const getUserIdForRoomId = jest.fn(); +const getDMRoomsForUserId = jest.fn(); +// @ts-ignore +DMRoomMap.sharedInstance = { getUserIdForRoomId, getDMRoomsForUserId }; + +const space1 = "!space1:server"; +const space2 = "!space2:server"; describe("SpaceWatcher", () => { stubClient(); @@ -50,17 +58,21 @@ describe("SpaceWatcher", () => { await testUtils.emitPromise(store, UPDATE_HOME_BEHAVIOUR); }; - let space1; - let space2; - beforeEach(async () => { filter = null; store.removeAllListeners(); - store.setActiveSpace(null); + store.setActiveSpace(MetaSpace.Home); client.getVisibleRooms.mockReturnValue(rooms = []); - space1 = mkSpace(space1Id); - space2 = mkSpace(space2Id); + mkSpace(space1); + mkSpace(space2); + + await SettingsStore.setValue("Spaces.enabledMetaSpaces", null, SettingLevel.DEVICE, { + [MetaSpace.Home]: true, + [MetaSpace.Favourites]: true, + [MetaSpace.People]: true, + [MetaSpace.Orphans]: true, + }); client.getRoom.mockImplementation(roomId => rooms.find(room => room.roomId === roomId)); await setupAsyncStoreWithClient(store, client); @@ -80,14 +92,14 @@ describe("SpaceWatcher", () => { expect(filter).toBeNull(); }); - it("sets space=null filter for all -> home transition", async () => { + it("sets space=Home filter for all -> home transition", async () => { await setShowAllRooms(true); new SpaceWatcher(mockRoomListStore); await setShowAllRooms(false); expect(filter).toBeInstanceOf(SpaceFilterCondition); - expect(filter["space"]).toBeNull(); + expect(filter["space"]).toBe(MetaSpace.Home); }); it("sets filter correctly for all -> space transition", async () => { @@ -126,7 +138,43 @@ describe("SpaceWatcher", () => { SpaceStore.instance.setActiveSpace(space1); expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter["space"]).toBe(space1); - SpaceStore.instance.setActiveSpace(null); + SpaceStore.instance.setActiveSpace(MetaSpace.Home); + + expect(filter).toBeNull(); + }); + + it("removes filter for favourites -> all transition", async () => { + await setShowAllRooms(true); + new SpaceWatcher(mockRoomListStore); + + SpaceStore.instance.setActiveSpace(MetaSpace.Favourites); + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(MetaSpace.Favourites); + SpaceStore.instance.setActiveSpace(MetaSpace.Home); + + expect(filter).toBeNull(); + }); + + it("removes filter for people -> all transition", async () => { + await setShowAllRooms(true); + new SpaceWatcher(mockRoomListStore); + + SpaceStore.instance.setActiveSpace(MetaSpace.People); + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(MetaSpace.People); + SpaceStore.instance.setActiveSpace(MetaSpace.Home); + + expect(filter).toBeNull(); + }); + + it("removes filter for orphans -> all transition", async () => { + await setShowAllRooms(true); + new SpaceWatcher(mockRoomListStore); + + SpaceStore.instance.setActiveSpace(MetaSpace.Orphans); + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(MetaSpace.Orphans); + SpaceStore.instance.setActiveSpace(MetaSpace.Home); expect(filter).toBeNull(); }); @@ -138,10 +186,36 @@ describe("SpaceWatcher", () => { new SpaceWatcher(mockRoomListStore); expect(filter).toBeInstanceOf(SpaceFilterCondition); expect(filter["space"]).toBe(space1); - SpaceStore.instance.setActiveSpace(null); + SpaceStore.instance.setActiveSpace(MetaSpace.Home); + + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(MetaSpace.Home); + }); + + it("updates filter correctly for space -> orphans transition", async () => { + await setShowAllRooms(false); + SpaceStore.instance.setActiveSpace(space1); + + new SpaceWatcher(mockRoomListStore); + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(space1); + SpaceStore.instance.setActiveSpace(MetaSpace.Orphans); + + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(MetaSpace.Orphans); + }); + + it("updates filter correctly for orphans -> people transition", async () => { + await setShowAllRooms(false); + SpaceStore.instance.setActiveSpace(MetaSpace.Orphans); + + new SpaceWatcher(mockRoomListStore); + expect(filter).toBeInstanceOf(SpaceFilterCondition); + expect(filter["space"]).toBe(MetaSpace.Orphans); + SpaceStore.instance.setActiveSpace(MetaSpace.People); expect(filter).toBeInstanceOf(SpaceFilterCondition); - expect(filter["space"]).toBe(null); + expect(filter["space"]).toBe(MetaSpace.People); }); it("updates filter correctly for space -> space transition", async () => { From 9abb2f5ff455d98dd88689737867204e608895c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 11 Nov 2021 14:37:29 +0100 Subject: [PATCH 007/118] Color cleanup 8 (#7108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * $accent-color -> $accent Signed-off-by: Šimon Brandner * Remove $accent-bg-color Signed-off-by: Šimon Brandner * $notice-primary-color -> $alert Signed-off-by: Šimon Brandner * Remove $notice-primary-bg-color Signed-off-by: Šimon Brandner * Remove $accent-50pct Signed-off-by: Šimon Brandner * $warning-color -> $alert Signed-off-by: Šimon Brandner * Remove $accent-darker Signed-off-by: Šimon Brandner * Remove $orange-warning-color Signed-off-by: Šimon Brandner * Remove $warning-bg-color Signed-off-by: Šimon Brandner * Remove $info-bg-color Signed-off-by: Šimon Brandner --- res/css/_common.scss | 26 ++++++++--------- res/css/structures/_CustomRoomTagPanel.scss | 2 +- res/css/structures/_GroupFilterPanel.scss | 4 +-- res/css/structures/_GroupView.scss | 6 ++-- res/css/structures/_HomePage.scss | 2 +- res/css/structures/_MatrixChat.scss | 2 +- res/css/structures/_RightPanel.scss | 10 +++---- res/css/structures/_RoomStatusBar.scss | 6 ++-- res/css/structures/_RoomView.scss | 8 +++--- res/css/structures/_SpaceHierarchy.scss | 4 +-- res/css/structures/_SpacePanel.scss | 4 +-- res/css/structures/_SpaceRoomView.scss | 10 +++---- res/css/structures/_TabbedView.scss | 8 +++--- res/css/structures/_ToastContainer.scss | 2 +- .../structures/auth/_CompleteSecurity.scss | 2 +- res/css/structures/auth/_Login.scss | 6 ++-- .../structures/auth/_SetupEncryptionBody.scss | 2 +- res/css/views/auth/_AuthBody.scss | 2 +- res/css/views/auth/_AuthButtons.scss | 2 +- .../auth/_InteractiveAuthEntryComponents.scss | 4 +-- res/css/views/auth/_PassphraseField.scss | 4 +-- .../views/avatars/_DecoratedRoomAvatar.scss | 2 +- .../avatars/_MemberStatusMessageAvatar.scss | 2 +- res/css/views/beta/_BetaCard.scss | 4 +-- .../context_menus/_IconizedContextMenu.scss | 8 +++--- .../_StatusMessageContextMenu.scss | 4 +-- .../dialogs/_AddExistingToSpaceDialog.scss | 6 ++-- .../views/dialogs/_AddressPickerDialog.scss | 2 +- .../_CreateCommunityPrototypeDialog.scss | 2 +- res/css/views/dialogs/_CreateRoomDialog.scss | 2 +- .../_CreateSpaceFromCommunityDialog.scss | 6 ++-- res/css/views/dialogs/_DevtoolsDialog.scss | 10 +++---- res/css/views/dialogs/_FeedbackDialog.scss | 4 +-- res/css/views/dialogs/_ForwardDialog.scss | 4 +-- res/css/views/dialogs/_InviteDialog.scss | 4 +-- res/css/views/dialogs/_LeaveSpaceDialog.scss | 4 +-- .../dialogs/_MessageEditHistoryDialog.scss | 2 +- .../views/dialogs/_ServerPickerDialog.scss | 2 +- res/css/views/dialogs/_SetEmailDialog.scss | 2 +- .../views/dialogs/_SpaceSettingsDialog.scss | 2 +- .../_TabbedIntegrationManagerDialog.scss | 6 ++-- res/css/views/dialogs/_TermsDialog.scss | 2 +- .../security/_AccessSecretStorageDialog.scss | 12 ++++---- .../security/_CreateKeyBackupDialog.scss | 4 +-- .../security/_CreateSecretStorageDialog.scss | 2 +- .../security/_RestoreKeyBackupDialog.scss | 4 +-- res/css/views/directory/_NetworkDropdown.scss | 4 +-- res/css/views/elements/_AccessibleButton.scss | 28 +++++++++---------- res/css/views/elements/_AddressSelector.scss | 2 +- res/css/views/elements/_AddressTile.scss | 4 +-- .../_DesktopCapturerSourcePicker.scss | 2 +- .../views/elements/_DirectorySearchBox.scss | 2 +- res/css/views/elements/_EditableItemList.scss | 2 +- res/css/views/elements/_EventListSummary.scss | 2 +- res/css/views/elements/_Field.scss | 8 +++--- .../views/elements/_ManageIntegsButton.scss | 2 +- res/css/views/elements/_ProgressBar.scss | 2 +- res/css/views/elements/_ReplyChain.scss | 2 +- res/css/views/elements/_RichText.scss | 4 +-- res/css/views/elements/_SSOButtons.scss | 6 ++-- res/css/views/elements/_Slider.scss | 6 ++-- res/css/views/elements/_StyledCheckbox.scss | 8 +++--- .../views/elements/_StyledRadioButton.scss | 4 +-- res/css/views/elements/_ToggleSwitch.scss | 2 +- res/css/views/elements/_Validation.scss | 8 +++--- res/css/views/emojipicker/_EmojiPicker.scss | 6 ++-- res/css/views/messages/_MFileBody.scss | 6 ++-- res/css/views/messages/_MImageBody.scss | 4 +-- .../views/messages/_ReactionsRowButton.scss | 2 +- res/css/views/messages/_TextualEvent.scss | 2 +- res/css/views/messages/_ViewSourceEvent.scss | 2 +- .../views/messages/_common_CryptoEvent.scss | 4 +-- .../views/right_panel/_RoomSummaryCard.scss | 2 +- res/css/views/right_panel/_UserInfo.scss | 8 +++--- res/css/views/right_panel/_WidgetCard.scss | 2 +- .../views/room_settings/_AliasSettings.scss | 4 +-- res/css/views/rooms/_AppsDrawer.scss | 2 +- res/css/views/rooms/_E2EIcon.scss | 4 +-- res/css/views/rooms/_EditMessageComposer.scss | 2 +- res/css/views/rooms/_EventBubbleTile.scss | 2 +- res/css/views/rooms/_EventTile.scss | 12 ++++---- res/css/views/rooms/_JumpToBottomButton.scss | 2 +- res/css/views/rooms/_LinkPreviewGroup.scss | 2 +- res/css/views/rooms/_MemberList.scss | 2 +- res/css/views/rooms/_MessageComposer.scss | 16 +++++------ res/css/views/rooms/_NotificationBadge.scss | 2 +- res/css/views/rooms/_RoomHeader.scss | 12 ++++---- .../views/rooms/_RoomUpgradeWarningBar.scss | 6 ++-- res/css/views/rooms/_SearchBar.scss | 6 ++-- res/css/views/rooms/_Stickers.scss | 2 +- .../views/rooms/_TopUnreadMessagesBar.scss | 2 +- .../views/settings/_IntegrationManager.scss | 2 +- res/css/views/settings/_JoinRuleSettings.scss | 4 +-- res/css/views/settings/_LayoutSwitcher.scss | 4 +-- res/css/views/settings/tabs/_SettingsTab.scss | 8 +++--- .../tabs/user/_AppearanceUserSettingsTab.scss | 2 +- .../tabs/user/_SecurityUserSettingsTab.scss | 4 +-- res/css/views/spaces/_SpaceBasicSettings.scss | 2 +- res/css/views/spaces/_SpaceCreateMenu.scss | 2 +- .../views/terms/_InlineTermsAgreement.scss | 4 +-- res/css/views/toasts/_AnalyticsToast.scss | 4 +-- res/css/views/voip/_DialPad.scss | 2 +- res/css/views/voip/_DialPadContextMenu.scss | 2 +- res/css/views/voip/_DialPadModal.scss | 2 +- res/css/views/voip/_VideoFeed.scss | 2 +- res/themes/dark/css/_dark.scss | 10 +++---- res/themes/legacy-dark/css/_legacy-dark.scss | 10 +++---- .../legacy-light/css/_legacy-light.scss | 26 +++++------------ res/themes/light-custom/css/_custom.scss | 8 +----- .../css/_light-high-contrast.scss | 19 +++++-------- res/themes/light/css/_light.scss | 28 ++++++------------- 111 files changed, 271 insertions(+), 306 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index 62d02388bbb..d6d139d2bd4 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -74,11 +74,11 @@ pre, code { .error, .warning, .text-error, .text-warning { - color: $warning-color; + color: $alert; } .text-success { - color: $accent-color; + color: $accent; } .text-muted { @@ -102,7 +102,7 @@ h2 { a:hover, a:link, a:visited { - color: $accent-color-alt; + color: $accent-alt; } input[type=text], @@ -238,7 +238,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { // // They are not used for layout!! #mx_theme_accentColor { - color: $accent-color; + color: $accent; } #mx_theme_secondaryAccentColor { @@ -364,7 +364,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { } .mx_Dialog_title.danger { - color: $warning-color; + color: $alert; } .mx_Dialog_cancelButton { @@ -414,8 +414,8 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color; - color: $accent-color; + border: 1px solid $accent; + color: $accent; background-color: $button-secondary-bg-color; font-family: inherit; } @@ -443,7 +443,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { .mx_Dialog_buttons button.mx_Dialog_primary, .mx_Dialog_buttons input[type="submit"].mx_Dialog_primary { color: $accent-fg-color; - background-color: $accent-color; + background-color: $accent; min-width: 156px; } @@ -451,15 +451,15 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { .mx_Dialog input[type="submit"].danger, .mx_Dialog_buttons button.danger, .mx_Dialog_buttons input[type="submit"].danger { - background-color: $warning-color; - border: solid 1px $warning-color; + background-color: $alert; + border: solid 1px $alert; color: $accent-fg-color; } .mx_Dialog button.warning, .mx_Dialog input[type="submit"].warning { - border: solid 1px $warning-color; - color: $warning-color; + border: solid 1px $alert; + color: $alert; } .mx_Dialog button:not(.mx_Dialog_nonDialogButton):disabled, @@ -499,7 +499,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { .mx_linkButton { cursor: pointer; - color: $accent-color; + color: $accent; } .mx_TextInputDialog_label { diff --git a/res/css/structures/_CustomRoomTagPanel.scss b/res/css/structures/_CustomRoomTagPanel.scss index be1138cf5b0..627644f1020 100644 --- a/res/css/structures/_CustomRoomTagPanel.scss +++ b/res/css/structures/_CustomRoomTagPanel.scss @@ -44,7 +44,7 @@ limitations under the License. .mx_CustomRoomTagPanel .mx_AccessibleButton.CustomRoomTagPanel_tileSelected::before { content: ''; height: 56px; - background-color: $accent-color-alt; + background-color: $accent-alt; width: 5px; position: absolute; left: -9px; diff --git a/res/css/structures/_GroupFilterPanel.scss b/res/css/structures/_GroupFilterPanel.scss index a101aba7e5a..bdbd9f76435 100644 --- a/res/css/structures/_GroupFilterPanel.scss +++ b/res/css/structures/_GroupFilterPanel.scss @@ -152,7 +152,7 @@ $groupFilterPanelWidth: 56px; // only applies in this file, used for calculation .mx_GroupFilterPanel .mx_TagTile.mx_TagTile_selected::before { content: ''; height: 100%; - background-color: $accent-color; + background-color: $accent; width: 4px; position: absolute; left: -12px; @@ -203,5 +203,5 @@ $groupFilterPanelWidth: 56px; // only applies in this file, used for calculation } .mx_TagTile_badgeHighlight { - background-color: $warning-color; + background-color: $alert; } diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss index 5e224b1f389..313a4b32cf8 100644 --- a/res/css/structures/_GroupView.scss +++ b/res/css/structures/_GroupView.scss @@ -81,13 +81,13 @@ limitations under the License. } .mx_GroupView_editable:focus { - border-bottom: 1px solid $accent-color !important; + border-bottom: 1px solid $accent !important; outline: none; box-shadow: none; } .mx_GroupView_header_isUserMember .mx_GroupView_header_name:hover div:not(.mx_GroupView_editable) { - color: $accent-color; + color: $accent; cursor: pointer; } @@ -229,7 +229,7 @@ limitations under the License. vertical-align: top; line-height: $font-24px; padding-left: 28px; - color: $accent-color; + color: $accent; } .mx_GroupView_rooms .mx_RoomDetailList { diff --git a/res/css/structures/_HomePage.scss b/res/css/structures/_HomePage.scss index b530d994c61..77da167099c 100644 --- a/res/css/structures/_HomePage.scss +++ b/res/css/structures/_HomePage.scss @@ -77,7 +77,7 @@ limitations under the License. font-size: $font-15px; line-height: $font-20px; color: #fff; // on all themes - background-color: $accent-color; + background-color: $accent; &::before { top: 20px; diff --git a/res/css/structures/_MatrixChat.scss b/res/css/structures/_MatrixChat.scss index fdf5cb1a038..80053154ab2 100644 --- a/res/css/structures/_MatrixChat.scss +++ b/res/css/structures/_MatrixChat.scss @@ -52,7 +52,7 @@ limitations under the License. .mx_MatrixChat_syncError { color: $accent-fg-color; - background-color: $warning-bg-color; + background-color: #DF2A8B; // Only used here border-radius: 5px; display: table; padding: 30px; diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index 91194024902..38a1fe9099a 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -71,10 +71,10 @@ limitations under the License. } &:hover { - background: rgba($accent-color, 0.1); + background: rgba($accent, 0.1); &::before { - background-color: $accent-color; + background-color: $accent; } } } @@ -104,7 +104,7 @@ limitations under the License. } $dot-size: 8px; -$pulse-color: $notice-primary-color; +$pulse-color: $alert; .mx_RightPanel_pinnedMessagesButton { &::before { @@ -175,7 +175,7 @@ $pulse-color: $notice-primary-color; .mx_RightPanel_headerButton_highlight { &::before { - background-color: $accent-color !important; + background-color: $accent !important; } } @@ -183,7 +183,7 @@ $pulse-color: $notice-primary-color; font-size: $font-8px; border-radius: 8px; color: $accent-fg-color; - background-color: $accent-color; + background-color: $accent; font-weight: bold; position: absolute; top: -4px; diff --git a/res/css/structures/_RoomStatusBar.scss b/res/css/structures/_RoomStatusBar.scss index bdfbca1afa2..a54ceae49e9 100644 --- a/res/css/structures/_RoomStatusBar.scss +++ b/res/css/structures/_RoomStatusBar.scss @@ -57,7 +57,7 @@ limitations under the License. .mx_RoomStatusBar_unreadMessagesBar { padding-top: 10px; - color: $warning-color; + color: $alert; cursor: pointer; } @@ -96,7 +96,7 @@ limitations under the License. } .mx_RoomStatusBar_unsentTitle { - color: $warning-color; + color: $alert; font-size: $font-15px; } @@ -167,7 +167,7 @@ limitations under the License. } .mx_RoomStatusBar_connectionLostBar_title { - color: $warning-color; + color: $alert; } .mx_RoomStatusBar_connectionLostBar_desc { diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 50fa304bd60..a61b330e24e 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -103,7 +103,7 @@ limitations under the License. .mx_RoomView_auxPanel_hiddenHighlights { border-bottom: 1px solid $primary-hairline-color; padding: 10px 26px; - color: $warning-color; + color: $alert; cursor: pointer; } @@ -241,8 +241,8 @@ li.mx_RoomView_myReadMarker_container { } hr.mx_RoomView_myReadMarker { - border-top: solid 1px $accent-color; - border-bottom: solid 1px $accent-color; + border-top: solid 1px $accent; + border-bottom: solid 1px $accent; margin-top: 0px; position: relative; top: -1px; @@ -299,7 +299,7 @@ hr.mx_RoomView_myReadMarker { .mx_RoomView_ongoingConfCallNotification { width: 100%; text-align: center; - background-color: $warning-color; + background-color: $alert; color: $accent-fg-color; font-weight: bold; padding: 6px 0; diff --git a/res/css/structures/_SpaceHierarchy.scss b/res/css/structures/_SpaceHierarchy.scss index 5735ef016d3..8ad167528b0 100644 --- a/res/css/structures/_SpaceHierarchy.scss +++ b/res/css/structures/_SpaceHierarchy.scss @@ -99,7 +99,7 @@ limitations under the License. .mx_SpaceHierarchy_error { position: relative; font-weight: $font-semi-bold; - color: $notice-primary-color; + color: $alert; font-size: $font-15px; line-height: $font-18px; margin: 20px auto 12px; @@ -238,7 +238,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background-color: $accent-color; + background-color: $accent; mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); } } diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index d822b9baf23..0785d4955f6 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -324,10 +324,10 @@ $activeBorderColor: $secondary-content; } .mx_IconizedContextMenu_optionList .mx_AccessibleButton.mx_SpacePanel_contextMenu_inviteButton { - color: $accent-color; + color: $accent; .mx_SpacePanel_iconInvite::before { - background-color: $accent-color; + background-color: $accent; mask-image: url('$(res)/img/element-icons/room/invite.svg'); } } diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index 51b5244c5fd..0f3470b0b45 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -49,10 +49,10 @@ $SpaceRoomViewInnerWidth: 428px; } &:hover { - border-color: $accent-color; + border-color: $accent; &::before { - background-color: $accent-color; + background-color: $accent; } > span { @@ -128,7 +128,7 @@ $SpaceRoomViewInnerWidth: 428px; font-weight: $font-semi-bold; font-size: $font-12px; line-height: $font-15px; - color: $notice-primary-color; + color: $alert; margin-bottom: 28px; } @@ -427,11 +427,11 @@ $SpaceRoomViewInnerWidth: 428px; } .mx_SpaceRoomView_inviteTeammates_inviteDialogButton { - color: $accent-color; + color: $accent; &::before { mask-image: url('$(res)/img/element-icons/room/invite.svg'); - background-color: $accent-color; + background-color: $accent; } } } diff --git a/res/css/structures/_TabbedView.scss b/res/css/structures/_TabbedView.scss index 0749783b7be..c7d3acfdbf7 100644 --- a/res/css/structures/_TabbedView.scss +++ b/res/css/structures/_TabbedView.scss @@ -44,7 +44,7 @@ limitations under the License. } .mx_TabbedView_tabLabel_active { - background-color: $accent-color; + background-color: $accent; color: $tab-label-active-fg-color; } @@ -89,14 +89,14 @@ limitations under the License. } .mx_TabbedView_tabLabel_active { - color: $accent-color; + color: $accent; .mx_TabbedView_tabLabel_text { - color: $accent-color; + color: $accent; } } .mx_TabbedView_tabLabel_active .mx_TabbedView_maskedIcon::before { - background-color: $accent-color; + background-color: $accent; } .mx_TabbedView_maskedIcon { diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index 55181a8b53d..d2b3babc3ab 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -76,7 +76,7 @@ limitations under the License. &::after { mask-image: url("$(res)/img/e2e/warning.svg"); - background-color: $notice-primary-color; + background-color: $alert; } } diff --git a/res/css/structures/auth/_CompleteSecurity.scss b/res/css/structures/auth/_CompleteSecurity.scss index 2c45069cfe1..bf5aeb15f56 100644 --- a/res/css/structures/auth/_CompleteSecurity.scss +++ b/res/css/structures/auth/_CompleteSecurity.scss @@ -63,7 +63,7 @@ limitations under the License. margin-inline-start: 18px; &.warning { - color: $warning-color; + color: $alert; } } } diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index c4aaaca1d05..2290b6a3455 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -50,7 +50,7 @@ limitations under the License. } .mx_Login_error { - color: $warning-color; + color: $alert; font-weight: bold; text-align: center; margin-top: 12px; @@ -63,7 +63,7 @@ limitations under the License. } .mx_Login_error.mx_Login_serverError.mx_Login_serverErrorNonFatal { - color: $orange-warning-color; + color: #ff8d13; // Only used here } .mx_Login_type_container { @@ -82,7 +82,7 @@ limitations under the License. .mx_Login_underlinedServerName { width: max-content; - border-bottom: 1px dashed $accent-color; + border-bottom: 1px dashed $accent; } div.mx_AccessibleButton_kind_link.mx_Login_forgot { diff --git a/res/css/structures/auth/_SetupEncryptionBody.scss b/res/css/structures/auth/_SetupEncryptionBody.scss index 24ee1145441..f40e31280b0 100644 --- a/res/css/structures/auth/_SetupEncryptionBody.scss +++ b/res/css/structures/auth/_SetupEncryptionBody.scss @@ -19,6 +19,6 @@ limitations under the License. margin-top: $font-14px; a.mx_SetupEncryptionBody_reset_link:is(:link, :hover, :visited) { - color: $warning-color; + color: $alert; } } diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index d27137eb252..8c6b6bdff00 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -68,7 +68,7 @@ limitations under the License. } input.error { - color: $warning-color; + color: $alert; } .mx_Field input { diff --git a/res/css/views/auth/_AuthButtons.scss b/res/css/views/auth/_AuthButtons.scss index 3a2ad2adf8a..a4a3cac37fc 100644 --- a/res/css/views/auth/_AuthButtons.scss +++ b/res/css/views/auth/_AuthButtons.scss @@ -38,7 +38,7 @@ limitations under the License. margin-right: 4px; min-width: 80px; - background-color: $accent-color; + background-color: $accent; color: $background; cursor: pointer; diff --git a/res/css/views/auth/_InteractiveAuthEntryComponents.scss b/res/css/views/auth/_InteractiveAuthEntryComponents.scss index ec07b765fd7..a37683935a7 100644 --- a/res/css/views/auth/_InteractiveAuthEntryComponents.scss +++ b/res/css/views/auth/_InteractiveAuthEntryComponents.scss @@ -56,7 +56,7 @@ limitations under the License. } .mx_InteractiveAuthEntryComponents_msisdnEntry:focus { - border: 1px solid $accent-color; + border: 1px solid $accent; } .mx_InteractiveAuthEntryComponents_msisdnSubmit { @@ -78,7 +78,7 @@ limitations under the License. } .mx_InteractiveAuthEntryComponents_termsSubmit:disabled { - background-color: $accent-color-darker; + background-color: #92caad; // Only used here cursor: default; } diff --git a/res/css/views/auth/_PassphraseField.scss b/res/css/views/auth/_PassphraseField.scss index bf8e7f44389..a197ebbe038 100644 --- a/res/css/views/auth/_PassphraseField.scss +++ b/res/css/views/auth/_PassphraseField.scss @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -$PassphraseStrengthHigh: $accent-color; +$PassphraseStrengthHigh: $accent; $PassphraseStrengthMedium: $username-variant5-color; -$PassphraseStrengthLow: $notice-primary-color; +$PassphraseStrengthLow: $alert; progress.mx_PassphraseField_progress { appearance: none; diff --git a/res/css/views/avatars/_DecoratedRoomAvatar.scss b/res/css/views/avatars/_DecoratedRoomAvatar.scss index ec6d99c04d1..60cd18520a0 100644 --- a/res/css/views/avatars/_DecoratedRoomAvatar.scss +++ b/res/css/views/avatars/_DecoratedRoomAvatar.scss @@ -56,7 +56,7 @@ limitations under the License. } .mx_DecoratedRoomAvatar_icon_online::before { - background-color: $accent-color; + background-color: $accent; } .mx_DecoratedRoomAvatar_icon_away::before { diff --git a/res/css/views/avatars/_MemberStatusMessageAvatar.scss b/res/css/views/avatars/_MemberStatusMessageAvatar.scss index 975b4e5ce96..7f2f53668f7 100644 --- a/res/css/views/avatars/_MemberStatusMessageAvatar.scss +++ b/res/css/views/avatars/_MemberStatusMessageAvatar.scss @@ -25,5 +25,5 @@ limitations under the License. } .mx_MemberStatusMessageAvatar_hasStatus .mx_BaseAvatar { - border-color: $accent-color; + border-color: $accent; } diff --git a/res/css/views/beta/_BetaCard.scss b/res/css/views/beta/_BetaCard.scss index ae7922e8027..268d7304ad6 100644 --- a/res/css/views/beta/_BetaCard.scss +++ b/res/css/views/beta/_BetaCard.scss @@ -85,7 +85,7 @@ limitations under the License. } .mx_BetaCard_betaPill { - background-color: $accent-color-alt; + background-color: $accent-alt; padding: 4px 10px; border-radius: 8px; text-transform: uppercase; @@ -100,7 +100,7 @@ limitations under the License. } } -$pulse-color: $accent-color-alt; +$pulse-color: $accent-alt; $dot-size: 12px; .mx_BetaDot { diff --git a/res/css/views/context_menus/_IconizedContextMenu.scss b/res/css/views/context_menus/_IconizedContextMenu.scss index ca40f18cd4c..e6b56c50370 100644 --- a/res/css/views/context_menus/_IconizedContextMenu.scss +++ b/res/css/views/context_menus/_IconizedContextMenu.scss @@ -125,21 +125,21 @@ limitations under the License. .mx_IconizedContextMenu_optionList_red { .mx_AccessibleButton { - color: $warning-color !important; + color: $alert !important; } .mx_IconizedContextMenu_icon::before { - background-color: $warning-color; + background-color: $alert; } } .mx_IconizedContextMenu_active { &.mx_AccessibleButton, .mx_AccessibleButton { - color: $accent-color !important; + color: $accent !important; } .mx_IconizedContextMenu_icon::before { - background-color: $accent-color; + background-color: $accent; } } diff --git a/res/css/views/context_menus/_StatusMessageContextMenu.scss b/res/css/views/context_menus/_StatusMessageContextMenu.scss index 1a97fb56c75..74a08a4f5bf 100644 --- a/res/css/views/context_menus/_StatusMessageContextMenu.scss +++ b/res/css/views/context_menus/_StatusMessageContextMenu.scss @@ -55,9 +55,9 @@ input.mx_StatusMessageContextMenu_message { } .mx_StatusMessageContextMenu_clear { - color: $warning-color; + color: $alert; background-color: transparent; - border: 1px solid $warning-color; + border: 1px solid $alert; } .mx_StatusMessageContextMenu_actionContainer .mx_Spinner { diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss index 8b19f506f54..5d53a996610 100644 --- a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss +++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss @@ -98,7 +98,7 @@ limitations under the License. font-weight: $font-semi-bold; font-size: $font-15px; line-height: $font-18px; - color: $notice-primary-color; + color: $alert; } .mx_AddExistingToSpace_errorCaption { @@ -199,7 +199,7 @@ limitations under the License. .mx_Dropdown_menu { .mx_SubspaceSelector_dropdownOptionActive { - color: $accent-color; + color: $accent; padding-right: 32px; position: relative; @@ -213,7 +213,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background-color: $accent-color; + background-color: $accent; mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); } } diff --git a/res/css/views/dialogs/_AddressPickerDialog.scss b/res/css/views/dialogs/_AddressPickerDialog.scss index a1147e6fbca..44e9f94c5f7 100644 --- a/res/css/views/dialogs/_AddressPickerDialog.scss +++ b/res/css/views/dialogs/_AddressPickerDialog.scss @@ -61,7 +61,7 @@ limitations under the License. .mx_AddressPickerDialog_error { margin-top: 10px; - color: $warning-color; + color: $alert; } .mx_AddressPickerDialog_cancel { diff --git a/res/css/views/dialogs/_CreateCommunityPrototypeDialog.scss b/res/css/views/dialogs/_CreateCommunityPrototypeDialog.scss index 81babc4c38d..a2378115a85 100644 --- a/res/css/views/dialogs/_CreateCommunityPrototypeDialog.scss +++ b/res/css/views/dialogs/_CreateCommunityPrototypeDialog.scss @@ -39,7 +39,7 @@ limitations under the License. } &.mx_CreateCommunityPrototypeDialog_subtext_error { - color: $warning-color; + color: $alert; } } diff --git a/res/css/views/dialogs/_CreateRoomDialog.scss b/res/css/views/dialogs/_CreateRoomDialog.scss index 9cfa8ce25a8..064b64c24ab 100644 --- a/res/css/views/dialogs/_CreateRoomDialog.scss +++ b/res/css/views/dialogs/_CreateRoomDialog.scss @@ -22,7 +22,7 @@ limitations under the License. list-style: none; font-weight: 600; cursor: pointer; - color: $accent-color; + color: $accent; // list-style doesn't do it for webkit &::-webkit-details-marker { diff --git a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss index f1af24cc5fb..72c2d13ae38 100644 --- a/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss +++ b/res/css/views/dialogs/_CreateSpaceFromCommunityDialog.scss @@ -106,7 +106,7 @@ limitations under the License. font-weight: $font-semi-bold; font-size: $font-15px; line-height: $font-18px; - color: $notice-primary-color; + color: $alert; } .mx_CreateSpaceFromCommunityDialog_errorCaption { @@ -168,7 +168,7 @@ limitations under the License. .mx_CreateSpaceFromCommunityDialog_SuccessInfoDialog_checkmark { position: relative; border-radius: 50%; - border: 3px solid $accent-color; + border: 3px solid $accent; width: 68px; height: 68px; margin: 12px auto 32px; @@ -178,7 +178,7 @@ limitations under the License. height: inherit; content: ''; position: absolute; - background-color: $accent-color; + background-color: $accent; mask-repeat: no-repeat; mask-position: center; mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss index 4d35e8d569c..738cc9ea22d 100644 --- a/res/css/views/dialogs/_DevtoolsDialog.scss +++ b/res/css/views/dialogs/_DevtoolsDialog.scss @@ -215,7 +215,7 @@ limitations under the License. th { // Colour choice: first one autocomplete gave me. - border-bottom: 1px solid $accent-color; + border-bottom: 1px solid $accent; text-align: left; } @@ -233,16 +233,16 @@ limitations under the License. tr:hover { // Colour choice: first one autocomplete gave me. - background-color: $accent-color-50pct; + background-color: $accent; } } .mx_DevTools_SettingsExplorer_mutable { - background-color: $accent-color; + background-color: $accent; } .mx_DevTools_SettingsExplorer_immutable { - background-color: $warning-color; + background-color: $alert; } .mx_DevTools_SettingsExplorer_edit { @@ -251,7 +251,7 @@ limitations under the License. } .mx_DevTools_SettingsExplorer_warning { - border: 2px solid $warning-color; + border: 2px solid $alert; border-radius: 4px; padding: 4px; margin-bottom: 8px; diff --git a/res/css/views/dialogs/_FeedbackDialog.scss b/res/css/views/dialogs/_FeedbackDialog.scss index 22559d91976..8674f2bab64 100644 --- a/res/css/views/dialogs/_FeedbackDialog.scss +++ b/res/css/views/dialogs/_FeedbackDialog.scss @@ -42,7 +42,7 @@ limitations under the License. } a, .mx_AccessibleButton_kind_link { - color: $accent-color; + color: $accent; text-decoration: underline; } @@ -111,7 +111,7 @@ limitations under the License. .mx_StyledRadioButton_checked { font-size: 24px; - border-color: $accent-color; + border-color: $accent; } &::after { diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index b90649fa247..97cd8b87879 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -138,7 +138,7 @@ limitations under the License. } &.mx_ForwardList_sending .mx_ForwardList_sendIcon { - background-color: $accent-color; + background-color: $accent; mask-image: url('$(res)/img/element-icons/circle-sending.svg'); mask-position: center; mask-repeat: no-repeat; @@ -148,7 +148,7 @@ limitations under the License. } &.mx_ForwardList_sent .mx_ForwardList_sendIcon { - background-color: $accent-color; + background-color: $accent; mask-image: url('$(res)/img/element-icons/circle-sent.svg'); mask-position: center; mask-repeat: no-repeat; diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss index a7531156141..cca17899048 100644 --- a/res/css/views/dialogs/_InviteDialog.scss +++ b/res/css/views/dialogs/_InviteDialog.scss @@ -362,7 +362,7 @@ limitations under the License. } .mx_InviteDialog_dialPad .mx_InviteDialog_dialPadField:focus-within { - border-color: $accent-color; + border-color: $accent; } .mx_InviteDialog_dialPadField .mx_Field_postfix { @@ -447,7 +447,7 @@ limitations under the License. margin-left: 32px; font-size: $font-15px; line-height: $font-24px; - color: $notice-primary-color; + color: $alert; } } } diff --git a/res/css/views/dialogs/_LeaveSpaceDialog.scss b/res/css/views/dialogs/_LeaveSpaceDialog.scss index c0f24871c64..baae73a90b9 100644 --- a/res/css/views/dialogs/_LeaveSpaceDialog.scss +++ b/res/css/views/dialogs/_LeaveSpaceDialog.scss @@ -69,8 +69,8 @@ limitations under the License. margin-top: 20px; .mx_Dialog_primary { - background-color: $notice-primary-color !important; // override default colour - border-color: $notice-primary-color; + background-color: $alert !important; // override default colour + border-color: $alert; } } } diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.scss b/res/css/views/dialogs/_MessageEditHistoryDialog.scss index f60bbc9589c..5838939d9bf 100644 --- a/res/css/views/dialogs/_MessageEditHistoryDialog.scss +++ b/res/css/views/dialogs/_MessageEditHistoryDialog.scss @@ -29,7 +29,7 @@ limitations under the License. } .mx_MessageEditHistoryDialog_error { - color: $warning-color; + color: $alert; text-align: center; } diff --git a/res/css/views/dialogs/_ServerPickerDialog.scss b/res/css/views/dialogs/_ServerPickerDialog.scss index 9a05751f918..4dde7cf8007 100644 --- a/res/css/views/dialogs/_ServerPickerDialog.scss +++ b/res/css/views/dialogs/_ServerPickerDialog.scss @@ -43,7 +43,7 @@ limitations under the License. } > a { - color: $accent-color; + color: $accent; margin-left: 8px; } } diff --git a/res/css/views/dialogs/_SetEmailDialog.scss b/res/css/views/dialogs/_SetEmailDialog.scss index a39d51dfce9..a07f83acdc2 100644 --- a/res/css/views/dialogs/_SetEmailDialog.scss +++ b/res/css/views/dialogs/_SetEmailDialog.scss @@ -29,5 +29,5 @@ limitations under the License. .mx_SetEmailDialog_email_input:focus { outline: none; box-shadow: none; - border: 1px solid $accent-color; + border: 1px solid $accent; } diff --git a/res/css/views/dialogs/_SpaceSettingsDialog.scss b/res/css/views/dialogs/_SpaceSettingsDialog.scss index 05cd55a570e..78fbd573bd2 100644 --- a/res/css/views/dialogs/_SpaceSettingsDialog.scss +++ b/res/css/views/dialogs/_SpaceSettingsDialog.scss @@ -21,7 +21,7 @@ limitations under the License. font-weight: $font-semi-bold; font-size: $font-12px; line-height: $font-15px; - color: $notice-primary-color; + color: $alert; margin-bottom: 28px; } diff --git a/res/css/views/dialogs/_TabbedIntegrationManagerDialog.scss b/res/css/views/dialogs/_TabbedIntegrationManagerDialog.scss index 0ab59c44a7e..6385dd76f52 100644 --- a/res/css/views/dialogs/_TabbedIntegrationManagerDialog.scss +++ b/res/css/views/dialogs/_TabbedIntegrationManagerDialog.scss @@ -35,7 +35,7 @@ limitations under the License. .mx_TabbedIntegrationManagerDialog_currentManager { width: 100%; height: 100%; - border-top: 1px solid $accent-color; + border-top: 1px solid $accent; iframe { background-color: #fff; @@ -48,7 +48,7 @@ limitations under the License. .mx_TabbedIntegrationManagerDialog_tab { display: inline-block; - border: 1px solid $accent-color; + border: 1px solid $accent; border-bottom: 0; border-top-left-radius: 3px; border-top-right-radius: 3px; @@ -57,6 +57,6 @@ limitations under the License. } .mx_TabbedIntegrationManagerDialog_currentTab { - background-color: $accent-color; + background-color: $accent; color: $accent-fg-color; } diff --git a/res/css/views/dialogs/_TermsDialog.scss b/res/css/views/dialogs/_TermsDialog.scss index 939a31dee69..18daf8514d9 100644 --- a/res/css/views/dialogs/_TermsDialog.scss +++ b/res/css/views/dialogs/_TermsDialog.scss @@ -42,7 +42,7 @@ limitations under the License. .mx_TermsDialog_link { display: inline-block; mask-image: url('$(res)/img/external-link.svg'); - background-color: $accent-color; + background-color: $accent; width: 10px; height: 10px; } diff --git a/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss index becf4dba04a..e8bde18232a 100644 --- a/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss +++ b/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss @@ -32,7 +32,7 @@ limitations under the License. } .mx_AccessSecretStorageDialog_reset_link { - color: $warning-color; + color: $alert; } } @@ -68,7 +68,7 @@ limitations under the License. .mx_AccessSecretStorageDialog_passPhraseInput { width: 300px; - border: 1px solid $accent-color; + border: 1px solid $accent; border-radius: 5px; } @@ -100,18 +100,18 @@ limitations under the License. } .mx_AccessSecretStorageDialog_recoveryKeyFeedback_valid { - color: $accent-color; + color: $accent; &::before { mask-image: url('$(res)/img/feather-customised/check.svg'); - background-color: $accent-color; + background-color: $accent; } } .mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid { - color: $warning-color; + color: $alert; &::before { mask-image: url('$(res)/img/feather-customised/x.svg'); - background-color: $warning-color; + background-color: $alert; } } diff --git a/res/css/views/dialogs/security/_CreateKeyBackupDialog.scss b/res/css/views/dialogs/security/_CreateKeyBackupDialog.scss index 9be98e25b27..4503e08fadc 100644 --- a/res/css/views/dialogs/security/_CreateKeyBackupDialog.scss +++ b/res/css/views/dialogs/security/_CreateKeyBackupDialog.scss @@ -20,7 +20,7 @@ limitations under the License. } .mx_CreateKeyBackupDialog_primaryContainer { - /* FIXME: plinth colour in new theme(s). background-color: $accent-color; */ + /* FIXME: plinth colour in new theme(s). background-color: $accent; */ padding: 20px; } @@ -38,7 +38,7 @@ limitations under the License. .mx_CreateKeyBackupDialog_passPhraseInput { flex: none; width: 250px; - border: 1px solid $accent-color; + border: 1px solid $accent; border-radius: 5px; padding: 10px; margin-bottom: 1em; diff --git a/res/css/views/dialogs/security/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/security/_CreateSecretStorageDialog.scss index 46b300f463d..3673f59f12d 100644 --- a/res/css/views/dialogs/security/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/security/_CreateSecretStorageDialog.scss @@ -72,7 +72,7 @@ limitations under the License. } .mx_CreateSecretStorageDialog_primaryContainer { - /* FIXME: plinth colour in new theme(s). background-color: $accent-color; */ + /* FIXME: plinth colour in new theme(s). background-color: $accent; */ padding-top: 20px; } diff --git a/res/css/views/dialogs/security/_RestoreKeyBackupDialog.scss b/res/css/views/dialogs/security/_RestoreKeyBackupDialog.scss index 5689d84bc52..b0b8f1d7a5f 100644 --- a/res/css/views/dialogs/security/_RestoreKeyBackupDialog.scss +++ b/res/css/views/dialogs/security/_RestoreKeyBackupDialog.scss @@ -20,14 +20,14 @@ limitations under the License. } .mx_RestoreKeyBackupDialog_primaryContainer { - /* FIXME: plinth colour in new theme(s). background-color: $accent-color; */ + /* FIXME: plinth colour in new theme(s). background-color: $accent; */ padding: 20px; } .mx_RestoreKeyBackupDialog_passPhraseInput, .mx_RestoreKeyBackupDialog_recoveryKeyInput { width: 300px; - border: 1px solid $accent-color; + border: 1px solid $accent; border-radius: 5px; padding: 10px; } diff --git a/res/css/views/directory/_NetworkDropdown.scss b/res/css/views/directory/_NetworkDropdown.scss index 2f9b549901b..eadca7326bf 100644 --- a/res/css/views/directory/_NetworkDropdown.scss +++ b/res/css/views/directory/_NetworkDropdown.scss @@ -73,7 +73,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-image: url('$(res)/img/feather-customised/x.svg'); - background-color: $notice-primary-color; + background-color: $alert; } } } @@ -108,7 +108,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-image: url('$(res)/img/feather-customised/check.svg'); - background-color: $accent-color; + background-color: $accent; } } } diff --git a/res/css/views/elements/_AccessibleButton.scss b/res/css/views/elements/_AccessibleButton.scss index 41cef2016b6..8559c89e415 100644 --- a/res/css/views/elements/_AccessibleButton.scss +++ b/res/css/views/elements/_AccessibleButton.scss @@ -35,20 +35,20 @@ limitations under the License. .mx_AccessibleButton_kind_primary { color: $button-primary-fg-color; - background-color: $accent-color; - border: 1px solid $accent-color; // account for size loss of no border + background-color: $accent; + border: 1px solid $accent; // account for size loss of no border font-weight: 600; } .mx_AccessibleButton_kind_primary_outline { - color: $accent-color; + color: $accent; background-color: $button-secondary-bg-color; - border: 1px solid $accent-color; + border: 1px solid $accent; font-weight: 600; } .mx_AccessibleButton_kind_secondary { - color: $accent-color; + color: $accent; font-weight: 600; } @@ -60,7 +60,7 @@ limitations under the License. .mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_primary_sm { padding: 5px 12px; color: $button-primary-fg-color; - background-color: $accent-color; + background-color: $accent; } .mx_AccessibleButton_kind_primary_sm.mx_AccessibleButton_disabled { @@ -69,13 +69,13 @@ limitations under the License. .mx_AccessibleButton_kind_danger { color: $button-danger-fg-color; - background-color: $notice-primary-color; + background-color: $alert; } .mx_AccessibleButton_kind_danger_outline { - color: $notice-primary-color; + color: $alert; background-color: transparent; - border: 1px solid $notice-primary-color; + border: 1px solid $alert; } .mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled { @@ -91,7 +91,7 @@ limitations under the License. .mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_danger_sm { padding: 5px 12px; color: $button-danger-fg-color; - background-color: $notice-primary-color; + background-color: $alert; } .mx_AccessibleButton_kind_danger_sm.mx_AccessibleButton_disabled { @@ -100,7 +100,7 @@ limitations under the License. } .mx_AccessibleButton_kind_link { - color: $accent-color; + color: $accent; } .mx_AccessibleButton_kind_link.mx_AccessibleButton_disabled { @@ -109,7 +109,7 @@ limitations under the License. .mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_link_sm { padding: 5px 12px; - color: $accent-color; + color: $accent; } .mx_AccessibleButton_kind_link_sm.mx_AccessibleButton_disabled { @@ -117,7 +117,7 @@ limitations under the License. } .mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_confirm_sm { - background-color: $accent-color; + background-color: $accent; &::before { mask-image: url('$(res)/img/feather-customised/check.svg'); @@ -125,7 +125,7 @@ limitations under the License. } .mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_cancel_sm { - background-color: $notice-primary-color; + background-color: $alert; &::before { mask-image: url('$(res)/img/feather-customised/x.svg'); diff --git a/res/css/views/elements/_AddressSelector.scss b/res/css/views/elements/_AddressSelector.scss index a7d463353b4..b066d62543e 100644 --- a/res/css/views/elements/_AddressSelector.scss +++ b/res/css/views/elements/_AddressSelector.scss @@ -21,7 +21,7 @@ limitations under the License. max-height: 116px; overflow-y: auto; border-radius: 3px; - border: solid 1px $accent-color; + border: solid 1px $accent; cursor: pointer; z-index: 1; } diff --git a/res/css/views/elements/_AddressTile.scss b/res/css/views/elements/_AddressTile.scss index 90c40842f75..7e646b1cf63 100644 --- a/res/css/views/elements/_AddressTile.scss +++ b/res/css/views/elements/_AddressTile.scss @@ -28,8 +28,8 @@ limitations under the License. .mx_AddressTile.mx_AddressTile_error { background-color: rgba(255, 0, 100, 0.1); - color: $warning-color; - border-color: $warning-color; + color: $alert; + border-color: $alert; } .mx_AddressTile_network { diff --git a/res/css/views/elements/_DesktopCapturerSourcePicker.scss b/res/css/views/elements/_DesktopCapturerSourcePicker.scss index b4a2c69b868..4a164d1f3f9 100644 --- a/res/css/views/elements/_DesktopCapturerSourcePicker.scss +++ b/res/css/views/elements/_DesktopCapturerSourcePicker.scss @@ -41,7 +41,7 @@ limitations under the License. &.mx_desktopCapturerSourcePicker_source_thumbnail_selected, &:hover, &:focus { - border-color: $accent-color; + border-color: $accent; } } diff --git a/res/css/views/elements/_DirectorySearchBox.scss b/res/css/views/elements/_DirectorySearchBox.scss index 941a765a297..f8da4a578fc 100644 --- a/res/css/views/elements/_DirectorySearchBox.scss +++ b/res/css/views/elements/_DirectorySearchBox.scss @@ -38,7 +38,7 @@ limitations under the License. } .mx_DirectorySearchBox_clear { - background-color: $warning-color; + background-color: $alert; mask: url('$(res)/img/cancel.svg'); mask-repeat: no-repeat; mask-position: center; diff --git a/res/css/views/elements/_EditableItemList.scss b/res/css/views/elements/_EditableItemList.scss index 8987510a184..91ef20539cf 100644 --- a/res/css/views/elements/_EditableItemList.scss +++ b/res/css/views/elements/_EditableItemList.scss @@ -33,7 +33,7 @@ limitations under the License. height: 14px; mask-image: url('$(res)/img/feather-customised/cancel.svg'); mask-repeat: no-repeat; - background-color: $warning-color; + background-color: $alert; mask-size: 100%; } diff --git a/res/css/views/elements/_EventListSummary.scss b/res/css/views/elements/_EventListSummary.scss index f3e9f77aa3d..bb82ff0dd70 100644 --- a/res/css/views/elements/_EventListSummary.scss +++ b/res/css/views/elements/_EventListSummary.scss @@ -36,7 +36,7 @@ limitations under the License. } .mx_EventListSummary_toggle { - color: $accent-color; + color: $accent; cursor: pointer; float: right; margin-right: 10px; diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index b6a692c9515..a00c73191bc 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -152,24 +152,24 @@ limitations under the License. .mx_Field_valid { &.mx_Field, &.mx_Field:focus-within { - border-color: $accent-color; + border-color: $accent; } &.mx_Field label, &.mx_Field:focus-within label { - color: $accent-color; + color: $accent; } } .mx_Field_invalid { &.mx_Field, &.mx_Field:focus-within { - border-color: $warning-color; + border-color: $alert; } &.mx_Field label, &.mx_Field:focus-within label { - color: $warning-color; + color: $alert; } } diff --git a/res/css/views/elements/_ManageIntegsButton.scss b/res/css/views/elements/_ManageIntegsButton.scss index fe8c76003b2..6fb82814ada 100644 --- a/res/css/views/elements/_ManageIntegsButton.scss +++ b/res/css/views/elements/_ManageIntegsButton.scss @@ -35,7 +35,7 @@ limitations under the License. font-size: 10pt; line-height: 1.5em; border-radius: 5px; - background-color: $accent-color; + background-color: $accent; color: $accent-fg-color; text-align: center; z-index: 1000; diff --git a/res/css/views/elements/_ProgressBar.scss b/res/css/views/elements/_ProgressBar.scss index f16163c87c3..f9b3abef5e6 100644 --- a/res/css/views/elements/_ProgressBar.scss +++ b/res/css/views/elements/_ProgressBar.scss @@ -22,7 +22,7 @@ progress.mx_ProgressBar { border: none; @mixin ProgressBarBorderRadius 6px; - @mixin ProgressBarColour $accent-color; + @mixin ProgressBarColour $accent; @mixin ProgressBarBgColour $progressbar-bg-color; ::-webkit-progress-value { transition: width 1s; diff --git a/res/css/views/elements/_ReplyChain.scss b/res/css/views/elements/_ReplyChain.scss index 587dbdfdfe5..b43ff084df0 100644 --- a/res/css/views/elements/_ReplyChain.scss +++ b/res/css/views/elements/_ReplyChain.scss @@ -20,7 +20,7 @@ limitations under the License. margin-right: 0; margin-bottom: 8px; padding: 0 10px; - border-left: 2px solid $accent-color; + border-left: 2px solid $accent; border-radius: 2px; .mx_ReplyChain_show { diff --git a/res/css/views/elements/_RichText.scss b/res/css/views/elements/_RichText.scss index ae60b9a2f65..3d20b73ba02 100644 --- a/res/css/views/elements/_RichText.scss +++ b/res/css/views/elements/_RichText.scss @@ -48,7 +48,7 @@ a.mx_Pill { } .mx_UserPill_selected { - background-color: $accent-color !important; + background-color: $accent !important; } /* More specific to override `.markdown-body a` color */ @@ -57,7 +57,7 @@ a.mx_Pill { .mx_EventTile_content .mx_AtRoomPill, .mx_MessageComposer_input .mx_AtRoomPill { color: $accent-fg-color; - background-color: $warning-color; + background-color: $alert; } /* More specific to override `.markdown-body a` color */ diff --git a/res/css/views/elements/_SSOButtons.scss b/res/css/views/elements/_SSOButtons.scss index a14998259d7..cb92a6d17d6 100644 --- a/res/css/views/elements/_SSOButtons.scss +++ b/res/css/views/elements/_SSOButtons.scss @@ -46,13 +46,13 @@ limitations under the License. } .mx_SSOButton_default { - color: $accent-color; + color: $accent; background-color: $button-secondary-bg-color; - border-color: $accent-color; + border-color: $accent; } .mx_SSOButton_default.mx_SSOButton_primary { color: $button-primary-fg-color; - background-color: $accent-color; + background-color: $accent; } .mx_SSOButton_mini { diff --git a/res/css/views/elements/_Slider.scss b/res/css/views/elements/_Slider.scss index 66393405bb1..6b590f34aff 100644 --- a/res/css/views/elements/_Slider.scss +++ b/res/css/views/elements/_Slider.scss @@ -56,7 +56,7 @@ limitations under the License. position: absolute; width: $slider-selection-dot-size; height: $slider-selection-dot-size; - background-color: $accent-color; + background-color: $accent; border-radius: 50%; z-index: 10; } @@ -72,7 +72,7 @@ limitations under the License. .mx_Slider_selection > hr { margin: 0; - border: 0.2em solid $accent-color; + border: 0.2em solid $accent; } .mx_Slider_dot { @@ -84,7 +84,7 @@ limitations under the License. } .mx_Slider_dotActive { - background-color: $accent-color; + background-color: $accent; } .mx_Slider_dotValue { diff --git a/res/css/views/elements/_StyledCheckbox.scss b/res/css/views/elements/_StyledCheckbox.scss index 398214b9b0f..be671f1e9c9 100644 --- a/res/css/views/elements/_StyledCheckbox.scss +++ b/res/css/views/elements/_StyledCheckbox.scss @@ -87,18 +87,18 @@ limitations under the License. } &:checked + label > .mx_Checkbox_background { - background: $accent-color; - border-color: $accent-color; + background: $accent; + border-color: $accent; } } .mx_Checkbox.mx_Checkbox_kind_outline input[type=checkbox] { & + label > .mx_Checkbox_background .mx_Checkbox_checkmark { - background: $accent-color; + background: $accent; } &:checked + label > .mx_Checkbox_background { background: transparent; - border-color: $accent-color; + border-color: $accent; } } diff --git a/res/css/views/elements/_StyledRadioButton.scss b/res/css/views/elements/_StyledRadioButton.scss index 3f92abe512d..f733fd6c8b3 100644 --- a/res/css/views/elements/_StyledRadioButton.scss +++ b/res/css/views/elements/_StyledRadioButton.scss @@ -21,7 +21,7 @@ limitations under the License. .mx_StyledRadioButton { $radio-circle-color: $quaternary-content; - $active-radio-circle-color: $accent-color; + $active-radio-circle-color: $accent; position: relative; display: flex; @@ -126,5 +126,5 @@ limitations under the License. } .mx_StyledRadioButton_checked { - border-color: $accent-color; + border-color: $accent; } diff --git a/res/css/views/elements/_ToggleSwitch.scss b/res/css/views/elements/_ToggleSwitch.scss index 2695e72d6da..e5e90e2d57d 100644 --- a/res/css/views/elements/_ToggleSwitch.scss +++ b/res/css/views/elements/_ToggleSwitch.scss @@ -32,7 +32,7 @@ limitations under the License. } .mx_ToggleSwitch.mx_ToggleSwitch_on { - background-color: $accent-color; + background-color: $accent; > .mx_ToggleSwitch_ball { left: calc(100% - $font-20px); diff --git a/res/css/views/elements/_Validation.scss b/res/css/views/elements/_Validation.scss index 94de00185e6..5546515d942 100644 --- a/res/css/views/elements/_Validation.scss +++ b/res/css/views/elements/_Validation.scss @@ -50,20 +50,20 @@ limitations under the License. } &.mx_Validation_valid { - color: $accent-color; + color: $accent; &::before { mask-image: url('$(res)/img/feather-customised/check.svg'); - background-color: $accent-color; + background-color: $accent; } } &.mx_Validation_invalid { - color: $warning-color; + color: $alert; &::before { mask-image: url('$(res)/img/feather-customised/x.svg'); - background-color: $warning-color; + background-color: $alert; } } } diff --git a/res/css/views/emojipicker/_EmojiPicker.scss b/res/css/views/emojipicker/_EmojiPicker.scss index 67ff7b4f642..2a59310cf75 100644 --- a/res/css/views/emojipicker/_EmojiPicker.scss +++ b/res/css/views/emojipicker/_EmojiPicker.scss @@ -52,7 +52,7 @@ limitations under the License. &:not(:disabled):hover { background-color: $focus-bg-color; - border-bottom: 2px solid $accent-color; + border-bottom: 2px solid $accent; } } @@ -82,7 +82,7 @@ limitations under the License. .mx_EmojiPicker_anchor_symbols::before { mask-image: url('$(res)/img/emojipicker/symbols.svg'); } .mx_EmojiPicker_anchor_visible { - border-bottom: 2px solid $accent-color; + border-bottom: 2px solid $accent; } .mx_EmojiPicker_search { @@ -178,7 +178,7 @@ limitations under the License. .mx_EmojiPicker_item_selected { color: rgba(0, 0, 0, .5); - border: 1px solid $accent-color; + border: 1px solid $accent; padding: 4px; } diff --git a/res/css/views/messages/_MFileBody.scss b/res/css/views/messages/_MFileBody.scss index e23696e6a94..ccb6b9a355a 100644 --- a/res/css/views/messages/_MFileBody.scss +++ b/res/css/views/messages/_MFileBody.scss @@ -15,7 +15,7 @@ limitations under the License. */ .mx_MFileBody_download { - color: $accent-color; + color: $accent; .mx_MFileBody_download_icon { // 12px instead of 14px to better match surrounding font size @@ -26,13 +26,13 @@ limitations under the License. mask-position: center; mask-repeat: no-repeat; mask-image: url("$(res)/img/download.svg"); - background-color: $accent-color; + background-color: $accent; display: inline-block; } } .mx_MFileBody_download a { - color: $accent-color; + color: $accent; text-decoration: none; cursor: pointer; } diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss index 920c3011f58..0981cbf113e 100644 --- a/res/css/views/messages/_MImageBody.scss +++ b/res/css/views/messages/_MImageBody.scss @@ -81,12 +81,12 @@ $timelineImageBorderRadius: 4px; background-color: $header-panel-bg-color; .mx_HiddenImagePlaceholder_button { - color: $accent-color; + color: $accent; span.mx_HiddenImagePlaceholder_eye { margin-right: 8px; - background-color: $accent-color; + background-color: $accent; mask-image: url('$(res)/img/feather-customised/eye.svg'); display: inline-block; width: 18px; diff --git a/res/css/views/messages/_ReactionsRowButton.scss b/res/css/views/messages/_ReactionsRowButton.scss index b3bff5480fd..2f20316d843 100644 --- a/res/css/views/messages/_ReactionsRowButton.scss +++ b/res/css/views/messages/_ReactionsRowButton.scss @@ -32,7 +32,7 @@ limitations under the License. &.mx_ReactionsRowButton_selected { background-color: $reaction-row-button-selected-bg-color; - border-color: $accent-color; + border-color: $accent; } &.mx_AccessibleButton_disabled { diff --git a/res/css/views/messages/_TextualEvent.scss b/res/css/views/messages/_TextualEvent.scss index e87fed90de4..607ed9cecba 100644 --- a/res/css/views/messages/_TextualEvent.scss +++ b/res/css/views/messages/_TextualEvent.scss @@ -19,7 +19,7 @@ limitations under the License. overflow-y: hidden; a { - color: $accent-color; + color: $accent; cursor: pointer; } } diff --git a/res/css/views/messages/_ViewSourceEvent.scss b/res/css/views/messages/_ViewSourceEvent.scss index b0e40a5152a..bdb036fe173 100644 --- a/res/css/views/messages/_ViewSourceEvent.scss +++ b/res/css/views/messages/_ViewSourceEvent.scss @@ -34,7 +34,7 @@ limitations under the License. mask-position: 0 center; mask-size: auto 12px; visibility: hidden; - background-color: $accent-color; + background-color: $accent; mask-image: url('$(res)/img/feather-customised/maximise.svg'); } diff --git a/res/css/views/messages/_common_CryptoEvent.scss b/res/css/views/messages/_common_CryptoEvent.scss index ad986575a29..6de3f45f02e 100644 --- a/res/css/views/messages/_common_CryptoEvent.scss +++ b/res/css/views/messages/_common_CryptoEvent.scss @@ -31,12 +31,12 @@ limitations under the License. &.mx_cryptoEvent_icon_verified::after { mask-image: url("$(res)/img/e2e/verified.svg"); - background-color: $accent-color; + background-color: $accent; } &.mx_cryptoEvent_icon_warning::after { mask-image: url("$(res)/img/e2e/warning.svg"); - background-color: $notice-primary-color; + background-color: $alert; } .mx_cryptoEvent_state, .mx_cryptoEvent_buttons { diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index e08a11cd36a..539793eef68 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -190,7 +190,7 @@ limitations under the License. } .mx_RoomSummaryCard_app_pinToggle::before { - background-color: $accent-color; + background-color: $accent; } } diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index a015ef29a72..8e01cf8b5e8 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -188,12 +188,12 @@ limitations under the License. .mx_UserInfo_field { cursor: pointer; - color: $accent-color; + color: $accent; line-height: $font-16px; margin: 8px 0; &.mx_UserInfo_destructive { - color: $warning-color; + color: $alert; } } @@ -225,12 +225,12 @@ limitations under the License. &.mx_UserInfo_device_verified { .mx_UserInfo_device_trusted { - color: $accent-color; + color: $accent; } } &.mx_UserInfo_device_unverified { .mx_UserInfo_device_trusted { - color: $warning-color; + color: $alert; } } diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss index 824f1fcb2f1..edf9ba7dd72 100644 --- a/res/css/views/right_panel/_WidgetCard.scss +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -58,6 +58,6 @@ limitations under the License. } .mx_WidgetCard_maxPinnedTooltip { - background-color: $notice-primary-color; + background-color: $alert; color: #ffffff; } diff --git a/res/css/views/room_settings/_AliasSettings.scss b/res/css/views/room_settings/_AliasSettings.scss index f8d92e7828f..3c7cdd803f6 100644 --- a/res/css/views/room_settings/_AliasSettings.scss +++ b/res/css/views/room_settings/_AliasSettings.scss @@ -22,7 +22,7 @@ limitations under the License. } .mx_AliasSettings_editable:focus { - border-bottom: 1px solid $accent-color; + border-bottom: 1px solid $accent; outline: none; box-shadow: none; } @@ -30,7 +30,7 @@ limitations under the License. .mx_AliasSettings { summary { cursor: pointer; - color: $accent-color; + color: $accent; font-weight: 600; list-style: none; diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 1276b13fdeb..30f969262fd 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -310,7 +310,7 @@ $MinWidth: 240px; .mx_AppPermissionWarning_helpIcon::before { display: inline-block; - background-color: $accent-color; + background-color: $accent; mask-repeat: no-repeat; mask-size: 12px; width: 12px; diff --git a/res/css/views/rooms/_E2EIcon.scss b/res/css/views/rooms/_E2EIcon.scss index 68ad44cf6ad..67916767b62 100644 --- a/res/css/views/rooms/_E2EIcon.scss +++ b/res/css/views/rooms/_E2EIcon.scss @@ -65,7 +65,7 @@ limitations under the License. .mx_E2EIcon_warning::after { mask-image: url('$(res)/img/e2e/warning.svg'); - background-color: $notice-primary-color; + background-color: $alert; } .mx_E2EIcon_normal::after { @@ -75,5 +75,5 @@ limitations under the License. .mx_E2EIcon_verified::after { mask-image: url('$(res)/img/e2e/verified.svg'); - background-color: $accent-color; + background-color: $accent; } diff --git a/res/css/views/rooms/_EditMessageComposer.scss b/res/css/views/rooms/_EditMessageComposer.scss index 136ae0d4ebe..af7f0f8049a 100644 --- a/res/css/views/rooms/_EditMessageComposer.scss +++ b/res/css/views/rooms/_EditMessageComposer.scss @@ -32,7 +32,7 @@ limitations under the License. padding: 3px 6px; &:focus { - border-color: $accent-color-50pct; + border-color: rgba($accent, 0.5); // Only ever used here } } diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 9332171bd1a..7e2a1ec45e7 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -43,7 +43,7 @@ limitations under the License. background-color: $event-highlight-bg-color; } - color: $warning-color; + color: $alert; } /* For replies */ diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 2ca72d6a436..a018ec3f740 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -121,14 +121,14 @@ $left-gutter: 64px; * TODO: ultimately we probably want some transition on here. */ &.mx_EventTile_selected > .mx_EventTile_line { - border-left: $accent-color $selected-message-border-width solid; + border-left: $accent $selected-message-border-width solid; padding-left: calc($left-gutter - $selected-message-border-width); background-color: $event-selected-color; } &.mx_EventTile_highlight, &.mx_EventTile_highlight .markdown-body { - color: $warning-color; + color: $alert; .mx_EventTile_line { background-color: $event-highlight-bg-color; @@ -146,7 +146,7 @@ $left-gutter: 64px; } .mx_EventTile_searchHighlight { - background-color: $accent-color; + background-color: $accent; color: $accent-fg-color; border-radius: 5px; padding-left: 2px; @@ -155,7 +155,7 @@ $left-gutter: 64px; } .mx_EventTile_searchHighlight a { - background-color: $accent-color; + background-color: $accent; color: $accent-fg-color; } @@ -435,7 +435,7 @@ $left-gutter: 64px; .mx_EventTile_e2eIcon_warning { &::after { mask-image: url('$(res)/img/e2e/warning.svg'); - background-color: $notice-primary-color; + background-color: $alert; } opacity: 1; } @@ -582,7 +582,7 @@ $left-gutter: 64px; } .mx_EventTile_content .markdown-body a { - color: $accent-color-alt; + color: $accent-alt; } .mx_EventTile_content .markdown-body blockquote { diff --git a/res/css/views/rooms/_JumpToBottomButton.scss b/res/css/views/rooms/_JumpToBottomButton.scss index 2b38b509de6..7c85f511826 100644 --- a/res/css/views/rooms/_JumpToBottomButton.scss +++ b/res/css/views/rooms/_JumpToBottomButton.scss @@ -47,7 +47,7 @@ limitations under the License. .mx_JumpToBottomButton_highlight .mx_JumpToBottomButton_badge { color: $secondary-accent-color; - background-color: $warning-color; + background-color: $alert; } .mx_JumpToBottomButton_scrollDown { diff --git a/res/css/views/rooms/_LinkPreviewGroup.scss b/res/css/views/rooms/_LinkPreviewGroup.scss index ed341904fd7..3edb0722e41 100644 --- a/res/css/views/rooms/_LinkPreviewGroup.scss +++ b/res/css/views/rooms/_LinkPreviewGroup.scss @@ -32,7 +32,7 @@ limitations under the License. } > .mx_AccessibleButton { - color: $accent-color; + color: $accent; text-align: center; } } diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index cf128ebf369..80b5b28969d 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -86,7 +86,7 @@ limitations under the License. .mx_MemberList_invite { flex: 0 0 auto; position: relative; - background-color: $accent-color; + background-color: $accent; border-radius: 4px; margin: 5px 9px 9px; display: flex; diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index e1c9bf41a4a..36472beae8d 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -170,18 +170,18 @@ limitations under the License. /* hack for FF as vertical alignment of custom placeholder text is broken */ .mx_MessageComposer_input textarea::-moz-placeholder { line-height: 100%; - color: $accent-color; + color: $accent; opacity: 1.0; } .mx_MessageComposer_input textarea::-webkit-input-placeholder { - color: $accent-color; + color: $accent; } .mx_MessageComposer_button_highlight { - background: rgba($accent-color, 0.25); + background: rgba($accent, 0.25); // make the icon the accent color too &::before { - background-color: $accent-color !important; + background-color: $accent !important; } } @@ -234,16 +234,16 @@ limitations under the License. &:hover, &.mx_MessageComposer_closeButtonMenu { &::after { - background: rgba($accent-color, 0.1); + background: rgba($accent, 0.1); } &::before { - background-color: $accent-color; + background-color: $accent; } } &.mx_MessageComposer_hangup:not(.mx_AccessibleButton_disabled)::before { - background-color: $warning-color; + background-color: $alert; } } @@ -282,7 +282,7 @@ limitations under the License. width: 32px; height: 32px; border-radius: 100%; - background-color: $accent-color; + background-color: $accent; &::before { position: absolute; diff --git a/res/css/views/rooms/_NotificationBadge.scss b/res/css/views/rooms/_NotificationBadge.scss index 670e057cfa2..c06f9f50751 100644 --- a/res/css/views/rooms/_NotificationBadge.scss +++ b/res/css/views/rooms/_NotificationBadge.scss @@ -36,7 +36,7 @@ limitations under the License. &.mx_NotificationBadge_highlighted { // TODO: Use a more specific variable - background-color: $warning-color; + background-color: $alert; } // These are the 3 background types diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 81dfa90c968..b98c71b9238 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -63,7 +63,7 @@ limitations under the License. } .mx_RoomHeader_textButton_danger { - background-color: $warning-color; + background-color: $alert; } .mx_RoomHeader_cancelButton { @@ -145,7 +145,7 @@ limitations under the License. } .mx_RoomHeader_name:hover div:not(.mx_RoomHeader_editable) { - color: $accent-color; + color: $accent; } .mx_RoomHeader_placeholder { @@ -159,7 +159,7 @@ limitations under the License. } .mx_RoomHeader_editable:focus { - border-bottom: 1px solid $accent-color !important; + border-bottom: 1px solid $accent !important; outline: none; box-shadow: none; } @@ -228,10 +228,10 @@ limitations under the License. } &:hover { - background: rgba($accent-color, 0.1); + background: rgba($accent, 0.1); &::before { - background-color: $accent-color; + background-color: $accent; } } } @@ -245,7 +245,7 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/apps.svg'); } .mx_RoomHeader_appsButton_highlight::before { - background-color: $accent-color; + background-color: $accent; } .mx_RoomHeader_searchButton::before { diff --git a/res/css/views/rooms/_RoomUpgradeWarningBar.scss b/res/css/views/rooms/_RoomUpgradeWarningBar.scss index 1c477cedfef..efa2ccc5315 100644 --- a/res/css/views/rooms/_RoomUpgradeWarningBar.scss +++ b/res/css/views/rooms/_RoomUpgradeWarningBar.scss @@ -34,16 +34,16 @@ limitations under the License. } .mx_RoomUpgradeWarningBar_header { - color: $warning-color; + color: $alert; font-weight: bold; } .mx_RoomUpgradeWarningBar_body { - color: $warning-color; + color: $alert; } .mx_RoomUpgradeWarningBar_upgradelink { - color: $warning-color; + color: $alert; text-decoration: underline; } diff --git a/res/css/views/rooms/_SearchBar.scss b/res/css/views/rooms/_SearchBar.scss index 234363245ab..f6d3478e201 100644 --- a/res/css/views/rooms/_SearchBar.scss +++ b/res/css/views/rooms/_SearchBar.scss @@ -31,7 +31,7 @@ limitations under the License. cursor: pointer; width: 37px; height: 37px; - background-color: $accent-color; + background-color: $accent; mask: url('$(res)/img/feather-customised/search-input.svg'); mask-repeat: no-repeat; mask-position: center; @@ -48,7 +48,7 @@ limitations under the License. font-size: $font-15px; cursor: pointer; color: $primary-content; - border-bottom: 2px solid $accent-color; + border-bottom: 2px solid $accent; font-weight: 600; } @@ -58,7 +58,7 @@ limitations under the License. } .mx_SearchBar_cancel { - background-color: $warning-color; + background-color: $alert; mask: url('$(res)/img/cancel.svg'); mask-repeat: no-repeat; mask-position: center; diff --git a/res/css/views/rooms/_Stickers.scss b/res/css/views/rooms/_Stickers.scss index b582c7efaf3..94d1c6b779a 100644 --- a/res/css/views/rooms/_Stickers.scss +++ b/res/css/views/rooms/_Stickers.scss @@ -42,7 +42,7 @@ .mx_Stickers_addLink { display: inline; cursor: pointer; - color: $accent-color; + color: $accent; } .mx_Stickers_hideStickers { diff --git a/res/css/views/rooms/_TopUnreadMessagesBar.scss b/res/css/views/rooms/_TopUnreadMessagesBar.scss index 7c7d96e713f..fbb7cb0b1e9 100644 --- a/res/css/views/rooms/_TopUnreadMessagesBar.scss +++ b/res/css/views/rooms/_TopUnreadMessagesBar.scss @@ -33,7 +33,7 @@ limitations under the License. height: 4px; border-radius: 16px; background-color: $secondary-accent-color; - border: 6px solid $accent-color; + border: 6px solid $accent; pointer-events: none; } diff --git a/res/css/views/settings/_IntegrationManager.scss b/res/css/views/settings/_IntegrationManager.scss index 81b01ab8de2..043e7201caf 100644 --- a/res/css/views/settings/_IntegrationManager.scss +++ b/res/css/views/settings/_IntegrationManager.scss @@ -40,5 +40,5 @@ limitations under the License. } .mx_IntegrationManager_error h3 { - color: $warning-color; + color: $alert; } diff --git a/res/css/views/settings/_JoinRuleSettings.scss b/res/css/views/settings/_JoinRuleSettings.scss index 721dbe039e2..76b1b6333af 100644 --- a/res/css/views/settings/_JoinRuleSettings.scss +++ b/res/css/views/settings/_JoinRuleSettings.scss @@ -17,9 +17,9 @@ limitations under the License. .mx_JoinRuleSettings_upgradeRequired { margin-left: 16px; padding: 4px 16px; - border: 1px solid $accent-color; + border: 1px solid $accent; border-radius: 8px; - color: $accent-color; + color: $accent; font-size: $font-12px; line-height: $font-15px; } diff --git a/res/css/views/settings/_LayoutSwitcher.scss b/res/css/views/settings/_LayoutSwitcher.scss index ef82c41b77e..78362190852 100644 --- a/res/css/views/settings/_LayoutSwitcher.scss +++ b/res/css/views/settings/_LayoutSwitcher.scss @@ -57,7 +57,7 @@ limitations under the License. } &.mx_LayoutSwitcher_RadioButton_selected { - border-color: $accent-color; + border-color: $accent; } } @@ -70,7 +70,7 @@ limitations under the License. } .mx_StyledRadioButton_checked { - background-color: rgba($accent-color, 0.08); + background-color: rgba($accent, 0.08); } .mx_EventTile { diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 5aa9db7e864..684935152ca 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -15,11 +15,11 @@ limitations under the License. */ .mx_SettingsTab { - color: $muted-fg-color; + color: $primary-content; } .mx_SettingsTab_warningText { - color: $warning-color; + color: $alert; } .mx_SettingsTab_heading { @@ -91,10 +91,10 @@ limitations under the License. .mx_SettingsTab_linkBtn { cursor: pointer; - color: $accent-color; + color: $accent; word-break: break-all; } .mx_SettingsTab a { - color: $accent-color-alt; + color: $accent-alt; } diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index c237856e606..e967139cd94 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -32,7 +32,7 @@ limitations under the License. } .mx_AppearanceUserSettingsTab_AdvancedToggle { - color: $accent-color; + color: $accent; cursor: pointer; } diff --git a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss index f1ad2352f22..80cb72f2547 100644 --- a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss @@ -35,7 +35,7 @@ limitations under the License. } .mx_SecurityUserSettingsTab_warning { - color: $notice-primary-color; + color: $alert; position: relative; padding-left: 40px; margin-top: 30px; @@ -50,7 +50,7 @@ limitations under the License. content: ""; top: 0; left: 0; - background-color: $notice-primary-color; + background-color: $alert; mask-image: url('$(res)/img/feather-customised/alert-triangle.svg'); } } diff --git a/res/css/views/spaces/_SpaceBasicSettings.scss b/res/css/views/spaces/_SpaceBasicSettings.scss index bff574ded36..a8e0e0e2f25 100644 --- a/res/css/views/spaces/_SpaceBasicSettings.scss +++ b/res/css/views/spaces/_SpaceBasicSettings.scss @@ -69,7 +69,7 @@ limitations under the License. } > .mx_SpaceBasicSettings_avatar_remove { - color: $notice-primary-color; + color: $alert; } } diff --git a/res/css/views/spaces/_SpaceCreateMenu.scss b/res/css/views/spaces/_SpaceCreateMenu.scss index 7084c2f20ee..06c799af577 100644 --- a/res/css/views/spaces/_SpaceCreateMenu.scss +++ b/res/css/views/spaces/_SpaceCreateMenu.scss @@ -116,7 +116,7 @@ $spacePanelWidth: 68px; } .mx_AccessibleButton_kind_link { - color: $accent-color; + color: $accent; position: relative; padding: 0; margin-left: 8px; diff --git a/res/css/views/terms/_InlineTermsAgreement.scss b/res/css/views/terms/_InlineTermsAgreement.scss index 1d0e3ea8c56..432f39a6934 100644 --- a/res/css/views/terms/_InlineTermsAgreement.scss +++ b/res/css/views/terms/_InlineTermsAgreement.scss @@ -19,7 +19,7 @@ limitations under the License. font-size: $font-14px; a { - color: $accent-color; + color: $accent; text-decoration: none; } @@ -35,7 +35,7 @@ limitations under the License. .mx_InlineTermsAgreement_link { display: inline-block; mask-image: url('$(res)/img/external-link.svg'); - background-color: $accent-color; + background-color: $accent; mask-repeat: no-repeat; mask-size: contain; width: 12px; diff --git a/res/css/views/toasts/_AnalyticsToast.scss b/res/css/views/toasts/_AnalyticsToast.scss index fdbe7f1c76b..15a94420fa5 100644 --- a/res/css/views/toasts/_AnalyticsToast.scss +++ b/res/css/views/toasts/_AnalyticsToast.scss @@ -17,11 +17,11 @@ limitations under the License. .mx_AnalyticsToast { .mx_AccessibleButton_kind_danger { background: none; - color: $accent-color; + color: $accent; } .mx_AccessibleButton_kind_primary { - background: $accent-color; + background: $accent; color: #ffffff; } } diff --git a/res/css/views/voip/_DialPad.scss b/res/css/views/voip/_DialPad.scss index 288f1f5d31a..c0ae2270f65 100644 --- a/res/css/views/voip/_DialPad.scss +++ b/res/css/views/voip/_DialPad.scss @@ -50,7 +50,7 @@ limitations under the License. .mx_DialPad_dialButton { /* Always show the dial button in the center grid column */ grid-column: 2; - background-color: $accent-color; + background-color: $accent; &::before { content: ''; diff --git a/res/css/views/voip/_DialPadContextMenu.scss b/res/css/views/voip/_DialPadContextMenu.scss index d2014241e9b..905486de8a3 100644 --- a/res/css/views/voip/_DialPadContextMenu.scss +++ b/res/css/views/voip/_DialPadContextMenu.scss @@ -47,7 +47,7 @@ limitations under the License. } .mx_DialPadContextMenu_header:focus-within { - border-bottom: 1px solid $accent-color; + border-bottom: 1px solid $accent; } .mx_DialPadContextMenu_title { diff --git a/res/css/views/voip/_DialPadModal.scss b/res/css/views/voip/_DialPadModal.scss index f378507f900..ff1ded029c3 100644 --- a/res/css/views/voip/_DialPadModal.scss +++ b/res/css/views/voip/_DialPadModal.scss @@ -35,7 +35,7 @@ limitations under the License. } .mx_DialPadModal_header:focus-within { - border-bottom: 1px solid $accent-color; + border-bottom: 1px solid $accent; } .mx_DialPadModal_title { diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss index 1f17a546928..1d3c3e1d502 100644 --- a/res/css/views/voip/_VideoFeed.scss +++ b/res/css/views/voip/_VideoFeed.scss @@ -27,7 +27,7 @@ limitations under the License. } &.mx_VideoFeed_speaking { - border: $accent-color 2px solid; + border: $accent 2px solid; .mx_VideoFeed_video { border-radius: 0; diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index f214681f583..3ab4d68df45 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -199,7 +199,7 @@ $eventbubble-reply-color: #C1C6CD; font-family: $font-family; font-size: $font-14px; color: $button-fg-color; - background-color: $accent-color; + background-color: $accent; width: auto; padding: 7px; padding-left: 1.5em; @@ -210,19 +210,19 @@ $eventbubble-reply-color: #C1C6CD; } @define-mixin mx_DialogButton_danger { - background-color: $accent-color; + background-color: $accent; } @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color !important; - color: $accent-color; + border: 1px solid $accent !important; + color: $accent; background-color: $button-secondary-bg-color; } @define-mixin mx_Dialog_link { - color: $accent-color; + color: $accent; text-decoration: none; } diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 99e223134f1..b9afbc51344 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -206,7 +206,7 @@ $eventbubble-reply-color: #C1C6CD; font-family: $font-family; font-size: $font-14px; color: $button-fg-color; - background-color: $accent-color; + background-color: $accent; width: auto; padding: 7px; padding-left: 1.5em; @@ -217,19 +217,19 @@ $eventbubble-reply-color: #C1C6CD; } @define-mixin mx_DialogButton_danger { - background-color: $accent-color; + background-color: $accent; } @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color !important; - color: $accent-color; + border: 1px solid $accent !important; + color: $accent; background-color: $button-secondary-bg-color; } @define-mixin mx_Dialog_link { - color: $accent-color; + color: $accent; text-decoration: none; } diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index afdda9da847..83255da36d9 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -14,10 +14,6 @@ $monospace-font-family: 'Inconsolata', 'Twemoji', 'Apple Color Emoji', 'Segoe UI // unified palette // try to use these colors when possible -$accent-color: #03b381; -$accent-bg-color: rgba(3, 179, 129, 0.16); -$notice-primary-color: #ff4b55; -$notice-primary-bg-color: rgba(255, 75, 85, 0.16); $header-panel-bg-color: #f3f8fd; // typical text (dark-on-white in light skin) @@ -35,20 +31,12 @@ $focus-bg-color: #dddddd; // button UI (white-on-green in light skin) $accent-fg-color: #ffffff; -$accent-color-50pct: rgba(3, 179, 129, 0.5); //#03b381 in rgb -$accent-color-darker: #92caad; -$accent-color-alt: #238cf5; +$accent-alt: #238cf5; $selection-fg-color: $primary-bg-color; $focus-brightness: 105%; -// warning colours -$warning-color: $notice-primary-color; // red -$orange-warning-color: #ff8d13; // used for true warnings -// background colour for warnings -$warning-bg-color: #df2a8b; -$info-bg-color: #2a9edf; $other-user-pill-bg-color: rgba(0, 0, 0, 0.1); // informational plinth @@ -223,7 +211,7 @@ $collapse-button-url: "$(res)/img/feather-customised/minimise.svg"; $expand-button-url: "$(res)/img/feather-customised/maximise.svg"; // e2e -$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color +$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent $e2e-unknown-color: #e8bf37; $e2e-unverified-color: #e8bf37; $e2e-warning-color: #ba6363; @@ -318,7 +306,7 @@ $groupFilterPanel-divider-color: $tertiary-content; font-family: $font-family; font-size: $font-14px; color: $button-fg-color; - background-color: $accent-color; + background-color: $accent; width: auto; padding: 7px; padding-left: 1.5em; @@ -332,7 +320,7 @@ $groupFilterPanel-divider-color: $tertiary-content; } @define-mixin mx_DialogButton_danger { - background-color: $accent-color; + background-color: $accent; } @define-mixin mx_DialogButton_small { @@ -344,13 +332,13 @@ $groupFilterPanel-divider-color: $tertiary-content; @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color !important; - color: $accent-color; + border: 1px solid $accent !important; + color: $accent; background-color: $button-secondary-bg-color; } @define-mixin mx_Dialog_link { - color: $accent-color; + color: $accent; text-decoration: none; } diff --git a/res/themes/light-custom/css/_custom.scss b/res/themes/light-custom/css/_custom.scss index 1f09fbbb511..7ad3d9a2548 100644 --- a/res/themes/light-custom/css/_custom.scss +++ b/res/themes/light-custom/css/_custom.scss @@ -37,10 +37,7 @@ $space-nav: rgba($panel-base, 0.1); // // --accent-color -$accent-color: var(--accent-color); -$accent-bg-color: var(--accent-color-15pct); $username-variant3-color: var(--accent-color); -$accent-color-50pct: var(--accent-color-50pct); //still needs alpha at .5 // // --timeline-background-color $button-secondary-bg-color: var(--timeline-background-color); @@ -112,13 +109,10 @@ $greyed-fg-color: var(--timeline-text-secondary-color); $info-plinth-fg-color: var(--timeline-text-secondary-color); // // --primary-color -$accent-color-alt: var(--primary-color); +$accent-alt: var(--primary-color); $input-focused-border-color: var(--primary-color); // // --warning-color -$warning-color: var(--warning-color); -$notice-primary-color: var(--warning-color); -$warning-color: var(--warning-color); $button-danger-disabled-bg-color: var(--warning-color-50pct); // still needs alpha at 0.5 // // --username colors (which use a 0-based index) diff --git a/res/themes/light-high-contrast/css/_light-high-contrast.scss b/res/themes/light-high-contrast/css/_light-high-contrast.scss index c423f99438d..c9d66aabf4f 100644 --- a/res/themes/light-high-contrast/css/_light-high-contrast.scss +++ b/res/themes/light-high-contrast/css/_light-high-contrast.scss @@ -18,13 +18,11 @@ $username-variant6-color: #1C7274; $username-variant7-color: #5C56F5; $username-variant8-color: #3E810A; -$accent-color: $accent; -$accent-color-50pct: rgba($accent-color, 0.5); -$accent-color-alt: $links; +$accent-alt: $links; $input-border-color: $secondary-content; $input-darker-bg-color: $quinary-content; $input-darker-fg-color: $secondary-content; -$input-focused-border-color: $accent-color; +$input-focused-border-color: $accent; $resend-button-divider-color: $input-darker-bg-color; $icon-button-color: $quaternary-content; $theme-button-bg-color: $quinary-content; @@ -36,30 +34,28 @@ $voice-record-stop-border-color: $quinary-content; $voice-record-icon-color: $tertiary-content; $appearance-tab-border-color: $input-darker-bg-color; $eventbubble-reply-color: $quaternary-content; -$notice-primary-color: $alert; -$warning-color: $notice-primary-color; // red $roomtopic-color: $secondary-content; @define-mixin mx_DialogButton_danger { - background-color: $accent-color; + background-color: $accent; } @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color !important; - color: $accent-color; + border: 1px solid $accent !important; + color: $accent; background-color: $button-secondary-bg-color; } @define-mixin mx_Dialog_link { - color: $accent-color; + color: $accent; text-decoration: none; } // Draw an outline on buttons with focus .mx_AccessibleButton:focus { - outline: 2px solid $accent-color; + outline: 2px solid $accent; outline-offset: 2px; } @@ -78,7 +74,6 @@ $roomtopic-color: $secondary-content; padding-left: 4px !important; } - .mx_BasicMessageComposer .mx_BasicMessageComposer_inputEmpty > :first-child::before { color: $secondary-content; opacity: 1 !important; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 76f142e066c..4e3da74bce8 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -33,10 +33,6 @@ $space-nav: rgba($tertiary-content, 0.15); // unified palette // try to use these colors when possible -$accent-color: $accent; -$accent-bg-color: rgba(3, 179, 129, 0.16); -$notice-primary-color: $alert; -$notice-primary-bg-color: rgba(255, 75, 85, 0.16); $header-panel-bg-color: #f3f8fd; // typical text (dark-on-white in light skin) @@ -50,20 +46,12 @@ $focus-bg-color: #dddddd; // button UI (white-on-green in light skin) $accent-fg-color: $background; -$accent-color-50pct: rgba($accent-color, 0.5); -$accent-color-darker: #92caad; -$accent-color-alt: #238CF5; +$accent-alt: #238CF5; $selection-fg-color: $background; $focus-brightness: 105%; -// warning colours -$warning-color: $notice-primary-color; // red -$orange-warning-color: #ff8d13; // used for true warnings -// background colour for warnings -$warning-bg-color: #DF2A8B; -$info-bg-color: #2A9EDF; $other-user-pill-bg-color: rgba(0, 0, 0, 0.1); // informational plinth @@ -214,7 +202,7 @@ $collapse-button-url: "$(res)/img/feather-customised/minimise.svg"; $expand-button-url: "$(res)/img/feather-customised/maximise.svg"; // e2e -$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color +$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent $e2e-unknown-color: #e8bf37; $e2e-unverified-color: #e8bf37; $e2e-warning-color: #ba6363; @@ -276,7 +264,7 @@ $tooltip-timeline-fg-color: $background; $breadcrumb-placeholder-bg-color: #e8eef5; -// These two don't change between themes. They are the $warning-color, but we don't +// These two don't change between themes. They are the $alert, but we don't // want custom themes to affect them by accident. $voice-record-stop-symbol-color: #ff5b55; $voice-record-live-circle-color: #ff5b55; @@ -311,7 +299,7 @@ $eventbubble-reply-color: $quaternary-content; font-family: $font-family; font-size: $font-14px; color: $button-fg-color; - background-color: $accent-color; + background-color: $accent; width: auto; padding: 7px; padding-left: 1.5em; @@ -325,7 +313,7 @@ $eventbubble-reply-color: $quaternary-content; } @define-mixin mx_DialogButton_danger { - background-color: $accent-color; + background-color: $accent; } @define-mixin mx_DialogButton_small { @@ -337,13 +325,13 @@ $eventbubble-reply-color: $quaternary-content; @define-mixin mx_DialogButton_secondary { // flip colours for the secondary ones font-weight: 600; - border: 1px solid $accent-color !important; - color: $accent-color; + border: 1px solid $accent !important; + color: $accent; background-color: $button-secondary-bg-color; } @define-mixin mx_Dialog_link { - color: $accent-color; + color: $accent; text-decoration: none; } From e2b59335cffb03d41058edb16ca6dc0a0d2f6189 Mon Sep 17 00:00:00 2001 From: Germain Date: Thu, 11 Nov 2021 13:56:44 +0000 Subject: [PATCH 008/118] Implement empty design list (#7115) --- res/css/views/right_panel/_ThreadPanel.scss | 58 +++++++++++++++++++++ src/components/structures/ThreadPanel.tsx | 29 ++++++++++- src/i18n/strings/en_EN.json | 3 ++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/res/css/views/right_panel/_ThreadPanel.scss b/res/css/views/right_panel/_ThreadPanel.scss index b890658bded..a0ba5512c49 100644 --- a/res/css/views/right_panel/_ThreadPanel.scss +++ b/res/css/views/right_panel/_ThreadPanel.scss @@ -290,3 +290,61 @@ limitations under the License. .mx_ThreadPanel_copyLinkToThread::before { mask-image: url('$(res)/img/element-icons/link.svg'); } + +.mx_ThreadPanel_empty { + border-radius: 8px; + background: $background; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: absolute; + top: 48px; + bottom: 8px; + left: 8px; + right: 8px; + padding: 20px; + + h2 { + color: $primary-content; + font-weight: 600; + font-size: $font-18px; + } + + p { + font-size: $font-15px; + color: $secondary-content; + } + + button { + border: none; + background: none; + color: $accent; + + &:hover, + &:active { + text-decoration: underline; + cursor: pointer; + } + } +} + +.mx_ThreadPanel_largeIcon { + width: 28px; + height: 28px; + padding: 18px; + background: $system; + border-radius: 50%; + + &::after { + content: ""; + width: inherit; + height: inherit; + mask-image: url('$(res)/img/element-icons/thread-summary.svg'); + mask-position: center; + display: inline-block; + mask-repeat: no-repeat; + mask-size: contain; + background-color: $secondary-content; + } +} diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index 3754e0d364d..5adaf617134 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -180,6 +180,30 @@ export const ThreadPanelHeader = ({ filterOption, setFilterOption }: {
    ; }; +interface EmptyThreadIProps { + filterOption: ThreadFilterType; + showAllThreadsCallback: () => void; +} + +const EmptyThread: React.FC = ({ filterOption, showAllThreadsCallback }) => { + return ; +}; + const ThreadPanel: React.FC = ({ roomId, onClose, permalinkCreator }) => { const mxClient = useContext(MatrixClientContext); const roomContext = useContext(RoomContext); @@ -216,7 +240,10 @@ const ThreadPanel: React.FC = ({ roomId, onClose, permalinkCreator }) => sendReadReceiptOnLoad={false} // No RR support in thread's MVP timelineSet={filteredTimelineSet} showUrlPreview={true} - empty={
    empty
    } + empty={ setFilterOption(ThreadFilterType.All)} + />} alwaysShowTimestamps={true} layout={Layout.Group} hideThreadedMessages={false} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1fab6eda59c..202b442e5aa 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3023,6 +3023,9 @@ "All threads": "All threads", "Shows all threads from current room": "Shows all threads from current room", "Show:": "Show:", + "Keep discussions organised with threads": "Keep discussions organised with threads", + "Threads help you keep conversations on-topic and easilytrack them over time. Create the first one by using the\"Reply in thread\" button on a message.": "Threads help you keep conversations on-topic and easilytrack them over time. Create the first one by using the\"Reply in thread\" button on a message.", + "Show all threads": "Show all threads", "Thread": "Thread", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", From f87e9d86a4089d348536b50f160fb032f8925da8 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 11 Nov 2021 14:34:55 +0000 Subject: [PATCH 009/118] Catch up MPollBody CSS with everything else that now uses just 'accent' (#7118) --- res/css/views/messages/_MPollBody.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/css/views/messages/_MPollBody.scss b/res/css/views/messages/_MPollBody.scss index 46051821a0a..cff1d46cb4c 100644 --- a/res/css/views/messages/_MPollBody.scss +++ b/res/css/views/messages/_MPollBody.scss @@ -85,13 +85,13 @@ limitations under the License. } .mx_MPollBody_option_checked { - border-color: $accent-color; + border-color: $accent; } .mx_StyledRadioButton_checked input[type="radio"] + div { border-width: 2px; - border-color: $accent-color; - background-color: $accent-color; + border-color: $accent; + background-color: $accent; background-image: url('$(res)/img/element-icons/check-white.svg'); background-size: 12px; background-repeat: no-repeat; From 2a1a180ece3d0ce73fa7af4a7c720e6369141e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 11 Nov 2021 15:48:11 +0100 Subject: [PATCH 010/118] Fix hover tile border (#7117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "Update highlighted event line to prevent content jumping (#7090)" This reverts commit 0f8272c6a1588d37571e45b8dcf7f12678a34f32. * Fix message jumps Signed-off-by: Šimon Brandner --- res/css/views/right_panel/_ThreadPanel.scss | 1 - res/css/views/rooms/_EventTile.scss | 26 ++++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/res/css/views/right_panel/_ThreadPanel.scss b/res/css/views/right_panel/_ThreadPanel.scss index a0ba5512c49..369c57cdd21 100644 --- a/res/css/views/right_panel/_ThreadPanel.scss +++ b/res/css/views/right_panel/_ThreadPanel.scss @@ -184,7 +184,6 @@ limitations under the License. .mx_EventTile, .mx_EventListSummary { // Account for scrollbar when hovering - margin: 0 2px; padding-top: 0; .mx_ThreadInfo { diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index a018ec3f740..f7b55709563 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -221,25 +221,25 @@ $left-gutter: 64px; &:hover.mx_EventTile_verified .mx_EventTile_line, &:hover.mx_EventTile_unverified .mx_EventTile_line, &:hover.mx_EventTile_unknown .mx_EventTile_line { - padding-left: calc($left-gutter); + padding-left: calc($left-gutter - $selected-message-border-width); } &:hover.mx_EventTile_verified .mx_EventTile_line { - box-shadow: inset $selected-message-border-width 0 0 0 $e2e-verified-color; + border-left: $e2e-verified-color $selected-message-border-width solid; } &:hover.mx_EventTile_unverified .mx_EventTile_line { - box-shadow: inset $selected-message-border-width 0 0 0 $e2e-verified-color; + border-left: $e2e-unverified-color $selected-message-border-width solid; } &:hover.mx_EventTile_unknown .mx_EventTile_line { - box-shadow: inset $selected-message-border-width 0 0 0 $e2e-verified-color; + border-left: $e2e-unknown-color $selected-message-border-width solid; } &:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, &:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, &:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line { - padding-left: calc($left-gutter + 18px); + padding-left: calc($left-gutter + 18px - $selected-message-border-width); } /* End to end encryption stuff */ @@ -822,7 +822,7 @@ $left-gutter: 64px; .mx_EventTile_senderDetails { display: flex; align-items: center; - gap: 6px; + gap: calc(6px + $selected-message-border-width); a { flex: 1; @@ -846,17 +846,21 @@ $left-gutter: 64px; display: none; } - .mx_EventTile_line { - padding-left: 0 !important; - order: 10 !important; - } - .mx_EventTile { width: 100%; display: flex; flex-direction: column; padding-top: 0; + .mx_EventTile_line { + padding-left: $selected-message-border-width !important; + order: 10 !important; + } + + &:hover .mx_EventTile_line { + padding-left: 0 !important; + } + .mx_MessageTimestamp { left: auto; right: 2px !important; From 155157dba18b6b3d488356c90f53dd28f732bd45 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 11 Nov 2021 16:32:30 +0000 Subject: [PATCH 011/118] Expose power level control for m.space.child (#7120) --- .../views/settings/tabs/room/RolesRoomSettingsTab.tsx | 5 +++++ src/i18n/strings/en_EN.json | 1 + 2 files changed, 6 insertions(+) diff --git a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx index 637215682ea..3e2c68de3a6 100644 --- a/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/RolesRoomSettingsTab.tsx @@ -33,6 +33,7 @@ import { logger } from "matrix-js-sdk/src/logger"; interface IEventShowOpts { isState?: boolean; hideForSpace?: boolean; + hideForRoom?: boolean; } interface IPowerLevelDescriptor { @@ -46,6 +47,7 @@ const plEventsToShow: Record = { [EventType.RoomAvatar]: { isState: true }, [EventType.RoomName]: { isState: true }, [EventType.RoomCanonicalAlias]: { isState: true }, + [EventType.SpaceChild]: { isState: true, hideForRoom: true }, [EventType.RoomHistoryVisibility]: { isState: true, hideForSpace: true }, [EventType.RoomPowerLevels]: { isState: true }, [EventType.RoomTopic]: { isState: true }, @@ -223,6 +225,7 @@ export default class RolesRoomSettingsTab extends React.Component { [EventType.RoomCanonicalAlias]: isSpaceRoom ? _td("Change main address for the space") : _td("Change main address for the room"), + [EventType.SpaceChild]: _td("Manage rooms in this space"), [EventType.RoomHistoryVisibility]: _td("Change history visibility"), [EventType.RoomPowerLevels]: _td("Change permissions"), [EventType.RoomTopic]: isSpaceRoom ? _td("Change description") : _td("Change topic"), @@ -413,6 +416,8 @@ export default class RolesRoomSettingsTab extends React.Component { const eventPowerSelectors = Object.keys(eventsLevels).map((eventType, i) => { if (isSpaceRoom && plEventsToShow[eventType].hideForSpace) { return null; + } else if (!isSpaceRoom && plEventsToShow[eventType].hideForRoom) { + return null; } let label = plEventsToLabels[eventType]; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 202b442e5aa..61d4313b1c7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1488,6 +1488,7 @@ "Change room name": "Change room name", "Change main address for the space": "Change main address for the space", "Change main address for the room": "Change main address for the room", + "Manage rooms in this space": "Manage rooms in this space", "Change history visibility": "Change history visibility", "Change permissions": "Change permissions", "Change description": "Change description", From cd1f660d047e3f6a792966775ea0453876a0ac17 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 11 Nov 2021 16:42:32 +0000 Subject: [PATCH 012/118] Fix missing spaces in threads copy (#7119) * Fix missing spaces in threads copy Fixes: https://github.com/vector-im/element-web/issues/19702 Type: defect * i18n --- src/components/structures/ThreadPanel.tsx | 4 ++-- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index 5adaf617134..ed1b52db3e4 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -189,8 +189,8 @@ const EmptyThread: React.FC = ({ filterOption, showAllThreads return