diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index ce28c168b9..3a0dd0395b 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -19,14 +19,14 @@ limitations under the License. each with a flex-shrink difference of 4 order of magnitude, so they ideally wouldn't affect each other. lowest category: .mx_RoomSubList - flex:-shrink: 10000000 + flex-shrink: 10000000 distribute size of items within the same categery by their size middle category: .mx_RoomSubList.resized-sized - flex:-shrink: 1000 + flex-shrink: 1000 applied when using the resizer, will have a max-height set to it, to limit the size highest category: .mx_RoomSubList.resized-all - flex:-shrink: 1 + flex-shrink: 1 small flex-shrink value (1), is only added if you can drag the resizer so far so in practice you can only assign this category if there is enough space. */ @@ -39,7 +39,7 @@ limitations under the License. } .mx_RoomSubList_nonEmpty { - min-height: 76px; + min-height: 70px; .mx_AutoHideScrollbar_offset { padding-bottom: 4px; @@ -154,7 +154,7 @@ limitations under the License. position: sticky; left: 0; right: 0; - height: 40px; + height: 30px; content: ""; display: block; z-index: 100; @@ -162,10 +162,10 @@ limitations under the License. } &.mx_IndicatorScrollbar_topOverflow > .mx_AutoHideScrollbar_offset { - margin-top: -40px; + margin-top: -30px; } &.mx_IndicatorScrollbar_bottomOverflow > .mx_AutoHideScrollbar_offset { - margin-bottom: -40px; + margin-bottom: -30px; } &.mx_IndicatorScrollbar_topOverflow::before { diff --git a/res/themes/dharma/css/_dharma.scss b/res/themes/dharma/css/_dharma.scss index ce1b7b2a68..2cc27f3be0 100644 --- a/res/themes/dharma/css/_dharma.scss +++ b/res/themes/dharma/css/_dharma.scss @@ -192,32 +192,37 @@ $progressbar-color: #000; // it has the appearance of a text box so the controls // appear to be part of the input -:not(.mx_textinput) > input[type=text], -:not(.mx_textinput) > input[type=search], -.mx_textinput { - display: block; - margin: 9px; - box-sizing: border-box; - background-color: transparent; - color: $input-darker-fg-color; - border-radius: 4px; - border: 1px solid #c1c1c1; -} +.mx_MatrixChat { -.mx_textinput { - display: flex; - align-items: center; -} + :not(.mx_textinput) > input[type=text], + :not(.mx_textinput) > input[type=search], + .mx_textinput { + display: block; + margin: 9px; + box-sizing: border-box; + background-color: transparent; + color: $input-darker-fg-color; + border-radius: 4px; + border: 1px solid #c1c1c1; + flex: 0 0 auto; + } -.mx_textinput > input[type=text], -.mx_textinput > input[type=search] { - border: none; - flex: 1; - color: inherit; //from .mx_textinput + .mx_textinput { + display: flex; + align-items: center; + + > input[type=text], + > input[type=search] { + border: none; + flex: 1; + color: inherit; //from .mx_textinput + } + } } input[type=text], -input[type=search] { +input[type=search], +input[type=password] { padding: 9px; font-family: $font-family; font-size: 14px; diff --git a/src/components/structures/AutoHideScrollbar.js b/src/components/structures/AutoHideScrollbar.js index a328d478bc..47ae24ba0f 100644 --- a/src/components/structures/AutoHideScrollbar.js +++ b/src/components/structures/AutoHideScrollbar.js @@ -69,6 +69,7 @@ export default class AutoHideScrollbar extends React.Component { this.onOverflow = this.onOverflow.bind(this); this.onUnderflow = this.onUnderflow.bind(this); this._collectContainerRef = this._collectContainerRef.bind(this); + this._needsOverflowListener = null; } onOverflow() { @@ -81,21 +82,35 @@ export default class AutoHideScrollbar extends React.Component { this.containerRef.classList.add("mx_AutoHideScrollbar_underflow"); } + checkOverflow() { + if (!this._needsOverflowListener) { + return; + } + if (this.containerRef.scrollHeight > this.containerRef.clientHeight) { + this.onOverflow(); + } else { + this.onUnderflow(); + } + } + + componentDidUpdate() { + this.checkOverflow(); + } + + componentDidMount() { + installBodyClassesIfNeeded(); + this._needsOverflowListener = + document.body.classList.contains("mx_scrollbar_nooverlay"); + if (this._needsOverflowListener) { + this.containerRef.addEventListener("overflow", this.onOverflow); + this.containerRef.addEventListener("underflow", this.onUnderflow); + } + this.checkOverflow(); + } + _collectContainerRef(ref) { if (ref && !this.containerRef) { this.containerRef = ref; - const needsOverflowListener = - document.body.classList.contains("mx_scrollbar_nooverlay"); - - if (needsOverflowListener) { - this.containerRef.addEventListener("overflow", this.onOverflow); - this.containerRef.addEventListener("underflow", this.onUnderflow); - } - if (ref.scrollHeight > ref.clientHeight) { - this.onOverflow(); - } else { - this.onUnderflow(); - } } if (this.props.wrappedRef) { this.props.wrappedRef(ref); @@ -103,14 +118,13 @@ export default class AutoHideScrollbar extends React.Component { } componentWillUnmount() { - if (this.containerRef) { + if (this._needsOverflowListener && this.containerRef) { this.containerRef.removeEventListener("overflow", this.onOverflow); this.containerRef.removeEventListener("underflow", this.onUnderflow); } } render() { - installBodyClassesIfNeeded(); return (
, ); - if (this.props.collapsedRhs) { - rightButtons.push( - - - , - ); - } } const rightPanel = !this.props.collapsedRhs ? : undefined; @@ -1311,7 +1302,7 @@ export default React.createClass({
{ rightButtons }
- +
diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js index 247131cfab..0fe246050c 100644 --- a/src/components/structures/IndicatorScrollbar.js +++ b/src/components/structures/IndicatorScrollbar.js @@ -21,41 +21,52 @@ export default class IndicatorScrollbar extends React.Component { constructor(props) { super(props); this._collectScroller = this._collectScroller.bind(this); + this._collectScrollerComponent = this._collectScrollerComponent.bind(this); this.checkOverflow = this.checkOverflow.bind(this); + this._scrollElement = null; + this._autoHideScrollbar = null; } _collectScroller(scroller) { - if (scroller && !this._scroller) { - this._scroller = scroller; - this._scroller.addEventListener("scroll", this.checkOverflow); + if (scroller && !this._scrollElement) { + this._scrollElement = scroller; + this._scrollElement.addEventListener("scroll", this.checkOverflow); this.checkOverflow(); } } + _collectScrollerComponent(autoHideScrollbar) { + this._autoHideScrollbar = autoHideScrollbar; + } + checkOverflow() { - const hasTopOverflow = this._scroller.scrollTop > 0; - const hasBottomOverflow = this._scroller.scrollHeight > - (this._scroller.scrollTop + this._scroller.clientHeight); + const hasTopOverflow = this._scrollElement.scrollTop > 0; + const hasBottomOverflow = this._scrollElement.scrollHeight > + (this._scrollElement.scrollTop + this._scrollElement.clientHeight); if (hasTopOverflow) { - this._scroller.classList.add("mx_IndicatorScrollbar_topOverflow"); + this._scrollElement.classList.add("mx_IndicatorScrollbar_topOverflow"); } else { - this._scroller.classList.remove("mx_IndicatorScrollbar_topOverflow"); + this._scrollElement.classList.remove("mx_IndicatorScrollbar_topOverflow"); } if (hasBottomOverflow) { - this._scroller.classList.add("mx_IndicatorScrollbar_bottomOverflow"); + this._scrollElement.classList.add("mx_IndicatorScrollbar_bottomOverflow"); } else { - this._scroller.classList.remove("mx_IndicatorScrollbar_bottomOverflow"); + this._scrollElement.classList.remove("mx_IndicatorScrollbar_bottomOverflow"); + } + + if (this._autoHideScrollbar) { + this._autoHideScrollbar.checkOverflow(); } } componentWillUnmount() { - if (this._scroller) { - this._scroller.removeEventListener("scroll", this.checkOverflow); + if (this._scrollElement) { + this._scrollElement.removeEventListener("scroll", this.checkOverflow); } } render() { - return ( + return ( { this.props.children } ); } diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 635c5de44e..0433ce25b3 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -62,7 +62,7 @@ const LoggedInView = React.createClass({ // Called with the credentials of a registered user (if they were a ROU that // transitioned to PWLU) onRegistered: PropTypes.func, - + collapsedRhs: PropTypes.bool, teamToken: PropTypes.string, // Used by the RoomView to handle joining rooms @@ -438,7 +438,7 @@ const LoggedInView = React.createClass({ eventPixelOffset={this.props.initialEventPixelOffset} key={this.props.currentRoomId || 'roomview'} disabled={this.props.middleDisabled} - collapsedRhs={this.props.collapseRhs} + collapsedRhs={this.props.collapsedRhs} ConferenceHandler={this.props.ConferenceHandler} />; break; @@ -488,7 +488,7 @@ const LoggedInView = React.createClass({ page_element = ; break; } diff --git a/src/components/structures/MainSplit.js b/src/components/structures/MainSplit.js index 6fd0274f1a..0427130eea 100644 --- a/src/components/structures/MainSplit.js +++ b/src/components/structures/MainSplit.js @@ -41,10 +41,13 @@ export default class MainSplit extends React.Component { {onResized: this._onResized}, ); resizer.setClassNames(classNames); - const rhsSize = window.localStorage.getItem("mx_rhs_size"); + let rhsSize = window.localStorage.getItem("mx_rhs_size"); if (rhsSize !== null) { - resizer.forHandleAt(0).resize(parseInt(rhsSize, 10)); + rhsSize = parseInt(rhsSize, 10); + } else { + rhsSize = 350; } + resizer.forHandleAt(0).resize(rhsSize); resizer.attach(); this.resizer = resizer; @@ -55,7 +58,7 @@ export default class MainSplit extends React.Component { } componentDidMount() { - if (this.props.panel && !this.collapsedRhs) { + if (this.props.panel && !this.props.collapsedRhs) { this._createResizer(); } } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 0a062afc43..187caa69df 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -161,7 +161,7 @@ export default React.createClass({ viewUserId: null, collapseLhs: false, - collapseRhs: false, + collapsedRhs: window.localStorage.getItem("mx_rhs_collapsed") === "true", leftDisabled: false, middleDisabled: false, rightDisabled: false, @@ -555,7 +555,7 @@ export default React.createClass({ break; case 'view_user': // FIXME: ugly hack to expand the RightPanel and then re-dispatch. - if (this.state.collapseRhs) { + if (this.state.collapsedRhs) { setTimeout(()=>{ dis.dispatch({ action: 'show_right_panel', @@ -656,13 +656,15 @@ export default React.createClass({ }); break; case 'hide_right_panel': + window.localStorage.setItem("mx_rhs_collapsed", true); this.setState({ - collapseRhs: true, + collapsedRhs: true, }); break; case 'show_right_panel': + window.localStorage.setItem("mx_rhs_collapsed", false); this.setState({ - collapseRhs: false, + collapsedRhs: false, }); break; case 'panel_disable': { @@ -1217,7 +1219,7 @@ export default React.createClass({ view: VIEWS.LOGIN, ready: false, collapseLhs: false, - collapseRhs: false, + collapsedRhs: false, currentRoomId: null, page_type: PageTypes.RoomDirectory, }); diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index b4fbc5406e..a4f97e0efd 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -110,8 +110,9 @@ const RoomSubList = React.createClass({ if (this.isCollapsableOnClick()) { // The header isCollapsable, so the click is to be interpreted as collapse and truncation logic const isHidden = !this.state.hidden; - this.setState({hidden: isHidden}); - this.props.onHeaderClick(isHidden); + this.setState({hidden: isHidden}, () => { + this.props.onHeaderClick(isHidden); + }); } else { // The header is stuck, so the click is to be interpreted as a scroll to the header this.props.onHeaderClick(this.state.hidden, this.refs.header.dataset.originalPosition); diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js index 4df3e837c7..ea1fa312c1 100644 --- a/src/components/structures/SearchBox.js +++ b/src/components/structures/SearchBox.js @@ -56,7 +56,6 @@ module.exports = React.createClass({ case 'focus_room_filter': if (this.refs.search) { this.refs.search.focus(); - this.refs.search.select(); } break; } @@ -83,6 +82,10 @@ module.exports = React.createClass({ } }, + _onFocus: function(ev) { + ev.target.select(); + }, + _clearSearch: function(source) { this.refs.search.value = ""; this.onChange(); @@ -108,6 +111,7 @@ module.exports = React.createClass({ ref="search" className="mx_textinput_icon mx_textinput_search" value={ this.state.searchTerm } + onFocus={ this._onFocus } onChange={ this.onChange } onKeyDown={ this._onKeyDown } placeholder={ _t('Filter room names') } diff --git a/src/components/views/elements/ResizeHandle.js b/src/components/views/elements/ResizeHandle.js index 863dfaaa93..578689b45c 100644 --- a/src/components/views/elements/ResizeHandle.js +++ b/src/components/views/elements/ResizeHandle.js @@ -21,6 +21,7 @@ const ResizeHandle = (props) => { ResizeHandle.propTypes = { vertical: PropTypes.bool, reverse: PropTypes.bool, + id: PropTypes.string, }; export default ResizeHandle; diff --git a/src/components/views/right_panel/GroupHeaderButtons.js b/src/components/views/right_panel/GroupHeaderButtons.js index af54787b2c..6fcba1d815 100644 --- a/src/components/views/right_panel/GroupHeaderButtons.js +++ b/src/components/views/right_panel/GroupHeaderButtons.js @@ -55,23 +55,23 @@ export default class GroupHeaderButtons extends HeaderButtons { } renderButtons() { - const isPhaseGroup = [ + const groupPhases = [ RightPanel.Phase.GroupMemberInfo, RightPanel.Phase.GroupMemberList, - ].includes(this.state.phase); - const isPhaseRoom = [ + ]; + const roomPhases = [ RightPanel.Phase.GroupRoomList, RightPanel.Phase.GroupRoomInfo, - ].includes(this.state.phase); + ]; return [ , , diff --git a/src/components/views/right_panel/HeaderButton.js b/src/components/views/right_panel/HeaderButton.js index a01d3444f1..bb9f613607 100644 --- a/src/components/views/right_panel/HeaderButton.js +++ b/src/components/views/right_panel/HeaderButton.js @@ -36,6 +36,7 @@ export default class HeaderButton extends React.Component { dis.dispatch({ action: 'view_right_panel_phase', phase: this.props.clickPhase, + fromHeader: true, }); } diff --git a/src/components/views/right_panel/HeaderButtons.js b/src/components/views/right_panel/HeaderButtons.js index 3c59c52089..f0479eb8be 100644 --- a/src/components/views/right_panel/HeaderButtons.js +++ b/src/components/views/right_panel/HeaderButtons.js @@ -18,6 +18,7 @@ limitations under the License. */ import React from 'react'; +import PropTypes from 'prop-types'; import dis from '../../../dispatcher'; export default class HeaderButtons extends React.Component { @@ -25,7 +26,7 @@ export default class HeaderButtons extends React.Component { super(props); this.state = { - phase: initialPhase, + phase: props.collapsedRhs ? null : initialPhase, isUserPrivilegedInGroup: null, }; this.onAction = this.onAction.bind(this); @@ -47,11 +48,42 @@ export default class HeaderButtons extends React.Component { }, extras)); } + isPhase(phases) { + if (this.props.collapsedRhs) { + return false; + } + if (Array.isArray(phases)) { + return phases.includes(this.state.phase); + } else { + return phases === this.state.phase; + } + } + onAction(payload) { if (payload.action === "view_right_panel_phase") { - this.setState({ - phase: payload.phase, - }); + // only actions coming from header buttons should collapse the right panel + if (this.state.phase === payload.phase && payload.fromHeader) { + dis.dispatch({ + action: 'hide_right_panel', + }); + this.setState({ + phase: null, + }); + } else { + if (this.props.collapsedRhs && payload.fromHeader) { + dis.dispatch({ + action: 'show_right_panel', + }); + // emit payload again as the RightPanel didn't exist up + // till show_right_panel, just without the fromHeader flag + // as that would hide the right panel again + dis.dispatch(Object.assign({}, payload, {fromHeader: false})); + + } + this.setState({ + phase: payload.phase, + }); + } } } @@ -62,3 +94,7 @@ export default class HeaderButtons extends React.Component { ; } } + +HeaderButtons.propTypes = { + collapsedRhs: PropTypes.bool, +}; diff --git a/src/components/views/right_panel/RoomHeaderButtons.js b/src/components/views/right_panel/RoomHeaderButtons.js index cd4bb1c229..53835777d5 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.js +++ b/src/components/views/right_panel/RoomHeaderButtons.js @@ -46,24 +46,24 @@ export default class RoomHeaderButtons extends HeaderButtons { } renderButtons() { - const isMembersPhase = [ + const membersPhases = [ RightPanel.Phase.RoomMemberList, RightPanel.Phase.RoomMemberInfo, - ].includes(this.state.phase); + ]; return [ , , , diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 139a400af3..4292fa6a4d 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -23,7 +23,6 @@ import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import MatrixClientPeg from '../../../MatrixClientPeg'; import Modal from "../../../Modal"; -import dis from "../../../dispatcher"; import RateLimitedFunc from '../../../ratelimitedfunc'; import * as linkify from 'linkifyjs'; @@ -146,10 +145,6 @@ module.exports = React.createClass({ MatrixClientPeg.get().sendStateEvent(this.props.room.roomId, 'm.room.avatar', {url: null}, ''); }, - onShowRhsClick: function(ev) { - dis.dispatch({ action: 'show_right_panel' }); - }, - onShareRoomClick: function(ev) { const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); Modal.createTrackedDialog('share room dialog', '', ShareDialog, { @@ -394,14 +389,6 @@ module.exports = React.createClass({ ; } - let rightPanelButtons; - if (this.props.collapsedRhs) { - rightPanelButtons = - - - ; - } - let rightRow; let manageIntegsButton; if (this.props.room && this.props.room.roomId && this.props.inRoom) { @@ -419,7 +406,6 @@ module.exports = React.createClass({ { manageIntegsButton } { forgetButton } { searchButton } - { rightPanelButtons } ; } @@ -433,7 +419,7 @@ module.exports = React.createClass({ { saveButton } { cancelButton } { rightRow } - + ); diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 9fb872cd32..7a06cc3da5 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -152,6 +152,8 @@ module.exports = React.createClass({ } this.subListSizes[id] = newSize; window.localStorage.setItem("mx_roomlist_sizes", JSON.stringify(this.subListSizes)); + // update overflow indicators + this._checkSubListsOverflow(); }, componentDidMount: function() { @@ -167,12 +169,10 @@ module.exports = React.createClass({ }); // load stored sizes - Object.entries(this.subListSizes).forEach(([id, size]) => { - const handle = this.resizer.forHandleWithId(id); - if (handle) { - handle.resize(size); - } + Object.keys(this.subListSizes).forEach((key) => { + this._restoreSubListSize(key); }); + this._checkSubListsOverflow(); this.resizer.attach(); this.mounted = true; @@ -181,7 +181,11 @@ module.exports = React.createClass({ componentDidUpdate: function(prevProps) { this._repositionIncomingCallBox(undefined, false); if (this.props.searchFilter !== prevProps.searchFilter) { - Object.values(this._subListRefs).forEach(l => l.checkOverflow()); + // restore sizes + Object.keys(this.subListSizes).forEach((key) => { + this._restoreSubListSize(key); + }); + this._checkSubListsOverflow(); } }, @@ -354,6 +358,11 @@ module.exports = React.createClass({ // Do this here so as to not render every time the selected tags // themselves change. selectedTags: TagOrderStore.getSelectedTags(), + }, () => { + // we don't need to restore any size here, do we? + // i guess we could have triggered a new group to appear + // that already an explicit size the last time it appeared ... + this._checkSubListsOverflow(); }); // this._lastRefreshRoomListTs = Date.now(); @@ -485,9 +494,30 @@ module.exports = React.createClass({ (filter[0] === '#' && room.getAliases().some((alias) => alias.toLowerCase().startsWith(lcFilter)))); }, - _persistCollapsedState: function(key, collapsed) { + _handleCollapsedState: function(key, collapsed) { + // persist collapsed state this.collapsedState[key] = collapsed; window.localStorage.setItem("mx_roomlist_collapsed", JSON.stringify(this.collapsedState)); + // load the persisted size configuration of the expanded sub list + if (!collapsed) { + this._restoreSubListSize(key); + } + // check overflow, as sub lists sizes have changed + // important this happens after calling resize above + this._checkSubListsOverflow(); + }, + + _restoreSubListSize(key) { + const size = this.subListSizes[key]; + const handle = this.resizer.forHandleWithId(key); + if (handle) { + handle.resize(size); + } + }, + + // check overflow for scroll indicator gradient + _checkSubListsOverflow() { + Object.values(this._subListRefs).forEach(l => l.checkOverflow()); }, _subListRef: function(key, ref) { @@ -520,7 +550,7 @@ module.exports = React.createClass({ const {key, label, onHeaderClick, ... otherProps} = props; const chosenKey = key || label; const onSubListHeaderClick = (collapsed) => { - this._persistCollapsedState(chosenKey, collapsed); + this._handleCollapsedState(chosenKey, collapsed); if (onHeaderClick) { onHeaderClick(collapsed); } diff --git a/src/resizer/resizer.js b/src/resizer/resizer.js index 7ef542a6e1..0e113b3664 100644 --- a/src/resizer/resizer.js +++ b/src/resizer/resizer.js @@ -84,8 +84,10 @@ export class Resizer { } _onMouseDown(event) { - const target = event.target; - if (!this._isResizeHandle(target) || target.parentElement !== this.container) { + // use closest in case the resize handle contains + // child dom nodes that can be the target + const resizeHandle = event.target && event.target.closest(`.${this.classNames.handle}`); + if (!resizeHandle || resizeHandle.parentElement !== this.container) { return; } // prevent starting a drag operation @@ -96,7 +98,7 @@ export class Resizer { this.container.classList.add(this.classNames.resizing); } - const {sizer, distributor} = this._createSizerAndDistributor(target); + const {sizer, distributor} = this._createSizerAndDistributor(resizeHandle); const onMouseMove = (event) => { const offset = sizer.offsetFromEvent(event);