add resize handles between 3 main app columns

This commit is contained in:
Bruno Windels 2018-09-24 16:07:42 +01:00
parent 313956dd99
commit 928b6d47c8
10 changed files with 372 additions and 9 deletions

View file

@ -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
src/resizer/event.js Normal file
View file

@ -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
src/resizer/index.js Normal file
View file

@ -0,0 +1,10 @@
import {Sizer} from "./sizer";
import {FixedDistributor, PercentageDistributor} from "./distributors";
import {makeResizeable} from "./event";
module.exports = {
makeResizeable,
Sizer,
FixedDistributor,
PercentageDistributor,
};

39
src/resizer/room.js Normal file
View file

@ -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,
};

70
src/resizer/sizer.js Normal file
View file

@ -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};