Files
2025-01-04 00:34:03 +01:00

232 lines
7.2 KiB
JavaScript

/**
* A DocumentSheet application responsible for displaying and editing a single Cards stack.
*/
class CardsConfig extends DocumentSheet {
/**
* The CardsConfig sheet is constructed by providing a Cards document and sheet-level options.
* @param {Cards} object The {@link Cards} object being configured.
* @param {DocumentSheetOptions} [options] Application configuration options.
*/
constructor(object, options) {
super(object, options);
this.options.classes.push(object.type);
}
/**
* The allowed sorting methods which can be used for this sheet
* @enum {string}
*/
static SORT_TYPES = {
STANDARD: "standard",
SHUFFLED: "shuffled"
};
/* -------------------------------------------- */
/** @inheritdoc */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["sheet", "cards-config"],
template: "templates/cards/cards-deck.html",
width: 620,
height: "auto",
closeOnSubmit: false,
viewPermission: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER,
dragDrop: [{dragSelector: "ol.cards li.card", dropSelector: "ol.cards"}],
tabs: [{navSelector: ".tabs", contentSelector: "form", initial: "cards"}],
scrollY: ["ol.cards"],
sort: this.SORT_TYPES.SHUFFLED
});
}
/* -------------------------------------------- */
/** @inheritdoc */
getData(options={}) {
// Sort cards
const sortFn = {
standard: this.object.sortStandard,
shuffled: this.object.sortShuffled
}[options?.sort || "standard"];
const cards = this.object.cards.contents.sort((a, b) => sortFn.call(this.object, a, b));
// Return rendering context
return foundry.utils.mergeObject(super.getData(options), {
cards: cards,
types: CONFIG.Cards.typeLabels,
inCompendium: !!this.object.pack
});
}
/* -------------------------------------------- */
/* Event Listeners and Handlers */
/* -------------------------------------------- */
/** @inheritdoc */
activateListeners(html) {
super.activateListeners(html);
// Card Actions
html.find(".card-control").click(this._onCardControl.bind(this));
// Intersection Observer
const cards = html.find("ol.cards");
const entries = cards.find("li.card");
const observer = new IntersectionObserver(this._onLazyLoadImage.bind(this), {root: cards[0]});
entries.each((i, li) => observer.observe(li));
}
/* -------------------------------------------- */
/**
* Handle card control actions which modify single cards on the sheet.
* @param {PointerEvent} event The originating click event
* @returns {Promise} A Promise which resolves once the handler has completed
* @protected
*/
async _onCardControl(event) {
const button = event.currentTarget;
const li = button.closest(".card");
const card = li ? this.object.cards.get(li.dataset.cardId) : null;
const cls = getDocumentClass("Card");
// Save any pending change to the form
await this._onSubmit(event, {preventClose: true, preventRender: true});
// Handle the control action
switch ( button.dataset.action ) {
case "create":
return cls.createDialog({ faces: [{}], face: 0 }, {parent: this.object, pack: this.object.pack});
case "edit":
return card.sheet.render(true);
case "delete":
return card.deleteDialog();
case "deal":
return this.object.dealDialog();
case "draw":
return this.object.drawDialog();
case "pass":
return this.object.passDialog();
case "play":
return this.object.playDialog(card);
case "reset":
return this.object.resetDialog();
case "shuffle":
this.options.sort = this.constructor.SORT_TYPES.SHUFFLED;
return this.object.shuffle();
case "toggleSort":
this.options.sort = {standard: "shuffled", shuffled: "standard"}[this.options.sort];
return this.render();
case "nextFace":
return card.update({face: card.face === null ? 0 : card.face+1});
case "prevFace":
return card.update({face: card.face === 0 ? null : card.face-1});
}
}
/* -------------------------------------------- */
/**
* Handle lazy-loading card face images.
* See {@link SidebarTab#_onLazyLoadImage}
* @param {IntersectionObserverEntry[]} entries The entries which are now in the observer frame
* @param {IntersectionObserver} observer The intersection observer instance
* @protected
*/
_onLazyLoadImage(entries, observer) {
return ui.cards._onLazyLoadImage.call(this, entries, observer);
}
/* -------------------------------------------- */
/** @inheritdoc */
_canDragStart(selector) {
return this.isEditable;
}
/* -------------------------------------------- */
/** @inheritdoc */
_onDragStart(event) {
const li = event.currentTarget;
const card = this.object.cards.get(li.dataset.cardId);
if ( !card ) return;
// Set data transfer
event.dataTransfer.setData("text/plain", JSON.stringify(card.toDragData()));
}
/* -------------------------------------------- */
/** @inheritdoc */
_canDragDrop(selector) {
return this.isEditable;
}
/* -------------------------------------------- */
/** @inheritdoc */
async _onDrop(event) {
const data = TextEditor.getDragEventData(event);
if ( data.type !== "Card" ) return;
const card = await Card.implementation.fromDropData(data);
if ( card.parent.id === this.object.id ) return this._onSortCard(event, card);
try {
return await card.pass(this.object);
} catch(err) {
Hooks.onError("CardsConfig#_onDrop", err, {log: "error", notify: "error"});
}
}
/* -------------------------------------------- */
/**
* Handle sorting a Card relative to other siblings within this document
* @param {Event} event The drag drop event
* @param {Card} card The card being dragged
* @private
*/
_onSortCard(event, card) {
// Identify a specific card as the drop target
let target = null;
const li = event.target.closest("[data-card-id]");
if ( li ) target = this.object.cards.get(li.dataset.cardId) ?? null;
// Don't sort on yourself.
if ( card === target ) return;
// Identify the set of siblings
const siblings = this.object.cards.filter(c => c.id !== card.id);
// Perform an integer-based sort
const updateData = SortingHelpers.performIntegerSort(card, {target, siblings}).map(u => {
return {_id: u.target.id, sort: u.update.sort};
});
return this.object.updateEmbeddedDocuments("Card", updateData);
}
}
/**
* A subclass of CardsConfig which provides a sheet representation for Cards documents with the "hand" type.
*/
class CardsHand extends CardsConfig {
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
template: "templates/cards/cards-hand.html"
});
}
}
/**
* A subclass of CardsConfig which provides a sheet representation for Cards documents with the "pile" type.
*/
class CardsPile extends CardsConfig {
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
template: "templates/cards/cards-pile.html"
});
}
}