Update the sidebar tab rearranger to use Drag and Drop (again)
This commit is contained in:
parent
60034dcee2
commit
e28901dcf2
6 changed files with 213 additions and 18 deletions
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
31
module/utils/performArraySort.mjs
Normal file
31
module/utils/performArraySort.mjs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
export function performArraySort(
|
||||
element,
|
||||
{ list, targetIndex },
|
||||
) {
|
||||
|
||||
// Case: same position
|
||||
if (list.indexOf(el => el === element) === targetIndex) {
|
||||
return Array.from(list);
|
||||
};
|
||||
|
||||
// Case: start of array
|
||||
if (targetIndex === 0) {
|
||||
list = list.filter(el => el !== element);
|
||||
return [element, ...list];
|
||||
};
|
||||
|
||||
// Case: end of array
|
||||
if (targetIndex === list.length - 1) {
|
||||
list = list.filter(el => el !== element);
|
||||
return [...list, element];
|
||||
};
|
||||
|
||||
// Case: middle of array
|
||||
const front = list
|
||||
.slice(0, targetIndex)
|
||||
.filter(el => el !== element);
|
||||
const back = list
|
||||
.slice(targetIndex)
|
||||
.filter(el => el !== element);
|
||||
return [...front, element, ...back];
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue