Merge pull request #5343 from matrix-org/t3chguy/ts/resizer

Convert resizer to Typescript
This commit is contained in:
Michael Telatynski 2020-10-20 23:54:17 +01:00 committed by GitHub
commit 42228719b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 145 additions and 117 deletions

View file

@ -52,6 +52,7 @@ import RoomListStore from "../../stores/room-list/RoomListStore";
import NonUrgentToastContainer from "./NonUrgentToastContainer";
import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload";
import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
import { ICollapseConfig } from "../../resizer/distributors/collapse";
// 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.
@ -205,13 +206,8 @@ class LoggedInView extends React.Component<IProps, IState> {
};
_createResizer() {
const classNames = {
handle: "mx_ResizeHandle",
vertical: "mx_ResizeHandle_vertical",
reverse: "mx_ResizeHandle_reverse",
};
let size;
const collapseConfig = {
const collapseConfig: ICollapseConfig = {
toggleSize: 260 - 50,
onCollapsed: (collapsed) => {
if (collapsed) {
@ -234,7 +230,11 @@ class LoggedInView extends React.Component<IProps, IState> {
},
};
const resizer = new Resizer(this._resizeContainer.current, CollapseDistributor, collapseConfig);
resizer.setClassNames(classNames);
resizer.setClassNames({
handle: "mx_ResizeHandle",
vertical: "mx_ResizeHandle_vertical",
reverse: "mx_ResizeHandle_reverse",
});
return resizer;
}

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 - 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -16,9 +16,16 @@ limitations under the License.
import FixedDistributor from "./fixed";
import ResizeItem from "../item";
import Resizer, {IConfig} from "../resizer";
import Sizer from "../sizer";
class CollapseItem extends ResizeItem {
notifyCollapsed(collapsed) {
export interface ICollapseConfig extends IConfig {
toggleSize: number;
onCollapsed?(collapsed: boolean, id: string, element: HTMLElement): void;
}
class CollapseItem extends ResizeItem<ICollapseConfig> {
notifyCollapsed(collapsed: boolean) {
const callback = this.resizer.config.onCollapsed;
if (callback) {
callback(collapsed, this.id, this.domNode);
@ -26,18 +33,20 @@ class CollapseItem extends ResizeItem {
}
}
export default class CollapseDistributor extends FixedDistributor {
static createItem(resizeHandle, resizer, sizer) {
export default class CollapseDistributor extends FixedDistributor<ICollapseConfig, CollapseItem> {
static createItem(resizeHandle: HTMLDivElement, resizer: Resizer<ICollapseConfig>, sizer: Sizer) {
return new CollapseItem(resizeHandle, resizer, sizer);
}
constructor(item, config) {
private readonly toggleSize: number;
private isCollapsed = false;
constructor(item: CollapseItem) {
super(item);
this.toggleSize = config && config.toggleSize;
this.isCollapsed = false;
this.toggleSize = item.resizer?.config?.toggleSize;
}
resize(newSize) {
public resize(newSize: number) {
const isCollapsedSize = newSize < this.toggleSize;
if (isCollapsedSize && !this.isCollapsed) {
this.isCollapsed = true;

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 - 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@ limitations under the License.
import ResizeItem from "../item";
import Sizer from "../sizer";
import Resizer, {IConfig} from "../resizer";
/**
distributors translate a moving cursor into
@ -27,29 +28,30 @@ they have two methods:
within the container bounding box. For internal use.
This method usually ends up calling `resize` once the start offset is subtracted.
*/
export default class FixedDistributor {
static createItem(resizeHandle, resizer, sizer) {
export default class FixedDistributor<C extends IConfig, I extends ResizeItem<any> = ResizeItem<C>> {
static createItem(resizeHandle: HTMLDivElement, resizer: Resizer, sizer: Sizer): ResizeItem {
return new ResizeItem(resizeHandle, resizer, sizer);
}
static createSizer(containerElement, vertical, reverse) {
static createSizer(containerElement: HTMLElement, vertical: boolean, reverse: boolean): Sizer {
return new Sizer(containerElement, vertical, reverse);
}
constructor(item) {
this.item = item;
private readonly beforeOffset: number;
constructor(public readonly item: I) {
this.beforeOffset = item.offset();
}
resize(size) {
public resize(size: number) {
this.item.setSize(size);
}
resizeFromContainerOffset(offset) {
public resizeFromContainerOffset(offset: number) {
this.resize(offset - this.beforeOffset);
}
start() {}
public start() {}
finish() {}
public finish() {}
}

View file

@ -1,5 +1,4 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
@ -15,6 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export FixedDistributor from "./distributors/fixed";
export CollapseDistributor from "./distributors/collapse";
export Resizer from "./resizer";
export {default as FixedDistributor} from "./distributors/fixed";
export {default as CollapseDistributor} from "./distributors/collapse";
export {default as Resizer} from "./resizer";

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 - 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,25 +14,30 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export default class ResizeItem {
constructor(handle, resizer, sizer) {
const id = handle.getAttribute("data-id");
const reverse = resizer.isReverseResizeHandle(handle);
const domNode = reverse ? handle.nextElementSibling : handle.previousElementSibling;
import Resizer, {IConfig} from "./resizer";
import Sizer from "./sizer";
this.domNode = domNode;
this.id = id;
this.reverse = reverse;
this.resizer = resizer;
this.sizer = sizer;
export default class ResizeItem<C extends IConfig = IConfig> {
public readonly domNode: HTMLElement;
protected readonly id: string;
protected reverse: boolean;
constructor(
handle: HTMLElement,
public readonly resizer: Resizer<C>,
public readonly sizer: Sizer,
) {
this.reverse = resizer.isReverseResizeHandle(handle);
this.domNode = <HTMLElement>(this.reverse ? handle.nextElementSibling : handle.previousElementSibling);
this.id = handle.getAttribute("data-id");
}
_copyWith(handle, resizer, sizer) {
const Ctor = this.constructor;
private copyWith(handle: HTMLElement, resizer: Resizer, sizer: Sizer) {
const Ctor = this.constructor as typeof ResizeItem;
return new Ctor(handle, resizer, sizer);
}
_advance(forwards) {
private advance(forwards: boolean) {
// opposite direction from fromResizeHandle to get back to handle
let handle = this.reverse ?
this.domNode.previousElementSibling :
@ -45,32 +50,32 @@ export default class ResizeItem {
} else {
handle = handle.previousElementSibling;
}
} while (handle && !this.resizer.isResizeHandle(handle));
} while (handle && !this.resizer.isResizeHandle(<HTMLElement>handle));
if (handle) {
const nextHandle = this._copyWith(handle, this.resizer, this.sizer);
const nextHandle = this.copyWith(<HTMLElement>handle, this.resizer, this.sizer);
nextHandle.reverse = this.reverse;
return nextHandle;
}
}
next() {
return this._advance(true);
public next() {
return this.advance(true);
}
previous() {
return this._advance(false);
public previous() {
return this.advance(false);
}
size() {
public size() {
return this.sizer.getItemSize(this.domNode);
}
offset() {
public offset() {
return this.sizer.getItemOffset(this.domNode);
}
setSize(size) {
public setSize(size: number) {
this.sizer.setItemSize(this.domNode, size);
const callback = this.resizer.config.onResized;
if (callback) {
@ -78,7 +83,7 @@ export default class ResizeItem {
}
}
clearSize() {
public clearSize() {
this.sizer.clearItemSize(this.domNode);
const callback = this.resizer.config.onResized;
if (callback) {
@ -86,22 +91,21 @@ export default class ResizeItem {
}
}
first() {
public first() {
const firstHandle = Array.from(this.domNode.parentElement.children).find(el => {
return this.resizer.isResizeHandle(el);
return this.resizer.isResizeHandle(<HTMLElement>el);
});
if (firstHandle) {
return this._copyWith(firstHandle, this.resizer, this.sizer);
return this.copyWith(<HTMLElement>firstHandle, this.resizer, this.sizer);
}
}
last() {
public last() {
const lastHandle = Array.from(this.domNode.parentElement.children).reverse().find(el => {
return this.resizer.isResizeHandle(el);
return this.resizer.isResizeHandle(<HTMLElement>el);
});
if (lastHandle) {
return this._copyWith(lastHandle, this.resizer, this.sizer);
return this.copyWith(<HTMLElement>lastHandle, this.resizer, this.sizer);
}
}
}

View file

@ -1,6 +1,5 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2018 - 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,86 +14,101 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/*
classNames:
import FixedDistributor from "./distributors/fixed";
import ResizeItem from "./item";
import Sizer from "./sizer";
interface IClassNames {
// class on resize-handle
handle: string
handle?: string;
// class on resize-handle
reverse: string
reverse?: string;
// class on resize-handle
vertical: string
vertical?: string;
// class on container
resizing: string
*/
resizing?: string;
}
export interface IConfig {
onResizeStart?(): void;
onResizeStop?(): void;
onResized?(size: number, id: string, element: HTMLElement): void;
}
export default class Resizer<C extends IConfig = IConfig> {
private classNames: IClassNames;
export default class Resizer {
// TODO move vertical/horizontal to config option/container class
// as it doesn't make sense to mix them within one container/Resizer
constructor(container, distributorCtor, config) {
constructor(
public container: HTMLElement,
private readonly distributorCtor: {
new(item: ResizeItem): FixedDistributor<C>;
createItem(resizeHandle: HTMLDivElement, resizer: Resizer, sizer: Sizer): ResizeItem;
createSizer(containerElement: HTMLElement, vertical: boolean, reverse: boolean): Sizer;
},
public readonly config?: C,
) {
if (!container) {
throw new Error("Resizer requires a non-null `container` arg");
}
this.container = container;
this.distributorCtor = distributorCtor;
this.config = config;
this.classNames = {
handle: "resizer-handle",
reverse: "resizer-reverse",
vertical: "resizer-vertical",
resizing: "resizer-resizing",
};
this._onMouseDown = this._onMouseDown.bind(this);
}
setClassNames(classNames) {
public setClassNames(classNames: IClassNames) {
this.classNames = classNames;
}
attach() {
this.container.addEventListener("mousedown", this._onMouseDown, false);
public attach() {
this.container.addEventListener("mousedown", this.onMouseDown, false);
}
detach() {
this.container.removeEventListener("mousedown", this._onMouseDown, false);
public 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
@return {FixedDistributor} a new distributor for the given handle
*/
forHandleAt(handleIndex) {
const handles = this._getResizeHandles();
public forHandleAt(handleIndex: number): FixedDistributor<C> {
const handles = this.getResizeHandles();
const handle = handles[handleIndex];
if (handle) {
const {distributor} = this._createSizerAndDistributor(handle);
const {distributor} = this.createSizerAndDistributor(<HTMLDivElement>handle);
return distributor;
}
}
forHandleWithId(id) {
const handles = this._getResizeHandles();
public forHandleWithId(id: string): FixedDistributor<C> {
const handles = this.getResizeHandles();
const handle = handles.find((h) => h.getAttribute("data-id") === id);
if (handle) {
const {distributor} = this._createSizerAndDistributor(handle);
const {distributor} = this.createSizerAndDistributor(<HTMLDivElement>handle);
return distributor;
}
}
isReverseResizeHandle(el) {
public isReverseResizeHandle(el: HTMLElement) {
return el && el.classList.contains(this.classNames.reverse);
}
isResizeHandle(el) {
public isResizeHandle(el: HTMLElement) {
return el && el.classList.contains(this.classNames.handle);
}
_onMouseDown(event) {
private onMouseDown = (event: MouseEvent) => {
// use closest in case the resize handle contains
// child dom nodes that can be the target
const resizeHandle = event.target && event.target.closest(`.${this.classNames.handle}`);
const resizeHandle = event.target && (<HTMLDivElement>event.target).closest(`.${this.classNames.handle}`);
if (!resizeHandle || resizeHandle.parentElement !== this.container) {
return;
}
@ -109,7 +123,7 @@ export default class Resizer {
this.config.onResizeStart();
}
const {sizer, distributor} = this._createSizerAndDistributor(resizeHandle);
const {sizer, distributor} = this.createSizerAndDistributor(<HTMLDivElement>resizeHandle);
distributor.start();
const onMouseMove = (event) => {
@ -133,21 +147,21 @@ export default class Resizer {
body.addEventListener("mouseup", finishResize, false);
document.addEventListener("mouseleave", finishResize, false);
body.addEventListener("mousemove", onMouseMove, false);
}
};
_createSizerAndDistributor(resizeHandle) {
private createSizerAndDistributor(resizeHandle: HTMLDivElement) {
const vertical = resizeHandle.classList.contains(this.classNames.vertical);
const reverse = this.isReverseResizeHandle(resizeHandle);
const Distributor = this.distributorCtor;
const sizer = Distributor.createSizer(this.container, vertical, reverse);
const item = Distributor.createItem(resizeHandle, this, sizer);
const distributor = new Distributor(item, this.config);
const distributor = new Distributor(item);
return {sizer, distributor};
}
_getResizeHandles() {
private getResizeHandles(): HTMLDivElement[] {
return Array.from(this.container.children).filter(el => {
return this.isResizeHandle(el);
});
return this.isResizeHandle(<HTMLElement>el);
}) as HTMLDivElement[];
}
}

View file

@ -1,5 +1,5 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2018 - 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -19,18 +19,18 @@ implements DOM/CSS operations for resizing.
The sizer determines what CSS mechanism is used for sizing items, like flexbox, ...
*/
export default class Sizer {
constructor(container, vertical, reverse) {
this.container = container;
this.reverse = reverse;
this.vertical = vertical;
}
constructor(
protected readonly container: HTMLElement,
protected readonly vertical: boolean,
protected readonly reverse: boolean,
) {}
/**
@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();
public getItemOffset(item: HTMLElement): number {
const offset = (this.vertical ? item.offsetTop : item.offsetLeft) - this.getOffset();
if (this.reverse) {
return this.getTotalSize() - (offset + this.getItemSize(item));
} else {
@ -42,33 +42,33 @@ export default class Sizer {
@param {Element} item the dom element being resized
@return {number} the width/height of an item in the container
*/
getItemSize(item) {
public getItemSize(item: HTMLElement): number {
return this.vertical ? item.offsetHeight : item.offsetWidth;
}
/** @return {number} the width/height of the container */
getTotalSize() {
public getTotalSize(): number {
return this.vertical ? this.container.offsetHeight : this.container.offsetWidth;
}
/** @return {number} container offset to offsetParent */
_getOffset() {
private getOffset(): number {
return this.vertical ? this.container.offsetTop : this.container.offsetLeft;
}
/** @return {number} container offset to document */
_getPageOffset() {
private getPageOffset(): number {
let element = this.container;
let offset = 0;
while (element) {
const pos = this.vertical ? element.offsetTop : element.offsetLeft;
offset = offset + pos;
element = element.offsetParent;
element = <HTMLElement>element.offsetParent;
}
return offset;
}
setItemSize(item, size) {
public setItemSize(item: HTMLElement, size: number) {
if (this.vertical) {
item.style.height = `${Math.round(size)}px`;
} else {
@ -76,7 +76,7 @@ export default class Sizer {
}
}
clearItemSize(item) {
public clearItemSize(item: HTMLElement) {
if (this.vertical) {
item.style.height = null;
} else {
@ -89,12 +89,12 @@ export default class Sizer {
@return {number} the distance between the cursor and the edge of the container,
along the applicable axis (vertical or horizontal)
*/
offsetFromEvent(event) {
public offsetFromEvent(event: MouseEvent) {
const pos = this.vertical ? event.pageY : event.pageX;
if (this.reverse) {
return (this._getPageOffset() + this.getTotalSize()) - pos;
return (this.getPageOffset() + this.getTotalSize()) - pos;
} else {
return pos - this._getPageOffset();
return pos - this.getPageOffset();
}
}
}