Skip to content

Commit

Permalink
add resize handles between 3 main app columns
Browse files Browse the repository at this point in the history
  • Loading branch information
bwindels committed Sep 24, 2018
1 parent 313956d commit 928b6d4
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 9 deletions.
1 change: 1 addition & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
@import "./views/elements/_MemberEventListSummary.scss";
@import "./views/elements/_ProgressBar.scss";
@import "./views/elements/_ReplyThread.scss";
@import "./views/elements/_ResizeHandle.scss";
@import "./views/elements/_RichText.scss";
@import "./views/elements/_RoleButton.scss";
@import "./views/elements/_Spinner.scss";
Expand Down
40 changes: 40 additions & 0 deletions res/css/views/elements/_ResizeHandle.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Copyright 2018 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_ResizeHandle {
cursor: row-resize;
flex: 0 0 auto;
background: blue;
padding: 1px
}

.mx_ResizeHandle.mx_ResizeHandle_horizontal {
width: 1px;
cursor: e-resize;
}

.mx_ResizeHandle.mx_ResizeHandle_vertical {
height: 1px;
cursor: s-resize;
}

.mx_ResizeHandle.mx_ResizeHandle_horizontal.mx_ResizeHandle_reverse {
cursor: w-resize;
}

.mx_ResizeHandle.mx_ResizeHandle_vertical.mx_ResizeHandle_reverse {
cursor: n-resize;
}
22 changes: 17 additions & 5 deletions src/components/structures/LoggedInView.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import RoomListStore from "../../stores/RoomListStore";

import TagOrderActions from '../../actions/TagOrderActions';
import RoomListActions from '../../actions/RoomListActions';

import ResizeHandle from '../views/elements/ResizeHandle';
import {makeResizeable, FixedDistributor} from '../../resizer'
// We need to fetch each pinned message individually (if we don't already have it)
// so each pinned message may trigger a request. Limit the number per room for sanity.
// NB. this is just for server notices rather than pinned messages in general.
Expand Down Expand Up @@ -91,6 +92,15 @@ const LoggedInView = React.createClass({
};
},

componentDidMount: function() {
const classNames = {
handle: "mx_ResizeHandle",
vertical: "mx_ResizeHandle_vertical",
reverse: "mx_ResizeHandle_reverse"
};
makeResizeable(this.resizeContainer, classNames, FixedDistributor);
},

componentWillMount: function() {
// stash the MatrixClient in case we log out before we are unmounted
this._matrixClient = this.props.matrixClient;
Expand Down Expand Up @@ -186,13 +196,13 @@ const LoggedInView = React.createClass({
_updateServerNoticeEvents: async function() {
const roomLists = RoomListStore.getRoomLists();
if (!roomLists['m.server_notice']) return [];

const pinnedEvents = [];
for (const room of roomLists['m.server_notice']) {
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");

if (!pinStateEvent || !pinStateEvent.getContent().pinned) continue;

const pinnedEventIds = pinStateEvent.getContent().pinned.slice(0, MAX_PINNED_NOTICES_PER_ROOM);
for (const eventId of pinnedEventIds) {
const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId, 0);
Expand All @@ -204,7 +214,7 @@ const LoggedInView = React.createClass({
serverNoticeEvents: pinnedEvents,
});
},


_onKeyDown: function(ev) {
/*
Expand Down Expand Up @@ -481,14 +491,16 @@ const LoggedInView = React.createClass({
<div className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers} onClick={this._onClick}>
{ topBar }
<DragDropContext onDragEnd={this._onDragEnd}>
<div className={bodyClasses}>
<div ref={(div) => this.resizeContainer = div} className={bodyClasses}>
<LeftPanel
collapsed={this.props.collapseLhs || false}
disabled={this.props.leftDisabled}
/>
<ResizeHandle/>
<main className='mx_MatrixChat_middlePanel'>
{ page_element }
</main>
<ResizeHandle reverse={true}/>
{ right_panel }
</div>
</DragDropContext>
Expand Down
26 changes: 26 additions & 0 deletions src/components/views/elements/ResizeHandle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

import React from 'react'; // eslint-disable-line no-unused-vars
import PropTypes from 'prop-types';

//see src/resizer for the actual resizing code, this is just the DOM for the resize handle
const ResizeHandle = (props) => {
const classNames = ['mx_ResizeHandle'];
if (props.vertical) {
classNames.push('mx_ResizeHandle_vertical');
} else {
classNames.push('mx_ResizeHandle_horizontal');
}
if (props.reverse) {
classNames.push('mx_ResizeHandle_reverse');
}
return (
<div className={classNames.join(' ')}/>
);
};

ResizeHandle.propTypes = {
vertical: PropTypes.bool,
reverse: PropTypes.bool,
};

export default ResizeHandle;
7 changes: 3 additions & 4 deletions src/components/views/rooms/RoomTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ module.exports = React.createClass({
},

render: function() {
this.state.badgeHover = true;
const isInvite = this.props.room.getMyMembership() === "invite";
const notificationCount = this.state.notificationCount;
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
Expand Down Expand Up @@ -337,10 +338,8 @@ module.exports = React.createClass({
{ dmIndicator }
</div>
</div>
<div className="mx_RoomTile_nameContainer">
{ label }
{ badge }
</div>
{ label }
{ badge }
{ /* { incomingCallBox } */ }
{ tooltip }
</AccessibleButton>;
Expand Down
96 changes: 96 additions & 0 deletions src/resizer/distributors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
class FixedDistributor {
constructor(container, items, handleIndex, direction, sizer) {
this.item = items[handleIndex + direction];
this.beforeOffset = sizer.getItemOffset(this.item);
this.sizer = sizer;
}

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

finish(_offset) {
}
}


class CollapseDistributor extends FixedDistributor {
constructor(container, items, handleIndex, direction, sizer) {
super(container, items, handleIndex, direction, sizer);
const style = getComputedStyle(this.item);
this.minWidth = parseInt(style.minWidth, 10); //auto becomes NaN
}

resize(offset) {
let newWidth = offset - this.sizer.getItemOffset(this.item);
if (this.minWidth > 0) {
if (offset < this.minWidth + 50) {
this.item.classList.add("collapsed");
newWidth = this.minWidth;
}
else {
this.item.classList.remove("collapsed");
}
}
super.resize(newWidth);
}
}

class PercentageDistributor {

constructor(container, items, handleIndex, direction, sizer) {
this.container = container;
this.totalSize = sizer.getTotalSize();
this.sizer = sizer;

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

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]);
});
}

finish(_offset) {

}

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,
};
70 changes: 70 additions & 0 deletions src/resizer/event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {Sizer} from "./sizer";

