import { __ID__, filePath } from "../consts.mjs"; import { performArraySort } from "../utils/performArraySort.mjs"; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { DragDrop } = foundry.applications.ux; const { getDocumentClass } = foundry.utils; export class SidebarTabRearranger extends HandlebarsApplicationMixin(ApplicationV2) { // #region Options static DEFAULT_OPTIONS = { tag: `form`, classes: [ __ID__, `SidebarTabRearranger`, ], window: { title: `OFT.apps.SidebarTabRearranger.title`, }, position: {}, form: { handler: this.#onSubmit, closeOnSubmit: true, submitOnChange: false, }, actions: { }, }; static PARTS = { list: { template: filePath(`templates/SidebarTabRearranger/list.hbs`) }, footer: { template: filePath(`templates/SidebarTabRearranger/footer.hbs`) }, }; // #endregion Options // #region Instance Data #order = []; constructor(...args) { super(...args); // TODO: define this using the game settings this.#order = Object.keys(ui.sidebar.constructor.TABS); }; // #endregion Instance Data // #region Lifecycle async _onRender(...args) { await super._onRender(...args); new DragDrop.implementation({ dragSelector: `.tab`, dropSelector: `.drop-zone`, callbacks: { dragstart: this.#onDragStart.bind(this), dragenter: this.#onDragEnter.bind(this), dragleave: this.#onDragLeave.bind(this), dragend: this.#onDragEnd.bind(this), drop: this.#onDrop.bind(this), }, }).bind(this.element); }; /** @this {SidebarTabRearranger} */ static async #onSubmit() {}; // #endregion Lifecycle // #region Data Prep async _prepareContext() { const ctx = { meta: { idp: this.id, }, }; const tabs = ui.sidebar.constructor.TABS; ctx.tabs = []; for (let i = 0; i < this.#order.length; i++) { const id = this.#order[i]; const tab = tabs[id]; if (!tab) { continue }; let { documentName, gmOnly, tooltip, icon } = tab; if (gmOnly && !game.user.isGM) { continue }; if (documentName) { tooltip ??= getDocumentClass(documentName).metadata.labelPlural; icon ??= CONFIG[documentName]?.sidebarIcon; }; ctx.tabs.push({ id, name: game.i18n.localize(tooltip), icon, nextIndex: i + 1, }); }; return ctx; }; // #endregion Data Prep // #region Actions // #endregion Actions // #region Drag & Drop #onDragStart(event) { /** @type {HTMLLIElement|undefined} */ const target = event.target.closest(`[data-tab-id]`); if (!target) { return }; const tabID = target.dataset.tabId; target.classList.add(`no-hover-styles`); event.dataTransfer.setDragImage(target, 0, 0); event.dataTransfer.setData(`oft/tab`, tabID); target.closest(`.tab-list`)?.classList.add(`dragging`); /* This timeout is required to get the difference between the drag image and the element in-DOM, because this puts the class removal in a subsequent event cycle instead of being handled in the current cycle. */ setTimeout(() => target.classList.remove(`no-hover-styles`), 0); }; #onDragEnter(event) { event.currentTarget.style.setProperty(`--colour`, `#00aa00`); }; #onDragLeave(event) { event.currentTarget.style.removeProperty(`--colour`); }; #onDragEnd() { this.element.querySelector(`.tab-list`)?.classList.remove(`dragging`); this.element .querySelectorAll(`[style="--colour: #00aa00"]`) .forEach(el => el.style.removeProperty(`--colour`)); }; #onDrop(event) { const droppedID = event.dataTransfer.getData(`oft/tab`); this.element.querySelector(`.tab-list`)?.classList.remove(`dragging`); event.currentTarget?.style?.removeProperty(`--colour`); if (!droppedID) { return }; const droppedIndex = this.#order.findIndex(t => t === droppedID); const dropTarget = event.currentTarget; const targetIndex = parseInt(dropTarget?.dataset.moveToIndex); if ( !dropTarget || droppedIndex < 0 || targetIndex === droppedIndex ) { return }; this.#order = performArraySort( droppedID, { targetIndex, list: this.#order }, ); this.render({ parts: [`list`] }); }; // #endregion Drag & Drop };