UNPKG

@hydroperx/tiles

Version:
153 lines (152 loc) 6.31 kB
// third-party imports import assert from "assert"; import { LayoutGroup } from "./Layout"; import * as Attributes from "./Attributes"; import { TileDraggableBehavior } from "./TileDraggableBehavior"; import { GroupDraggableBehavior } from "./GroupDraggableBehavior"; /** * Group factory. */ export class GroupFactory { // Constructor constructor($) { Object.defineProperty(this, "$", { enumerable: true, configurable: true, writable: true, value: $ }); // } // Adds a group. add(params) { let { id, label } = params; // Enforce non-duplicate ID assert(!this.$._state.groups.has(id), "Duplicate group ID: " + id); // Keep groups contiguous this.$._keep_groups_contiguous(); // Default label = empty label ?? (label = ""); // State + index const existing_indices = Array.from(this.$._state.groups.values()).map(g => g.index); const index = existing_indices.length == 0 ? 0 : Math.max.apply(null, existing_indices) + 1; this.$._state.groups.set(id, { index, label }); // Group div const div = document.createElement("div"); div.setAttribute(Attributes.ATTR_ID, id); div.setAttribute(Attributes.ATTR_DRAGGING, id); div.classList.add(this.$._class_names.group); div.style.display = "flex"; div.style.flexDirection = "column"; div.style.position = "absolute"; div.style.transition = "transform 0.2s ease"; div.style.gap = this.$._tile_gap + "em"; this.$._container.appendChild(div); // Group's label div const labelDiv = document.createElement("div"); labelDiv.classList.add(this.$._class_names.groupLabel); labelDiv.style.height = this.$._label_height + "em"; div.appendChild(labelDiv); // Group label's text div const labelTextDiv = document.createElement("div"); labelTextDiv.classList.add(this.$._class_names.groupLabelText); labelTextDiv.style.wordBreak = "keep-all"; labelTextDiv.innerText = label; labelDiv.appendChild(labelTextDiv); // Group's tiles div const tilesDiv = document.createElement("div"); tilesDiv.classList.add(this.$._class_names.groupTiles); tilesDiv.style.overflow = "hidden"; tilesDiv.style.position = "relative"; div.appendChild(tilesDiv); // Layout group const layout_group = new LayoutGroup(this.$._layout, id, div, this.$._dir == "vertical" ? this.$._group_width : undefined, this.$._dir == "horizontal" ? this.$._height : undefined); this.$._layout.groups.push(layout_group); this.$._deferred_rearrange(); // Install draggable behavior if (this.$._drag_enabled) { new GroupDraggableBehavior(this.$, id).install(); } // addedgroup signal this.$.dispatchEvent(new CustomEvent("addedgroup", { detail: { group: layout_group, div, labelDiv, labelTextDiv, tilesDiv }, })); // State update signal this.$._state_update_signal(); } // Removes a group. remove(id) { // Do nothing if dragging a tile. const tiles_dragging = Array.from(this.$._container.getElementsByClassName(this.$._class_names.tile)) .some(button => button.getAttribute(Attributes.ATTR_DRAGGING) == "true"); if (tiles_dragging) { return; } // Do nothing if dragging a group. const groups_dragging = Array.from(this.$._container.getElementsByClassName(this.$._class_names.group)) .some(div => div.getAttribute(Attributes.ATTR_DRAGGING) == "true"); if (groups_dragging) { return; } // Delete from state this.$._state.groups.delete(id); // Delete from layout const layout_group = this.$._layout.groups.find(group => group.id == id); assert(!!layout_group, "Group '" + id + "' not found."); this.$._layout.groups.splice(this.$._layout.groups.indexOf(layout_group), 1); // Discard draggable new GroupDraggableBehavior(this.$, id).uninstall(); // Remove from DOM if (layout_group.div) { if (this.$._group_removal_work) { this.$._group_removal_work(layout_group.div).then(() => { layout_group.div.remove(); }); } else { layout_group.div.remove(); } } // If any tile is checked, trigger selection change event. if (layout_group.div) { const any_checked = [...layout_group.div.getElementsByClassName(this.$._class_names.tile)] .some(button => button.getAttribute(Attributes.ATTR_CHECKED) == "true"); if (any_checked) { const all_buttons = [ ...this.$._container.getElementsByClassName(this.$._class_names.tile), ].map(btn => [ btn.getAttribute(Attributes.ATTR_ID), btn.getAttribute(Attributes.ATTR_CHECKED) === "true", ]); // Emit selectionchange event this.$.dispatchEvent(new CustomEvent("selectionchange", { detail: { tiles: all_buttons.filter(([, y]) => y).map(([id]) => id), } })); } } // Remove tiles efficiently const tiles_to_remove = []; for (const [tile, tile_state] of this.$._state.tiles) { if (tile_state.group == id) { tiles_to_remove.push(tile); } } for (const tile of tiles_to_remove) { this.$._state.tiles.delete(tile); if (this.$._drag_enabled) { new TileDraggableBehavior(this.$, tile).uninstall(); } this.$._buttons.get(tile)?.remove(); this.$._buttons.delete(tile); } // Keep groups contiguous this.$._keep_groups_contiguous(); // Rearrange this.$._deferred_rearrange(); // State update signal this.$._deferred_state_update_signal(); } }