From 44198ea97dd229d95e5f8542b1aa84ff9642377c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 4 Apr 2019 15:06:03 -0600 Subject: [PATCH 1/3] Sync breadcrumb rooms through account data Fixes https://github.com/vector-im/riot-web/issues/9315 Other clients would need to listen for and update im.vector.riot.breadcrumb_rooms in account data. --- src/components/views/rooms/RoomBreadcrumbs.js | 96 ++++++++++++++----- src/settings/Settings.js | 4 + .../handlers/AccountSettingsHandler.js | 18 ++++ 3 files changed, 94 insertions(+), 24 deletions(-) diff --git a/src/components/views/rooms/RoomBreadcrumbs.js b/src/components/views/rooms/RoomBreadcrumbs.js index 314b2912cd..60dae54e5c 100644 --- a/src/components/views/rooms/RoomBreadcrumbs.js +++ b/src/components/views/rooms/RoomBreadcrumbs.js @@ -24,6 +24,7 @@ import classNames from 'classnames'; import sdk from "../../../index"; import * as RoomNotifs from '../../../RoomNotifs'; import * as FormattingUtils from "../../../utils/FormattingUtils"; +import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; const MAX_ROOMS = 20; @@ -38,22 +39,22 @@ export default class RoomBreadcrumbs extends React.Component { componentWillMount() { this._dispatcherRef = dis.register(this.onAction); - const roomStr = localStorage.getItem("mx_breadcrumb_rooms"); - if (roomStr) { - try { - const roomIds = JSON.parse(roomStr); - this.setState({ - rooms: roomIds.map((r) => { - return { - room: MatrixClientPeg.get().getRoom(r), - animated: false, - }; - }).filter((r) => r.room), - }); - } catch (e) { - console.error("Failed to parse breadcrumbs:", e); + let storedRooms = SettingsStore.getValue("breadcrumb_rooms"); + if (!storedRooms || !storedRooms.length) { + // Fallback to the rooms stored in localstorage for those who would have had this. + // TODO: Remove this after a bit - the feature was only on develop, so a few weeks should be plenty time. + const roomStr = localStorage.getItem("mx_breadcrumb_rooms"); + if (roomStr) { + try { + storedRooms = JSON.parse(roomStr); + } catch (e) { + console.error("Failed to parse breadcrumbs:", e); + } } } + this._loadRoomIds(storedRooms || []); + + this._settingWatchRef = SettingsStore.watchSetting("breadcrumb_rooms", null, this.onBreadcrumbsChanged); MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership); MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt); @@ -64,6 +65,8 @@ export default class RoomBreadcrumbs extends React.Component { componentWillUnmount() { dis.unregister(this._dispatcherRef); + SettingsStore.unwatchSetting(this._settingWatchRef); + const client = MatrixClientPeg.get(); if (client) { client.removeListener("Room.myMembership", this.onMyMembership); @@ -84,8 +87,8 @@ export default class RoomBreadcrumbs extends React.Component { } } - const roomStr = JSON.stringify(rooms.map((r) => r.room.roomId)); - localStorage.setItem("mx_breadcrumb_rooms", roomStr); + const roomIds = rooms.map((r) => r.room.roomId); + SettingsStore.setValue("breadcrumb_rooms", null, SettingLevel.ACCOUNT, roomIds); } onAction(payload) { @@ -125,17 +128,48 @@ export default class RoomBreadcrumbs extends React.Component { } }; - _calculateRoomBadges(room) { - if (!room) return; + onBreadcrumbsChanged = (settingName, roomId, level, valueAtLevel, value) => { + if (!value) return; - const rooms = this.state.rooms.slice(); - const roomModel = rooms.find((r) => r.room.roomId === room.roomId); - if (!roomModel) return; // No applicable room, so don't do math on it + const currentState = this.state.rooms.map((r) => r.room.roomId); + if (currentState.length === value.length) { + let changed = false; + for (let i = 0; i < currentState.length; i++) { + if (currentState[i] !== value[i]) { + changed = true; + break; + } + } + if (!changed) return; + } + + this._loadRoomIds(value); + }; + + _loadRoomIds(roomIds) { + // If we're here, the list changed. + const rooms = roomIds.map((r) => MatrixClientPeg.get().getRoom(r)).filter((r) => r).map((r) => { + const badges = this._calculateBadgesForRoom(r) || {}; + return { + room: r, + animated: false, + ...badges, + }; + }); + this.setState({ + rooms: rooms, + }); + } + + _calculateBadgesForRoom(room) { + if (!room) return null; // Reset the notification variables for simplicity - roomModel.redBadge = false; - roomModel.formattedCount = "0"; - roomModel.showCount = false; + const roomModel = { + redBadge: false, + formattedCount: "0", + showCount: false, + }; const notifState = RoomNotifs.getRoomNotifsState(room.roomId); if (RoomNotifs.MENTION_BADGE_STATES.includes(notifState)) { @@ -155,6 +189,20 @@ export default class RoomBreadcrumbs extends React.Component { } } + return roomModel; + } + + _calculateRoomBadges(room) { + if (!room) return; + + const rooms = this.state.rooms.slice(); + const roomModel = rooms.find((r) => r.room.roomId === room.roomId); + if (!roomModel) return; // No applicable room, so don't do math on it + + const badges = this._calculateBadgesForRoom(room); + if (!badges) return; // No badges for some reason + + Object.assign(roomModel, badges); this.setState({rooms}); } diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 6e17ffbbd7..098ef2c17b 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -258,6 +258,10 @@ export const SETTINGS = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, default: "en", }, + "breadcrumb_rooms": { + supportedLevels: ['account'], + default: [], + }, "analyticsOptIn": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, displayName: _td('Send analytics data'), diff --git a/src/settings/handlers/AccountSettingsHandler.js b/src/settings/handlers/AccountSettingsHandler.js index 675b2f8ead..71cef52c4e 100644 --- a/src/settings/handlers/AccountSettingsHandler.js +++ b/src/settings/handlers/AccountSettingsHandler.js @@ -19,6 +19,8 @@ import MatrixClientPeg from '../../MatrixClientPeg'; import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; import {SettingLevel} from "../SettingsStore"; +const BREADCRUMBS_EVENT_TYPE = "im.vector.riot.breadcrumb_rooms"; + /** * Gets and sets settings at the "account" level for the current user. * This handler does not make use of the roomId parameter. @@ -55,6 +57,9 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa const val = event.getContent()[settingName]; this._watchers.notifyUpdate(settingName, null, SettingLevel.ACCOUNT, val); } + } else if (event.getType() === BREADCRUMBS_EVENT_TYPE) { + const val = event.getContent()['rooms'] || []; + this._watchers.notifyUpdate("breadcrumb_rooms", null, SettingLevel.ACCOUNT, val); } } @@ -68,6 +73,12 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa return !content['disable']; } + // Special case for breadcrumbs + if (settingName === "breadcrumb_rooms") { + const content = this._getSettings(BREADCRUMBS_EVENT_TYPE) || {}; + return content['rooms'] || []; + } + const settings = this._getSettings() || {}; let preferredValue = settings[settingName]; @@ -89,6 +100,13 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa return MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", content); } + // Special case for breadcrumbs + if (settingName === "breadcrumb_rooms") { + const content = this._getSettings(BREADCRUMBS_EVENT_TYPE) || {}; + content['rooms'] = newValue; + return MatrixClientPeg.get().setAccountData(BREADCRUMBS_EVENT_TYPE, content); + } + const content = this._getSettings() || {}; content[settingName] = newValue; return MatrixClientPeg.get().setAccountData("im.vector.web.settings", content); From 406196e11c3e3f998f77d06acb946202aea4576f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 4 Apr 2019 15:10:17 -0600 Subject: [PATCH 2/3] Move import to avoid future merge conflicts --- src/components/views/rooms/RoomBreadcrumbs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomBreadcrumbs.js b/src/components/views/rooms/RoomBreadcrumbs.js index 60dae54e5c..0856ed892c 100644 --- a/src/components/views/rooms/RoomBreadcrumbs.js +++ b/src/components/views/rooms/RoomBreadcrumbs.js @@ -18,13 +18,13 @@ limitations under the License. import React from "react"; import dis from "../../../dispatcher"; import MatrixClientPeg from "../../../MatrixClientPeg"; +import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import AccessibleButton from '../elements/AccessibleButton'; import RoomAvatar from '../avatars/RoomAvatar'; import classNames from 'classnames'; import sdk from "../../../index"; import * as RoomNotifs from '../../../RoomNotifs'; import * as FormattingUtils from "../../../utils/FormattingUtils"; -import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; const MAX_ROOMS = 20; From 64a22236c3383f0879744b47c9e817e8b25ea51e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 4 Apr 2019 16:21:57 -0600 Subject: [PATCH 3/3] Handle cases where the user rapidly clicks between rooms Once the user has breadcrumbs, there should always be breadcrumbs. Therefore it is safe to ignore any updates which have zero entries. --- src/components/views/rooms/RoomBreadcrumbs.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomBreadcrumbs.js b/src/components/views/rooms/RoomBreadcrumbs.js index 0856ed892c..0950032aa2 100644 --- a/src/components/views/rooms/RoomBreadcrumbs.js +++ b/src/components/views/rooms/RoomBreadcrumbs.js @@ -88,7 +88,9 @@ export default class RoomBreadcrumbs extends React.Component { } const roomIds = rooms.map((r) => r.room.roomId); - SettingsStore.setValue("breadcrumb_rooms", null, SettingLevel.ACCOUNT, roomIds); + if (roomIds.length > 0) { + SettingsStore.setValue("breadcrumb_rooms", null, SettingLevel.ACCOUNT, roomIds); + } } onAction(payload) { @@ -147,6 +149,8 @@ export default class RoomBreadcrumbs extends React.Component { }; _loadRoomIds(roomIds) { + if (!roomIds || roomIds.length <= 0) return; // Skip updates with no rooms + // If we're here, the list changed. const rooms = roomIds.map((r) => MatrixClientPeg.get().getRoom(r)).filter((r) => r).map((r) => { const badges = this._calculateBadgesForRoom(r) || {};