Add drag and drop support for reordering the attributes manually in the Attribute Manager
This commit is contained in:
parent
fec638cb22
commit
b4ec9745e7
4 changed files with 123 additions and 5 deletions
|
|
@ -1,9 +1,11 @@
|
||||||
import { __ID__, filePath } from "../consts.mjs";
|
import { __ID__, filePath } from "../consts.mjs";
|
||||||
|
import { attributeSorter } from "../utils/attributeSort.mjs";
|
||||||
import { Logger } from "../utils/Logger.mjs";
|
import { Logger } from "../utils/Logger.mjs";
|
||||||
import { toID } from "../utils/toID.mjs";
|
import { toID } from "../utils/toID.mjs";
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
const { deepClone, diffObject, randomID, setProperty } = foundry.utils;
|
const { deepClone, diffObject, mergeObject, performIntegerSort, randomID, setProperty } = foundry.utils;
|
||||||
|
const { DragDrop, TextEditor } = foundry.applications.ux;
|
||||||
|
|
||||||
export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2) {
|
export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
||||||
|
|
@ -16,7 +18,7 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
],
|
],
|
||||||
position: {
|
position: {
|
||||||
width: 400,
|
width: 400,
|
||||||
height: 350,
|
height: `auto`,
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
|
@ -68,6 +70,18 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
for (const input of elements) {
|
for (const input of elements) {
|
||||||
input.addEventListener(`change`, this.#bindListener.bind(this));
|
input.addEventListener(`change`, this.#bindListener.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
new DragDrop.implementation({
|
||||||
|
dragSelector: `[data-attribute]`,
|
||||||
|
permissions: {
|
||||||
|
dragstart: this._canDragStart.bind(this),
|
||||||
|
drop: this._canDragDrop.bind(this),
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
dragstart: this._onDragStart.bind(this),
|
||||||
|
drop: this._onDrop.bind(this),
|
||||||
|
},
|
||||||
|
}).bind(this.element);
|
||||||
};
|
};
|
||||||
// #endregion Lifecycle
|
// #endregion Lifecycle
|
||||||
|
|
||||||
|
|
@ -93,11 +107,13 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
attrs.push({
|
attrs.push({
|
||||||
id,
|
id,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
displayName: data.isNew ? `New Attribute` : data.name,
|
||||||
|
sort: data.sort,
|
||||||
isRange: data.isRange,
|
isRange: data.isRange,
|
||||||
isNew: data.isNew ?? false,
|
isNew: data.isNew ?? false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
ctx.attrs = attrs;
|
ctx.attrs = attrs.sort(attributeSorter);
|
||||||
};
|
};
|
||||||
// #endregion Data Prep
|
// #endregion Data Prep
|
||||||
|
|
||||||
|
|
@ -119,7 +135,7 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
|
|
||||||
Logger.debug(`Updating ${binding} value to ${value}`);
|
Logger.debug(`Updating ${binding} value to ${value}`);
|
||||||
setProperty(this.#attributes, binding, value);
|
setProperty(this.#attributes, binding, value);
|
||||||
await this.render();
|
await this.render({ parts: [ `attributes` ]});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @this {AttributeManager} */
|
/** @this {AttributeManager} */
|
||||||
|
|
@ -127,6 +143,7 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
const id = randomID();
|
const id = randomID();
|
||||||
this.#attributes[id] = {
|
this.#attributes[id] = {
|
||||||
name: ``,
|
name: ``,
|
||||||
|
sort: Number.POSITIVE_INFINITY,
|
||||||
isRange: false,
|
isRange: false,
|
||||||
isNew: true,
|
isNew: true,
|
||||||
};
|
};
|
||||||
|
|
@ -168,4 +185,87 @@ export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
await this.#doc.update({ "system.attr": diff });
|
await this.#doc.update({ "system.attr": diff });
|
||||||
};
|
};
|
||||||
// #endregion Actions
|
// #endregion Actions
|
||||||
|
|
||||||
|
// #region Drag & Drop
|
||||||
|
_canDragStart() {
|
||||||
|
return this.#doc.isOwner;
|
||||||
|
};
|
||||||
|
|
||||||
|
_canDragDrop() {
|
||||||
|
return this.#doc.isOwner;
|
||||||
|
};
|
||||||
|
|
||||||
|
_onDragStart(event) {
|
||||||
|
const target = event.currentTarget;
|
||||||
|
if (`link` in event.target.dataset) { return };
|
||||||
|
let dragData;
|
||||||
|
|
||||||
|
if (target.dataset.attribute) {
|
||||||
|
const attributeID = target.dataset.attribute;
|
||||||
|
const attribute = this.#attributes[attributeID];
|
||||||
|
dragData = {
|
||||||
|
_id: attributeID,
|
||||||
|
sort: attribute.sort,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!dragData) { return };
|
||||||
|
event.dataTransfer.setData(`text/plain`, JSON.stringify(dragData));
|
||||||
|
};
|
||||||
|
|
||||||
|
_onDrop(event) {
|
||||||
|
const dropped = TextEditor.implementation.getDragEventData(event);
|
||||||
|
|
||||||
|
const dropTarget = event.target.closest(`[data-attribute]`);
|
||||||
|
if (!dropTarget) { return };
|
||||||
|
const targetID = dropTarget.dataset.attribute;
|
||||||
|
let target;
|
||||||
|
|
||||||
|
// Not moving location, ignore drop event
|
||||||
|
if (targetID === dropped._id) { return };
|
||||||
|
|
||||||
|
// Determine all of the siblings and create sort data
|
||||||
|
const siblings = [];
|
||||||
|
for (const element of dropTarget.parentElement.children) {
|
||||||
|
const siblingID = element.dataset.attribute;
|
||||||
|
const attr = this.#attributes[siblingID];
|
||||||
|
const sibling = {
|
||||||
|
_id: siblingID,
|
||||||
|
sort: attr.sort,
|
||||||
|
};
|
||||||
|
if (siblingID && siblingID !== dropped._id) {
|
||||||
|
siblings.push(sibling);
|
||||||
|
};
|
||||||
|
if (siblingID === targetID) {
|
||||||
|
target = sibling;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortUpdates = performIntegerSort(
|
||||||
|
dropped,
|
||||||
|
{
|
||||||
|
target,
|
||||||
|
siblings,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateEntries = sortUpdates.map(({ target, update }) => {
|
||||||
|
return [ `${target._id}.sort`, update.sort ];
|
||||||
|
});
|
||||||
|
const update = Object.fromEntries(updateEntries);
|
||||||
|
|
||||||
|
mergeObject(
|
||||||
|
this.#attributes,
|
||||||
|
update,
|
||||||
|
{
|
||||||
|
insertKeys: false,
|
||||||
|
insertValues: false,
|
||||||
|
inplace: true,
|
||||||
|
performDeletions: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.render({ parts: [ `attributes` ] });
|
||||||
|
};
|
||||||
|
// #endregion Drag & Drop
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
.attribute {
|
.attribute {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto auto;
|
grid-template-columns: min-content 1fr repeat(3, auto);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
|
@ -18,6 +18,18 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
taf-icon {
|
||||||
|
cursor: grab;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,11 @@
|
||||||
class="attribute"
|
class="attribute"
|
||||||
data-attribute="{{ attr.id }}"
|
data-attribute="{{ attr.id }}"
|
||||||
>
|
>
|
||||||
|
<taf-icon
|
||||||
|
name="icons/drag-handle"
|
||||||
|
var:stroke="currentColor"
|
||||||
|
var:fill="currentColor"
|
||||||
|
></taf-icon>
|
||||||
{{#if attr.isNew}}
|
{{#if attr.isNew}}
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue