mirror of
https://github.com/element-hq/element-web
synced 2024-11-23 17:56:01 +03:00
Merge pull request #2210 from matrix-org/bwindels/resizehandles
Redesign: resizeable/collapsible sections
This commit is contained in:
commit
933028120b
18 changed files with 671 additions and 159 deletions
|
@ -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";
|
||||
|
|
|
@ -15,32 +15,20 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_LeftPanel {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
border-right: 1px solid $panel-divider-color;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_container {
|
||||
display: flex;
|
||||
/* LeftPanel 260px */
|
||||
flex: 0 0 260px;
|
||||
min-width: 260px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_container.mx_LeftPanel_container_hasTagPanel {
|
||||
/* TagPanel 70px + LeftPanel 260px */
|
||||
flex: 0 0 330px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_container_collapsed {
|
||||
.mx_LeftPanel_container.collapsed {
|
||||
min-width: unset;
|
||||
/* Collapsed LeftPanel 70px */
|
||||
flex: 0 0 70px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_container_collapsed.mx_LeftPanel_container_hasTagPanel {
|
||||
.mx_LeftPanel_container.collapsed.mx_LeftPanel_container_hasTagPanel {
|
||||
/* TagPanel 70px + Collapsed LeftPanel 70px */
|
||||
flex: 0 0 140px;
|
||||
}
|
||||
|
@ -57,6 +45,15 @@ limitations under the License.
|
|||
|
||||
}
|
||||
|
||||
.mx_LeftPanel {
|
||||
background-color: $secondary-accent-color;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mx_LeftPanel .mx_AppTile_mini {
|
||||
height: 132px;
|
||||
}
|
||||
|
@ -70,7 +67,7 @@ limitations under the License.
|
|||
z-index: 6;
|
||||
}
|
||||
|
||||
.mx_LeftPanel.collapsed .mx_BottomLeftMenu {
|
||||
.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu {
|
||||
flex: 0 0 160px;
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
@ -93,7 +90,7 @@ limitations under the License.
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoleButton {
|
||||
.mx_LeftPanel_container.collapsed .mx_RoleButton {
|
||||
margin-right: 0px ! important;
|
||||
padding-top: 3px ! important;
|
||||
padding-bottom: 3px ! important;
|
||||
|
@ -117,7 +114,7 @@ limitations under the License.
|
|||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel.collapsed .mx_BottomLeftMenu_settings {
|
||||
.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu_settings {
|
||||
float: none;
|
||||
}
|
||||
|
||||
|
@ -126,7 +123,7 @@ limitations under the License.
|
|||
flex: 0 0 50px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel.collapsed .mx_BottomLeftMenu {
|
||||
.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu {
|
||||
flex: 0 0 160px;
|
||||
}
|
||||
|
||||
|
|
|
@ -68,19 +68,7 @@ limitations under the License.
|
|||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.mx_MatrixChat .mx_LeftPanel {
|
||||
order: 1;
|
||||
background-color: $secondary-accent-color;
|
||||
flex: 0 0 260px;
|
||||
}
|
||||
|
||||
.mx_MatrixChat .mx_LeftPanel.collapsed {
|
||||
flex: 0 0 60px;
|
||||
}
|
||||
|
||||
.mx_MatrixChat .mx_MatrixChat_middlePanel {
|
||||
order: 2;
|
||||
|
||||
background-color: $primary-bg-color;
|
||||
|
||||
flex: 1;
|
||||
|
@ -100,13 +88,3 @@ limitations under the License.
|
|||
*/
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mx_MatrixChat .mx_RightPanel {
|
||||
order: 3;
|
||||
|
||||
flex: 0 0 235px;
|
||||
}
|
||||
|
||||
.mx_MatrixChat .mx_RightPanel.collapsed {
|
||||
flex: 0 0 122px;
|
||||
}
|
||||
|
|
|
@ -15,8 +15,10 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
.mx_RightPanel {
|
||||
overflow-x: hidden;
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
|
||||
min-width: 250px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ limitations under the License.
|
|||
|
||||
.mx_RoomSubList_labelContainer {
|
||||
height: 31px; /* mx_RoomSubList_label height including border */
|
||||
width: 235px; /* LHS Panel width */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
@ -39,7 +38,6 @@ limitations under the License.
|
|||
color: $roomsublist-label-fg-color;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
width: 203px; /* padding + width = LHS Panel width */
|
||||
height: 19px; /* height + padding = 31px = mx_RoomSubList_label height */
|
||||
margin-left: 16px;
|
||||
padding-left: 16px; /* gutter */
|
||||
|
@ -57,15 +55,6 @@ limitations under the License.
|
|||
/* pointer-events: none; */
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomSubList_label {
|
||||
height: 17px;
|
||||
width: 28px; /* collapsed LHS Panel width */
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomSubList_labelContainer {
|
||||
width: 28px; /* collapsed LHS Panel width */
|
||||
}
|
||||
|
||||
.mx_RoomSubList_roomCount {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
|
@ -75,10 +64,6 @@ limitations under the License.
|
|||
text-transform: none;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomSubList_roomCount {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_badge {
|
||||
display: inline-block;
|
||||
min-width: 15px;
|
||||
|
@ -101,12 +86,6 @@ limitations under the License.
|
|||
filter: brightness($focus-brightness);
|
||||
}
|
||||
|
||||
/*
|
||||
.collapsed .mx_RoomSubList_badge {
|
||||
display: none;
|
||||
}
|
||||
*/
|
||||
|
||||
.mx_RoomSubList_badgeHighlight {
|
||||
background-color: $warning-color;
|
||||
}
|
||||
|
@ -123,11 +102,6 @@ limitations under the License.
|
|||
border-right: 7px solid transparent;
|
||||
}
|
||||
|
||||
/* Hide the bottom of speech bubble */
|
||||
.collapsed .mx_RoomSubList_badgeHighlight:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_chevron {
|
||||
left: 0px;
|
||||
pointer-events: none;
|
||||
|
@ -165,10 +139,6 @@ limitations under the License.
|
|||
background-color: $secondary-accent-color;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomSubList_ellipsis {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_line {
|
||||
display: inline-block;
|
||||
width: 159px;
|
||||
|
@ -176,10 +146,6 @@ limitations under the License.
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomSubList_line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_more {
|
||||
display: inline-block;
|
||||
text-transform: uppercase;
|
||||
|
@ -193,10 +159,6 @@ limitations under the License.
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomSubList_more {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_moreBadge {
|
||||
display: inline-block;
|
||||
min-width: 15px;
|
||||
|
@ -233,12 +195,6 @@ limitations under the License.
|
|||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomSubList_moreBadge {
|
||||
position: static;
|
||||
margin-left: 16px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_ellipsis .mx_RoomSubList_chevronDown {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
|
@ -246,3 +202,40 @@ limitations under the License.
|
|||
}
|
||||
|
||||
|
||||
.collapsed {
|
||||
.mx_RoomSubList_label {
|
||||
height: 17px;
|
||||
width: 28px; /* collapsed LHS Panel width */
|
||||
}
|
||||
|
||||
.mx_RoomSubList_labelContainer {
|
||||
width: 28px; /* collapsed LHS Panel width */
|
||||
}
|
||||
|
||||
.mx_RoomSubList_roomCount {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide the bottom of speech bubble */
|
||||
.mx_RoomSubList_badgeHighlight:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_moreBadge {
|
||||
position: static;
|
||||
margin-left: 16px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_ellipsis {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_more {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
32
res/css/views/elements/_ResizeHandle.scss
Normal file
32
res/css/views/elements/_ResizeHandle.scss
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
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: $panel-divider-color;
|
||||
padding: 1px
|
||||
}
|
||||
|
||||
.mx_ResizeHandle.mx_ResizeHandle_horizontal {
|
||||
width: 1px;
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.mx_ResizeHandle.mx_ResizeHandle_vertical {
|
||||
height: 1px;
|
||||
cursor: row-resize;
|
||||
}
|
|
@ -15,12 +15,13 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
.mx_RoomTile {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 40px;
|
||||
margin: 0px 9px 0px 9px;
|
||||
|
||||
margin: 0px 3px;
|
||||
position: relative;
|
||||
background-color: $secondary-accent-color;
|
||||
}
|
||||
|
||||
|
@ -31,26 +32,18 @@ limitations under the License.
|
|||
left: -12px;
|
||||
}
|
||||
|
||||
|
||||
.mx_RoomTile_nameContainer {
|
||||
display: inline-block;
|
||||
width: 180px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.mx_RoomTile_avatar_container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_RoomTile_avatar {
|
||||
display: inline-block;
|
||||
flex: 0;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 14px;
|
||||
padding-right: 12px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_RoomTile_avatar_container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_RoomTile_dm {
|
||||
|
@ -62,19 +55,13 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_RoomTile_name {
|
||||
display: inline-block;
|
||||
flex: 1 5 auto;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
width: 165px;
|
||||
vertical-align: middle;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 3px;
|
||||
padding: 6px;
|
||||
color: $roomtile-name-color;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
|
@ -82,25 +69,30 @@ limitations under the License.
|
|||
/* color: rgba(69, 69, 69, 0.5); */
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomTile_nameContainer {
|
||||
width: 60px; /* colapsed panel width */
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomTile_name {
|
||||
.collapsed {
|
||||
.mx_RoomTile_name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.collapsed .mx_RoomTile_badge {
|
||||
top: 0px;
|
||||
.mx_RoomTile_badge {
|
||||
min-width: 12px;
|
||||
border-radius: 16px;
|
||||
padding: 0px 4px 0px 4px;
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide the bottom of speech bubble */
|
||||
.collapsed .mx_RoomTile_highlight .mx_RoomTile_badge:after {
|
||||
/* Hide the bottom of speech bubble */
|
||||
.mx_RoomTile_highlight .mx_RoomTile_badge:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_RoomTile_badge {
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 15px;
|
||||
right: 5px;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* This is the bottom of the speech bubble */
|
||||
|
@ -116,12 +108,8 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_RoomTile_badge {
|
||||
display: inline-block;
|
||||
flex: 0 1 content;
|
||||
min-width: 15px;
|
||||
height: 15px;
|
||||
position: absolute;
|
||||
right: 8px; /*gutter */
|
||||
top: 9px;
|
||||
border-radius: 8px;
|
||||
color: $accent-fg-color;
|
||||
font-weight: 600;
|
||||
|
|
|
@ -155,7 +155,7 @@ class Tinter {
|
|||
|
||||
tint(primaryColor, secondaryColor, tertiaryColor) {
|
||||
return;
|
||||
|
||||
// eslint-disable-next-line no-unreachable
|
||||
this.currentTint[0] = primaryColor;
|
||||
this.currentTint[1] = secondaryColor;
|
||||
this.currentTint[2] = tertiaryColor;
|
||||
|
|
|
@ -192,20 +192,13 @@ var LeftPanel = React.createClass({
|
|||
topBox = <SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />;
|
||||
}
|
||||
*/
|
||||
const classes = classNames(
|
||||
"mx_LeftPanel",
|
||||
{
|
||||
"collapsed": this.props.collapsed,
|
||||
},
|
||||
);
|
||||
|
||||
const tagPanelEnabled = !SettingsStore.getValue("TagPanel.disableTagPanel");
|
||||
const tagPanel = tagPanelEnabled ? <TagPanel /> : <div />;
|
||||
|
||||
const containerClasses = classNames(
|
||||
"mx_LeftPanel_container", "mx_fadable",
|
||||
{
|
||||
"mx_LeftPanel_container_collapsed": this.props.collapsed,
|
||||
"collapsed": this.props.collapsed,
|
||||
"mx_LeftPanel_container_hasTagPanel": tagPanelEnabled,
|
||||
"mx_fadable_faded": this.props.disabled,
|
||||
},
|
||||
|
@ -214,7 +207,7 @@ var LeftPanel = React.createClass({
|
|||
return (
|
||||
<div className={containerClasses}>
|
||||
{ tagPanel }
|
||||
<aside className={classes} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
|
||||
<aside className={"mx_LeftPanel"} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
|
||||
{ topBox }
|
||||
<CallPreview ConferenceHandler={VectorConferenceHandler} />
|
||||
<RoomList
|
||||
|
|
|
@ -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 {Resizer, CollapseDistributor} 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,12 @@ const LoggedInView = React.createClass({
|
|||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.resizer = this._createResizer();
|
||||
this.resizer.attach();
|
||||
this._loadResizerPreferences();
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// stash the MatrixClient in case we log out before we are unmounted
|
||||
this._matrixClient = this.props.matrixClient;
|
||||
|
@ -120,6 +127,7 @@ const LoggedInView = React.createClass({
|
|||
if (this._sessionStoreToken) {
|
||||
this._sessionStoreToken.remove();
|
||||
}
|
||||
this.resizer.detach();
|
||||
},
|
||||
|
||||
// Child components assume that the client peg will not be null, so give them some
|
||||
|
@ -145,6 +153,49 @@ const LoggedInView = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
_createResizer() {
|
||||
const classNames = {
|
||||
handle: "mx_ResizeHandle",
|
||||
vertical: "mx_ResizeHandle_vertical",
|
||||
reverse: "mx_ResizeHandle_reverse"
|
||||
};
|
||||
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');
|
||||
}
|
||||
}
|
||||
},
|
||||
onResized: (size, item) => {
|
||||
if (item.classList.contains("mx_LeftPanel_container")) {
|
||||
window.localStorage.setItem("mx_lhs_size", '' + size);
|
||||
} else if(item.classList.contains("mx_RightPanel")) {
|
||||
window.localStorage.setItem("mx_rhs_size", '' + size);
|
||||
}
|
||||
},
|
||||
};
|
||||
const resizer = new Resizer(
|
||||
this.resizeContainer,
|
||||
CollapseDistributor,
|
||||
collapseConfig);
|
||||
resizer.setClassNames(classNames);
|
||||
return resizer;
|
||||
},
|
||||
|
||||
_loadResizerPreferences() {
|
||||
const lhsSize = window.localStorage.getItem("mx_lhs_size");
|
||||
if (lhsSize !== null) {
|
||||
this.resizer.forHandleAt(0).resize(parseInt(lhsSize, 10));
|
||||
}
|
||||
const rhsSize = window.localStorage.getItem("mx_rhs_size");
|
||||
if (rhsSize !== null) {
|
||||
this.resizer.forHandleAt(1).resize(parseInt(rhsSize, 10));
|
||||
}
|
||||
},
|
||||
|
||||
onAccountData: function(event) {
|
||||
if (event.getType() === "im.vector.web.settings") {
|
||||
this.setState({
|
||||
|
@ -361,6 +412,10 @@ const LoggedInView = React.createClass({
|
|||
this.setState({mouseDown: null});
|
||||
},
|
||||
|
||||
_setResizeContainerRef(div) {
|
||||
this.resizeContainer = div;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const LeftPanel = sdk.getComponent('structures.LeftPanel');
|
||||
const RightPanel = sdk.getComponent('structures.RightPanel');
|
||||
|
@ -507,14 +562,16 @@ const LoggedInView = React.createClass({
|
|||
<div className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers} onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp}>
|
||||
{ topBar }
|
||||
<DragDropContext onDragEnd={this._onDragEnd}>
|
||||
<div className={bodyClasses}>
|
||||
<div ref={this._setResizeContainerRef} className={bodyClasses}>
|
||||
<LeftPanel
|
||||
collapsed={this.props.collapseLhs || false}
|
||||
collapsed={this.props.collapseLhs || this.state.collapseLhs || false}
|
||||
disabled={this.props.leftDisabled}
|
||||
/>
|
||||
<ResizeHandle/>
|
||||
<main className='mx_MatrixChat_middlePanel'>
|
||||
{ page_element }
|
||||
</main>
|
||||
<ResizeHandle reverse={true}/>
|
||||
{ right_panel }
|
||||
</div>
|
||||
</DragDropContext>
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
|
||||
class TopLeftMenu extends React.Component {
|
||||
|
||||
|
@ -30,7 +29,7 @@ class TopLeftMenu extends React.Component {
|
|||
render() {
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
const avatarHeight = 28;
|
||||
const name = "My stuff"
|
||||
const name = "My stuff";
|
||||
|
||||
return (
|
||||
<div className="mx_TopLeftMenu">
|
||||
|
@ -43,7 +42,7 @@ class TopLeftMenu extends React.Component {
|
|||
<div className="mx_TopLeftMenu_name">
|
||||
{ name }
|
||||
</div>
|
||||
<img className="mx_TopLeftMenu_chevron" src="img/topleft-chevron.svg" width="11" height="6"/>
|
||||
<img className="mx_TopLeftMenu_chevron" src="img/topleft-chevron.svg" width="11" height="6" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
26
src/components/views/elements/ResizeHandle.js
Normal file
26
src/components/views/elements/ResizeHandle.js
Normal file
|
@ -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;
|
|
@ -337,10 +337,8 @@ module.exports = React.createClass({
|
|||
{ dmIndicator }
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_RoomTile_nameContainer">
|
||||
{ label }
|
||||
{ badge }
|
||||
</div>
|
||||
{ /* { incomingCallBox } */ }
|
||||
{ tooltip }
|
||||
</AccessibleButton>;
|
||||
|
|
128
src/resizer/distributors.js
Normal file
128
src/resizer/distributors.js
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
distributors translate a moving cursor into
|
||||
CSS/DOM changes by calling the sizer
|
||||
|
||||
they have one method, `resize` that receives
|
||||
the offset from the container edge of where
|
||||
the mouse cursor is.
|
||||
*/
|
||||
class FixedDistributor {
|
||||
constructor(sizer, item, config) {
|
||||
this.sizer = sizer;
|
||||
this.item = item;
|
||||
this.beforeOffset = sizer.getItemOffset(this.item);
|
||||
this.onResized = config.onResized;
|
||||
}
|
||||
|
||||
resize(offset) {
|
||||
const itemSize = offset - this.beforeOffset;
|
||||
this.sizer.setItemSize(this.item, itemSize);
|
||||
if (this.onResized) {
|
||||
this.onResized(itemSize, this.item);
|
||||
}
|
||||
return itemSize;
|
||||
}
|
||||
|
||||
sizeFromOffset(offset) {
|
||||
return offset - this.beforeOffset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CollapseDistributor extends FixedDistributor {
|
||||
constructor(sizer, item, config) {
|
||||
super(sizer, item, config);
|
||||
this.toggleSize = config && config.toggleSize;
|
||||
this.onCollapsed = config && config.onCollapsed;
|
||||
this.isCollapsed = false;
|
||||
}
|
||||
|
||||
resize(offset) {
|
||||
const newSize = this.sizeFromOffset(offset);
|
||||
const isCollapsedSize = newSize < this.toggleSize;
|
||||
if (isCollapsedSize && !this.isCollapsed) {
|
||||
this.isCollapsed = true;
|
||||
if (this.onCollapsed) {
|
||||
this.onCollapsed(true, this.item);
|
||||
}
|
||||
} else if (!isCollapsedSize && this.isCollapsed) {
|
||||
if (this.onCollapsed) {
|
||||
this.onCollapsed(false, this.item);
|
||||
}
|
||||
this.isCollapsed = false;
|
||||
}
|
||||
if (!isCollapsedSize) {
|
||||
super.resize(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
27
src/resizer/index.js
Normal file
27
src/resizer/index.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import {Sizer} from "./sizer";
|
||||
import {FixedDistributor, CollapseDistributor, PercentageDistributor} from "./distributors";
|
||||
import {Resizer} from "./resizer";
|
||||
|
||||
module.exports = {
|
||||
Resizer,
|
||||
Sizer,
|
||||
FixedDistributor,
|
||||
CollapseDistributor,
|
||||
PercentageDistributor,
|
||||
};
|
138
src/resizer/resizer.js
Normal file
138
src/resizer/resizer.js
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
export class Resizer {
|
||||
constructor(container, distributorCtor, distributorCfg, sizerCtor = Sizer) {
|
||||
this.container = container;
|
||||
this.distributorCtor = distributorCtor;
|
||||
this.distributorCfg = distributorCfg;
|
||||
this.sizerCtor = sizerCtor;
|
||||
this.classNames = {
|
||||
handle: "resizer-handle",
|
||||
reverse: "resizer-reverse",
|
||||
vertical: "resizer-vertical",
|
||||
resizing: "resizer-resizing",
|
||||
};
|
||||
this._onMouseDown = this._onMouseDown.bind(this);
|
||||
}
|
||||
|
||||
setClassNames(classNames) {
|
||||
this.classNames = classNames;
|
||||
}
|
||||
|
||||
attach() {
|
||||
this.container.addEventListener("mousedown", this._onMouseDown, false);
|
||||
}
|
||||
|
||||
detach() {
|
||||
this.container.removeEventListener("mousedown", this._onMouseDown, false);
|
||||
}
|
||||
|
||||
/**
|
||||
Gives the distributor for a specific resize handle, as if you would have started
|
||||
to drag that handle. Can be used to manipulate the size of an item programmatically.
|
||||
@param {number} handleIndex the index of the resize handle in the container
|
||||
@return {Distributor} a new distributor for the given handle
|
||||
*/
|
||||
forHandleAt(handleIndex) {
|
||||
const handles = this._getResizeHandles();
|
||||
const handle = handles[handleIndex];
|
||||
const {distributor} = this._createSizerAndDistributor(handle);
|
||||
return distributor;
|
||||
}
|
||||
|
||||
_isResizeHandle(el) {
|
||||
return el && el.classList.contains(this.classNames.handle);
|
||||
}
|
||||
|
||||
_onMouseDown(event) {
|
||||
const target = event.target;
|
||||
if (!this._isResizeHandle(target) || target.parentElement !== this.container) {
|
||||
return;
|
||||
}
|
||||
// prevent starting a drag operation
|
||||
event.preventDefault();
|
||||
// mark as currently resizing
|
||||
if (this.classNames.resizing) {
|
||||
this.container.classList.add(this.classNames.resizing);
|
||||
}
|
||||
|
||||
const {sizer, distributor} = this._createSizerAndDistributor(target);
|
||||
|
||||
const onMouseMove = (event) => {
|
||||
const offset = sizer.offsetFromEvent(event);
|
||||
distributor.resize(offset);
|
||||
};
|
||||
|
||||
const body = document.body;
|
||||
const onMouseUp = (event) => {
|
||||
if (this.classNames.resizing) {
|
||||
this.container.classList.remove(this.classNames.resizing);
|
||||
}
|
||||
body.removeEventListener("mouseup", onMouseUp, false);
|
||||
body.removeEventListener("mousemove", onMouseMove, false);
|
||||
};
|
||||
body.addEventListener("mouseup", onMouseUp, false);
|
||||
body.addEventListener("mousemove", onMouseMove, false);
|
||||
}
|
||||
|
||||
_createSizerAndDistributor(resizeHandle) {
|
||||
const vertical = resizeHandle.classList.contains(this.classNames.vertical);
|
||||
const reverse = resizeHandle.classList.contains(this.classNames.reverse);
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const sizer = new this.sizerCtor(this.container, vertical, reverse);
|
||||
|
||||
const items = this._getResizableItems();
|
||||
const prevItem = resizeHandle.previousElementSibling;
|
||||
// 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];
|
||||
// eslint-disable-next-line new-cap
|
||||
const distributor = new this.distributorCtor(
|
||||
sizer, item, this.distributorCfg,
|
||||
items, this.container);
|
||||
return {sizer, distributor};
|
||||
}
|
||||
|
||||
_getResizableItems() {
|
||||
return Array.from(this.container.children).filter(el => {
|
||||
return !this._isResizeHandle(el) && (
|
||||
this._isResizeHandle(el.previousElementSibling) ||
|
||||
this._isResizeHandle(el.nextElementSibling));
|
||||
});
|
||||
}
|
||||
|
||||
_getResizeHandles() {
|
||||
return Array.from(this.container.children).filter(el => {
|
||||
return this._isResizeHandle(el);
|
||||
});
|
||||
}
|
||||
}
|
55
src/resizer/room.js
Normal file
55
src/resizer/room.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
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,
|
||||
};
|
100
src/resizer/sizer.js
Normal file
100
src/resizer/sizer.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
implements DOM/CSS operations for resizing.
|
||||
The sizer determines what CSS mechanism is used for sizing items, like flexbox, ...
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@param {Element} item the dom element being resized
|
||||
@return {number} 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@param {Element} item the dom element being resized
|
||||
@return {number} the width/height of an item in the container
|
||||
*/
|
||||
getItemSize(item) {
|
||||
return this.vertical ? item.offsetHeight : item.offsetWidth;
|
||||
}
|
||||
|
||||
/** @return {number} the width/height of the container */
|
||||
getTotalSize() {
|
||||
return this.vertical ? this.container.offsetHeight : this.container.offsetWidth;
|
||||
}
|
||||
|
||||
/** @return {number} 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`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@param {MouseEvent} event the mouse event
|
||||
@return {number} the distance between the cursor and the edge of the container,
|
||||
along the applicable axis (vertical or horizontal)
|
||||
*/
|
||||
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};
|
Loading…
Reference in a new issue