diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index 5ee13189..65eafe09 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -12,6 +12,4 @@ export { default as DhpWeapon } from './sheets/items/weapon.mjs'; export { default as DhpArmor } from './sheets/items/armor.mjs'; export { default as DhpChatMessage } from './chatMessage.mjs'; export { default as DhpEnvironment } from './sheets/environment.mjs'; -export { default as DhActiveEffectConfig } from './sheets/activeEffectConfig.mjs'; - -export * as pseudoDocumentSheet from './sheets/pseudo-documents/_module.mjs'; +export { default as DhActiveEffectConfig } from './sheets/activeEffectConfig.mjs'; \ No newline at end of file diff --git a/module/applications/sheets/pseudo-documents/_module.mjs b/module/applications/sheets/pseudo-documents/_module.mjs deleted file mode 100644 index 9dc4d356..00000000 --- a/module/applications/sheets/pseudo-documents/_module.mjs +++ /dev/null @@ -1 +0,0 @@ -export {default as PseudoDocumentSheet }from "./pseudo-documents-sheet.mjs"; \ No newline at end of file diff --git a/module/applications/sheets/pseudo-documents/pseudo-documents-sheet.mjs b/module/applications/sheets/pseudo-documents/pseudo-documents-sheet.mjs deleted file mode 100644 index 487da420..00000000 --- a/module/applications/sheets/pseudo-documents/pseudo-documents-sheet.mjs +++ /dev/null @@ -1,66 +0,0 @@ -const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; - -export default class PseudoDocumentSheet extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(options) { - super(options); - this.#pseudoDocument = options.document; - } - - /** - * The UUID of the associated pseudo-document - * @type {string} - */ - get pseudoUuid() { - return this.pseudoDocument.uuid; - } - - #pseudoDocument; - - /** - * The pseudo-document instance this sheet represents - * @type {object} - */ - get pseudoDocument() { - return this.#pseudoDocument; - } - - static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet'], - position: { width: 600 }, - form: { - handler: PseudoDocumentSheet.#onSubmitForm, - submitOnChange: true, - closeOnSubmit: false - }, - dragDrop: [{ dragSelector: null, dropSelector: null }], - }; - - static PARTS = { - header: { template: 'systems/daggerheart/templates/sheets/pseudo-documents/header.hbs' }, - }; - - /** @inheritDoc */ - async _prepareContext(options) { - const context = await super._prepareContext(options); - const document = this.pseudoDocument; - return Object.assign(context, { - document, - source: document._source, - editable: this.isEditable, - user: game.user, - rootId: this.id, - }); - } - - /** - * Form submission handler - * @param {SubmitEvent | Event} event - The originating form submission or input change event - * @param {HTMLFormElement} form - The form element that was submitted - * @param {foundry.applications.ux.FormDataExtended} formData - Processed data for the submitted form - */ - static async #onSubmitForm(event, form, formData) { - const submitData = foundry.utils.expandObject(formData.object); - await this.pseudoDocument.update(submitData); - } -} diff --git a/module/config/pseudoConfig.mjs b/module/config/pseudoConfig.mjs deleted file mode 100644 index 297d42cf..00000000 --- a/module/config/pseudoConfig.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import { pseudoDocuments } from "../data/_module.mjs"; -import { pseudoDocumentSheet } from "../applications/_module.mjs"; - -//CONFIG.daggerheart.pseudoDocuments -export default { - sheetClass: pseudoDocumentSheet.PseudoDocumentSheet, - feature: { - label: "DAGGERHEART.Feature.Label", - documentClass: pseudoDocuments.feature.BaseFeatureData, - types: { - weapon: { - label: "DAGGERHEART.Feature.Weapon.Label", - documentClass: pseudoDocuments.feature.WeaponFeature, - } - } - } -}; \ No newline at end of file diff --git a/module/config/system.mjs b/module/config/system.mjs index be2fc1aa..b9755317 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -5,7 +5,6 @@ import * as ITEM from './itemConfig.mjs'; import * as SETTINGS from './settingsConfig.mjs'; import * as EFFECTS from './effectConfig.mjs'; import * as ACTIONS from './actionConfig.mjs'; -import pseudoDocuments from "./pseudoConfig.mjs"; export const SYSTEM_ID = 'daggerheart'; @@ -18,5 +17,4 @@ export const SYSTEM = { SETTINGS, EFFECTS, ACTIONS, - pseudoDocuments }; diff --git a/module/data/_module.mjs b/module/data/_module.mjs index e43ddb99..828c5275 100644 --- a/module/data/_module.mjs +++ b/module/data/_module.mjs @@ -8,4 +8,3 @@ export * as items from './item/_module.mjs'; export { actionsTypes } from './action/_module.mjs'; export * as messages from './chat-message/_modules.mjs'; export * as fields from './fields/_module.mjs'; -export * as pseudoDocuments from './pseudo-documents/_module.mjs'; diff --git a/module/data/fields/_module.mjs b/module/data/fields/_module.mjs index 3a573a0b..af61157b 100644 --- a/module/data/fields/_module.mjs +++ b/module/data/fields/_module.mjs @@ -1,3 +1,2 @@ export { default as FormulaField } from './formulaField.mjs'; export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs'; -export { default as PseudoDocumentsField } from './pseudoDocumentsField.mjs'; diff --git a/module/data/fields/pseudoDocumentsField.mjs b/module/data/fields/pseudoDocumentsField.mjs deleted file mode 100644 index 0f48af5b..00000000 --- a/module/data/fields/pseudoDocumentsField.mjs +++ /dev/null @@ -1,56 +0,0 @@ -import PseudoDocument from '../pseudo-documents/base/pseudoDocument.mjs'; - -const { TypedObjectField, TypedSchemaField } = foundry.data.fields; - -/** - * @typedef _PseudoDocumentsFieldOptions - * @property {Number} [max] - The maximum amount of elements (default: `Infinity`) - * @property {String[]} [validTypes] - Allowed pseudo-documents types (default: `[]`) - * @property {Function} [validateKey] - callback for validate keys of the object; - - * @typedef {foundry.data.types.DataFieldOptions & _PseudoDocumentsFieldOptions} PseudoDocumentsFieldOptions - */ -export default class PseudoDocumentsField extends TypedObjectField { - /** - * @param {PseudoDocument} model - The PseudoDocument of each entry in this collection. - * @param {PseudoDocumentsFieldOptions} [options] - Options which configure the behavior of the field - * @param {foundry.data.types.DataFieldContext} [context] - Additional context which describes the field - */ - constructor(model, options = {}, context = {}) { - options.validateKey ||= key => foundry.data.validators.isValidId(key); - if (!foundry.utils.isSubclass(model, PseudoDocument)) throw new Error('The model must be a PseudoDocument'); - - const allTypes = model.TYPES; - - const filteredTypes = options.validTypes - ? Object.fromEntries( - Object.entries(allTypes).filter(([key]) => options.validTypes.includes(key)) - ) - : allTypes; - - const field = new TypedSchemaField(filteredTypes); - super(field, options, context); - } - - /** @inheritdoc */ - static get _defaults() { - return Object.assign(super._defaults, { - max: Infinity, - validTypes: [] - }); - } - - /** @override */ - _validateType(value, options = {}) { - if (Object.keys(value).length > this.max) throw new Error(`cannot have more than ${this.max} elements`); - return super._validateType(value, options); - } - - /** @override */ - initialize(value, model, options = {}) { - if (!value) return; - value = super.initialize(value, model, options); - const collection = new foundry.utils.Collection(Object.values(value).map(d => [d._id, d])); - return collection; - } -} diff --git a/module/data/pseudo-documents/_module.mjs b/module/data/pseudo-documents/_module.mjs deleted file mode 100644 index 6f50a137..00000000 --- a/module/data/pseudo-documents/_module.mjs +++ /dev/null @@ -1,2 +0,0 @@ -export { default as base } from './base/pseudoDocument.mjs'; -export * as feature from './feature/_module.mjs'; diff --git a/module/data/pseudo-documents/base/base.mjs b/module/data/pseudo-documents/base/base.mjs deleted file mode 100644 index 4d5313ba..00000000 --- a/module/data/pseudo-documents/base/base.mjs +++ /dev/null @@ -1,213 +0,0 @@ -/** - * @typedef {object} PseudoDocumentMetadata - * @property {string} name - The document name of this pseudo-document - * @property {Record} embedded - Record of document names and their collection paths - * @property {typeof foundry.applications.api.ApplicationV2} [sheetClass] - The class used to render this pseudo-document - * @property {string} defaultArtwork - The default image used for newly created documents - */ - -/** - * @class Base class for pseudo-documents - * @extends {foundry.abstract.DataModel} - */ -export default class BasePseudoDocument extends foundry.abstract.DataModel { - /** - * Pseudo-document metadata. - * @returns {PseudoDocumentMetadata} - */ - static get metadata() { - return { - name: '', - embedded: {}, - defaultArtwork: foundry.documents.Item.DEFAULT_ICON, - sheetClass: CONFIG.daggerheart.pseudoDocuments.sheetClass, - }; - } - - /** @override */ - static LOCALIZATION_PREFIXES = ['DOCUMENT']; - - /** @inheritdoc */ - static defineSchema() { - const { fields } = foundry.data; - - return { - _id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }), - name: new fields.StringField({ required: true, blank: false, textSearch: true }), - img: new fields.FilePathField({ categories: ['IMAGE'], initial: this.metadata.defaultArtwork }), - description: new fields.HTMLField({ textSearch: true }) - }; - } - - /* -------------------------------------------- */ - /* Instance Properties */ - /* -------------------------------------------- */ - - /** - * The id of this pseudo-document. - * @type {string} - */ - get id() { - return this._id; - } - - /* -------------------------------------------- */ - - /** - * The uuid of this document. - * @type {string} - */ - get uuid() { - let parent = this.parent; - while (!(parent instanceof BasePseudoDocument) && !(parent instanceof foundry.abstract.Document)) - parent = parent.parent; - return [parent.uuid, this.constructor.metadata.name, this.id].join('.'); - } - - /* -------------------------------------------- */ - - /** - * The parent document of this pseudo-document. - * @type {foundry.abstract.Document} - */ - get document() { - let parent = this; - while (!(parent instanceof foundry.abstract.Document)) parent = parent.parent; - return parent; - } - - /* -------------------------------------------- */ - - /** - * Item to which this PseudoDocument belongs, if applicable. - * @type {foundry.documents.Item|null} - */ - get item() { - return this.parent?.parent instanceof Item ? this.parent.parent : null; - } - - /* -------------------------------------------- */ - - /** - * Actor to which this PseudoDocument's item belongs, if the item is embedded. - * @type {foundry.documents.Actor|null} - */ - get actor() { - return this.item?.parent ?? null; - } - - /* -------------------------------------------- */ - - /** - * The property path to this pseudo document relative to its parent document. - * @type {string} - */ - get fieldPath() { - const fp = this.schema.fieldPath; - let path = fp.slice(0, fp.lastIndexOf('element') - 1); - - if (this.parent instanceof BasePseudoDocument) { - path = [this.parent.fieldPath, this.parent.id, path].join('.'); - } - - return path; - } - - /* -------------------------------------------- */ - /* Embedded Document Methods */ - /* -------------------------------------------- */ - - /** - * Retrieve an embedded pseudo-document. - * @param {string} embeddedName The document name of the embedded pseudo-document. - * @param {string} id The id of the embedded pseudo-document. - * @param {object} [options] Retrieval options. - * @param {boolean} [options.strinct] Throw an error if the embedded pseudo-document does not exist? - * @returns {PseudoDocument|null} - */ - getEmbeddedDocument(embeddedName, id, { strict = false } = {}) { - const embeds = this.constructor.metadata.embedded ?? {}; - if (embeddedName in embeds) { - return foundry.utils.getProperty(this, embeds[embeddedName]).get(id, { strict }) ?? null; - } - return null; - } - - /* -------------------------------------------- */ - /* CRUD Operations */ - /* -------------------------------------------- */ - - /** - * Does this pseudo-document exist in the document's source? - * @type {boolean} - */ - get isSource() { - const source = foundry.utils.getProperty(this.document._source, this.fieldPath); - if (foundry.utils.getType(source) !== 'Object') { - throw new Error('Source is not an object!'); - } - return this.id in source; - } - - /** - * Create a new instance of this pseudo-document. - * @param {object} [data] The data used for the creation. - * @param {object} operation The context of the update operation. - * @param {foundry.abstract.Document} operation.parent The parent of this document. - * @returns {Promise} A promise that resolves to the updated document. - */ - static async create(data = {}, { parent, ...operation } = {}) { - if (!parent) { - throw new Error('A parent document must be specified for the creation of a pseudo-document!'); - } - const id = - operation.keepId && foundry.data.validators.isValidId(data._id) ? data._id : foundry.utils.randomID(); - - const fieldPath = parent.system.constructor.metadata.embedded?.[this.metadata.name]; - if (!fieldPath) { - throw new Error( - `A ${parent.documentName} of type '${parent.type}' does not support ${this.metadata.name}!` - ); - } - - const update = { [`system.${fieldPath}.${id}`]: { ...data, _id: id } }; - const updatedParent = await parent.update(update, operation); - return foundry.utils.getProperty(updatedParent, `system.${fieldPath}.${id}`); - } - - /** - * Delete this pseudo-document. - * @param {object} [operation] The context of the operation. - * @returns {Promise} A promise that resolves to the updated document. - */ - async delete(operation = {}) { - if (!this.isSource) throw new Error('You cannot delete a non-source pseudo-document!'); - const update = { [`${this.fieldPath}.-=${this.id}`]: null }; - return this.document.update(update, operation); - } - - /** - * Duplicate this pseudo-document. - * @returns {Promise} A promise that resolves to the updated document. - */ - async duplicate() { - if (!this.isSource) throw new Error('You cannot duplicate a non-source pseudo-document!'); - const docData = foundry.utils.mergeObject(this.toObject(), { - name: game.i18n.format('DOCUMENT.CopyOf', { name: this.name }) - }); - return this.constructor.create(docData, { parent: this.document }); - } - - /** - * Update this pseudo-document. - * @param {object} [change] The change to perform. - * @param {object} [operation] The context of the operation. - * @returns {Promise} A promise that resolves to the updated document. - */ - async update(change = {}, operation = {}) { - if (!this.isSource) throw new Error('You cannot update a non-source pseudo-document!'); - const path = [this.fieldPath, this.id].join('.'); - const update = { [path]: change }; - return this.document.update(update, operation); - } -} diff --git a/module/data/pseudo-documents/base/pseudoDocument.mjs b/module/data/pseudo-documents/base/pseudoDocument.mjs deleted file mode 100644 index 2db23ef5..00000000 --- a/module/data/pseudo-documents/base/pseudoDocument.mjs +++ /dev/null @@ -1,59 +0,0 @@ -import BasePseudoDocument from './base.mjs'; -import SheetManagementMixin from './sheetManagementMixin.mjs'; - -/** @extends BasePseudoDocument */ -export default class PseudoDocument extends SheetManagementMixin(BasePseudoDocument) { - static get TYPES() { - const { types } = CONFIG.daggerheart.pseudoDocuments[this.metadata.name]; - const typeEntries = Object.entries(types).map(([key, { documentClass }]) => [key, documentClass]); - return (this._TYPES ??= Object.freeze(Object.fromEntries(typeEntries))); - } - - static _TYPES; - - /** - * The type of this shape. - * @type {string} - */ - static TYPE = ''; - - /* -------------------------------------------- */ - - static getTypesChoices(validTypes) { - const { types } = CONFIG.daggerheart.pseudoDocuments[model.metadata.name]; - const typeEntries = Object.entries(types) - .map(([key, { label }]) => [key, label]) - .filter(([key]) => !validTypes || validTypes.includes(key)); - - return Object.entries(typeEntries); - } - - /* -------------------------------------------- */ - - /** @override */ - static defineSchema() { - const { fields } = foundry.data; - - return Object.assign(super.defineSchema(), { - type: new fields.StringField({ - required: true, - blank: false, - initial: this.TYPE, - validate: value => value === this.TYPE, - validationError: `must be equal to "${this.TYPE}"` - }) - }); - } - - /** @inheritdoc */ - static async create(data = {}, { parent, ...operation } = {}) { - data = foundry.utils.deepClone(data); - if (!data.type) data.type = Object.keys(this.TYPES)[0]; - if (!(data.type in this.TYPES)) { - throw new Error( - `The '${data.type}' type is not a valid type for a '${this.metadata.documentName}' pseudo-document!` - ); - } - return super.create(data, { parent, ...operation }); - } -} diff --git a/module/data/pseudo-documents/base/sheetManagementMixin.mjs b/module/data/pseudo-documents/base/sheetManagementMixin.mjs deleted file mode 100644 index 796faf51..00000000 --- a/module/data/pseudo-documents/base/sheetManagementMixin.mjs +++ /dev/null @@ -1,158 +0,0 @@ -import BasePseudoDocument from './base.mjs'; -const { ApplicationV2 } = foundry.applications.api; - -/** - * A mixin that adds sheet management capabilities to pseudo-documents - * @template {typeof BasePseudoDocument} T - * @param {T} Base - * @returns {T & typeof PseudoDocumentWithSheets} - */ -export default function SheetManagementMixin(Base) { - class PseudoDocumentWithSheets extends Base { - /** - * Reference to the sheet of this pseudo-document. - * @type {ApplicationV2|null} - */ - get sheet() { - if (this._sheet) return this._sheet; - const cls = this.constructor.metadata.sheetClass ?? ApplicationV2; - - if (!foundry.utils.isSubclass(cls, ApplicationV2)) { - return void ui.notifications.error( - 'Daggerheart | Error on PseudoDocument | sheetClass must be ApplicationV2' - ); - } - - const sheet = new cls({ document: this }); - this._sheet = sheet; - return sheet; - } - - /* -------------------------------------------- */ - /* Static Properties */ - /* -------------------------------------------- */ - - /** - * Set of apps what should be re-render. - * @type {Set} - * @internal - */ - _apps = new Set(); - - /* -------------------------------------------- */ - - /** - * Existing sheets of a specific type for a specific document. - * @type {ApplicationV2 | null} - */ - _sheet = null; - - /* -------------------------------------------- */ - /* Display Methods */ - /* -------------------------------------------- */ - - /** - * Render all the Application instances which are connected to this PseudoDocument. - * @param {ApplicationRenderOptions} [options] Rendering options. - */ - render(options) { - for (const app of this._apps ?? []) { - app.render({ window: { title: app.title }, ...options }); - } - } - - /* -------------------------------------------- */ - - /** - * Register an application to respond to updates to a certain document. - * @param {ApplicationV2} app Application to update. - * @internal - */ - _registerApp(app) { - this._apps.add(app); - } - - /* -------------------------------------------- */ - - /** - * Remove an application from the render registry. - * @param {ApplicationV2} app Application to stop watching. - */ - _unregisterApp(app) { - this._apps.delete(app); - } - - /* -------------------------------------------- */ - /* Drag and Drop */ - /* -------------------------------------------- */ - - /** - * Serialize salient information for this PseudoDocument when dragging it. - * @returns {object} An object of drag data. - */ - toDragData() { - const dragData = { type: this.documentName, data: this.toObject() }; - if (this.id) dragData.uuid = this.uuid; - return dragData; - } - - /* -------------------------------------------- */ - /* Dialog Methods */ - /* -------------------------------------------- */ - - /** - * Spawn a dialog for creating a new PseudoDocument. - * @param {object} [data] Data to pre-populate the document with. - * @param {object} context - * @param {foundry.documents.Item} context.parent A parent for the document. - * @param {string[]|null} [context.types] A list of types to restrict the choices to, or null for no restriction. - * @returns {Promise} - */ - static async createDialog(data = {}, { parent, types = null, ...options } = {}) { - // TODO - } - - /** - * Present a Dialog form to confirm deletion of this PseudoDocument. - * @param {object} [options] - Additional options passed to `DialogV2.confirm`; - * @returns {Promise} A Promise which resolves to the deleted PseudoDocument. - */ - async deleteDialog(options = {}) { - const type = game.i18n.localize(this.constructor.metadata.label); - const content = options.content ?? `

- ${game.i18n.localize("AreYouSure")} - ${game.i18n.format("SIDEBAR.DeleteWarning", { type })} -

`; - - return foundry.applications.api.DialogV2.confirm({ - content, - yes: { callback: () => this.delete(operation) }, - window: { - icon: "fa-solid fa-trash", - title: `${game.i18n.format("DOCUMENT.Delete", { type })}: ${this.name}` - }, - ...options - }); - } - - /** - * Gets the default new name for a Document - * @param {object} collection - Collection of Documents - * @returns {string} - */ - static defaultName(collection) { - const documentName = this.metadata.name; - const takenNames = new Set(); - for (const document of collection) takenNames.add(document.name); - - const config = CONFIG.daggerheart.pseudoDocuments[documentName]; - const baseName = game.i18n.localize(config.label); - let name = baseName; - let index = 1; - while (takenNames.has(name)) name = `${baseName} (${++index})`; - return name; - } - } - - return PseudoDocumentWithSheets; -} diff --git a/module/data/pseudo-documents/feature/_module.mjs b/module/data/pseudo-documents/feature/_module.mjs deleted file mode 100644 index 794f5d27..00000000 --- a/module/data/pseudo-documents/feature/_module.mjs +++ /dev/null @@ -1,2 +0,0 @@ -export { default as BaseFeatureData } from './baseFeatureData.mjs'; -export { default as WeaponFeature } from './weaponFeature.mjs'; diff --git a/module/data/pseudo-documents/feature/baseFeatureData.mjs b/module/data/pseudo-documents/feature/baseFeatureData.mjs deleted file mode 100644 index 61d1468d..00000000 --- a/module/data/pseudo-documents/feature/baseFeatureData.mjs +++ /dev/null @@ -1,24 +0,0 @@ -import PseudoDocument from '../base/pseudoDocument.mjs'; - -export default class BaseFeatureData extends PseudoDocument { - /**@inheritdoc */ - static get metadata() { - return foundry.utils.mergeObject( - super.metadata, - { - name: 'feature', - embedded: {}, - //sheetClass: null //TODO: define feature-sheet - }, - { inplace: false } - ); - } - - static defineSchema() { - const { fields } = foundry.data; - const schema = super.defineSchema(); - return Object.assign(schema, { - subtype: new fields.StringField({ initial: 'test' }) - }); - } -} diff --git a/module/data/pseudo-documents/feature/weaponFeature.mjs b/module/data/pseudo-documents/feature/weaponFeature.mjs deleted file mode 100644 index c8039ede..00000000 --- a/module/data/pseudo-documents/feature/weaponFeature.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import BaseFeatureData from './baseFeatureData.mjs'; - -export default class WeaponFeature extends BaseFeatureData { - /**@override */ - static TYPE = 'weapon'; -}