diff --git a/res/css/_components.scss b/res/css/_components.scss
index 1e1d3e6596..c43d9edc16 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -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";
diff --git a/res/css/views/elements/_ResizeHandle.scss b/res/css/views/elements/_ResizeHandle.scss
new file mode 100644
index 0000000000..550dcff911
--- /dev/null
+++ b/res/css/views/elements/_ResizeHandle.scss
@@ -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;
+}
diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js
index 0c4688a411..174a742c44 100644
--- a/src/components/structures/LoggedInView.js
+++ b/src/components/structures/LoggedInView.js
@@ -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.
@@ -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;
@@ -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);
@@ -204,7 +214,7 @@ const LoggedInView = React.createClass({
serverNoticeEvents: pinnedEvents,
});
},
-
+
_onKeyDown: function(ev) {
/*
@@ -481,14 +491,16 @@ const LoggedInView = React.createClass({
{ topBar }
-
+
this.resizeContainer = div} className={bodyClasses}>
+
{ page_element }
+
{ right_panel }
diff --git a/src/components/views/elements/ResizeHandle.js b/src/components/views/elements/ResizeHandle.js
new file mode 100644
index 0000000000..ae324a752b
--- /dev/null
+++ b/src/components/views/elements/ResizeHandle.js
@@ -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 (
+
+ );
+};
+
+ResizeHandle.propTypes = {
+ vertical: PropTypes.bool,
+ reverse: PropTypes.bool,
+};
+
+export default ResizeHandle;
diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js
index a8552aa142..e70ea210f4 100644
--- a/src/components/views/rooms/RoomTile.js
+++ b/src/components/views/rooms/RoomTile.js
@@ -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");
@@ -337,10 +338,8 @@ module.exports = React.createClass({
{ dmIndicator }
-
- { label }
- { badge }
-
+ { label }
+ { badge }
{ /* { incomingCallBox } */ }
{ tooltip }
;
diff --git a/src/resizer/distributors.js b/src/resizer/distributors.js
new file mode 100644
index 0000000000..bef3377df2
--- /dev/null
+++ b/src/resizer/distributors.js
@@ -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,
+};
diff --git a/src/resizer/event.js b/src/resizer/event.js
new file mode 100644
index 0000000000..3baa67e097
--- /dev/null
+++ b/src/resizer/event.js
@@ -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};
diff --git a/src/resizer/index.js b/src/resizer/index.js
new file mode 100644
index 0000000000..923471b1f2
--- /dev/null
+++ b/src/resizer/index.js
@@ -0,0 +1,10 @@
+import {Sizer} from "./sizer";
+import {FixedDistributor, PercentageDistributor} from "./distributors";
+import {makeResizeable} from "./event";
+
+module.exports = {
+ makeResizeable,
+ Sizer,
+ FixedDistributor,
+ PercentageDistributor,
+};
diff --git a/src/resizer/room.js b/src/resizer/room.js
new file mode 100644
index 0000000000..0080cca3eb
--- /dev/null
+++ b/src/resizer/room.js
@@ -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,
+};
diff --git a/src/resizer/sizer.js b/src/resizer/sizer.js
new file mode 100644
index 0000000000..2dc116714f
--- /dev/null
+++ b/src/resizer/sizer.js
@@ -0,0 +1,70 @@
+class Sizer {
+ constructor(container, vertical, reverse) {
+ this.container = container;
+ this.reverse = reverse;
+ this.vertical = vertical;
+ }
+
+ getItemPercentage(item) {
+ /*
+ const flexGrow = window.getComputedStyle(item).flexGrow;
+ if (flexGrow === "") {
+ return null;
+ }
+ return parseInt(flexGrow) / 1000;
+ */
+ const style = window.getComputedStyle(item);
+ const sizeStr = this.vertical ? style.height : style.width;
+ const size = parseInt(sizeStr, 10);
+ return size / this.getTotalSize();
+ }
+
+ setItemPercentage(item, percent) {
+ item.style.flexGrow = Math.round(percent * 1000);
+ }
+
+ /** returns how far the edge of the item is from the edge of the container */
+ getItemOffset(item) {
+ const offset = (this.vertical ? item.offsetTop : item.offsetLeft) - this._getOffset();
+ if (this.reverse) {
+ return this.getTotalSize() - (offset + this.getItemSize(item));
+ } else {
+ return offset;
+ }
+ }
+
+ /** returns the width/height of an item in the container */
+ getItemSize(item) {
+ return this.vertical ? item.offsetHeight : item.offsetWidth;
+ }
+
+ /** returns the width/height of the container */
+ getTotalSize() {
+ return this.vertical ? this.container.offsetHeight : this.container.offsetWidth;
+ }
+
+ /** container offset to offsetParent */
+ _getOffset() {
+ return this.vertical ? this.container.offsetTop : this.container.offsetLeft;
+ }
+
+ setItemSize(item, size) {
+ if (this.vertical) {
+ item.style.height = `${Math.round(size)}px`;
+ } else {
+ item.style.width = `${Math.round(size)}px`;
+ }
+ }
+
+ /** returns the position of cursor at event relative to the edge of the container */
+ offsetFromEvent(event) {
+ const pos = this.vertical ? event.pageY : event.pageX;
+ if (this.reverse) {
+ return (this._getOffset() + this.getTotalSize()) - pos;
+ } else {
+ return pos - this._getOffset();
+ }
+ }
+}
+
+module.exports = {Sizer};