Merge branch 'develop' into travis/breadcrumbs/telemetry

This commit is contained in:
Travis Ralston 2019-04-05 09:35:38 -06:00
commit e2edae3383
8 changed files with 168 additions and 39 deletions

View file

@ -19,10 +19,15 @@ limitations under the License.
height: 42px; height: 42px;
padding: 8px; padding: 8px;
padding-bottom: 0; padding-bottom: 0;
overflow-x: visible;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
// Autohide the scrollbar
overflow-x: hidden;
&:hover {
overflow-x: visible;
}
.mx_AutoHideScrollbar_offset { .mx_AutoHideScrollbar_offset {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -41,6 +46,12 @@ limitations under the License.
top: -3px; top: -3px;
right: -4px; right: -4px;
} }
.mx_RoomBreadcrumbs_dmIndicator {
position: absolute;
bottom: 0;
right: -4px;
}
} }
.mx_RoomBreadcrumbs_animate { .mx_RoomBreadcrumbs_animate {

View file

@ -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) { _collectScroller(scroller) {
if (scroller && !this._scrollElement) { if (scroller && !this._scrollElement) {
this._scrollElement = scroller; this._scrollElement = scroller;

View file

@ -536,7 +536,9 @@ module.exports = React.createClass({
payload.data.description || payload.data.name); payload.data.description || payload.data.name);
break; break;
case 'picture_snapshot': case 'picture_snapshot':
this.uploadFile(payload.file); return ContentMessages.sharedInstance().sendContentListToRoom(
[payload.file], this.state.room.roomId, MatrixClientPeg.get(),
);
break; break;
case 'notifier_enabled': case 'notifier_enabled':
case 'upload_started': case 'upload_started':

View file

@ -47,7 +47,7 @@ import {Completion} from "../../../autocomplete/Autocompleter";
import Markdown from '../../../Markdown'; import Markdown from '../../../Markdown';
import ComposerHistoryManager from '../../../ComposerHistoryManager'; import ComposerHistoryManager from '../../../ComposerHistoryManager';
import MessageComposerStore from '../../../stores/MessageComposerStore'; import MessageComposerStore from '../../../stores/MessageComposerStore';
import ContentMessage from '../../../ContentMessages'; import ContentMessages from '../../../ContentMessages';
import {MATRIXTO_URL_PATTERN} from '../../../linkify-matrix'; import {MATRIXTO_URL_PATTERN} from '../../../linkify-matrix';
@ -139,8 +139,6 @@ export default class MessageComposerInput extends React.Component {
// js-sdk Room object // js-sdk Room object
room: PropTypes.object.isRequired, room: PropTypes.object.isRequired,
onFilesPasted: PropTypes.func,
onInputStateChanged: 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 // neither chrome nor firefox let you paste a plain file copied
// from Finder) but more images copied from a different website // from Finder) but more images copied from a different website
// / word processor etc. // / word processor etc.
return ContentMessage.sharedInstance().sendContentListToRoom( return ContentMessages.sharedInstance().sendContentListToRoom(
transfer.files, this.props.room.roomId, this.client, transfer.files, this.props.room.roomId, this.client,
); );
case 'html': { case 'html': {

View file

@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
'use strict';
import React from "react"; import React from "react";
import dis from "../../../dispatcher"; import dis from "../../../dispatcher";
import MatrixClientPeg from "../../../MatrixClientPeg"; import MatrixClientPeg from "../../../MatrixClientPeg";
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import RoomAvatar from '../avatars/RoomAvatar'; import RoomAvatar from '../avatars/RoomAvatar';
import classNames from 'classnames'; import classNames from 'classnames';
@ -25,6 +25,8 @@ import sdk from "../../../index";
import Analytics from "../../../Analytics"; import Analytics from "../../../Analytics";
import * as RoomNotifs from '../../../RoomNotifs'; import * as RoomNotifs from '../../../RoomNotifs';
import * as FormattingUtils from "../../../utils/FormattingUtils"; import * as FormattingUtils from "../../../utils/FormattingUtils";
import DMRoomMap from "../../../utils/DMRoomMap";
import {_t} from "../../../languageHandler";
const MAX_ROOMS = 20; const MAX_ROOMS = 20;
@ -39,22 +41,22 @@ export default class RoomBreadcrumbs extends React.Component {
componentWillMount() { componentWillMount() {
this._dispatcherRef = dis.register(this.onAction); this._dispatcherRef = dis.register(this.onAction);
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"); const roomStr = localStorage.getItem("mx_breadcrumb_rooms");
if (roomStr) { if (roomStr) {
try { try {
const roomIds = JSON.parse(roomStr); storedRooms = JSON.parse(roomStr);
this.setState({
rooms: roomIds.map((r) => {
return {
room: MatrixClientPeg.get().getRoom(r),
animated: false,
};
}).filter((r) => r.room),
});
} catch (e) { } catch (e) {
console.error("Failed to parse breadcrumbs:", 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.myMembership", this.onMyMembership);
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt); MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
@ -65,6 +67,8 @@ export default class RoomBreadcrumbs extends React.Component {
componentWillUnmount() { componentWillUnmount() {
dis.unregister(this._dispatcherRef); dis.unregister(this._dispatcherRef);
SettingsStore.unwatchSetting(this._settingWatchRef);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (client) { if (client) {
client.removeListener("Room.myMembership", this.onMyMembership); client.removeListener("Room.myMembership", this.onMyMembership);
@ -78,15 +82,17 @@ export default class RoomBreadcrumbs extends React.Component {
const rooms = this.state.rooms.slice(); const rooms = this.state.rooms.slice();
if (rooms.length) { if (rooms.length) {
const {room, animated} = rooms[0]; const roomModel = rooms[0];
if (!animated) { if (!roomModel.animated) {
rooms[0] = {room, animated: true}; roomModel.animated = true;
setTimeout(() => this.setState({rooms}), 0); setTimeout(() => this.setState({rooms}), 0);
} }
} }
const roomStr = JSON.stringify(rooms.map((r) => r.room.roomId)); const roomIds = rooms.map((r) => r.room.roomId);
localStorage.setItem("mx_breadcrumb_rooms", roomStr); if (roomIds.length > 0) {
SettingsStore.setValue("breadcrumb_rooms", null, SettingLevel.ACCOUNT, roomIds);
}
} }
onAction(payload) { onAction(payload) {
@ -126,17 +132,50 @@ export default class RoomBreadcrumbs extends React.Component {
} }
}; };
_calculateRoomBadges(room) { onBreadcrumbsChanged = (settingName, roomId, level, valueAtLevel, value) => {
if (!room) return; if (!value) return;
const rooms = this.state.rooms.slice(); const currentState = this.state.rooms.map((r) => r.room.roomId);
const roomModel = rooms.find((r) => r.room.roomId === room.roomId); if (currentState.length === value.length) {
if (!roomModel) return; // No applicable room, so don't do math on it 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 // Reset the notification variables for simplicity
roomModel.redBadge = false; const roomModel = {
roomModel.formattedCount = "0"; redBadge: false,
roomModel.showCount = false; formattedCount: "0",
showCount: false,
};
const notifState = RoomNotifs.getRoomNotifsState(room.roomId); const notifState = RoomNotifs.getRoomNotifsState(room.roomId);
if (RoomNotifs.MENTION_BADGE_STATES.includes(notifState)) { 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}); this.setState({rooms});
} }
_appendRoomId(roomId) { _appendRoomId(roomId) {
const room = MatrixClientPeg.get().getRoom(roomId); let room = MatrixClientPeg.get().getRoom(roomId);
if (!room) { if (!room) return;
return;
}
const rooms = this.state.rooms.slice(); 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); const existingIdx = rooms.findIndex((r) => r.room.roomId === room.roomId);
if (existingIdx !== -1) { if (existingIdx !== -1) {
rooms.splice(existingIdx, 1); rooms.splice(existingIdx, 1);
} }
rooms.splice(0, 0, {room, animated: false}); rooms.splice(0, 0, {room, animated: false});
if (rooms.length > MAX_ROOMS) { if (rooms.length > MAX_ROOMS) {
rooms.splice(MAX_ROOMS, rooms.length - MAX_ROOMS); rooms.splice(MAX_ROOMS, rooms.length - MAX_ROOMS);
} }
this.setState({rooms}); this.setState({rooms});
if (this.refs.scroller) {
this.refs.scroller.moveToOrigin();
}
} }
_viewRoom(room, index) { _viewRoom(room, index) {
@ -197,6 +269,11 @@ export default class RoomBreadcrumbs extends React.Component {
this.setState({rooms}); this.setState({rooms});
} }
_isDmRoom(room) {
const dmRooms = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
return Boolean(dmRooms);
}
render() { render() {
const Tooltip = sdk.getComponent('elements.Tooltip'); const Tooltip = sdk.getComponent('elements.Tooltip');
const IndicatorScrollbar = sdk.getComponent('structures.IndicatorScrollbar'); const IndicatorScrollbar = sdk.getComponent('structures.IndicatorScrollbar');
@ -234,11 +311,23 @@ export default class RoomBreadcrumbs extends React.Component {
badge = <div className={badgeClasses}>{r.formattedCount}</div>; badge = <div className={badgeClasses}>{r.formattedCount}</div>;
} }
let dmIndicator;
if (this._isDmRoom(r.room)) {
dmIndicator = <img
src={require("../../../../res/img/icon_person.svg")}
className="mx_RoomBreadcrumbs_dmIndicator"
width="13"
height="15"
alt={_t("Direct Chat")}
/>;
}
return ( return (
<AccessibleButton className={classes} key={r.room.roomId} onClick={() => this._viewRoom(r.room, i)} <AccessibleButton className={classes} key={r.room.roomId} onClick={() => this._viewRoom(r.room, i)}
onMouseEnter={() => this._onMouseEnter(r.room)} onMouseLeave={() => this._onMouseLeave(r.room)}> onMouseEnter={() => this._onMouseEnter(r.room)} onMouseLeave={() => this._onMouseLeave(r.room)}>
<RoomAvatar room={r.room} width={32} height={32} /> <RoomAvatar room={r.room} width={32} height={32} />
{badge} {badge}
{dmIndicator}
{tooltip} {tooltip}
</AccessibleButton> </AccessibleButton>
); );

View file

@ -761,6 +761,7 @@
"Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s", "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", "Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Seen by %(displayName)s (%(userName)s) at %(dateTime)s",
"Replying": "Replying", "Replying": "Replying",
"Direct Chat": "Direct Chat",
"No rooms to show": "No rooms to show", "No rooms to show": "No rooms to show",
"Unnamed room": "Unnamed room", "Unnamed room": "Unnamed room",
"World readable": "World readable", "World readable": "World readable",
@ -1264,7 +1265,6 @@
"Forget": "Forget", "Forget": "Forget",
"Favourite": "Favourite", "Favourite": "Favourite",
"Low Priority": "Low Priority", "Low Priority": "Low Priority",
"Direct Chat": "Direct Chat",
"Clear status": "Clear status", "Clear status": "Clear status",
"Update status": "Update status", "Update status": "Update status",
"Set status": "Set status", "Set status": "Set status",

View file

@ -258,6 +258,10 @@ export const SETTINGS = {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
default: "en", default: "en",
}, },
"breadcrumb_rooms": {
supportedLevels: ['account'],
default: [],
},
"analyticsOptIn": { "analyticsOptIn": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
displayName: _td('Send analytics data'), displayName: _td('Send analytics data'),

View file

@ -19,6 +19,8 @@ import MatrixClientPeg from '../../MatrixClientPeg';
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
import {SettingLevel} from "../SettingsStore"; 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. * Gets and sets settings at the "account" level for the current user.
* This handler does not make use of the roomId parameter. * 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]; const val = event.getContent()[settingName];
this._watchers.notifyUpdate(settingName, null, SettingLevel.ACCOUNT, val); 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']; 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() || {}; const settings = this._getSettings() || {};
let preferredValue = settings[settingName]; let preferredValue = settings[settingName];
@ -89,6 +100,13 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
return MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", content); 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() || {}; const content = this._getSettings() || {};
content[settingName] = newValue; content[settingName] = newValue;
return MatrixClientPeg.get().setAccountData("im.vector.web.settings", content); return MatrixClientPeg.get().setAccountData("im.vector.web.settings", content);