mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 13:14:58 +08:00
Merge pull request #2364 from matrix-org/bwindels/fixresizepersistenceandscrollindicator-again
Redesign: left panel fixes
This commit is contained in:
commit
acba36c975
@ -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 {
|
||||
|
@ -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 (<div
|
||||
ref={this._collectContainerRef}
|
||||
className={["mx_AutoHideScrollbar", this.props.className].join(" ")}
|
||||
|
@ -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 (<AutoHideScrollbar wrappedRef={this._collectScroller} {... this.props}>
|
||||
return (<AutoHideScrollbar ref={this._collectScrollerComponent} wrappedRef={this._collectScroller} {... this.props}>
|
||||
{ this.props.children }
|
||||
</AutoHideScrollbar>);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -21,6 +21,7 @@ const ResizeHandle = (props) => {
|
||||
ResizeHandle.propTypes = {
|
||||
vertical: PropTypes.bool,
|
||||
reverse: PropTypes.bool,
|
||||
id: PropTypes.string,
|
||||
};
|
||||
|
||||
export default ResizeHandle;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user