import { __ID__, filePath } from "../consts.mjs"; import { performArraySort } from "../utils/performArraySort.mjs"; import { key as rearrangeSidebarTabsKey } from "../tweaks/rearrangeSidebarTabs.mjs"; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { SettingsConfig } = foundry.applications.settings; 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: { unsetSetting: this.#unsetSetting, }, }; 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); this.#order = Object.keys(ui.sidebar.constructor.TABS); }; get worldSave() { return game.settings.get(__ID__, `${rearrangeSidebarTabsKey}World`); }; get userSave() { return game.settings.get(__ID__, `${rearrangeSidebarTabsKey}User`); }; // #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(event) { const { scope } = event.submitter.dataset; if (!scope || ![`User`, `World`].includes(scope)) { return }; if (scope === `World` && this.userSave != null) { return; } game.settings.set( __ID__, `${rearrangeSidebarTabsKey}${scope}`, this.#order, ); SettingsConfig.reloadConfirm({ world: scope === `World` }); }; // #endregion Lifecycle // #region Data Prep async _prepareContext() { const ctx = { meta: { idp: this.id, isGM: game.user.isGM, }, showUserOverrideWarning: game.user.isGM && this.userSave != null, }; 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 /** @this {SidebarTabRearranger} */ static async #unsetSetting(event, target) { const { scope } = target.dataset; if (!scope || ![`User`, `World`].includes(scope)) { return }; await game.settings.set(__ID__, `${rearrangeSidebarTabsKey}${scope}`, null); SettingsConfig.reloadConfirm({ world: scope === `World` }); }; // #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 };