Skip to content

Instantly share code, notes, and snippets.

@bwindels
Last active October 18, 2018 10:28
Show Gist options
  • Save bwindels/084c6dd10cdc38660ff490a87750c910 to your computer and use it in GitHub Desktop.
Save bwindels/084c6dd10cdc38660ff490a87750c910 to your computer and use it in GitHub Desktop.
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();
}
}
}
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 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 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);
}
}
}
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);
}
}
const RESIZE_HANDLE_CLASS = "resize-handle";
const REVERSE_CLASS = "reverse";
function makeResizeable(container, distributorCtor, sizerCtor = Sizer) {
function handleMouseDown(event) {
const target = event.target;
if (!target.classList.contains(RESIZE_HANDLE_CLASS) || target.parentElement !== container) {
return;
}
// prevent starting a drag operation
event.preventDefault();
container.classList.add("resizing");
const resizeHandle = event.target;
const vertical = resizeHandle.classList.contains("vertical");
const reverse = resizeHandle.classList.contains(REVERSE_CLASS);
const direction = reverse ? 0 : -1;
const sizer = new sizerCtor(container, vertical, reverse);
const items = Array.prototype.slice.apply(container.children).filter(el => {
return !el.classList.contains(RESIZE_HANDLE_CLASS) && el.tagName.indexOf('H') !== 0;
});
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) => {
container.classList.remove("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);
}
window.PercentageDistributor = PercentageDistributor;
window.FixedDistributor = FixedDistributor;
window.CollapseDistributor = CollapseDistributor;
window.RoomDistributor = RoomDistributor;
window.Sizer = Sizer;
window.RoomSizer = RoomSizer;
window.makeResizeable = makeResizeable;
body {
padding: 0;
margin: 0;
}
.resize-handle {
cursor: row-resize;
flex: 0 0 auto;
background: blue;
padding: 2px
}
.resize-handle.vertical {
height: 1px;
cursor: s-resize;
}
.resize-handle.horizontal {
width: 1px;
cursor: e-resize;
}
.resize-handle.vertical.reverse {
cursor: n-resize;
}
.resize-handle.horizontal.reverse {
cursor: w-resize;
}
#container {
height: 100vh;
display: flex;
flex-direction: row;
}
.leftpanel, .rightpanel {
flex: 0 0 auto;
background: red;
}
.leftpanel {
max-height: 100%;
min-width: 150px;
display: flex;
flex-direction: row;
}
.communities {
flex: 0 0 content;
overflow-y: auto;
overflow-x: hidden;
background-color: darkblue;
}
.communities > ul {
width: 70px;
list-style: none;
padding: 0;
margin: 0;
}
.communities li {
margin: 2px 5px;
width: 60px;
height: 60px;
border-radius: 30px;
background: pink;
overflow: hidden;
}
.leftpanel-rooms {
flex: 1;
max-height: 100%;
min-width: 80px;
display: flex;
flex-direction: column;
align-items: stretch; /* align items in Cross Axis */
}
.leftpanel.collapsed {
background: darkred;
}
.collapsed .leftpanel-rooms li {
background: white;
text-overflow: clip;
border-radius: 2em;
margin: 0.2em;
padding: 1em;
width: 1em;
height: 1em;
}
.leftpanel-rooms {
display: flex;
flex: 1 1 max-content;
flex-direction: column;
background: green;
color: white;
}
.leftpanel-rooms > .header {
flex: 0 0 auto;
}
h1 {
margin: 0;
padding: 10px 0;
}
.roomlists h2 {
flex: 0 0 auto;
background: darkgreen;
margin: 0;
padding: 4px;
}
.roomlists h2 > button {
display: inline-block;
padding: 2px;
border-radius: 10px;
border: none;
float: right;
background-color: lightgrey;
color: black;
}
.roomlist {
margin: 0px;
padding: 0px 10px;
list-style: none;
overflow-y: auto;
flex: 0 0 min-content;
}
.roomlist li {
overflow-x: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.middlepanel {
flex: 1 1 auto;
background: orange;
overflow-y: auto;
}
.rightpanel {
flex: 0 0 auto;
min-width: 200px;
}
.show-content {
flex-basis: content;
flex-grow: 0;
flex-shrink: 1;
background-color: lime;
}
.show-available {
min-height: 40px;
flex-basis: content;
flex-grow: 0;
flex-shrink: 10000;
background-color: pink;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="roomlist.css">
</head>
<body>
<div id="container">
<div class="leftpanel">
<div class="communities">
<ul>
<li>Matrix</li>
<li>Matrix</li>
<li>Matrix</li>
<li>Matrix</li>
<li>Matrix</li>
<li>Matrix</li>
<li>Matrix</li>
<li>Matrix</li>
<li>Matrix</li>
<li>Matrix</li>
<li>Matrix</li>
<li>Matrix</li>
<li>Matrix</li>
</ul>
</div>
<div class="leftpanel-rooms roomlists">
<h1>+matrix:matrix.org</h1>
<h2>People<button>+/-</button></h2>
<ul class="people roomlist show-content"></ul>
<div class="resize-handle vertical"></div>
<h2>Rooms<button>+/-</button></h2>
<ul class="rooms roomlist show-available">
<li>A room with a view and a long name</li>
<li id="toggleroom">room edf</li>
</ul>
<div class="resize-handle vertical"></div>
<h2>Low priority<button>+/-</button></h2>
<ul class="low-priority roomlist show-available"></ul>
</div>
</div>
<div class="resize-handle horizontal"></div>
<div class="middlepanel">
<h2>Timeline</h2>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>MatrixClient.prototype.getGroup = function(groupId) {
return this.store.getGroup(groupId);
};
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
<p>11:04 Bruno: event number 111</p>
</div>
<div class="resize-handle horizontal reverse"></div>
<div class="rightpanel">
<h2>Memberlist</h2>
</div>
</div>
<script type="text/javascript" src="resize.js"></script>
<script type="text/javascript">
const roomlists = document.querySelector(".roomlists");
makeResizeable(document.getElementById("container"), CollapseDistributor);
makeResizeable(roomlists, RoomDistributor, RoomSizer);
(function() {
const toggleroom = document.getElementById("toggleroom");
let visible = true;
setInterval(() => {
toggleroom.style.display = visible ? 'none' : 'block';
visible = !visible;
}, 5000);
})();
const h2s = Array.from(roomlists.querySelectorAll("h2"));
h2s.forEach((h2) => {
h2.addEventListener("click", (event) => {
if (event.target.tagName === "BUTTON") {
const list = event.target.parentElement.nextElementSibling;
const remove = event.altKey;
if (remove) {
for (var i = 0; i < Math.min(5, list.childElementCount); i++) {
removeItem(list);
}
} else {
for (var i = 0; i < 5; i++) {
addItem(list);
}
}
} else {
const list = event.target.nextElementSibling;
if (list.style.display === "none") {
list.style.display = null;
} else {
list.style.display = "none";
}
}
}, false);
});
const lists = Array.from(roomlists.querySelectorAll("ul"));
lists.forEach((ul) => {
if (ul.classList.contains("people")) {
for (var i = 0; i < 10; i++) {
addItem(ul);
}
} else if (ul.classList.contains("rooms")) {
for (var i = 0; i < 40; i++) {
addItem(ul);
}
} else {
for (var i = 0; i < 5; i++) {
addItem(ul);
}
}
});
function addItem(list) {
const li = document.createElement("li");
li.appendChild(document.createTextNode(`Room ${list.childElementCount + 1}`));
list.appendChild(li);
}
function removeItem(list) {
list.removeChild(list.lastChild);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment