191 lines
5.3 KiB
JavaScript
191 lines
5.3 KiB
JavaScript
import { createItemFromElement, deleteItemFromElement, editItemFromElement, updateForeignDocumentFromEvent } from "../utils.mjs";
|
|
import { DicePool } from "../DicePool.mjs";
|
|
import { RichEditor } from "../RichEditor.mjs";
|
|
import { toBoolean } from "../../consts.mjs";
|
|
|
|
/**
|
|
* A mixin that takes the class from HandlebarsApplicationMixin and combines it
|
|
* with utility functions / data that is used across all RipCrypt applications
|
|
*/
|
|
export function GenericAppMixin(HandlebarsApp) {
|
|
class GenericRipCryptApp extends HandlebarsApp {
|
|
|
|
// #region Options
|
|
static DEFAULT_OPTIONS = {
|
|
classes: [
|
|
`ripcrypt`,
|
|
],
|
|
actions: {
|
|
roll: this.#rollDice,
|
|
createItem(_event, target) { // uses arrow-less function for "this"
|
|
const parent = this.document;
|
|
createItemFromElement(target, { parent });
|
|
},
|
|
editItem: (_event, target) => editItemFromElement(target),
|
|
deleteItem: (_event, target) => deleteItemFromElement(target),
|
|
openRichEditor: this.#openRichEditor,
|
|
},
|
|
};
|
|
|
|
static themes = {
|
|
dark: `SETTINGS.UI.FIELDS.colorScheme.dark`,
|
|
};
|
|
// #endregion
|
|
|
|
// #region Instance Data
|
|
/** @type {Map<string, PopoverEventManager>} */
|
|
_popoverManagers = new Map();
|
|
/** @type {Map<number, string>} */
|
|
_hookIDs = new Map();
|
|
/** @type {string | null} */
|
|
#focus = null;
|
|
// #endregion
|
|
|
|
// #region Lifecycle
|
|
/**
|
|
* @override
|
|
* Making it so that if the app is already open, it's brought to
|
|
* top after being re-rendered as normal
|
|
*/
|
|
async render(options = {}, _options = {}) {
|
|
await super.render(options, _options);
|
|
const instance = foundry.applications.instances.get(this.id);
|
|
if (instance !== undefined && options.orBringToFront) {
|
|
instance.bringToFront();
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @override
|
|
* This method overrides Foundry's default behaviour for caching the focused
|
|
* element so that it actually works when the application has a root partial
|
|
*/
|
|
async _preRender(...args) {
|
|
if (this.rendered) {
|
|
const target = this.element.querySelector(`:focus`);
|
|
if (target) {
|
|
if (target.id) {
|
|
this.#focus = `#${CSS.escape(target.id)}`;
|
|
}
|
|
else if (target.name) {
|
|
this.#focus = `${target.tagName}[name="${target.name}"]`;
|
|
};
|
|
};
|
|
};
|
|
return super._preRender(...args);
|
|
};
|
|
|
|
/** @override */
|
|
async _onRender(...args) {
|
|
await super._onRender(...args);
|
|
|
|
/*
|
|
Rendering each of the popover managers associated with this app allows us
|
|
to have them be dynamic and update when their parent application is rerendered,
|
|
this could eventually be something we can move into the Document's apps
|
|
collection so Foundry auto-rerenders it, but because it isn't actually
|
|
associated with the Document (as it's dependendant on the Application), I
|
|
decided that it would be best to do my own handling for it.
|
|
*/
|
|
for (const manager of this._popoverManagers.values()) {
|
|
manager.render();
|
|
};
|
|
|
|
/*
|
|
Foreign update listeners so that we can easily update items that may not
|
|
be this document itself, but are useful to be able to be edited from this
|
|
sheet. Primarily useful for editing the Actors' Item collection, or an Items'
|
|
ActiveEffect collection.
|
|
*/
|
|
this.element.querySelectorAll(`input[data-foreign-update-on]`).forEach(el => {
|
|
const events = el.dataset.foreignUpdateOn.split(`,`);
|
|
for (const event of events) {
|
|
el.addEventListener(event, updateForeignDocumentFromEvent);
|
|
};
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @override
|
|
* This method overrides Foundry's default behaviour for caching the focused
|
|
* element so that it actually works when the application has a root partial
|
|
*/
|
|
async _postRender(...args) {
|
|
if (this.rendered) {
|
|
const target = this.element.querySelector(this.#focus);
|
|
target?.focus();
|
|
};
|
|
this.#focus = null;
|
|
return super._postRender(...args);
|
|
};
|
|
|
|
async _prepareContext(_options) {
|
|
const ctx = {};
|
|
|
|
ctx.meta ??= {};
|
|
ctx.meta.idp = this.id;
|
|
if (this.document) {
|
|
ctx.meta.limited = this.document.limited;
|
|
ctx.meta.editable = this.isEditable || game.user.isGM;
|
|
ctx.meta.embedded = this.document.isEmbedded;
|
|
};
|
|
|
|
return ctx;
|
|
};
|
|
|
|
_tearDown(options) {
|
|
// Clear all popovers associated with the app
|
|
for (const manager of this._popoverManagers.values()) {
|
|
manager.destroy();
|
|
};
|
|
this._popoverManagers.clear();
|
|
|
|
// Remove any hooks added for this app
|
|
for (const [id, hook] of this._hookIDs.entries()) {
|
|
Hooks.off(hook, id);
|
|
};
|
|
this._hookIDs.clear();
|
|
|
|
super._tearDown(options);
|
|
};
|
|
// #endregion
|
|
|
|
// #region Actions
|
|
/** @this {GenericRipCryptApp} */
|
|
static async #rollDice(_event, target) {
|
|
const data = target.dataset;
|
|
const diceCount = parseInt(data.diceCount);
|
|
const flavor = data.flavor;
|
|
|
|
const dp = new DicePool({ diceCount, flavor });
|
|
dp.render({ force: true });
|
|
};
|
|
|
|
/** @this {GenericRipCryptApp} */
|
|
static async #openRichEditor(_event, target) {
|
|
const data = target.dataset;
|
|
const {
|
|
uuid,
|
|
path,
|
|
collaborative,
|
|
compact,
|
|
} = data;
|
|
|
|
if (!uuid || !path) {
|
|
console.error(`Rich Editor requires a document uuid and path to edit`);
|
|
return;
|
|
};
|
|
|
|
const document = await fromUuid(uuid);
|
|
const app = new RichEditor({
|
|
document,
|
|
path,
|
|
collaborative: toBoolean(collaborative),
|
|
compact: toBoolean(compact ),
|
|
});
|
|
app.render({ force: true });
|
|
};
|
|
// #endregion
|
|
};
|
|
return GenericRipCryptApp;
|
|
};
|