From 0939a40d3a1cb760dfd7afe030059b13cbc3910e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 26 Nov 2018 14:41:15 +0100 Subject: [PATCH 01/10] adjust room distributor to roomsublist dom structure also better classes to apply --- src/resizer/room.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/resizer/room.js b/src/resizer/room.js index 3d68d16f9b..2b79c4adf8 100644 --- a/src/resizer/room.js +++ b/src/resizer/room.js @@ -22,15 +22,14 @@ class RoomSizer extends Sizer { const isString = typeof size === "string"; const cl = item.classList; if (isString) { - item.style.flex = null; - if (size === "show-content") { - cl.add("show-content"); - cl.remove("show-available"); + if (size === "resized-all") { + cl.add("resized-all"); + cl.remove("resized-sized"); item.style.maxHeight = null; } } else { - cl.add("show-available"); - //item.style.flex = `0 1 ${Math.round(size)}px`; + cl.add("resized-sized"); + cl.remove("resized-all"); item.style.maxHeight = `${Math.round(size)}px`; } } @@ -39,9 +38,10 @@ class RoomSizer extends Sizer { class RoomDistributor extends FixedDistributor { resize(offset) { const itemSize = offset - this.sizer.getItemOffset(this.item); - - if (itemSize > this.item.scrollHeight) { - this.sizer.setItemSize(this.item, "show-content"); + const scrollItem = this.item.querySelector(".mx_RoomSubList_scroll"); + const fixedHeight = this.item.offsetHeight - scrollItem.offsetHeight; + if (itemSize > (fixedHeight + scrollItem.scrollHeight)) { + this.sizer.setItemSize(this.item, "resized-all"); } else { this.sizer.setItemSize(this.item, itemSize); } From 8929ff9b5e57b955f59f9f3fee80b84f9e1bb934 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 26 Nov 2018 14:41:39 +0100 Subject: [PATCH 02/10] use room resize classes --- src/components/views/rooms/RoomList.js | 4 ++-- src/resizer/index.js | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 126ff5b3b9..230e6b1e66 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -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))$/; @@ -136,7 +136,7 @@ module.exports = React.createClass({ componentDidMount: function() { this.dispatcherRef = dis.register(this.onAction); - this.resizer = new Resizer(this.resizeContainer, FixedDistributor, null, FlexSizer); + this.resizer = new Resizer(this.resizeContainer, RoomDistributor, null, RoomSizer); this.resizer.setClassNames({ handle: "mx_ResizeHandle", vertical: "mx_ResizeHandle_vertical", diff --git a/src/resizer/index.js b/src/resizer/index.js index df7a839b9b..69e1f572e6 100644 --- a/src/resizer/index.js +++ b/src/resizer/index.js @@ -17,6 +17,7 @@ limitations under the License. import {Sizer, FlexSizer} from "./sizer"; import {FixedDistributor, CollapseDistributor, PercentageDistributor} from "./distributors"; import {Resizer} from "./resizer"; +import {RoomSizer, RoomDistributor} from "./room"; module.exports = { Resizer, @@ -25,4 +26,6 @@ module.exports = { FixedDistributor, CollapseDistributor, PercentageDistributor, + RoomSizer, + RoomDistributor, }; From 8e77a6716caad5af60e17bdedc3f2e2c8ee2ef77 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 26 Nov 2018 14:49:30 +0100 Subject: [PATCH 03/10] don't set initial size based on item count anymore --- src/components/structures/RoomSubList.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 6399fd80d9..91b29d4665 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -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
+ return
{this._getHeaderJsx()}
; } else { - const heightEstimation = (len * 44) + 31 + (8 + 8); - const style = { - maxHeight: `${heightEstimation}px`, - }; const tiles = this.makeRoomTiles(); tiles.push(...this.props.extraTiles); - return
+ return
{this._getHeaderJsx()} { tiles } From 61d9fe95e90d734a8bb537e098fc3802268eb160 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 26 Nov 2018 14:50:29 +0100 Subject: [PATCH 04/10] add 3 flex-shrink categories + make it work with overflow indicators --- res/css/structures/_RoomSubList.scss | 95 ++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 28 deletions(-) diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index c156f1f07e..8db0d4b836 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -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 { @@ -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_IndicatorScrollbar_topOverflow > .mx_AutoHideScrollbar_offset { + margin-top: -40px; + } + &.mx_IndicatorScrollbar_bottomOverflow > .mx_AutoHideScrollbar_offset { + margin-bottom: -40px; + } -.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::before { + top: 0; + background: linear-gradient($secondary-accent-color, transparent); + } -.mx_RoomSubList_scroll.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 { From 257bac2b090ac9a643565863cc1bf5eed97d3239 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 26 Nov 2018 14:50:53 +0100 Subject: [PATCH 05/10] unrelated fix for searchbox being squashed by other roomlist content --- res/css/views/rooms/_RoomList.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss index 30a569d41f..8f78e3bb7a 100644 --- a/res/css/views/rooms/_RoomList.scss +++ b/res/css/views/rooms/_RoomList.scss @@ -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; From 3e496833fc5b5ec1764134a09b6d18617b1d356d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 26 Nov 2018 16:41:49 +0100 Subject: [PATCH 06/10] remove unused PercentageDistributor it's not used and we need to make some api changes that don't work with it (resize will receive itemSize which it doesn't really support) --- src/resizer/distributors.js | 48 ------------------------------------- src/resizer/index.js | 3 +-- 2 files changed, 1 insertion(+), 50 deletions(-) diff --git a/src/resizer/distributors.js b/src/resizer/distributors.js index 29e97e0bce..3c3fd72621 100644 --- a/src/resizer/distributors.js +++ b/src/resizer/distributors.js @@ -73,55 +73,7 @@ class CollapseDistributor extends FixedDistributor { } } -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, }; diff --git a/src/resizer/index.js b/src/resizer/index.js index 69e1f572e6..0720fa36ce 100644 --- a/src/resizer/index.js +++ b/src/resizer/index.js @@ -15,7 +15,7 @@ limitations under the License. */ import {Sizer, FlexSizer} from "./sizer"; -import {FixedDistributor, CollapseDistributor, PercentageDistributor} from "./distributors"; +import {FixedDistributor, CollapseDistributor} from "./distributors"; import {Resizer} from "./resizer"; import {RoomSizer, RoomDistributor} from "./room"; @@ -25,7 +25,6 @@ module.exports = { FlexSizer, FixedDistributor, CollapseDistributor, - PercentageDistributor, RoomSizer, RoomDistributor, }; From dae509d0eb8614a4d2521b3679226f0fb2ac5ced Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 26 Nov 2018 16:42:58 +0100 Subject: [PATCH 07/10] introduce resizeFromContainerOffset method on distributor up till now, resize received the offset of the resize handle within the container upon resizing, and would then calculate it's new size. For this first item in the container, this is the same, and has therefor not been a problem yet. Now however, we'll need to be able to programatically (from localStorage) set the size of any roomsublist, so need a method we can call with just the size and not an offset within the container. The resizer calls the new method, which subsequently calls resize. This also has the nice side-effect that you can now easily call super.resize after having transformed the new item size --- src/resizer/distributors.js | 27 +++++++++++++++------------ src/resizer/resizer.js | 2 +- src/resizer/room.js | 11 +++++++---- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/resizer/distributors.js b/src/resizer/distributors.js index 3c3fd72621..caf677a18f 100644 --- a/src/resizer/distributors.js +++ b/src/resizer/distributors.js @@ -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; @@ -68,7 +71,7 @@ class CollapseDistributor extends FixedDistributor { this.isCollapsed = false; } if (!isCollapsedSize) { - super.resize(offset); + super.resize(newSize); } } } diff --git a/src/resizer/resizer.js b/src/resizer/resizer.js index c5112e5139..b7d17bc807 100644 --- a/src/resizer/resizer.js +++ b/src/resizer/resizer.js @@ -88,7 +88,7 @@ export class Resizer { const onMouseMove = (event) => { const offset = sizer.offsetFromEvent(event); - distributor.resize(offset); + distributor.resizeFromContainerOffset(offset); }; const body = document.body; diff --git a/src/resizer/room.js b/src/resizer/room.js index 2b79c4adf8..70f8be219a 100644 --- a/src/resizer/room.js +++ b/src/resizer/room.js @@ -36,16 +36,19 @@ class RoomSizer extends Sizer { } class RoomDistributor extends FixedDistributor { - resize(offset) { - const itemSize = offset - this.sizer.getItemOffset(this.item); + resize(itemSize) { const scrollItem = this.item.querySelector(".mx_RoomSubList_scroll"); const fixedHeight = this.item.offsetHeight - scrollItem.offsetHeight; if (itemSize > (fixedHeight + scrollItem.scrollHeight)) { - this.sizer.setItemSize(this.item, "resized-all"); + super.resize("resized-all"); } else { - this.sizer.setItemSize(this.item, itemSize); + super.resize(itemSize); } } + + resizeFromContainerOffset(offset) { + return this.resize(offset - this.sizer.getItemOffset(this.item)); + } } module.exports = { From 35fc5307b6b181c85053a3b17e365b45c05c3f2d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 26 Nov 2018 16:45:55 +0100 Subject: [PATCH 08/10] remove unneeded params (as we're going to change their meaning) --- src/components/structures/LoggedInView.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 585fd0f7d4..635c5de44e 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -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); }, }; From f7a37be6ddc15e6fc528cdb4c99043beedec68d1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 26 Nov 2018 16:46:27 +0100 Subject: [PATCH 09/10] support associating an id with a resize item/handle --- src/components/views/elements/ResizeHandle.js | 2 +- src/resizer/resizer.js | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/views/elements/ResizeHandle.js b/src/components/views/elements/ResizeHandle.js index 4c09278f84..b5487b1fc1 100644 --- a/src/components/views/elements/ResizeHandle.js +++ b/src/components/views/elements/ResizeHandle.js @@ -14,7 +14,7 @@ const ResizeHandle = (props) => { classNames.push('mx_ResizeHandle_reverse'); } return ( -
+
); }; diff --git a/src/resizer/resizer.js b/src/resizer/resizer.js index b7d17bc807..7ef542a6e1 100644 --- a/src/resizer/resizer.js +++ b/src/resizer/resizer.js @@ -64,8 +64,19 @@ export class Resizer { forHandleAt(handleIndex) { const handles = this._getResizeHandles(); const handle = handles[handleIndex]; - const {distributor} = this._createSizerAndDistributor(handle); - return distributor; + if (handle) { + const {distributor} = this._createSizerAndDistributor(handle); + return distributor; + } + } + + forHandleWithId(id) { + const handles = this._getResizeHandles(); + const handle = handles.find((h) => h.getAttribute("data-id") === id); + if (handle) { + const {distributor} = this._createSizerAndDistributor(handle); + return distributor; + } } _isResizeHandle(el) { @@ -79,6 +90,7 @@ export class Resizer { } // prevent starting a drag operation event.preventDefault(); + // mark as currently resizing if (this.classNames.resizing) { this.container.classList.add(this.classNames.resizing); @@ -115,9 +127,10 @@ export class Resizer { // if reverse, resize the item after the handle instead of before, so + 1 const itemIndex = items.indexOf(prevItem) + (reverse ? 1 : 0); const item = items[itemIndex]; + const id = resizeHandle.getAttribute("data-id"); // eslint-disable-next-line new-cap const distributor = new this.distributorCtor( - sizer, item, this.distributorCfg, + sizer, item, id, this.distributorCfg, items, this.container); return {sizer, distributor}; } From 0c7d51d70d6cbb59e2746cf9bd388d82ea3b6717 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 26 Nov 2018 16:46:57 +0100 Subject: [PATCH 10/10] persist room sub list sizes --- src/components/views/rooms/RoomList.js | 34 +++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 230e6b1e66..d4599e5d8a 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -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, @@ -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, RoomDistributor, null, RoomSizer); + 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; }, @@ -476,7 +500,7 @@ module.exports = React.createClass({ if (!isLast) { return components.concat( subList, - + ); } else { return components.concat(subList); @@ -484,6 +508,10 @@ module.exports = React.createClass({ }, []); }, + _collectResizeContainer: function(el) { + this.resizeContainer = el; + }, + render: function() { let subLists = [ { @@ -560,7 +588,7 @@ module.exports = React.createClass({ const subListComponents = this._mapSubListProps(subLists); return ( -
this.resizeContainer = d} className="mx_RoomList"> +
{ subListComponents }
);