Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Redesign: improve room sub list sizing & persist sizes #2297

Merged
merged 10 commits into from
Nov 27, 2018
95 changes: 67 additions & 28 deletions res/css/structures/_RoomSubList.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,51 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

/* a word of explanation about the flex-shrink values employed here:
there are 3 priotized categories of screen real-estate grabbing,
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
distribute size of items within the same categery by their size
middle category: .mx_RoomSubList.resized-sized
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
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.
*/

.mx_RoomSubList {
min-height: 31px;
flex: 0 1 auto;
flex: 0 100000000 auto;
display: flex;
flex-direction: column;
}

.mx_RoomSubList_nonEmpty {
margin-bottom: 4px;
min-height: 76px;

.mx_AutoHideScrollbar_offset {
padding-bottom: 4px;
}
}

.mx_RoomSubList_hidden {
flex: none !important;
}

.mx_RoomSubList.resized-all {
flex: 0 1 auto;
}

.mx_RoomSubList.resized-sized {
/* resizer set max-height on resized-sized,
so that limits the height and hence
needs a very small flex-shrink */
flex: 0 10000 auto;
}

.mx_RoomSubList_labelContainer {
Expand Down Expand Up @@ -105,39 +141,42 @@ limitations under the License.
}

.mx_RoomSubList_scroll {
/* let rooms list grab all available space */
/* let rooms list grab as much space as it needs (auto),
potentially overflowing and showing a scrollbar */
flex: 0 1 auto;
padding: 0 8px;
}

.mx_RoomSubList_scroll.mx_IndicatorScrollbar_topOverflow::before,
.mx_RoomSubList_scroll.mx_IndicatorScrollbar_bottomOverflow::after {
position: sticky;
left: 0;
right: 0;
height: 40px;
content: "";
display: block;
z-index: 100;
pointer-events: none;
}

