@hydroperx/tiles
Version:
Metro tile layout
313 lines (312 loc) • 9.26 kB
JavaScript
// Third-party imports
import { gsap } from "gsap";
// Local imports
import * as Attributes from "./Attributes";
import { BaseLayout } from "./BaseLayout";
/**
* Layout.
*/
export class Layout {
/**
* Constructor.
*/
constructor($) {
/**
* Tiles back-reference.
*/
Object.defineProperty(this, "$", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Ordered groups.
*/
Object.defineProperty(this, "groups", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
this.$ = $;
}
}
/**
* Group.
*/
export class LayoutGroup {
/**
* Constructor.
*/
constructor($, id, div, width, height) {
Object.defineProperty(this, "$", {
enumerable: true,
configurable: true,
writable: true,
value: $
});
Object.defineProperty(this, "id", {
enumerable: true,
configurable: true,
writable: true,
value: id
});
Object.defineProperty(this, "div", {
enumerable: true,
configurable: true,
writable: true,
value: div
});
/**
* Unordered tiles.
* @hidden
*/
Object.defineProperty(this, "_tiles", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
/**
* A structure with fine-grained control over tiles.
* @hidden
*/
Object.defineProperty(this, "_layout", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this._layout = new BaseLayout({ width, height });
}
/**
* Returns an immutable unordered list of the contained tiles.
*/
getTiles() {
return [...this._tiles.values()];
}
/**
* Returns a specific tile.
*/
getTile(id) {
return this._tiles.get(id) ?? null;
}
/**
* Returns whether a tile exists in this group.
*/
hasTile(id) {
return this._tiles.has(id);
}
/**
* Returns whether the group is empty or not.
*/
isEmpty() {
return this._tiles.size == 0;
}
/**
* Layout size in small tiles unit (1x1).
*/
getLayoutSize() {
return this._layout.getLayoutSize();
}
/**
* Rearranges group tiles and resizes the group's tiles div.
*/
rearrange() {
// Reposition tiles (update the group's width/height EM together)
let changed = false;
let tiles_width_em = 0, tiles_height_em = 0;
if (this.$.$._dir == "vertical") {
tiles_width_em =
this.$.$._group_width * this.$.$._small_size +
(this.$.$._group_width - 1) * this.$.$._tile_gap;
}
else {
tiles_height_em =
this.$.$._height * this.$.$._small_size +
(this.$.$._height - 1) * this.$.$._tile_gap;
}
const to_tween_y_late = [];
for (const [, tile] of this._tiles) {
const x_em = tile.x * this.$.$._small_size + tile.x * this.$.$._tile_gap;
const y_em = tile.y * this.$.$._small_size + tile.y * this.$.$._tile_gap;
const w_em = tile.width * this.$.$._small_size + (tile.width - 1) * this.$.$._tile_gap;
const h_em = tile.height * this.$.$._small_size + (tile.height - 1) * this.$.$._tile_gap;
// change tiles size em
tiles_width_em = Math.max(x_em + w_em, tiles_width_em);
tiles_height_em = Math.max(y_em + h_em, tiles_height_em);
// new X/Y state
const state = this.$.$._state.tiles.get(tile.id);
if (state) {
const old_x = state.x, old_y = state.y;
state.x = tile.x;
state.y = tile.y;
// state change
if (!(old_x == state.x && old_y == state.y)) {
changed = true;
}
// affect button
if (tile.button && tile.button.getAttribute(Attributes.ATTR_DRAGGING) != "true") {
if (tile.positioned) {
if (tile.tween) {
tile.tween.kill();
}
if (old_x != state.x && old_y != state.y) {
// change only Y
tile.button.style.transform = `translateX(${x_em}em) translateY(-1000em)`;
to_tween_y_late.push({ tile, button: tile.button, hEM: h_em, yEM: y_em });
}
else {
// change either only X or only Y
tile.tween = gsap.to(tile.button, {
x: x_em + "em",
y: y_em + "em",
duration: 0.18
});
}
// first position
}
else {
tile.button.style.transform = `translateX(${x_em}em) translateY(${y_em}em)`;
tile.positioned = true;
}
}
}
}
// Tween Y from off view
const middle = tiles_height_em / 2;
for (const { tile, button, hEM, yEM } of to_tween_y_late) {
tile.tween = gsap.fromTo(tile.button, {
y: (yEM + hEM / 2 < middle ? -hEM : tiles_height_em + hEM) + "em",
}, {
y: yEM + "em",
duration: 0.18
});
}
// Resize groupTiles div
if (this.div) {
const group_tiles_div = this.div.getElementsByClassName(this.$.$._class_names.groupTiles)[0];
let min_w = 0;
if (this.$.$._dir == "horizontal") {
min_w = 18;
}
group_tiles_div.style.width = Math.max(min_w, tiles_width_em) + "em";
group_tiles_div.style.height = tiles_height_em + "em";
}
// State update signal
if (changed) {
this.$.$._deferred_state_update_signal();
}
}
}
/**
* Tile.
*/
export class LayoutTile {
/**
* Cosntructor.
* @param button If `null` indicates this is a placeholder tile.
*/
constructor(id, button) {
Object.defineProperty(this, "id", {
enumerable: true,
configurable: true,
writable: true,
value: id
});
Object.defineProperty(this, "button", {
enumerable: true,
configurable: true,
writable: true,
value: button
});
/**
* Cached tween.
*/
Object.defineProperty(this, "tween", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
/**
* Cached indicator for initial position.
*/
Object.defineProperty(this, "positioned", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
/**
* Parent layout group.
*/
Object.defineProperty(this, "$", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
}
/**
* Attempts to contributes the tile to the layout.
* If `x` and `y` are both given as `null`, then the
* method is guaranteed to always succeed, contributing
* the tile to the best last position.
*/
addTo($, x, y, width, height) {
if ($._layout.addTile(this.id, x, y, width, height)) {
$._tiles.set(this.id, this);
this.$ = $;
return true;
}
return false;
}
/**
* Removes the tile from the parent `LayoutGroup`.
* This method does not, however, remove the tile
* from the overall state.
*/
remove() {
this.positioned = false;
this.$._layout.removeTile(this.id);
this.$._tiles.delete(this.id);
}
/**
* X coordinate in small tiles.
*/
get x() {
return this.$._layout.tiles.get(this.id).x;
}
/**
* Y coordinate in small tiles.
*/
get y() {
return this.$._layout.tiles.get(this.id).y;
}
/**
* Width in small tiles.
*/
get width() {
return this.$._layout.tiles.get(this.id).width;
}
/**
* Height in small tiles.
*/
get height() {
return this.$._layout.tiles.get(this.id).height;
}
/**
* Moves position.
*/
move(x, y) {
return this.$._layout.moveTile(this.id, x, y);
}
/**
* Resizes tile.
*/
resize(width, height) {
return this.$._layout.resizeTile(this.id, width, height);
}
}