Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#5156] Add dialog for splitting items in inventory into two stacks #5157

Open
wants to merge 1 commit into
base: 4.3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3458,6 +3458,11 @@
"DND5E.SpellUsage": "Spell Usage",
"DND5E.Spellbook": "Spellbook",
"DND5E.Spent": "Spent",
"DND5E.SplitStack": {
"Action": "Split",
"Title": "Split Stack"
},

"DND5E.StartingEquipment": {
"Title": "Starting Equipment",
"Action": {
Expand Down
17 changes: 17 additions & 0 deletions less/elements.less
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ copyable-text {
}
}

/* ---------------------------------- */
/* Double Range Picker */
/* ---------------------------------- */

double-range-picker {
display: flex;
align-items: center;
gap: 0.5rem;
> input[type="range"] { flex: 1; }
> input[type="number"] {
flex: 0 0 40px;
text-align: center;
padding: 0;
font-size: 0.8em;
}
}

/* ---------------------------------- */
/* Filter State */
/* ---------------------------------- */
Expand Down
2 changes: 1 addition & 1 deletion less/v2/forms.less
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@
}
}

range-picker {
range-picker, double-range-picker {
input[type="range"] {
--range-thumb-background-color: var(--dnd5e-color-card);
--range-thumb-border-color: var(--dnd5e-color-gold);
Expand Down
8 changes: 5 additions & 3 deletions module/applications/components/_module.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import AdoptedStyleSheetMixin from "./adopted-stylesheet-mixin.mjs";
import CheckboxElement from "./checkbox.mjs";
import CopyableTextElement from "./copyable-text.mjs";
import DamageApplicationElement from "./damage-application.mjs";
import DoubleRangePickerElement from "./double-range-picker.mjs";
import EffectApplicationElement from "./effect-application.mjs";
import EffectsElement from "./effects.mjs";
import EnchantmentApplicationElement from "./enchantment-application.mjs";
Expand All @@ -19,6 +20,7 @@ window.customElements.define("dnd5e-checkbox", CheckboxElement);
window.customElements.define("dnd5e-effects", EffectsElement);
window.customElements.define("dnd5e-icon", IconElement);
window.customElements.define("dnd5e-inventory", InventoryElement);
window.customElements.define("double-range-picker", DoubleRangePickerElement);
window.customElements.define("effect-application", EffectApplicationElement);
window.customElements.define("enchantment-application", EnchantmentApplicationElement);
window.customElements.define("filigree-box", FiligreeBoxElement);
Expand All @@ -28,7 +30,7 @@ window.customElements.define("proficiency-cycle", ProficiencyCycleElement);
window.customElements.define("slide-toggle", SlideToggleElement);

export {
AdoptedStyleSheetMixin, CopyableTextElement, CheckboxElement, DamageApplicationElement, EffectApplicationElement,
EffectsElement, EnchantmentApplicationElement, FiligreeBoxElement, FilterStateElement, IconElement,
InventoryElement, ItemListControlsElement, ProficiencyCycleElement, SlideToggleElement
AdoptedStyleSheetMixin, CopyableTextElement, CheckboxElement, DamageApplicationElement, DoubleRangePickerElement,
EffectApplicationElement, EffectsElement, EnchantmentApplicationElement, FiligreeBoxElement, FilterStateElement,
IconElement, InventoryElement, ItemListControlsElement, ProficiencyCycleElement, SlideToggleElement
};
102 changes: 102 additions & 0 deletions module/applications/components/double-range-picker.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* Version of the default range picker that has number inputs on both sides.
*/
export default class DoubleRangePickerElement extends foundry.applications.elements.HTMLRangePickerElement {
constructor() {
super();
this.#min = Number(this.getAttribute("min")) ?? 0;
this.#max = Number(this.getAttribute("max")) ?? 1;
}

/** @override */
static tagName = "double-range-picker";

/**
* The range input.
* @type {HTMLInputElement}
*/
#rangeInput;

/**
* The left number input.
* @type {HTMLInputElement}
*/
#leftInput;

/**
* The right number input.
* @type {HTMLInputElement}
*/
#rightInput;

/**
* The minimum allowed value for the range.
* @type {number}
*/
#min;

/**
* The maximum allowed value for the range.
* @type {number}
*/
#max;

/* -------------------------------------------- */

/** @inheritDoc */
_buildElements() {
const [range, right] = super._buildElements();
this.#rangeInput = range;
this.#rightInput = right;
this.#leftInput = this.#rightInput.cloneNode();
return [this.#leftInput, this.#rangeInput, this.#rightInput];
}

/* -------------------------------------------- */

/** @inheritDoc */
_refresh() {
super._refresh();
if ( !this.#rangeInput ) return;
this.#leftInput.valueAsNumber = this.#max - this.#rightInput.valueAsNumber + this.#min;
}

/* -------------------------------------------- */

/** @inheritDoc */
_activateListeners() {
super._activateListeners();
this.#rangeInput.addEventListener("input", this.#onDragSlider.bind(this));
this.#leftInput.addEventListener("change", this.#onChangeInput.bind(this));
}

/* -------------------------------------------- */

/**
* Update display of the number input as the range slider is actively changed.
* @param {InputEvent} event The originating input event
*/
#onDragSlider(event) {
event.preventDefault();
this.#leftInput.valueAsNumber = this.#max - this.#rangeInput.valueAsNumber + this.#min;
}

/* -------------------------------------------- */

/**
* Handle changes to the left input.
* @param {InputEvent} event The originating input change event
*/
#onChangeInput(event) {
event.stopPropagation();
this.value = this.#max - event.currentTarget.valueAsNumber + this.#min;
}

/* -------------------------------------------- */

/** @override */
_toggleDisabled(disabled) {
super._toggleDisabled(disabled);
this.#leftInput.toggleAttribute("disabled", disabled);
}
}
8 changes: 8 additions & 0 deletions module/applications/components/inventory.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {parseInputDelta} from "../../utils.mjs";
import CurrencyManager from "../currency-manager.mjs";
import ContextMenu5e from "../context-menu.mjs";
import ItemSheet5e2 from "../item/item-sheet-2.mjs";
import SplitStackDialog from "../item/split-stack-dialog.mjs";

/**
* Custom element that handles displaying actor & container inventories.
Expand Down Expand Up @@ -231,6 +232,13 @@ export default class InventoryElement extends HTMLElement {
&& !this.actor?.[game.release.generation < 13 ? "compendium" : "collection"]?.locked,
group: "action"
},
{
name: "DND5E.SplitStack.Title",
icon: '<i class="fa-solid fa-arrows-split-up-and-left"></i>',
callback: () => new SplitStackDialog({ document: item }).render({ force: true }),
condition: () => item.isOwner && !compendiumLocked && ((item.system.quantity ?? 0) > 1),
group: "action"
},
{
name: "DND5E.ConcentrationBreak",
icon: '<dnd5e-icon src="systems/dnd5e/icons/svg/break-concentration.svg"></dnd5e-icon>',
Expand Down
69 changes: 69 additions & 0 deletions module/applications/item/split-stack-dialog.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Dialog5e from "../api/dialog.mjs";

/**
* Small dialog for splitting a stack of items into two.
*/
export default class SplitStackDialog extends Dialog5e {
/** @override */
static DEFAULT_OPTIONS = {
buttons: [{
id: "split",
label: "DND5E.SplitStack.Action",
icon: "fa-solid fa-arrows-split-up-and-left"
}],
classes: ["split-stack"],
document: null,
form: {
handler: SplitStackDialog.#handleFormSubmission
},
position: {
width: 400
},
window: {
title: "DND5E.SplitStack.Title"
}
};

/* -------------------------------------------- */

/** @inheritDoc */
static PARTS = {
...super.PARTS,
content: {
template: "systems/dnd5e/templates/apps/split-stack-dialog.hbs"
}
};

/* -------------------------------------------- */
/* Rendering */
/* -------------------------------------------- */

/** @override */
async _prepareContentContext(context, options) {
const total = this.options.document.system.quantity ?? 1;
context.max = Math.max(1, total - 1);
context.left = Math.ceil(total / 2);
context.right = total - context.left;
return context;
}

/* -------------------------------------------- */
/* Form Handling */
/* -------------------------------------------- */

/**
* Handle submission of the dialog.
* @this {SplitStackDialog}
* @param {Event|SubmitEvent} event The form submission event.
* @param {HTMLFormElement} form The submitted form.
* @param {FormDataExtended} formData Data from the dialog.
*/
static async #handleFormSubmission(event, form, formData) {
const right = formData.object.right ?? 0;
const left = (this.options.document.system.quantity ?? 1) - right;
if ( left === this.options.document.system.quantity ) return;
await this.options.document.update({ "system.quantity": left }, { render: false });
Copy link
Contributor

@krbz999 krbz999 Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is render: false to prevent the actor sheet rendering twice? This would prevent the item sheet rendering too, though. So maybe just Promise.all like is done with advancements? (Or swap the order of operations here.)

await this.options.document.clone({ "system.quantity": right }, { addSource: true, save: true });
this.close();
}
}
3 changes: 3 additions & 0 deletions templates/apps/split-stack-dialog.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<section class="flexrow">
<double-range-picker name="right" value="{{ right }}" min="1" max="{{ max }}" step="1"></double-range-picker>
</section>