// overflow indicators
.mx_RoomSubList:not(.resized-all) > .mx_RoomSubList_scroll {
&.mx_IndicatorScrollbar_topOverflow::before,
&.mx_IndicatorScrollbar_bottomOverflow::after {
position: sticky;
left: 0;
right: 0;
height: 40px;
content: "";
display: block;
z-index: 100;
pointer-events: none;
}

.mx_RoomSubList_scroll.mx_IndicatorScrollbar_topOverflow > .mx_AutoHideScrollbar_offset {
margin-top: -40px;
}
.mx_RoomSubList_scroll.mx_IndicatorScrollbar_bottomOverflow > .mx_AutoHideScrollbar_offset {
margin-bottom: -40px;
}
&.mx_IndicatorScrollbar_topOverflow > .mx_AutoHideScrollbar_offset {
margin-top: -40px;
}
&.mx_IndicatorScrollbar_bottomOverflow > .mx_AutoHideScrollbar_offset {
margin-bottom: -40px;
}

.mx_RoomSubList_scroll.mx_IndicatorScrollbar_topOverflow::before {
top: 0;
background: linear-gradient($secondary-accent-color, transparent);
}
&.mx_IndicatorScrollbar_topOverflow::before {
top: 0;
background: linear-gradient($secondary-accent-color, transparent);
}

.mx_RoomSubList_scroll.mx_IndicatorScrollbar_bottomOverflow::after {
bottom: 0;
background: linear-gradient(transparent, $secondary-accent-color);
&.mx_IndicatorScrollbar_bottomOverflow::after {
bottom: 0;
background: linear-gradient(transparent, $secondary-accent-color);
}
}

.collapsed {
Expand Down
4 changes: 4 additions & 0 deletions res/css/views/rooms/_RoomList.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ limitations under the License.
min-height: 0;
}

.mx_SearchBox {
flex: none;
}

/* hide resize handles next to collapsed / empty sublists */
.mx_RoomList .mx_RoomSubList:not(.mx_RoomSubList_nonEmpty) + .mx_ResizeHandle {
display: none;
Expand Down
12 changes: 5 additions & 7 deletions src/components/structures/LoggedInView.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,13 @@ const LoggedInView = React.createClass({
};
const collapseConfig = {
toggleSize: 260 - 50,
onCollapsed: (collapsed, item) => {
if (item.classList.contains("mx_LeftPanel_container")) {
this.setState({collapseLhs: collapsed});
if (collapsed) {
window.localStorage.setItem("mx_lhs_size", '0');
}
onCollapsed: (collapsed) => {
this.setState({collapseLhs: collapsed});
if (collapsed) {
window.localStorage.setItem("mx_lhs_size", '0');
}
},
onResized: (size, item) => {
onResized: (size) => {
window.localStorage.setItem("mx_lhs_size", '' + size);
},
};
Expand Down
9 changes: 3 additions & 6 deletions src/components/structures/RoomSubList.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,20 +318,17 @@ const RoomSubList = React.createClass({
if (len) {
const subListClasses = classNames({
"mx_RoomSubList": true,
"mx_RoomSubList_hidden": this.state.hidden,
"mx_RoomSubList_nonEmpty": len && !this.state.hidden,
});
if (this.state.hidden) {
return <div className={subListClasses} style={{flexBasis: "unset", flexGrow: "unset"}}>
return <div className={subListClasses}>
{this._getHeaderJsx()}
</div>;
} else {
const heightEstimation = (len * 44) + 31 + (8 + 8);
const style = {
maxHeight: `${heightEstimation}px`,
};
const tiles = this.makeRoomTiles();
tiles.push(...this.props.extraTiles);
return <div style={style} className={subListClasses}>
return <div className={subListClasses}>
{this._getHeaderJsx()}
<IndicatorScrollbar className="mx_RoomSubList_scroll">
{ tiles }
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/elements/ResizeHandle.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ResizeHandle = (props) => {
classNames.push('mx_ResizeHandle_reverse');
}
return (
<div className={classNames.join(' ')} />
<div className={classNames.join(' ')} data-id={props.id} />
);
};

Expand Down
36 changes: 32 additions & 4 deletions src/components/views/rooms/RoomList.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import GroupStore from '../../../stores/GroupStore';
import RoomSubList from '../../structures/RoomSubList';
import ResizeHandle from '../elements/ResizeHandle';

import {Resizer, FixedDistributor, FlexSizer} from '../../../resizer'
import {Resizer, RoomDistributor, RoomSizer} from '../../../resizer'
const HIDE_CONFERENCE_CHANS = true;
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;

Expand Down Expand Up @@ -70,6 +70,10 @@ module.exports = React.createClass({
},

getInitialState: function() {

const sizesJson = window.localStorage.getItem("mx_roomlist_sizes");
this.subListSizes = sizesJson ? JSON.parse(sizesJson) : {};

return {
isLoadingLeftRooms: false,
totalRoomCount: null,
Expand Down Expand Up @@ -134,14 +138,34 @@ module.exports = React.createClass({
this._delayedRefreshRoomListLoopCount = 0;
},

_onSubListResize: function(newSize, id) {
if (!id) {
return;
}
if (typeof newSize === "string") {
newSize = Number.MAX_SAFE_INTEGER;
}
this.subListSizes[id] = newSize;
window.localStorage.setItem("mx_roomlist_sizes", JSON.stringify(this.subListSizes));
},

componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
this.resizer = new Resizer(this.resizeContainer, FixedDistributor, null, FlexSizer);
const cfg = {
onResized: this._onSubListResize,
};
this.resizer = new Resizer(this.resizeContainer, RoomDistributor, cfg, RoomSizer);
this.resizer.setClassNames({
handle: "mx_ResizeHandle",
vertical: "mx_ResizeHandle_vertical",
reverse: "mx_ResizeHandle_reverse"
});

// load stored sizes
Object.entries(this.subListSizes).forEach(([id, size]) => {
this.resizer.forHandleWithId(id).resize(size);
});

this.resizer.attach();
this.mounted = true;
},
Expand Down Expand Up @@ -476,14 +500,18 @@ module.exports = React.createClass({
if (!isLast) {
return components.concat(
subList,
<ResizeHandle key={chosenKey+"-resizer"} vertical={true} />
<ResizeHandle key={chosenKey+"-resizer"} vertical={true} id={chosenKey} />
);
} else {
return components.concat(subList);
}
}, []);
},

_collectResizeContainer: function(el) {
this.resizeContainer = el;
},

render: function() {
let subLists = [
{
Expand Down Expand Up @@ -560,7 +588,7 @@ module.exports = React.createClass({
const subListComponents = this._mapSubListProps(subLists);

return (
<div ref={(d) => this.resizeContainer = d} className="mx_RoomList">
<div ref={this._collectResizeContainer} className="mx_RoomList">
{ subListComponents }
</div>
);
Expand Down
75 changes: 15 additions & 60 deletions src/resizer/distributors.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,43 +18,46 @@ limitations under the License.
distributors translate a moving cursor into
CSS/DOM changes by calling the sizer

they have one method, `resize` that receives
they have two methods:
`resize` receives then new item size
`resizeFromContainerOffset` receives resize handle location
within the container bounding box. For internal use.
This method usually ends up calling `resize` once the start offset is subtracted.
the offset from the container edge of where
the mouse cursor is.
*/
class FixedDistributor {
constructor(sizer, item, config) {
constructor(sizer, item, id, config) {
this.sizer = sizer;
this.item = item;
this.id = id;
this.beforeOffset = sizer.getItemOffset(this.item);
this.onResized = config && config.onResized;
}

resize(offset) {
const itemSize = offset - this.beforeOffset;
resize(itemSize) {
this.sizer.setItemSize(this.item, itemSize);
if (this.onResized) {
this.onResized(itemSize, this.item);
this.onResized(itemSize, this.id, this.item);
}
return itemSize;
}

sizeFromOffset(offset) {
return offset - this.beforeOffset;
resizeFromContainerOffset(offset) {
this.resize(offset - this.beforeOffset);
}
}


class CollapseDistributor extends FixedDistributor {
constructor(sizer, item, config) {
super(sizer, item, config);
constructor(sizer, item, id, config) {
super(sizer, item, id, config);
this.toggleSize = config && config.toggleSize;
this.onCollapsed = config && config.onCollapsed;
this.isCollapsed = false;
}

resize(offset) {
const newSize = this.sizeFromOffset(offset);
resize(newSize) {
const isCollapsedSize = newSize < this.toggleSize;
if (isCollapsedSize && !this.isCollapsed) {
this.isCollapsed = true;
Expand All @@ -68,60 +71,12 @@ class CollapseDistributor extends FixedDistributor {
this.isCollapsed = false;
}
if (!isCollapsedSize) {
super.resize(offset);
super.resize(newSize);
}
}
}

class PercentageDistributor {
constructor(sizer, item, _config, items, container) {
this.container = container;
this.totalSize = sizer.getTotalSize();
this.sizer = sizer;

const itemIndex = items.indexOf(item);
this.beforeItems = items.slice(0, itemIndex);
this.afterItems = items.slice(itemIndex);
const percentages = PercentageDistributor._getPercentages(sizer, items);
this.beforePercentages = percentages.slice(0, itemIndex);
this.afterPercentages = percentages.slice(itemIndex);
}

resize(offset) {
const percent = offset / this.totalSize;
const beforeSum =
this.beforePercentages.reduce((total, p) => total + p, 0);
const beforePercentages =
this.beforePercentages.map(p => (p / beforeSum) * percent);
const afterSum =
this.afterPercentages.reduce((total, p) => total + p, 0);
const afterPercentages =
this.afterPercentages.map(p => (p / afterSum) * (1 - percent));

this.beforeItems.forEach((item, index) => {
this.sizer.setItemPercentage(item, beforePercentages[index]);
});
this.afterItems.forEach((item, index) => {
this.sizer.setItemPercentage(item, afterPercentages[index]);
});
}

static _getPercentages(sizer, items) {
const percentages = items.map(i => sizer.getItemPercentage(i));
const setPercentages = percentages.filter(p => p !== null);
const unsetCount = percentages.length - setPercentages.length;
const setTotal = setPercentages.reduce((total, p) => total + p, 0);
const implicitPercentage = (1 - setTotal) / unsetCount;
return percentages.map(p => p === null ? implicitPercentage : p);
}

static setPercentage(el, percent) {
el.style.flexGrow = Math.round(percent * 1000);
}
}

module.exports = {
FixedDistributor,
CollapseDistributor,
PercentageDistributor,
};
Loading