import { d3Event, drag as d3Drag, HTMLWidget, Platform, select as d3Select, Utility } from "@hpcc-js/common";
import * as _GridList from "grid-list";
import { Cell } from "./Cell.ts";
import "../src/Grid.css";
const GridList = (_GridList && _GridList.default) || _GridList;
export type ICellPosition = [number, number, number, number];
export class Grid extends HTMLWidget {
divItems;
gridList;
items;
itemsMap;
origItems;
cellWidth;
cellHeight;
dragItem;
dragItemPos;
_d3Drag;
_d3DragResize;
_selectionBag;
_scrollBarWidth;
constructor() {
super();
this._tag = "div";
this._selectionBag = new Utility.Selection(this);
this.content([]);
}
getDimensions() {
const size = { width: 0, height: 0 };
this.content().forEach(function (cell) {
if (size.width < cell.gridCol() + cell.gridColSpan()) {
size.width = cell.gridCol() + cell.gridColSpan();
}
if (size.height < cell.gridRow() + cell.gridRowSpan()) {
size.height = cell.gridRow() + cell.gridRowSpan();
}
}, this);
return size;
}
clearContent(widget) {
this.content(this.content().filter(function (contentWidget) {
if (!widget) {
contentWidget.target(null);
return false;
}
let w: any = contentWidget;
while (w) {
if (widget === w) {
contentWidget.target(null);
return false;
}
w = w.widget ? w.widget() : null;
}
return true;
}));
}
setContent(row, col, widget, title?, rowSpan?, colSpan?) {
rowSpan = rowSpan || 1;
colSpan = colSpan || 1;
title = title || "";
this.content(this.content().filter(function (contentWidget) {
if (contentWidget.gridRow() === row && contentWidget.gridCol() === col) {
contentWidget.target(null);
return false;
}
return true;
}));
if (widget) {
const cell = new Cell()
.gridRow(row)
.gridCol(col)
.widget(widget)
.title(title)
.gridRowSpan(rowSpan)
.gridColSpan(colSpan)
;
this.content().push(cell);
}
return this;
}
sortedContent() {
return this.content().sort(function (l, r) {
if (l.gridRow() === r.gridRow()) {
return l.gridCol() - r.gridCol();
}
return l.gridRow() - r.gridRow();
});
}
getCell(row, col) {
let retVal = null;
this.content().some(function (cell) {
if (row >= cell.gridRow() && row < cell.gridRow() + cell.gridRowSpan() &&
col >= cell.gridCol() && col < cell.gridCol() + cell.gridColSpan()) {
retVal = cell;
return true;
}
return false;
});
return retVal;
}
getWidgetCell(id) {
let retVal = null;
this.content().some(function (cell) {
if (cell.widget().id() === id) {
retVal = cell;
return true;
}
return false;
});
return retVal;
}
getContent(id) {
let retVal = null;
this.content().some(function (cell) {
if (cell.widget().id() === id) {
retVal = cell.widget();
return true;
}
return false;
});
return retVal;
}
cellToGridItem(cell) {
return {
x: cell.gridCol(),
y: cell.gridRow(),
w: cell.gridColSpan(),
h: cell.gridRowSpan(),
id: cell.id(),
cell
};
}
gridItemToCell(item) {
item.cell
.gridCol(item.x)
.gridRow(item.y)
.gridColSpan(item.w)
.gridRowSpan(item.h)
;
}
resetItemsPos() {
this.origItems.forEach(function (origItem) {
const item = this.itemsMap[origItem.id];
item.x = origItem.x;
item.y = origItem.y;
}, this);
}
initGridList() {
this.itemsMap = {};
this.items = this.content().map(function (cell) {
const retVal = this.cellToGridItem(cell);
this.itemsMap[retVal.id] = retVal;
return retVal;
}, this);
this.origItems = this.content().map(this.cellToGridItem);
this.gridList = new GridList(this.items, {
direction: this.snapping(),
lanes: this.snapping() === "horizontal" ? this.snappingRows() : this.snappingColumns()
});
}
killGridList() {
this.gridList = null;
delete this.items;
delete this.itemsMap;
}
enter(domNode, element) {
super.enter(domNode, element);
this._scrollBarWidth = Platform.getScrollbarWidth();
const context = this;
this._d3Drag = d3Drag()
.subject(function (_d) {
const d = context.cellToGridItem(_d);
return { x: d.x * context.cellWidth, y: d.y * context.cellHeight };
})
.on("start", function (_d: any) {
if (!context.designMode()) return;
d3Event().sourceEvent.stopPropagation();
context.initGridList();
const d = context.itemsMap[_d.id()];
context.dragItem = element.append("div")
.attr("class", "dragging")
.style("transform", function () { return "translate(" + d.x * context.cellWidth + "px, " + d.y * context.cellHeight + "px)"; })
.style("width", function () { return d.w * context.cellWidth - context.gutter() + "px"; })
.style("height", function () { return d.h * context.cellHeight - context.gutter() + "px"; })
;
context.selectionBagClick(_d);
})
.on("drag", function (_d: any) {
if (!context.designMode()) return;
const event = d3Event();
event.sourceEvent.stopPropagation();
const d = context.itemsMap[_d.id()];
if (event.x < 0) {
event.x = 0;
}
if (event.x + d.w * context.cellWidth > context.snappingColumns() * context.cellWidth) {
event.x = context.snappingColumns() * context.cellWidth - d.w * context.cellWidth;
}
if (event.y < 0) {
event.y = 0;
}
if (event.y + d.h * context.cellWidth > context.snappingRows() * context.cellWidth) {
event.y = context.snappingRows() * context.cellWidth - d.h * context.cellWidth;
}
const pos = [Math.max(0, Math.floor((event.x + context.cellWidth / 2) / context.cellWidth)), Math.max(0, Math.floor((event.y + context.cellHeight / 2) / context.cellHeight))];
if (d.x !== pos[0] || d.y !== pos[1]) {
if (context.snapping() !== "none") {
context.resetItemsPos();
context.gridList.moveItemToPosition(d, pos);
} else {
d.x = pos[0];
d.y = pos[1];
}
if (_d.gridCol() !== d.x || _d.gridRow() !== d.y) {
context.items.forEach(context.gridItemToCell);
context.updateGrid(false, 100);
}
}
context.dragItem
.style("transform", function () { return "translate(" + event.x + "px, " + event.y + "px)"; })
.style("width", function () { return d.w * context.cellWidth + "px"; })
.style("height", function () { return d.h * context.cellHeight + "px"; })
;
})
.on("end", function () {
if (!context.designMode()) return;
d3Event().sourceEvent.stopPropagation();
context.dragItem.remove();
context.dragItem = null;
context.killGridList();
})
;
this._d3DragResize = d3Drag()
.subject(function (_d) {
const d = context.cellToGridItem(_d);
return { x: (d.x + d.w - 1) * context.cellWidth, y: (d.y + d.h - 1) * context.cellHeight };
})
.on("start", function (_d: any) {
if (!context.designMode()) return;
d3Event().sourceEvent.stopPropagation();
context.initGridList();
const d = context.itemsMap[_d.id()];
context.dragItem = element.append("div")
.attr("class", "resizing")
.style("transform", function () { return "translate(" + d.x * context.cellWidth + "px, " + d.y * context.cellHeight + "px)"; })
.style("width", function () { return d.w * context.cellWidth - context.gutter() + "px"; })
.style("height", function () { return d.h * context.cellHeight - context.gutter() + "px"; })
;
context.dragItemPos = { x: d.x, y: d.y };
})
.on("drag", function (_d: any) {
if (!context.designMode()) return;
const event = d3Event();
event.sourceEvent.stopPropagation();
const d = context.itemsMap[_d.id()];
const pos = [Math.max(0, Math.round(event.x / context.cellWidth)), Math.max(0, Math.round(event.y / context.cellHeight))];
const size = {
w: Math.max(1, pos[0] - d.x + 1),
h: Math.max(1, pos[1] - d.y + 1)
};
if (d.w !== size.w || d.h !== size.h) {
if (context.snapping() !== "none") {
context.resetItemsPos();
context.gridList.resizeItem(d, size);
} else {
d.w = size.w;
d.h = size.h;
}
if (_d.gridColSpan() !== d.w || _d.gridRowSpan() !== d.h) {
context.items.forEach(context.gridItemToCell);
context.updateGrid(d.id, 100);
}
}
context.dragItem
.style("width", function () { return (-d.x + 1) * context.cellWidth + event.x - context.gutter() + "px"; })
.style("height", function () { return (-d.y + 1) * context.cellHeight + event.y - context.gutter() + "px"; })
;
})
.on("end", function () {
if (!context.designMode()) return;
d3Event().sourceEvent.stopPropagation();
context.dragItem.remove();
context.dragItem = null;
context.killGridList();
})
;
}
updateGrid(resize, transitionDuration: number = 0, _noRender: boolean = false) {
transitionDuration = transitionDuration || 0;
const context = this;
this.divItems
.classed("draggable", this.designMode())
.transition().duration(transitionDuration)
.style("left", function (d) { return d.gridCol() * context.cellWidth + context.gutter() / 2 + "px"; })
.style("top", function (d) { return d.gridRow() * context.cellHeight + context.gutter() / 2 + "px"; })
.style("width", function (d) { return d.gridColSpan() * context.cellWidth - context.gutter() + "px"; })
.style("height", function (d) { return d.gridRowSpan() * context.cellHeight - context.gutter() + "px"; })
.on("end", function (d) {
d
.surfaceShadow_default(context.surfaceShadow())
.surfacePadding_default(context.surfacePadding())
.surfaceBorderWidth_default(context.surfaceBorderWidth())
.surfaceBackgroundColor_default(context.surfaceBackgroundColor())
;
if (resize === true || resize === d.id()) {
d
.resize()
.lazyRender()
;
}
})
;
}
update(domNode, element2) {
super.update(domNode, element2);
this._placeholderElement.style("overflow-x", this.fitTo() === "width" ? "hidden" : null);
this._placeholderElement.style("overflow-y", this.fitTo() === "width" ? "scroll" : null);
const dimensions = this.getDimensions();
const clientWidth = this.width() - (this.fitTo() === "width" ? this._scrollBarWidth : 0);
this.cellWidth = clientWidth / dimensions.width;
this.cellHeight = this.fitTo() === "all" ? this.height() / dimensions.height : this.cellWidth;
if (this.designMode()) {
const cellLaneRatio = Math.min(this.width() / this.snappingColumns(), this.height() / this.snappingRows());
const laneWidth = Math.floor(cellLaneRatio);
this.cellWidth = laneWidth;
this.cellHeight = this.cellWidth;
}
// Grid ---
const context = this;
const divItems = element2.selectAll("#" + this.id() + " > .ddCell").data(this.content(), function (d) { return d.id(); });
this.divItems = divItems.enter().append("div")
.attr("class", "ddCell")
.each(function (d) {
d.target(this);
d.__grid_watch = d.monitor(function (key, newVal, oldVal) {
if (context._renderCount && (key === "snapping" || key.indexOf("grid") === 0) && newVal !== oldVal) {
if (!context.gridList) {
// API Call (only needed when not dragging) ---
context.initGridList();
if (context.snapping() !== "none") {
context.gridList.resizeGrid(context.snapping() === "horizontal" ? context.snappingRows() : context.snappingColumns());
}
context.items.forEach(context.gridItemToCell);
context.updateGrid(d.id(), 100);
context.killGridList();
}
}
});
const element = d3Select(this);
element.append("div")
.attr("class", "resizeHandle")
.call(context._d3DragResize)
.append("div")
.attr("class", "resizeHandleDisplay")
;
}).merge(divItems)
;
this.divItems.each(function (d) {
const element = d3Select(this);
if (context.designMode()) {
element.call(context._d3Drag);
} else {
element
.on("mousedown.drag", null)
.on("touchstart.drag", null)
;
}
});
this.divItems.select(".resizeHandle")
.style("display", this.designMode() ? null : "none")
;
this.updateGrid(true);
divItems.exit()
.each(function (d) {
d.target(null);
if (d.__grid_watch) {
d.__grid_watch.remove();
}
})
.remove()
;
// Snapping ---
const lanesBackground = element2.selectAll("#" + this.id() + " > .laneBackground").data(this.designMode() ? [""] : []);
lanesBackground.enter().insert("div", ":first-child")
.attr("class", "laneBackground")
.style("left", "1px")
.style("top", "1px")
.on("click", function () {
context.selectionBagClear();
})
.merge(lanesBackground)
.style("width", (this.snappingColumns() * this.cellWidth) + "px")
.style("height", (this.snappingRows() * this.cellHeight) + "px")
;
lanesBackground.exit()
.each(function () {
context.selectionBagClear();
})
.remove()
;
const lanes = element2.selectAll("#" + this.id() + " > .lane").data(this.designMode() ? [""] : []);
lanes.enter().append("div")
.attr("class", "lane")
.style("left", "1px")
.style("top", "1px")
;
lanes
.style("display", this.showLanes() ? null : "none")
.style("width", (this.snappingColumns() * this.cellWidth) + "px")
.style("height", (this.snappingRows() * this.cellHeight) + "px")
.style("background-image", "linear-gradient(to right, grey 1px, transparent 1px), linear-gradient(to bottom, grey 1px, transparent 1px)")
.style("background-size", this.cellWidth + "px " + this.cellHeight + "px")
;
lanes.exit()
.remove()
;
}
exit(domNode, element) {
this.content().forEach(w => w.target(null));
super.exit(domNode, element);
}
_createSelectionObject(d) {
return {
_id: d._id,
element: () => {
return d._element;
},
widget: d
};
}
selection(_) {
if (!arguments.length) return this._selectionBag.get().map(function (d) { return d._id; });
this._selectionBag.set(_.map(function (row) {
return this._createSelectionObject(row);
}, this));
return this;
}
selectionBagClear() {
if (!this._selectionBag.isEmpty()) {
this._selectionBag.clear();
this.postSelectionChange();
}
}
selectionBagClick(d) {
if (d !== null) {
const selectionObj = this._createSelectionObject(d);
if (d3Event().sourceEvent.ctrlKey) {
if (this._selectionBag.isSelected(selectionObj)) {
this._selectionBag.remove(selectionObj);
this.postSelectionChange();
} else {
this._selectionBag.append(selectionObj);
this.postSelectionChange();
}
} else {
const selected = this._selectionBag.get();
if (selected.length === 1 && selected[0]._id === selectionObj._id) {
this.selectionBagClear();
} else {
this._selectionBag.set([selectionObj]);
}
this.postSelectionChange();
}
}
}
postSelectionChange() {
}
applyLayout(layoutArr: ICellPosition[]) {
this.divItems.each((d, i) => {
if (layoutArr[i]) {
const [x, y, w, h] = layoutArr[i];
d
.gridCol(x)
.gridRow(y)
.gridColSpan(w)
.gridRowSpan(h)
;
}
});
this.updateGrid(true);
}
vizActivation(elem) {
}
}
Grid.prototype._class += " layout_Grid";
export interface Grid {
designMode(): boolean;
designMode(_: boolean): this;
showLanes(): boolean;
showLanes(_: boolean): this;
fitTo(): string;
fitTo(_: string): this;
snapping(): string;
snapping(_: string): this;
snappingColumns(): number;
snappingColumns(_: number): this;
snappingRows(): number;
snappingRows(_: number): this;
snappingColumns_default(): number;
snappingColumns_default(_: number): this;
snappingRows_default(): number;
snappingRows_default(_: number): this;
gutter(): number;
gutter(_: number): this;
surfaceShadow(): boolean;
surfaceShadow(_: boolean): this;
surfacePadding(): string;
surfacePadding(_: string): this;
surfaceBorderWidth(): number;
surfaceBorderWidth(_: number): this;
surfaceBackgroundColor(): string;
surfaceBackgroundColor(_: string): this;
content(): Cell[];
content(_: Cell[]): this;
}
Grid.prototype.publish("designMode", false, "boolean", "Design Mode", null, { tags: ["Basic"] });
Grid.prototype.publish("showLanes", true, "boolean", "Show snapping lanes when in design mode", null, { tags: ["Basic"], disable: w => !w.designMode() });
Grid.prototype.publish("fitTo", "all", "set", "Sizing Strategy", ["all", "width"], { tags: ["Basic"] });
Grid.prototype.publish("snapping", "vertical", "set", "Snapping Strategy", ["vertical", "horizontal", "none"]);
Grid.prototype.publish("snappingColumns", 12, "number", "Snapping Columns");
Grid.prototype.publish("snappingRows", 16, "number", "Snapping Rows");
Grid.prototype.publish("gutter", 6, "number", "Gap Between Widgets", null, { tags: ["Basic"] });
Grid.prototype.publish("surfaceShadow", true, "boolean", "3D Shadow");
Grid.prototype.publish("surfacePadding", null, "string", "Cell Padding (px)", null, { tags: ["Intermediate"] });
Grid.prototype.publish("surfaceBorderWidth", 1, "number", "Width (px) of Cell Border", null, { tags: ["Intermediate"] });
Grid.prototype.publish("surfaceBackgroundColor", null, "html-color", "Surface Background Color", null, { tags: ["Advanced"] });
Grid.prototype.publish("content", [], "widgetArray", "widgets", null, { tags: ["Basic"], render: false });