{ _t("Preferences") }
-
- { _t("Room list") }
- { this.renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS) }
-
+ { !SettingsStore.getValue("feature_breadcrumbs_v2") &&
+
+ { _t("Room list") }
+ { this.renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS) }
+
+ }
{ _t("Spaces") }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 7a471ccfd3..a905bba635 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -850,6 +850,7 @@
"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",
+ "Use new room breadcrumbs": "Use new room breadcrumbs",
"Don't send read receipts": "Don't send read receipts",
"Font size": "Font size",
"Use custom size": "Use custom size",
@@ -1693,6 +1694,7 @@
"Unknown": "Unknown",
"Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s",
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Seen by %(displayName)s (%(userName)s) at %(dateTime)s",
+ "Recently viewed": "Recently viewed",
"Replying": "Replying",
"Room %(name)s": "Room %(name)s",
"Recently visited rooms": "Recently visited rooms",
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 3ae546ab56..2e008eaf3d 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -355,6 +355,13 @@ export const SETTINGS: {[setting: string]: ISetting} = {
new ReloadOnChangeController(),
]),
},
+ "feature_breadcrumbs_v2": {
+ isFeature: true,
+ labsGroup: LabGroup.Rooms,
+ supportedLevels: LEVELS_FEATURE,
+ displayName: _td("Use new room breadcrumbs"),
+ default: false,
+ },
"RoomList.backgroundImage": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: null,
@@ -711,6 +718,7 @@ export const SETTINGS: {[setting: string]: ISetting} = {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td("Show shortcuts to recently viewed rooms above the room list"),
default: true,
+ controller: new IncompatibleController("feature_breadcrumbs_v2", true),
},
"showHiddenEventsInTimeline": {
displayName: _td("Show hidden events in timeline"),
diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts
index 5f93c8fe68..b9cf33c6f6 100644
--- a/src/stores/BreadcrumbsStore.ts
+++ b/src/stores/BreadcrumbsStore.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2020 The Matrix.org Foundation C.I.C.
+Copyright 2020 - 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.
@@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import SettingsStore from "../settings/SettingsStore";
import { Room } from "matrix-js-sdk/src/models/room";
+import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
+
+import SettingsStore from "../settings/SettingsStore";
import { ActionPayload } from "../dispatcher/payloads";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
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 "./spaces/SpaceStore";
import { Action } from "../dispatcher/actions";
import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";
@@ -44,6 +44,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient {
SettingsStore.monitorSetting("breadcrumb_rooms", null);
SettingsStore.monitorSetting("breadcrumbs", null);
+ SettingsStore.monitorSetting("feature_breadcrumbs_v2", null);
}
public static get instance(): BreadcrumbsStore {
@@ -58,8 +59,9 @@ export class BreadcrumbsStore extends AsyncStoreWithClient {
return this.state.enabled && this.meetsRoomRequirement;
}
- private get meetsRoomRequirement(): boolean {
- return this.matrixClient && this.matrixClient.getVisibleRooms().length >= 20;
+ public get meetsRoomRequirement(): boolean {
+ if (SettingsStore.getValue("feature_breadcrumbs_v2")) return true;
+ return this.matrixClient?.getVisibleRooms().length >= 20;
}
protected async onAction(payload: ActionPayload) {
@@ -69,7 +71,9 @@ export class BreadcrumbsStore extends AsyncStoreWithClient {
const settingUpdatedPayload = payload as SettingUpdatedPayload;
if (settingUpdatedPayload.settingName === 'breadcrumb_rooms') {
await this.updateRooms();
- } else if (settingUpdatedPayload.settingName === 'breadcrumbs') {
+ } else if (settingUpdatedPayload.settingName === 'breadcrumbs' ||
+ settingUpdatedPayload.settingName === 'feature_breadcrumbs_v2'
+ ) {
await this.updateState({ enabled: SettingsStore.getValue("breadcrumbs", null) });
}
} else if (payload.action === Action.ViewRoom) {
@@ -126,7 +130,6 @@ export class BreadcrumbsStore extends AsyncStoreWithClient {
}
private async appendRoom(room: Room) {
- if (SpaceStore.spacesEnabled && room.isSpaceRoom()) return; // hide space rooms
let updated = false;
const rooms = (this.state.rooms || []).slice(); // cheap clone
diff --git a/test/components/views/elements/InteractiveTooltip-test.ts b/test/components/views/elements/InteractiveTooltip-test.ts
new file mode 100644
index 0000000000..fcf3da8eb5
--- /dev/null
+++ b/test/components/views/elements/InteractiveTooltip-test.ts
@@ -0,0 +1,162 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import '../../../skinned-sdk';
+import { Direction, mouseWithinRegion } from "../../../../src/components/views/elements/InteractiveTooltip";
+
+describe("InteractiveTooltip", () => {
+ describe("mouseWithinRegion", () => {
+ it("direction=left", () => {
+ const targetRect = {
+ width: 20,
+ height: 20,
+ top: 300,
+ right: 370,
+ bottom: 320,
+ left: 350,
+ } as DOMRect;
+
+ const contentRect = {
+ width: 100,
+ height: 400,
+ top: 100,
+ right: 200,
+ bottom: 500,
+ left: 100,
+ } as DOMRect;
+
+ // just within top left corner of contentRect
+ expect(mouseWithinRegion(101, 101, Direction.Left, targetRect, contentRect)).toBe(true);
+ // just outside top left corner of contentRect, within buffer
+ expect(mouseWithinRegion(101, 90, Direction.Left, targetRect, contentRect)).toBe(true);
+ // just within top right corner of targetRect
+ expect(mouseWithinRegion(369, 301, Direction.Left, targetRect, contentRect)).toBe(true);
+ // within the top triangular portion of the trapezoid
+ expect(mouseWithinRegion(300, 200, Direction.Left, targetRect, contentRect)).toBe(true);
+ // within the bottom triangular portion of the trapezoid
+ expect(mouseWithinRegion(300, 350, Direction.Left, targetRect, contentRect)).toBe(true);
+ // outside the top triangular portion of the trapezoid
+ expect(mouseWithinRegion(300, 140, Direction.Left, targetRect, contentRect)).toBe(false);
+ // outside the bottom triangular portion of the trapezoid
+ expect(mouseWithinRegion(300, 460, Direction.Left, targetRect, contentRect)).toBe(false);
+ });
+
+ it("direction=right", () => {
+ const targetRect = {
+ width: 20,
+ height: 20,
+ top: 300,
+ right: 370,
+ bottom: 320,
+ left: 350,
+ } as DOMRect;
+
+ const contentRect = {
+ width: 100,
+ height: 400,
+ top: 100,
+ right: 620,
+ bottom: 500,
+ left: 520,
+ } as DOMRect;
+
+ // just within top right corner of contentRect
+ expect(mouseWithinRegion(619, 101, Direction.Right, targetRect, contentRect)).toBe(true);
+ // just outside top right corner of contentRect, within buffer
+ expect(mouseWithinRegion(619, 90, Direction.Right, targetRect, contentRect)).toBe(true);
+ // just within top left corner of targetRect
+ expect(mouseWithinRegion(351, 301, Direction.Right, targetRect, contentRect)).toBe(true);
+ // within the top triangular portion of the trapezoid
+ expect(mouseWithinRegion(420, 200, Direction.Right, targetRect, contentRect)).toBe(true);
+ // within the bottom triangular portion of the trapezoid
+ expect(mouseWithinRegion(420, 350, Direction.Right, targetRect, contentRect)).toBe(true);
+ // outside the top triangular portion of the trapezoid
+ expect(mouseWithinRegion(420, 140, Direction.Right, targetRect, contentRect)).toBe(false);
+ // outside the bottom triangular portion of the trapezoid
+ expect(mouseWithinRegion(420, 460, Direction.Right, targetRect, contentRect)).toBe(false);
+ });
+
+ it("direction=top", () => {
+ const targetRect = {
+ width: 20,
+ height: 20,
+ top: 300,
+ right: 370,
+ bottom: 320,
+ left: 350,
+ } as DOMRect;
+
+ const contentRect = {
+ width: 400,
+ height: 100,
+ top: 100,
+ right: 550,
+ bottom: 200,
+ left: 150,
+ } as DOMRect;
+
+ // just within top right corner of contentRect
+ expect(mouseWithinRegion(549, 101, Direction.Top, targetRect, contentRect)).toBe(true);
+ // just outside top right corner of contentRect, within buffer
+ expect(mouseWithinRegion(549, 99, Direction.Top, targetRect, contentRect)).toBe(true);
+ // just within bottom left corner of targetRect
+ expect(mouseWithinRegion(351, 319, Direction.Top, targetRect, contentRect)).toBe(true);
+ // within the left triangular portion of the trapezoid
+ expect(mouseWithinRegion(240, 260, Direction.Top, targetRect, contentRect)).toBe(true);
+ // within the right triangular portion of the trapezoid
+ expect(mouseWithinRegion(480, 260, Direction.Top, targetRect, contentRect)).toBe(true);
+ // outside the left triangular portion of the trapezoid
+ expect(mouseWithinRegion(220, 260, Direction.Top, targetRect, contentRect)).toBe(false);
+ // outside the right triangular portion of the trapezoid
+ expect(mouseWithinRegion(500, 260, Direction.Top, targetRect, contentRect)).toBe(false);
+ });
+
+ it("direction=bottom", () => {
+ const targetRect = {
+ width: 20,
+ height: 20,
+ top: 300,
+ right: 370,
+ bottom: 320,
+ left: 350,
+ } as DOMRect;
+
+ const contentRect = {
+ width: 400,
+ height: 100,
+ top: 420,
+ right: 550,
+ bottom: 520,
+ left: 150,
+ } as DOMRect;
+
+ // just within bottom left corner of contentRect
+ expect(mouseWithinRegion(101, 519, Direction.Bottom, targetRect, contentRect)).toBe(true);
+ // just outside bottom left corner of contentRect, within buffer
+ expect(mouseWithinRegion(101, 521, Direction.Bottom, targetRect, contentRect)).toBe(true);
+ // just within top left corner of targetRect
+ expect(mouseWithinRegion(351, 301, Direction.Bottom, targetRect, contentRect)).toBe(true);
+ // within the left triangular portion of the trapezoid
+ expect(mouseWithinRegion(240, 360, Direction.Bottom, targetRect, contentRect)).toBe(true);
+ // within the right triangular portion of the trapezoid
+ expect(mouseWithinRegion(480, 360, Direction.Bottom, targetRect, contentRect)).toBe(true);
+ // outside the left triangular portion of the trapezoid
+ expect(mouseWithinRegion(220, 360, Direction.Bottom, targetRect, contentRect)).toBe(false);
+ // outside the right triangular portion of the trapezoid
+ expect(mouseWithinRegion(500, 360, Direction.Bottom, targetRect, contentRect)).toBe(false);
+ });
+ });
+});