/*
classNames:
// class on resize-handle
handle: string
// class on resize-handle
reverse: string
// class on resize-handle
vertical: string
// class on container
resizing: string
*/

function makeResizeable(container, classNames, distributorCtor, sizerCtor = Sizer) {

function isResizeHandle(el) {
return el && el.classList.contains(classNames.handle);
}

function handleMouseDown(event) {
const target = event.target;
if (!isResizeHandle(target) || target.parentElement !== container) {
return;
}
// prevent starting a drag operation
event.preventDefault();
// mark as currently resizing
if (classNames.resizing) {
container.classList.add(classNames.resizing);
}

const resizeHandle = event.target;
const vertical = resizeHandle.classList.contains(classNames.vertical);
const reverse = resizeHandle.classList.contains(classNames.reverse);
const direction = reverse ? 0 : -1;

const sizer = new sizerCtor(container, vertical, reverse);

const items = Array.from(container.children).filter(el => {
return !isResizeHandle(el) && (
isResizeHandle(el.previousElementSibling) ||
isResizeHandle(el.nextElementSibling));
});
const prevItem = resizeHandle.previousElementSibling;
const handleIndex = items.indexOf(prevItem) + 1;
const distributor = new distributorCtor(container, items, handleIndex, direction, sizer);

const onMouseMove = (event) => {
const offset = sizer.offsetFromEvent(event);
distributor.resize(offset);
};

const body = document.body;
const onMouseUp = (event) => {
if (classNames.resizing) {
container.classList.remove(classNames.resizing);
}
const offset = sizer.offsetFromEvent(event);
distributor.finish(offset);
body.removeEventListener("mouseup", onMouseUp, false);
body.removeEventListener("mousemove", onMouseMove, false);
};
body.addEventListener("mouseup", onMouseUp, false);
body.addEventListener("mousemove", onMouseMove, false);
}
container.addEventListener("mousedown", handleMouseDown, false);
}

module.exports = {makeResizeable};
10 changes: 10 additions & 0 deletions src/resizer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {Sizer} from "./sizer";
import {FixedDistributor, PercentageDistributor} from "./distributors";
import {makeResizeable} from "./event";

module.exports = {
makeResizeable,
Sizer,
FixedDistributor,
PercentageDistributor,
};
39 changes: 39 additions & 0 deletions src/resizer/room.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {Sizer} from "./sizer";
import {FixedDistributor} from "./distributors";

class RoomSizer extends Sizer {
setItemSize(item, size) {
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");
item.style.maxHeight = null;
}
} else {
cl.add("show-available");
//item.style.flex = `0 1 ${Math.round(size)}px`;
item.style.maxHeight = `${Math.round(size)}px`;
}
}

}

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");
} else {
this.sizer.setItemSize(this.item, itemSize);
}
}
}

module.exports = {
RoomSizer,
RoomDistributor,
};
Loading

0 comments on commit 928b6d4

Please sign in to comment.