@hydroperx/tiles
Version:
Metro tile layout
173 lines (172 loc) • 6.66 kB
JavaScript
// Third-party imports
import getOffset from "getoffset";
// Local imports
import * as OffsetUtils from "./utils/OffsetUtils";
import * as ScaleUtils from "./utils/ScaleUtils";
import { Layout } from "./Layout";
import * as Attributes from "./Attributes";
/**
* Vertical layout.
*/
export class VerticalLayout extends Layout {
/**
* Constructor.
*/
constructor($) {
super($);
}
/**
* Rearranges group tiles.
*/
rearrange() {
// Column Y in EM
const column_y = new Map();
// Group width in EM
const group_w = this.$._group_width * this.$._small_size + (this.$._group_width - 1) * this.$._tile_gap;
// Parent width in EM
const parent_w = this.$._inline_groups * group_w + (this.$._inline_groups - 1) * this.$._group_gap;
// Parent height in EM
let parent_h = 0;
let max_rows_found = new Map();
// Rearrange group tiles and reposition groups
for (let i = 0; i < this.groups.length; i++) {
const group = this.groups[i];
group.rearrange();
// ignore from layout any group being dragged.
if (group.div?.getAttribute(Attributes.ATTR_DRAGGING) == "true") {
continue;
}
// Reposition group
const column = i % this.$._inline_groups;
const left = column * group_w + column * this.$._group_gap;
const top = column_y.get(column) ?? 0;
let h = 0;
if (group.div) {
group.div.style.transform = `translateX(${left}em) translateY(${top}em)`;
h = ((group.div.getBoundingClientRect().height / ScaleUtils.getScale(group.div).y) / this.$._em);
}
else {
h = this.$._small_size * 2;
}
parent_h = Math.max(parent_h, top + h);
column_y.set(column, top + h + this.$._group_gap);
max_rows_found.set(column, (max_rows_found.get(column) ?? 0) + 1);
}
// parent height has some additional increase
parent_h += this.$._small_size * 2;
// Set parent size
const max_rows = Math.max(...Array.from(max_rows_found.values()));
parent_h += max_rows == 0 ? 0 : (max_rows - 1) * this.$._group_gap;
this.$._container.style.width = parent_w + "em";
this.$._container.style.height = parent_h + "em";
}
/**
* Snaps location to grid.
*/
snapToGrid(tile) {
// Base offset
const offset = getOffset(tile, this.$._container);
OffsetUtils.divideOffsetBy(offset, this.$._em);
// Basics
let resultX = 0, resultY = 0;
const column_y = new Map();
// resultX: Find group and tile index
const groupWidth = this.$._group_width * this.$._small_size + (this.$._group_width - 1) * this.$._tile_gap;
if (offset.x < (-this.$._small_size * 2) - this.$._tile_gap * 4) {
return null;
}
let groupIdx = -1;
let groupColumn = -1;
let groupStartX = 0;
for (let col = 0; col < this.$._inline_groups; col++) {
const startX = col * groupWidth + col * this.$._group_gap;
const endX = startX + groupWidth;
if (offset.x >= startX - this.$._small_size / 2 - this.$._tile_gap && offset.x < endX + this.$._small_size / 2) {
groupColumn = col;
groupStartX = startX;
break;
}
}
if (groupColumn === -1) {
return null;
}
// Find groupIdx in this.groups for this column
for (let i = 0, col = 0; i < this.groups.length; i++, col = i % this.$._inline_groups) {
if (col === groupColumn) {
groupIdx = i;
break;
}
}
// Find tile index within group
let tileX = 0;
let tileStartX = groupStartX;
for (; tileX < this.$._group_width; tileX++) {
if (offset.x < tileStartX + this.$._small_size / 2) {
break;
}
tileStartX += this.$._small_size + this.$._tile_gap;
}
resultX = tileX;
// resultY
// Compute vertical positions for each group as in rearrange()
const groupTops = new Map(); // group index -> top Y
for (let i = 0; i < this.groups.length; i++) {
const column = i % this.$._inline_groups;
const prevY = column_y.get(column) ?? 0;
groupTops.set(i, prevY);
const group = this.groups[i];
let h = 0;
if (group.div) {
h = ((group.div.getBoundingClientRect().height / ScaleUtils.getScale(group.div).y) / this.$._em);
}
else {
h = this.$._small_size * 2;
}
column_y.set(column, prevY + h + this.$._group_gap);
}
// Now snap to the group in the correct column whose bounds contain offset.y
for (let i = 0; i < this.groups.length; i++) {
const group = this.groups[i];
const column = i % this.$._inline_groups;
if (column !== groupColumn)
continue;
const groupStartY = groupTops.get(i);
let h = 0;
if (group.div) {
h = ((group.div.getBoundingClientRect().height / ScaleUtils.getScale(group.div).y) / this.$._em);
}
else {
h = this.$._small_size * 2;
}
const groupEndY = groupStartY + h;
if (offset.y >= groupStartY - this.$._small_size && offset.y < groupEndY + this.$._small_size) {
// Snap to tile within group
let accY = groupStartY + this.$._label_height + this.$._tile_gap;
let tileY = 0;
for (; accY < groupEndY; tileY++) {
if (offset.y < accY + this.$._small_size / 2) {
resultY = tileY;
return {
group: group.id,
x: resultX,
y: resultY,
};
}
accY += this.$._small_size + this.$._tile_gap;
}
resultY = tileY;
return {
group: group.id,
x: resultX,
y: resultY,
};
}
}
// Request an anonymous group
return {
group: undefined,
x: 0,
y: 0,
};
}
}