+
{ this._getHeaderJsx() }
{ content }
diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js
index 01e6d280c6..4f92d0cad6 100644
--- a/src/components/views/rooms/RoomList.js
+++ b/src/components/views/rooms/RoomList.js
@@ -36,7 +36,8 @@ import GroupStore from '../../../stores/GroupStore';
import RoomSubList from '../../structures/RoomSubList';
import ResizeHandle from '../elements/ResizeHandle';
-import {Resizer, RoomSubListDistributor} from '../../../resizer'
+import {Resizer} from '../../../resizer'
+import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2';
const HIDE_CONFERENCE_CHANS = true;
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
@@ -79,6 +80,23 @@ module.exports = React.createClass({
const collapsedJson = window.localStorage.getItem("mx_roomlist_collapsed");
this.subListSizes = sizesJson ? JSON.parse(sizesJson) : {};
this.collapsedState = collapsedJson ? JSON.parse(collapsedJson) : {};
+ this._layoutSections = [];
+
+ this._layout = new Layout((key, size) => {
+ const subList = this._subListRefs[key];
+ if (subList) {
+ subList.setHeight(size);
+ }
+ // update overflow indicators
+ this._checkSubListsOverflow();
+ // don't store height for collapsed sublists
+ if(!this.collapsedState[key]) {
+ this.subListSizes[key] = size;
+ window.localStorage.setItem("mx_roomlist_sizes",
+ JSON.stringify(this.subListSizes));
+ }
+ }, this.subListSizes, this.collapsedState);
+
return {
isLoadingLeftRooms: false,
totalRoomCount: null,
@@ -146,54 +164,38 @@ module.exports = React.createClass({
this._delayedRefreshRoomListLoopCount = 0;
},
- _onSubListResize: function(newSize, id) {
- if (!id) {
- return;
- }
- if (typeof newSize === "string") {
- newSize = Number.MAX_SAFE_INTEGER;
- }
- if (newSize === null) {
- delete this.subListSizes[id];
- } else {
- this.subListSizes[id] = newSize;
- }
- window.localStorage.setItem("mx_roomlist_sizes", JSON.stringify(this.subListSizes));
- // update overflow indicators
- this._checkSubListsOverflow();
- },
-
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
const cfg = {
- onResized: this._onSubListResize,
+ layout: this._layout,
};
- this.resizer = new Resizer(this.resizeContainer, RoomSubListDistributor, cfg);
+ this.resizer = new Resizer(this.resizeContainer, Distributor, cfg);
this.resizer.setClassNames({
handle: "mx_ResizeHandle",
vertical: "mx_ResizeHandle_vertical",
reverse: "mx_ResizeHandle_reverse"
});
-
- // load stored sizes
- Object.keys(this.subListSizes).forEach((key) => {
- this._restoreSubListSize(key);
- });
+ this._layout.update(
+ this._layoutSections,
+ this.resizeContainer && this.resizeContainer.offsetHeight,
+ );
this._checkSubListsOverflow();
this.resizer.attach();
+ window.addEventListener("resize", this.onWindowResize);
this.mounted = true;
},
componentDidUpdate: function(prevProps) {
this._repositionIncomingCallBox(undefined, false);
- if (this.props.searchFilter !== prevProps.searchFilter) {
- // restore sizes
- Object.keys(this.subListSizes).forEach((key) => {
- this._restoreSubListSize(key);
- });
- this._checkSubListsOverflow();
- }
+ // if (this.props.searchFilter !== prevProps.searchFilter) {
+ // this._checkSubListsOverflow();
+ // }
+ this._layout.update(
+ this._layoutSections,
+ this.resizeContainer && this.resizeContainer.clientHeight,
+ );
+ // TODO: call layout.setAvailableHeight, window height was changed when bannerShown prop was changed
},
onAction: function(payload) {
@@ -222,6 +224,7 @@ module.exports = React.createClass({
componentWillUnmount: function() {
this.mounted = false;
+ window.removeEventListener("resize", this.onWindowResize);
dis.unregister(this.dispatcherRef);
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("Room", this.onRoom);
@@ -251,6 +254,17 @@ module.exports = React.createClass({
this._delayedRefreshRoomList.cancelPendingCall();
},
+ onWindowResize: function() {
+ if (this.mounted && this._layout && this.resizeContainer &&
+ Array.isArray(this._layoutSections)
+ ) {
+ this._layout.update(
+ this._layoutSections,
+ this.resizeContainer.offsetHeight
+ );
+ }
+ },
+
onRoom: function(room) {
this.updateVisibleRooms();
},
@@ -551,22 +565,16 @@ module.exports = React.createClass({
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);
+ if (collapsed) {
+ this._layout.collapseSection(key);
+ } else {
+ this._layout.expandSection(key, this.subListSizes[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());
@@ -581,6 +589,7 @@ module.exports = React.createClass({
},
_mapSubListProps: function(subListsProps) {
+ this._layoutSections = [];
const defaultProps = {
collapsed: this.props.collapsed,
isFiltered: !!this.props.searchFilter,
@@ -599,6 +608,7 @@ module.exports = React.createClass({
return subListsProps.reduce((components, props, i) => {
props = Object.assign({}, defaultProps, props);
const isLast = i === subListsProps.length - 1;
+ const len = props.list.length + (props.extraTiles ? props.extraTiles.length : 0);
const {key, label, onHeaderClick, ... otherProps} = props;
const chosenKey = key || label;
const onSubListHeaderClick = (collapsed) => {
@@ -608,7 +618,10 @@ module.exports = React.createClass({
}
};
const startAsHidden = props.startAsHidden || this.collapsedState[chosenKey];
-
+ this._layoutSections.push({
+ id: chosenKey,
+ count: len,
+ });
let subList = (
max) return max;
+ if (height < min) return min;
+ return height;
+}
+
+export class Layout {
+ constructor(applyHeight, initialSizes, collapsedState) {
+ this._applyHeight = applyHeight;
+ this._sections = [];
+ this._collapsedState = Object.assign({}, collapsedState);
+ this._availableHeight = 0;
+ // heights stored by section section id
+ this._sectionHeights = Object.assign({}, initialSizes);
+ // in-progress heights, while dragging. Committed on mouse-up.
+ this._heights = [];
+ }
+
+ setAvailableHeight(newSize) {
+ this._availableHeight = newSize;
+ // needs more work
+ this._applyNewSize();
+ }
+
+ expandSection(id, height) {
+ this._collapsedState[id] = false;
+ this._applyNewSize();
+ this.openHandle(id).setHeight(height).finish();
+ }
+
+ collapseSection(id) {
+ this._collapsedState[id] = true;
+ this._applyNewSize();
+ }
+
+ // [{id, count}]
+ update(sections, availableHeight) {
+ if (Number.isFinite(availableHeight)) {
+ this._availableHeight = availableHeight;
+ }
+ const totalHeight = this._getAvailableHeight();
+ this._sections.forEach((section, i) => {
+ if (!this._sectionHeights.hasOwnProperty(section.id)) {
+ this._sectionHeights[section.id] = clamp(
+ totalHeight / this._sections.length,
+ this._getMinHeight(i),
+ this._getMaxHeight(i),
+ );
+ }
+ });
+ this._sections = sections;
+ this._applyNewSize();
+ }
+
+ openHandle(id) {
+ const index = this._getSectionIndex(id);
+ //log(`openHandle resolved ${id} to ${index}`);
+ return new Handle(this, index, this._sectionHeights[id]);
+ }
+
+ _getAvailableHeight() {
+ const nonCollapsedSectionCount = this._sections.reduce((count, section) => {
+ const collapsed = this._collapsedState[section.id];
+ return count + (collapsed ? 0 : 1);
+ }, 0);
+ return this._availableHeight - ((nonCollapsedSectionCount - 1) * handleHeight);
+ }
+
+ _applyNewSize() {
+ const newHeight = this._getAvailableHeight();
+ const currHeight = this._sections.reduce((sum, section) => {
+ return sum + this._sectionHeights[section.id];
+ }, 0);
+ const offset = newHeight - currHeight;
+ this._heights = this._sections.map((section) => this._sectionHeights[section.id]);
+ const sections = this._sections.map((_, i) => i);
+ this._applyOverflow(-offset, sections, true);
+ this._applyHeights();
+ this._commitHeights();
+ }
+
+ _getSectionIndex(id) {
+ return this._sections.findIndex((s) => s.id === id);
+ }
+
+ _getMaxHeight(i) {
+ const section = this._sections[i];
+ const collapsed = this._collapsedState[section.id];
+
+ if (collapsed) {
+ return this._sectionHeight(0);
+ } else {
+ return 100000;
+ // return this._sectionHeight(section.count);
+ }
+ }
+
+ _sectionHeight(count) {
+ return 36 + (count === 0 ? 0 : 4 + (count * 34));
+ }
+
+ _getMinHeight(i) {
+ const section = this._sections[i];
+ const collapsed = this._collapsedState[section.id];
+ const maxItems = collapsed ? 0 : 1;
+ // log("_getMinHeight", i, section);
+ return this._sectionHeight(Math.min(section.count, maxItems));
+ }
+
+ _applyOverflow(overflow, sections, blend) {
+ //log("applyOverflow", overflow, sections);
+ // take the given overflow amount, and applies it to the given sections.
+ // calls itself recursively until it has distributed all the overflow
+ // or run out of unclamped sections.
+
+ const unclampedSections = [];
+
+ let overflowPerSection = blend ? (overflow / sections.length) : overflow;
+ for (const i of sections) {
+ const newHeight = clamp(
+ this._heights[i] - overflowPerSection,
+ this._getMinHeight(i),
+ this._getMaxHeight(i),
+ );
+ if (newHeight == this._heights[i] - overflowPerSection) {
+ unclampedSections.push(i);
+ }
+ // when section is growing, overflow increases?
+ // 100 -= 200 - 300
+ // 100 -= -100
+ // 200
+ overflow -= this._heights[i] - newHeight;
+ // console.log(`this._heights[${i}] (${this._heights[i]}) - newHeight (${newHeight}) = ${this._heights[i] - newHeight}`);
+ // console.log(`changing ${this._heights[i]} to ${newHeight}`);
+ this._heights[i] = newHeight;
+ // console.log(`for section ${i} overflow is ${overflow}`);
+ if (!blend) {
+ overflowPerSection = overflow;
+ if (Math.abs(overflow) < 1.0) break;
+ }
+ }
+
+ if (Math.abs(overflow) > 1.0 && unclampedSections.length > 0) {
+ // we weren't able to distribute all the overflow so recurse and try again
+ // log("recursing with", overflow, unclampedSections);
+ overflow = this._applyOverflow(overflow, unclampedSections, blend);
+ }
+
+ return overflow;
+ }
+
+ _rebalanceAbove(anchor, overflowAbove) {
+ if (Math.abs(overflowAbove) > 1.0) {
+ // log(`trying to rebalance upstream with ${overflowAbove}`);
+ const sections = [];
+ for (let i = anchor - 1; i >= 0; i--) {
+ sections.push(i);
+ }
+ overflowAbove = this._applyOverflow(overflowAbove, sections);
+ }
+ return overflowAbove;
+ }
+
+ _rebalanceBelow(anchor, overflowBelow) {
+ if (Math.abs(overflowBelow) > 1.0) {
+ // log(`trying to rebalance downstream with ${overflowBelow}`);
+ const sections = [];
+ for (let i = anchor + 1; i < this._sections.length; i++) {
+ sections.push(i);
+ }
+ overflowBelow = this._applyOverflow(overflowBelow, sections);
+ //log(`rebalanced downstream with ${overflowBelow}`);
+ }
+ return overflowBelow;
+ }
+
+ // @param offset the amount the anchor is moved from what is stored in _sectionHeights, positive if downwards
+ // if we're clamped, return the offset we should be clamped at.
+ _relayout(anchor = 0, offset = 0, clamped = false) {
+ this._heights = this._sections.map((section) => this._sectionHeights[section.id]);
+ // are these the amounts the items above/below shrank/grew and need to be relayouted?
+ let overflowAbove;
+ let overflowBelow;
+ const maxHeight = this._getMaxHeight(anchor);
+ const minHeight = this._getMinHeight(anchor);
+ // new height > max ?
+ if (this._heights[anchor] + offset > maxHeight) {
+ // we're pulling downwards and clamped
+ // overflowAbove = minus how much are we above max height?
+ overflowAbove = (maxHeight - this._heights[anchor]) - offset;
+ overflowBelow = offset;
+ // log(`pulling downwards clamped at max: ${overflowAbove} ${overflowBelow}`);
+ } else if (this._heights[anchor] + offset < minHeight) { // new height < min?
+ // we're pulling upwards and clamped
+ // overflowAbove = ??? (offset is negative here, so - offset will add)
+ overflowAbove = (minHeight - this._heights[anchor]) - offset;
+ overflowBelow = offset;
+ // log(`pulling upwards clamped at min: ${overflowAbove} ${overflowBelow}`);
+ } else {
+ overflowAbove = 0;
+ overflowBelow = offset;
+ // log(`resizing the anchor: ${overflowAbove} ${overflowBelow}`);
+ }
+ this._heights[anchor] = clamp(this._heights[anchor] + offset, minHeight, maxHeight);
+
+ // these are reassigned the amount of overflow that could not be rebalanced
+ // meaning we dragged the handle too far and it can't follow the cursor anymore
+ overflowAbove = this._rebalanceAbove(anchor, overflowAbove);
+ overflowBelow = this._rebalanceBelow(anchor, overflowBelow);
+
+ if (!clamped) { // to avoid risk of infinite recursion
+ // clamp to avoid overflowing or underflowing the page
+ if (Math.abs(overflowAbove) > 1.0) {
+ // log(`clamping with overflowAbove ${overflowAbove}`);
+ // here we do the layout again with offset - the amount of space we took too much
+ this._relayout(anchor, offset + overflowAbove, true);
+ return offset + overflowAbove;
+ }
+
+ if (Math.abs(overflowBelow) > 1.0) {
+ // here we do the layout again with offset - the amount of space we took too much
+ // log(`clamping with overflowBelow ${overflowBelow}`);
+ this._relayout(anchor, offset - overflowBelow, true);
+ return offset - overflowBelow;
+ }
+ }
+
+ this._applyHeights();
+ return undefined;
+ }
+
+ _applyHeights() {
+ log("updating layout, heights are now", this._heights);
+ // apply the heights
+ for (let i = 0; i < this._sections.length; i++) {
+ const section = this._sections[i];
+ this._applyHeight(section.id, this._heights[i]);
+ }
+ }
+
+ _commitHeights() {
+ this._sections.forEach((section, i) => {
+ this._sectionHeights[section.id] = this._heights[i];
+ });
+ }
+}
+
+class Handle {
+ constructor(layout, anchor, height) {
+ this._layout = layout;
+ this._anchor = anchor;
+ this._initialHeight = height;
+ }
+
+ setHeight(height) {
+ this._layout._relayout(this._anchor, height - this._initialHeight);
+ return this;
+ }
+
+ finish() {
+ this._layout._commitHeights();
+ return this;
+ }
+}
+
+export class Distributor extends FixedDistributor {
+ constructor(item, cfg) {
+ super(item);
+ const layout = cfg.layout;
+ this._handle = layout.openHandle(item.id);
+ }
+
+ finish() {
+ this._handle.finish();
+ }
+
+ resize(height) {
+ this._handle.setHeight(height);
+ }
+}