diff --git a/res/css/views/rooms/_RoomBreadcrumbs.scss b/res/css/views/rooms/_RoomBreadcrumbs.scss index 7e7ab5f23b..6c3eb0420a 100644 --- a/res/css/views/rooms/_RoomBreadcrumbs.scss +++ b/res/css/views/rooms/_RoomBreadcrumbs.scss @@ -19,10 +19,15 @@ limitations under the License. height: 42px; padding: 8px; padding-bottom: 0; - overflow-x: visible; display: flex; flex-direction: row; + // Autohide the scrollbar + overflow-x: hidden; + &:hover { + overflow-x: visible; + } + .mx_AutoHideScrollbar_offset { display: flex; flex-direction: row; @@ -41,6 +46,12 @@ limitations under the License. top: -3px; right: -4px; } + + .mx_RoomBreadcrumbs_dmIndicator { + position: absolute; + bottom: 0; + right: -4px; + } } .mx_RoomBreadcrumbs_animate { diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js index 263a0a22ba..03e1f8db04 100644 --- a/src/components/structures/IndicatorScrollbar.js +++ b/src/components/structures/IndicatorScrollbar.js @@ -40,6 +40,13 @@ export default class IndicatorScrollbar extends React.Component { }; } + moveToOrigin() { + if (!this._scrollElement) return; + + this._scrollElement.scrollLeft = 0; + this._scrollElement.scrollTop = 0; + } + _collectScroller(scroller) { if (scroller && !this._scrollElement) { this._scrollElement = scroller; diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 47de3f2cb0..0ddbd06b4f 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -536,7 +536,9 @@ module.exports = React.createClass({ payload.data.description || payload.data.name); break; case 'picture_snapshot': - this.uploadFile(payload.file); + return ContentMessages.sharedInstance().sendContentListToRoom( + [payload.file], this.state.room.roomId, MatrixClientPeg.get(), + ); break; case 'notifier_enabled': case 'upload_started': diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index a0b66e40e1..f71e208eb8 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -47,7 +47,7 @@ import {Completion} from "../../../autocomplete/Autocompleter"; import Markdown from '../../../Markdown'; import ComposerHistoryManager from '../../../ComposerHistoryManager'; import MessageComposerStore from '../../../stores/MessageComposerStore'; -import ContentMessage from '../../../ContentMessages'; +import ContentMessages from '../../../ContentMessages'; import {MATRIXTO_URL_PATTERN} from '../../../linkify-matrix'; @@ -139,8 +139,6 @@ export default class MessageComposerInput extends React.Component { // js-sdk Room object room: PropTypes.object.isRequired, - onFilesPasted: PropTypes.func, - onInputStateChanged: PropTypes.func, }; @@ -1014,7 +1012,7 @@ export default class MessageComposerInput extends React.Component { // neither chrome nor firefox let you paste a plain file copied // from Finder) but more images copied from a different website // / word processor etc. - return ContentMessage.sharedInstance().sendContentListToRoom( + return ContentMessages.sharedInstance().sendContentListToRoom( transfer.files, this.props.room.roomId, this.client, ); case 'html': { diff --git a/src/components/views/rooms/RoomBreadcrumbs.js b/src/components/views/rooms/RoomBreadcrumbs.js index fffeed3b17..51677bf471 100644 --- a/src/components/views/rooms/RoomBreadcrumbs.js +++ b/src/components/views/rooms/RoomBreadcrumbs.js @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; 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'; @@ -25,6 +25,8 @@ import sdk from "../../../index"; import Analytics from "../../../Analytics"; import * as RoomNotifs from '../../../RoomNotifs'; import * as FormattingUtils from "../../../utils/FormattingUtils"; +import DMRoomMap from "../../../utils/DMRoomMap"; +import {_t} from "../../../languageHandler"; const MAX_ROOMS = 20; @@ -39,22 +41,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); @@ -65,6 +67,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); @@ -78,15 +82,17 @@ export default class RoomBreadcrumbs extends React.Component { const rooms = this.state.rooms.slice(); if (rooms.length) { - const {room, animated} = rooms[0]; - if (!animated) { - rooms[0] = {room, animated: true}; + const roomModel = rooms[0]; + if (!roomModel.animated) { + roomModel.animated = true; setTimeout(() => this.setState({rooms}), 0); } } - const roomStr = JSON.stringify(rooms.map((r) => r.room.roomId)); - localStorage.setItem("mx_breadcrumb_rooms", roomStr); + const roomIds = rooms.map((r) => r.room.roomId); + if (roomIds.length > 0) { + SettingsStore.setValue("breadcrumb_rooms", null, SettingLevel.ACCOUNT, roomIds); + } } onAction(payload) { @@ -126,17 +132,50 @@ 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 (!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) || {}; + 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)) { @@ -156,24 +195,57 @@ 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}); } _appendRoomId(roomId) { - const room = MatrixClientPeg.get().getRoom(roomId); - if (!room) { - return; - } + let room = MatrixClientPeg.get().getRoom(roomId); + if (!room) return; + const rooms = this.state.rooms.slice(); + + // If the room is upgraded, use that room instead. We'll also splice out + // any children of the room. + const history = MatrixClientPeg.get().getRoomUpgradeHistory(roomId); + if (history.length > 1) { + room = history[history.length - 1]; // Last room is most recent + + // Take out any room that isn't the most recent room + for (let i = 0; i < history.length - 1; i++) { + const idx = rooms.findIndex((r) => r.room.roomId === history[i].roomId); + if (idx !== -1) rooms.splice(idx, 1); + } + } + const existingIdx = rooms.findIndex((r) => r.room.roomId === room.roomId); if (existingIdx !== -1) { rooms.splice(existingIdx, 1); } + rooms.splice(0, 0, {room, animated: false}); + if (rooms.length > MAX_ROOMS) { rooms.splice(MAX_ROOMS, rooms.length - MAX_ROOMS); } this.setState({rooms}); + + if (this.refs.scroller) { + this.refs.scroller.moveToOrigin(); + } } _viewRoom(room, index) { @@ -197,6 +269,11 @@ export default class RoomBreadcrumbs extends React.Component { this.setState({rooms}); } + _isDmRoom(room) { + const dmRooms = DMRoomMap.shared().getUserIdForRoomId(room.roomId); + return Boolean(dmRooms); + } + render() { const Tooltip = sdk.getComponent('elements.Tooltip'); const IndicatorScrollbar = sdk.getComponent('structures.IndicatorScrollbar'); @@ -234,11 +311,23 @@ export default class RoomBreadcrumbs extends React.Component { badge =