Update the sidebar tab rearranger to use Drag and Drop (again)

This commit is contained in:
Oliver 2026-02-22 23:53:27 -07:00
parent 60034dcee2
commit e28901dcf2
6 changed files with 213 additions and 18 deletions

View file

@ -1,6 +1,8 @@
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) {
@ -20,7 +22,8 @@ export class SidebarTabRearranger extends HandlebarsApplicationMixin(Application
closeOnSubmit: true,
submitOnChange: false,
},
actions: {},
actions: {
},
};
static PARTS = {
@ -30,9 +33,33 @@ export class SidebarTabRearranger extends HandlebarsApplicationMixin(Application
// #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
@ -47,7 +74,11 @@ export class SidebarTabRearranger extends HandlebarsApplicationMixin(Application
const tabs = ui.sidebar.constructor.TABS;
ctx.tabs = [];
for (const [id, tab] of Object.entries(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 };
@ -60,6 +91,7 @@ export class SidebarTabRearranger extends HandlebarsApplicationMixin(Application
id,
name: game.i18n.localize(tooltip),
icon,
nextIndex: i + 1,
});
};
@ -69,4 +101,64 @@ export class SidebarTabRearranger extends HandlebarsApplicationMixin(Application
// #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
};