diff --git a/res/css/structures/_RoomSearch.scss b/res/css/structures/_RoomSearch.scss index a35b7e35390..e1902dca179 100644 --- a/res/css/structures/_RoomSearch.scss +++ b/res/css/structures/_RoomSearch.scss @@ -35,6 +35,7 @@ limitations under the License. mask-repeat: no-repeat; background-color: $secondary-content; margin-left: 7px; + margin-bottom: 2px; } .mx_RoomSearch_input { @@ -44,14 +45,18 @@ limitations under the License. padding: 0; height: 100%; width: 100%; - font-size: $font-12px; - line-height: $font-16px; &:not(.mx_RoomSearch_inputExpanded)::placeholder { color: $tertiary-content !important; // !important to override default app-wide styles } } + .mx_RoomSearch_input, + .mx_RoomSearch_spotlightTriggerText { + font-size: $font-12px; + line-height: $font-16px; + } + &.mx_RoomSearch_hasQuery { border-color: $secondary-content; } @@ -108,4 +113,36 @@ limitations under the License. } } } + + &.mx_RoomSearch_spotlightTrigger { + cursor: pointer; + min-width: 0; + + .mx_RoomSearch_spotlightTriggerText { + color: $tertiary-content; + flex: 1; + min-width: 0; + // the following rules are to match that of a real input field + overflow: hidden; + margin: 9px; + font-weight: $font-semi-bold; + } + + &:hover { + background-color: $tertiary-content; + + .mx_RoomSearch_spotlightTriggerText { + color: $background; + } + + .mx_RoomSearch_shortcutPrompt { + background-color: $background; + color: $secondary-content; + } + + .mx_RoomSearch_icon { + background-color: $background; + } + } + } } diff --git a/res/css/views/beta/_BetaCard.scss b/res/css/views/beta/_BetaCard.scss index 268d7304ad6..dbbc008a069 100644 --- a/res/css/views/beta/_BetaCard.scss +++ b/res/css/views/beta/_BetaCard.scss @@ -56,6 +56,14 @@ limitations under the License. line-height: $font-15px; color: $secondary-content; margin-top: 20px; + + > h4 { + margin: 0; + } + + > p { + margin-top: 0; + } } } @@ -64,6 +72,7 @@ limitations under the License. width: 300px; object-fit: contain; height: 100%; + border-radius: 4px; } } @@ -90,6 +99,7 @@ limitations under the License. border-radius: 8px; text-transform: uppercase; font-size: 12px; + font-weight: $font-semi-bold; line-height: 15px; color: #FFFFFF; display: inline-block; diff --git a/res/css/views/dialogs/_SpotlightDialog.scss b/res/css/views/dialogs/_SpotlightDialog.scss index 9b65c2e01e3..f06ed59fb3d 100644 --- a/res/css/views/dialogs/_SpotlightDialog.scss +++ b/res/css/views/dialogs/_SpotlightDialog.scss @@ -223,18 +223,13 @@ limitations under the License. text-overflow: ellipsis; } - .mx_SpotlightDialog_recentSearches { - overflow-y: hidden; - height: calc(100% - 190px); - - > h4 > .mx_AccessibleButton_kind_link { - padding: 0; - float: right; - font-weight: normal; - font-size: $font-12px; - line-height: $font-15px; - color: $secondary-content; - } + .mx_SpotlightDialog_recentSearches > h4 > .mx_AccessibleButton_kind_link { + padding: 0; + float: right; + font-weight: normal; + font-size: $font-12px; + line-height: $font-15px; + color: $secondary-content; } .mx_SpotlightDialog_enterPrompt { @@ -253,28 +248,21 @@ limitations under the License. font-size: $font-12px; line-height: $font-15px; color: $secondary-content; - padding: 16px 16px 20px; + padding: 12px 16px 16px; display: flex; border-top: 1px solid $quinary-content; + .mx_BetaCard_betaPill { + margin-right: 12px; + height: min-content; + align-self: center; + } + > span { - position: relative; - padding-left: 20px; align-self: center; - &::before { - background-color: $secondary-content; - content: ""; - mask-repeat: no-repeat; - mask-position: center; - mask-size: contain; - mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); - width: 16px; - height: 16px; - position: absolute; - left: 0; - top: 50%; - transform: translateY(-50%); + .mx_AccessibleButton_kind_link_inline { + padding: 0; } } diff --git a/res/css/views/dialogs/_UserSettingsDialog.scss b/res/css/views/dialogs/_UserSettingsDialog.scss index b5331e40b1e..b013b4998af 100644 --- a/res/css/views/dialogs/_UserSettingsDialog.scss +++ b/res/css/views/dialogs/_UserSettingsDialog.scss @@ -54,7 +54,7 @@ limitations under the License. } .mx_UserSettingsDialog_labsIcon::before { - mask-image: url('$(res)/img/element-icons/settings/lab-flags.svg'); + mask-image: url('$(res)/img/element-icons/settings/flask.svg'); } .mx_UserSettingsDialog_mjolnirIcon::before { diff --git a/res/img/betas/new_search_experience.gif b/res/img/betas/new_search_experience.gif new file mode 100644 index 00000000000..6dc1e001f3e Binary files /dev/null and b/res/img/betas/new_search_experience.gif differ diff --git a/res/img/element-icons/settings/flask.svg b/res/img/element-icons/settings/flask.svg new file mode 100644 index 00000000000..7b8f2fda62b --- /dev/null +++ b/res/img/element-icons/settings/flask.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/settings/lab-flags.svg b/res/img/element-icons/settings/lab-flags.svg deleted file mode 100644 index b96aa17d261..00000000000 --- a/res/img/element-icons/settings/lab-flags.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/Rooms.ts b/src/Rooms.ts index 0b17cdcc438..8f085944589 100644 --- a/src/Rooms.ts +++ b/src/Rooms.ts @@ -158,6 +158,20 @@ function guessDMRoomTargetId(room: Room, myUserId: string): string { return oldestUser.userId; } +export function spaceContextDetailsText(space: Room): string { + if (!space.isSpaceRoom()) return undefined; + + const [parent, ...otherParents] = SpaceStore.instance.getKnownParents(space.roomId); + if (parent) { + return _t("%(spaceName)s and %(count)s others", { + spaceName: space.client.getRoom(parent).name, + count: otherParents.length, + }); + } + + return space.getCanonicalAlias(); +} + export function roomContextDetailsText(room: Room): string { if (room.isSpaceRoom()) return undefined; diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index fa3d7108527..df087e0415c 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -91,7 +91,7 @@ import { RoomUpdateCause } from "../../stores/room-list/models"; import SecurityCustomisations from "../../customisations/Security"; import Spinner from "../views/elements/Spinner"; import QuestionDialog from "../views/dialogs/QuestionDialog"; -import UserSettingsDialog from '../views/dialogs/UserSettingsDialog'; +import UserSettingsDialog, { UserTab } from '../views/dialogs/UserSettingsDialog'; import CreateGroupDialog from '../views/dialogs/CreateGroupDialog'; import CreateRoomDialog from '../views/dialogs/CreateRoomDialog'; import RoomDirectory from './RoomDirectory'; @@ -117,6 +117,7 @@ import { showSpaceInvite } from "../../utils/space"; import AccessibleButton from "../views/elements/AccessibleButton"; import { ActionPayload } from "../../dispatcher/payloads"; import { SummarizedNotificationState } from "../../stores/notifications/SummarizedNotificationState"; +import GenericToast from '../views/toasts/GenericToast'; /** constants for MatrixChat.state.view */ export enum Views { @@ -1551,6 +1552,42 @@ export default class MatrixChat extends React.PureComponent { showNotificationsToast(false); } + if (!localStorage.getItem("mx_seen_feature_spotlight_toast")) { + setTimeout(() => { + // Skip the toast if the beta is already enabled or the user has changed the setting from default + if (SettingsStore.getValue("feature_spotlight") || + SettingsStore.getValue("feature_spotlight", null, true) !== null) { + return; + } + + const key = "BETA_SPOTLIGHT_TOAST"; + ToastStore.sharedInstance().addOrReplaceToast({ + key, + title: _t("New search beta available"), + props: { + description: _t("We're testing a new search to make finding what you want quicker.\n"), + acceptLabel: _t("Learn more"), + onAccept: () => { + dis.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Labs, + }); + localStorage.setItem("mx_seen_feature_spotlight_toast", "true"); + ToastStore.sharedInstance().dismissToast(key); + }, + rejectLabel: _t("Dismiss"), + onReject: () => { + localStorage.setItem("mx_seen_feature_spotlight_toast", "true"); + ToastStore.sharedInstance().dismissToast(key); + }, + }, + icon: "labs", + component: GenericToast, + priority: 9, + }); + }, 5 * 60 * 1000); // show after 5 minutes to not overload user with toasts on launch + } + dis.fire(Action.FocusSendMessageComposer); this.setState({ ready: true, diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 1151b915ad3..4b58e42ad19 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -34,6 +34,7 @@ import SettingsStore from "../../settings/SettingsStore"; import Modal from "../../Modal"; import SpotlightDialog from "../views/dialogs/SpotlightDialog"; import { ALTERNATE_KEY_NAME, KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; +import ToastStore from "../../stores/ToastStore"; interface IProps { isMinimized: boolean; @@ -46,11 +47,13 @@ interface IProps { interface IState { query: string; focused: boolean; + spotlightBetaEnabled: boolean; } @replaceableComponent("structures.RoomSearch") export default class RoomSearch extends React.PureComponent { private readonly dispatcherRef: string; + private readonly betaRef: string; private inputRef: React.RefObject = createRef(); private searchFilter: NameFilterCondition = new NameFilterCondition(); @@ -60,11 +63,13 @@ export default class RoomSearch extends React.PureComponent { this.state = { query: "", focused: false, + spotlightBetaEnabled: SettingsStore.getValue("feature_spotlight"), }; this.dispatcherRef = defaultDispatcher.register(this.onAction); // clear filter when changing spaces, in future we may wish to maintain a filter per-space SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.clearInput); + this.betaRef = SettingsStore.watchSetting("feature_spotlight", null, this.onSpotlightChange); } public componentDidUpdate(prevProps: Readonly, prevState: Readonly): void { @@ -85,8 +90,18 @@ export default class RoomSearch extends React.PureComponent { public componentWillUnmount() { defaultDispatcher.unregister(this.dispatcherRef); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.clearInput); + SettingsStore.unwatchSetting(this.betaRef); } + private onSpotlightChange = () => { + const spotlightBetaEnabled = SettingsStore.getValue("feature_spotlight"); + if (this.state.spotlightBetaEnabled !== spotlightBetaEnabled) { + this.setState({ spotlightBetaEnabled }); + } + // in case the user was in settings at the 5-minute mark, dismiss the toast + ToastStore.sharedInstance().dismissToast("BETA_SPOTLIGHT_TOAST"); + }; + private openSpotlight() { Modal.createTrackedDialog("Spotlight", "", SpotlightDialog, {}, "mx_SpotlightDialog_wrapper", false, true); } @@ -95,11 +110,7 @@ export default class RoomSearch extends React.PureComponent { if (payload.action === Action.ViewRoom && payload.clear_search) { this.clearInput(); } else if (payload.action === 'focus_room_filter') { - if (SettingsStore.getValue("feature_spotlight")) { - this.openSpotlight(); - } else { - this.inputRef.current?.focus(); - } + this.focus(); } }; @@ -110,10 +121,10 @@ export default class RoomSearch extends React.PureComponent { }; private openSearch = () => { - if (SettingsStore.getValue("feature_spotlight")) { + if (this.state.spotlightBetaEnabled) { this.openSpotlight(); } else { - defaultDispatcher.dispatch({ action: "show_left_panel" }); + // dispatched as it needs handling by MatrixChat too defaultDispatcher.dispatch({ action: "focus_room_filter" }); } }; @@ -124,14 +135,8 @@ export default class RoomSearch extends React.PureComponent { }; private onFocus = (ev: React.FocusEvent) => { - if (SettingsStore.getValue("feature_spotlight")) { - ev.preventDefault(); - ev.stopPropagation(); - this.openSpotlight(); - } else { - this.setState({ focused: true }); - ev.target.select(); - } + this.setState({ focused: true }); + ev.target.select(); }; private onBlur = (ev: React.FocusEvent) => { @@ -159,7 +164,7 @@ export default class RoomSearch extends React.PureComponent { }; public focus = (): void => { - if (SettingsStore.getValue("feature_spotlight")) { + if (this.state.spotlightBetaEnabled) { this.openSpotlight(); } else { this.inputRef.current?.focus(); @@ -172,6 +177,7 @@ export default class RoomSearch extends React.PureComponent { 'mx_RoomSearch_hasQuery': this.state.query, 'mx_RoomSearch_focused': this.state.focused, 'mx_RoomSearch_minimized': this.props.isMinimized, + 'mx_RoomSearch_spotlightTrigger': this.state.spotlightBetaEnabled, }); const inputClasses = classNames({ @@ -180,8 +186,9 @@ export default class RoomSearch extends React.PureComponent { }); let icon = ( -
+
); + let input = ( { onBlur={this.onBlur} onChange={this.onChange} onKeyDown={this.onKeyDown} - placeholder={SettingsStore.getValue("feature_spotlight") ? _t("Search") : _t("Filter")} + placeholder={_t("Filter")} autoComplete="off" /> ); + let clearButton = ( { onClick={this.clearInput} /> ); - let shortcutPrompt =
+ + let shortcutPrompt =
{ isMac ? "⌘ K" : _t(ALTERNATE_KEY_NAME[Key.CONTROL]) + " K" }
; @@ -221,8 +230,18 @@ export default class RoomSearch extends React.PureComponent { shortcutPrompt = null; } + if (this.state.spotlightBetaEnabled) { + return + { icon } + { input &&
+ { _t("Search") } +
} + { shortcutPrompt } +
; + } + return ( -
+
{ icon } { input } { shortcutPrompt } diff --git a/src/components/views/beta/BetaCard.tsx b/src/components/views/beta/BetaCard.tsx index c2ba869ab47..c1d823d8769 100644 --- a/src/components/views/beta/BetaCard.tsx +++ b/src/components/views/beta/BetaCard.tsx @@ -14,8 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode, useState } from "react"; import classNames from "classnames"; +import { sleep } from "matrix-js-sdk/src/utils"; import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; @@ -26,6 +27,8 @@ import Modal from "../../../Modal"; import BetaFeedbackDialog from "../dialogs/BetaFeedbackDialog"; import SdkConfig from "../../../SdkConfig"; import SettingsFlag from "../elements/SettingsFlag"; +import { useFeatureEnabled } from "../../../hooks/useSettings"; +import InlineSpinner from "../elements/InlineSpinner"; // XXX: Keep this around for re-use in future Betas @@ -42,10 +45,10 @@ export const BetaPill = ({ onClick }: { onClick?: () => void }) => { })} tooltip={
- { _t("Spaces is a beta feature") } + { _t("This is a beta feature") }
- { _t("Tap for more info") } + { _t("Click for more info") }
} onClick={onClick} @@ -67,10 +70,20 @@ export const BetaPill = ({ onClick }: { onClick?: () => void }) => { const BetaCard = ({ title: titleOverride, featureId }: IProps) => { const info = SettingsStore.getBetaInfo(featureId); + const value = useFeatureEnabled(featureId); + const [busy, setBusy] = useState(false); if (!info) return null; // Beta is invalid/disabled - const { title, caption, disclaimer, image, feedbackLabel, feedbackSubheading, extraSettings } = info; - const value = SettingsStore.getValue(featureId); + const { + title, + caption, + disclaimer, + image, + feedbackLabel, + feedbackSubheading, + extraSettings, + requiresRefresh, + } = info; let feedbackButton; if (value && feedbackLabel && feedbackSubheading && SdkConfig.get().bug_report_endpoint_url) { @@ -84,6 +97,15 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => { ; } + let content: ReactNode; + if (busy) { + content = ; + } else if (value) { + content = _t("Leave the beta"); + } else { + content = _t("Join the beta"); + } + return
@@ -91,14 +113,26 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => { { titleOverride || _t(title) } - { _t(caption) } + { caption() }
{ feedbackButton } SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)} + onClick={async () => { + setBusy(true); + // make it look like we're doing something for two seconds, + // otherwise users think clicking did nothing + if (!requiresRefresh) { + await sleep(2000); + } + await SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value); + if (!requiresRefresh) { + setBusy(false); + } + }} kind={feedbackButton ? "primary_outline" : "primary"} + disabled={busy} > - { value ? _t("Leave the beta") : _t("Join the beta") } + { content }
{ disclaimer &&
diff --git a/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx b/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx index d68569b1265..17fab187d0a 100644 --- a/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx +++ b/src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx @@ -52,8 +52,8 @@ const GenericFeatureFeedbackDialog: React.FC = ({ Modal.createTrackedDialog("Feedback Sent", rageshakeLabel, InfoDialog, { title, - description: _t("Thank you for your feedback, we really appreciate it."), - button: _t("Done"), + description: _t("Feedback sent! Thanks, we appreciate it!"), + button: _t("Close"), hasCloseButton: false, fixedWidth: false, }); @@ -68,7 +68,7 @@ const GenericFeatureFeedbackDialog: React.FC = ({ { subheading }   { _t("Your platform and username will be noted to help us use your feedback as much as we can.") } - +   { children }
diff --git a/src/components/views/dialogs/SpotlightDialog.tsx b/src/components/views/dialogs/SpotlightDialog.tsx index 6ec05124def..f18d8206d00 100644 --- a/src/components/views/dialogs/SpotlightDialog.tsx +++ b/src/components/views/dialogs/SpotlightDialog.tsx @@ -18,6 +18,7 @@ import React, { ChangeEvent, ComponentProps, KeyboardEvent, + RefObject, useCallback, useContext, useEffect, @@ -52,11 +53,10 @@ import DMRoomMap from "../../../utils/DMRoomMap"; import { mediaFromMxc } from "../../../customisations/Media"; import BaseAvatar from "../avatars/BaseAvatar"; import Spinner from "../elements/Spinner"; -import { roomContextDetailsText } from "../../../Rooms"; +import { roomContextDetailsText, spaceContextDetailsText } from "../../../Rooms"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import { Action } from "../../../dispatcher/actions"; import Modal from "../../../Modal"; -import GenericFeatureFeedbackDialog from "./GenericFeatureFeedbackDialog"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import RoomViewStore from "../../../stores/RoomViewStore"; import { showStartChatInviteDialog } from "../../../RoomInvite"; @@ -64,6 +64,10 @@ import SettingsStore from "../../../settings/SettingsStore"; import { SettingLevel } from "../../../settings/SettingLevel"; import NotificationBadge from "../rooms/NotificationBadge"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; +import { BetaPill } from "../beta/BetaCard"; +import { UserTab } from "./UserSettingsDialog"; +import BetaFeedbackDialog from "./BetaFeedbackDialog"; +import SdkConfig from "../../../SdkConfig"; const MAX_RECENT_SEARCHES = 10; const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons @@ -106,10 +110,10 @@ const useRecentSearches = (): [Room[], () => void] => { }; const ResultDetails = ({ room }: { room: Room }) => { - const roomContextDetails = roomContextDetailsText(room); - if (roomContextDetails) { + const contextDetails = room.isSpaceRoom() ? spaceContextDetailsText(room) : roomContextDetailsText(room); + if (contextDetails) { return
- { roomContextDetails } + { contextDetails }
; } @@ -166,6 +170,10 @@ const useSpaceResults = (space?: Room, query?: string): [IHierarchyRoom[], boole return [results, hierarchy?.loading ?? false]; }; +function refIsForRecentlyViewed(ref: RefObject): boolean { + return ref.current?.id.startsWith("mx_SpotlightDialog_button_recentlyViewed_"); +} + const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => { const cli = MatrixClientPeg.get(); const rovingContext = useContext(RovingTabIndexContext); @@ -245,7 +253,7 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => viewRoom(room.roomId, true); }} > - + { room.name } @@ -385,9 +393,10 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => viewRoom(room.roomId, true); }} > - + { room.name } +
)) } @@ -450,6 +459,8 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => }; const onKeyDown = (ev: KeyboardEvent) => { + let ref: RefObject; + switch (ev.key) { case Key.ARROW_UP: case Key.ARROW_DOWN: @@ -457,18 +468,36 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => ev.preventDefault(); if (rovingContext.state.refs.length > 0) { - const idx = rovingContext.state.refs.indexOf(rovingContext.state.activeRef); - const ref = findSiblingElement(rovingContext.state.refs, idx + (ev.key === Key.ARROW_UP ? -1 : 1)); - - if (ref) { - rovingContext.dispatch({ - type: Type.SetFocus, - payload: { ref }, - }); - ref.current?.scrollIntoView({ - block: "nearest", - }); + let refs = rovingContext.state.refs; + if (!query) { + // If the current selection is not in the recently viewed row then only include the + // first recently viewed so that is the target when the user is switching into recently viewed. + const keptRecentlyViewedRef = refIsForRecentlyViewed(rovingContext.state.activeRef) + ? rovingContext.state.activeRef + : refs.find(refIsForRecentlyViewed); + // exclude all other recently viewed items from the list so up/down arrows skip them + refs = refs.filter(ref => ref === keptRecentlyViewedRef || !refIsForRecentlyViewed(ref)); } + + const idx = refs.indexOf(rovingContext.state.activeRef); + ref = findSiblingElement(refs, idx + (ev.key === Key.ARROW_UP ? -1 : 1)); + } + break; + + case Key.ARROW_LEFT: + case Key.ARROW_RIGHT: + // only handle these keys when we are in the recently viewed row of options + if (!query && + rovingContext.state.refs.length > 0 && + refIsForRecentlyViewed(rovingContext.state.activeRef) + ) { + // we only intercept left/right arrows when the field is empty, and they'd do nothing anyway + ev.stopPropagation(); + ev.preventDefault(); + + const refs = rovingContext.state.refs.filter(refIsForRecentlyViewed); + const idx = refs.indexOf(rovingContext.state.activeRef); + ref = findSiblingElement(refs, idx + (ev.key === Key.ARROW_LEFT ? -1 : 1)); } break; @@ -478,16 +507,34 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => rovingContext.state.activeRef?.current?.click(); break; } + + if (ref) { + rovingContext.dispatch({ + type: Type.SetFocus, + payload: { ref }, + }); + ref.current?.scrollIntoView({ + block: "nearest", + }); + } }; + const openFeedback = SdkConfig.get().bug_report_endpoint_url ? () => { + Modal.createTrackedDialog("Spotlight Feedback", "feature_spotlight", BetaFeedbackDialog, { + featureId: "feature_spotlight", + }); + } : null; + const activeDescendant = rovingContext.state.activeRef?.current?.id; return <>
- { _t("Use to scroll results", {}, { + { _t("Use to scroll", {}, { arrows: () => <>
+ { !query &&
} + { !query &&
} , }) }
@@ -517,24 +564,24 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) =>
- - { activeSpace - ? _t("Searching rooms and chats you're in and %(spaceName)s", { spaceName: activeSpace.name }) - : _t("Searching rooms and chats you're in") } - - { + defaultDispatcher.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Labs, + }); + onFinished(); + }} /> + { openFeedback && _t("Results not as expected? Please give feedback.", {}, { + a: sub => + { sub } + , + }) } + { openFeedback && { - Modal.createTrackedDialog("Spotlight Feedback", "", GenericFeatureFeedbackDialog, { - title: _t("Spotlight search feedback"), - subheading: _t("Thank you for trying Spotlight search. " + - "Your feedback will help inform the next versions."), - rageshakeLabel: "spotlight-feedback", - }); - }} + onClick={openFeedback} > { _t("Feedback") } - + }
; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 97b7a0e37b5..8bdb9e89caf 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -885,7 +885,15 @@ "Show extensible event representation of events": "Show extensible event representation of events", "Show info about bridges in room settings": "Show info about bridges in room settings", "Use new room breadcrumbs": "Use new room breadcrumbs", - "New spotlight search experience": "New spotlight search experience", + "New search experience": "New search experience", + "The new search": "The new search", + "A new, quick way to search spaces and rooms you're in.": "A new, quick way to search spaces and rooms you're in.", + "This feature is a work in progress, we'd love to hear your feedback.": "This feature is a work in progress, we'd love to hear your feedback.", + "How can I give feedback?": "How can I give feedback?", + "To feedback, join the beta, start a search and click on feedback.": "To feedback, join the beta, start a search and click on feedback.", + "How can I leave the beta?": "How can I leave the beta?", + "To leave, just return to this page or click on the beta badge when you search.": "To leave, just return to this page or click on the beta badge when you search.", + "Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Thank you for trying the beta, please go into as much detail as you can so we can improve it.", "Right panel stays open (defaults to room member list)": "Right panel stays open (defaults to room member list)", "Jump to date (adds /jumptodate and jump to date headers)": "Jump to date (adds /jumptodate and jump to date headers)", "Don't send read receipts": "Don't send read receipts", @@ -2570,8 +2578,7 @@ "Forward message": "Forward message", "Message preview": "Message preview", "Search for rooms or people": "Search for rooms or people", - "Thank you for your feedback, we really appreciate it.": "Thank you for your feedback, we really appreciate it.", - "Done": "Done", + "Feedback sent! Thanks, we appreciate it!": "Feedback sent! Thanks, we appreciate it!", "You may contact me if you have any follow up questions": "You may contact me if you have any follow up questions", "Confirm abort of host creation": "Confirm abort of host creation", "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Are you sure you wish to abort creation of the host? The process cannot be continued.", @@ -2779,11 +2786,8 @@ "To search messages, look for this icon at the top of a room ": "To search messages, look for this icon at the top of a room ", "Recent searches": "Recent searches", "Clear": "Clear", - "Use to scroll results": "Use to scroll results", - "Searching rooms and chats you're in and %(spaceName)s": "Searching rooms and chats you're in and %(spaceName)s", - "Searching rooms and chats you're in": "Searching rooms and chats you're in", - "Spotlight search feedback": "Spotlight search feedback", - "Thank you for trying Spotlight search. Your feedback will help inform the next versions.": "Thank you for trying Spotlight search. Your feedback will help inform the next versions.", + "Use to scroll": "Use to scroll", + "Results not as expected? Please give feedback.": "Results not as expected? Please give feedback.", "To help us prevent this in future, please send us logs.": "To help us prevent this in future, please send us logs.", "Missing session data": "Missing session data", "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.", @@ -2803,6 +2807,7 @@ "Not Trusted": "Not Trusted", "Manually Verify by Text": "Manually Verify by Text", "Interactively verify by Emoji": "Interactively verify by Emoji", + "Done": "Done", "Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)", "Upload files": "Upload files", "Upload all": "Upload all", @@ -2900,8 +2905,8 @@ "Revoke permissions": "Revoke permissions", "Move left": "Move left", "Move right": "Move right", - "Spaces is a beta feature": "Spaces is a beta feature", - "Tap for more info": "Tap for more info", + "This is a beta feature": "This is a beta feature", + "Click for more info": "Click for more info", "Beta": "Beta", "Leave the beta": "Leave the beta", "Join the beta": "Join the beta", @@ -3037,6 +3042,8 @@ "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", "Unable to copy room link": "Unable to copy room link", "Unable to copy a link to the room to the clipboard.": "Unable to copy a link to the room to the clipboard.", + "New search beta available": "New search beta available", + "We're testing a new search to make finding what you want quicker.\n": "We're testing a new search to make finding what you want quicker.\n", "Signed Out": "Signed Out", "For security, this session has been signed out. Please sign in again.": "For security, this session has been signed out. Please sign in again.", "Terms and Conditions": "Terms and Conditions", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index f60587a7e7f..d3400129394 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -16,9 +16,9 @@ limitations under the License. */ import { MatrixClient } from 'matrix-js-sdk/src/client'; -import { ReactNode } from "react"; +import React, { ReactNode } from "react"; -import { _td } from '../languageHandler'; +import { _t, _td } from '../languageHandler'; import { NotificationBodyEnabledController, NotificationsEnabledController, @@ -41,6 +41,7 @@ import ReducedMotionController from './controllers/ReducedMotionController'; import IncompatibleController from "./controllers/IncompatibleController"; import { ImageSize } from "./enums/ImageSize"; import { MetaSpace } from "../stores/spaces"; +import SdkConfig from "../SdkConfig"; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times const LEVELS_ROOM_SETTINGS = [ @@ -155,12 +156,13 @@ interface IBaseSetting { // XXX: Keep this around for re-use in future Betas betaInfo?: { title: string; // _td - caption: string; // _td + caption: () => ReactNode; disclaimer?: (enabled: boolean) => ReactNode; image: string; // require(...) feedbackSubheading?: string; feedbackLabel?: string; extraSettings?: string[]; + requiresRefresh?: boolean; }; } @@ -336,8 +338,27 @@ export const SETTINGS: {[setting: string]: ISetting} = { isFeature: true, labsGroup: LabGroup.Rooms, supportedLevels: LEVELS_FEATURE, - displayName: _td("New spotlight search experience"), - default: false, + displayName: _td("New search experience"), + default: false, + betaInfo: { + title: _td("The new search"), + caption: () => <> +

{ _t("A new, quick way to search spaces and rooms you're in.") }

+

{ _t("This feature is a work in progress, we'd love to hear your feedback.") }

+ , + disclaimer: () => <> + { SdkConfig.get().bug_report_endpoint_url && <> +

{ _t("How can I give feedback?") }

+

{ _t("To feedback, join the beta, start a search and click on feedback.") }

+ } +

{ _t("How can I leave the beta?") }

+

{ _t("To leave, just return to this page or click on the beta badge when you search.") }

+ , + feedbackLabel: "spotlight-feedback", + feedbackSubheading: _td("Thank you for trying the beta, " + + "please go into as much detail as you can so we can improve it."), + image: require("../../res/img/betas/new_search_experience.gif"), + }, }, "feature_right_panel_default_open": { isFeature: true, diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 63b00731916..35961b104b8 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -95,7 +95,7 @@ const getRoomFn: FetchRoomFn = (room: Room) => { export class SpaceStoreClass extends AsyncStoreWithClient { // The spaces representing the roots of the various tree-like hierarchies private rootSpaces: Room[] = []; - // Map from room ID to set of spaces which list it as a child + // Map from room/space ID to set of spaces which list it as a child private parentMap = new EnhancedMap>(); // Map from SpaceKey to SpaceNotificationState instance representing that space private notificationStateMap = new Map();