mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-15 21:21:08 +01:00
FEAT: add baseDataModel logic
This commit is contained in:
parent
746e0f239a
commit
70d8f37d3c
7 changed files with 254 additions and 7 deletions
1
module/_types.d.ts
vendored
1
module/_types.d.ts
vendored
|
|
@ -0,0 +1 @@
|
||||||
|
import './data/pseudo-documents/_types';
|
||||||
|
|
@ -8,3 +8,4 @@ export { default as DhpEnvironment } from './environment.mjs';
|
||||||
|
|
||||||
export * as items from './item/_module.mjs';
|
export * as items from './item/_module.mjs';
|
||||||
export * as messages from './chat-message/_modules.mjs';
|
export * as messages from './chat-message/_modules.mjs';
|
||||||
|
export * as fields from "./fields/_module.mjs";
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
* @property {string} type - The system type that this data model represents.
|
* @property {string} type - The system type that this data model represents.
|
||||||
* @property {boolean} hasDescription - Indicates whether items of this type have description field
|
* @property {boolean} hasDescription - Indicates whether items of this type have description field
|
||||||
* @property {boolean} isQuantifiable - Indicates whether items of this type have quantity field
|
* @property {boolean} isQuantifiable - Indicates whether items of this type have quantity field
|
||||||
|
* @property {Record<string,string>} embedded - Record of document names of pseudo-documents and the path to the collection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
@ -16,7 +17,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
label: "Base Item",
|
label: "Base Item",
|
||||||
type: "base",
|
type: "base",
|
||||||
hasDescription: false,
|
hasDescription: false,
|
||||||
isQuantifiable: false
|
isQuantifiable: false,
|
||||||
|
embedded: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
5
module/data/pseudo-documents/_module.mjs
Normal file
5
module/data/pseudo-documents/_module.mjs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import BasePseudoDocument from "./base.mjs";
|
||||||
|
|
||||||
|
export {
|
||||||
|
BasePseudoDocument
|
||||||
|
}
|
||||||
30
module/data/pseudo-documents/_types.d.ts
vendored
Normal file
30
module/data/pseudo-documents/_types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import DataModel from '@common/abstract/data.mjs';
|
||||||
|
|
||||||
|
export type PseudoDocumentMetadata = {
|
||||||
|
/* The document name of this pseudo-document. */
|
||||||
|
name: string;
|
||||||
|
/** The localization string for this pseudo-document */
|
||||||
|
label: string;
|
||||||
|
/** The font-awesome icon for this pseudo-document type */
|
||||||
|
icon: string;
|
||||||
|
/* Record of document names of pseudo-documents and the path to the collection. */
|
||||||
|
embedded: Record<string, string>;
|
||||||
|
/* The class used to render this pseudo-document. */
|
||||||
|
sheetClass?: PseudoDocumentSheet;
|
||||||
|
/* The default image used for newly created documents. */
|
||||||
|
defaultArtwork: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base data model for pseudo-documents.
|
||||||
|
*/
|
||||||
|
declare class BasePseudoDocument extends DataModel {
|
||||||
|
/** The _id which identifies this pseudo-document */
|
||||||
|
_id: string;
|
||||||
|
/** The name of this pseudo-document */
|
||||||
|
name: string;
|
||||||
|
/** An image file path which provides the artwork for this pseudo-document */
|
||||||
|
img: string;
|
||||||
|
/** An HTML text description for this pseudo-document */
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
206
module/data/pseudo-documents/base.mjs
Normal file
206
module/data/pseudo-documents/base.mjs
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
/** @import { PseudoDocumentMetadata, BasePseudoDocument } from "./_types" */
|
||||||
|
|
||||||
|
/**@implements {BasePseudoDocument}*/
|
||||||
|
export default class BasePseudoDocument extends foundry.abstract.DataModel {
|
||||||
|
/**
|
||||||
|
* Pseudo-document metadata.
|
||||||
|
* @type {PseudoDocumentMetadata}
|
||||||
|
*/
|
||||||
|
static get metadata() {
|
||||||
|
return {
|
||||||
|
name: null,
|
||||||
|
label: "",
|
||||||
|
icon: "",
|
||||||
|
embedded: {},
|
||||||
|
sheetClass: null,
|
||||||
|
defaultArtwork: foundry.documents.Item.DEFAULT_ICON,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @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 {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.invalid] Retrieve an invalid pseudo-document?
|
||||||
|
* @param {boolean} [options.strinct] Throw an error if the embedded pseudo-document does not exist?
|
||||||
|
* @returns {PseudoDocument|null}
|
||||||
|
*/
|
||||||
|
getEmbeddedDocument(embeddedName, id, { invalid = false, strict = false } = {}) {
|
||||||
|
const embeds = this.constructor.metadata.embedded ?? {};
|
||||||
|
if (embeddedName in embeds) {
|
||||||
|
const path = embeds[embeddedName];
|
||||||
|
return foundry.utils.getProperty(this, path).get(id, { invalid, 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<foundry.abstract.Document>} 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 } };
|
||||||
|
return parent.update(update, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete this pseudo-document.
|
||||||
|
* @param {object} [operation] The context of the operation.
|
||||||
|
* @returns {Promise<foundry.abstract.Document>} 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<foundry.abstract.Document>} 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<foundry.abstract.Document>} 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
export default class DhpItem extends Item {
|
export default class DhpItem extends Item {
|
||||||
prepareData() {
|
/** @inheritdoc */
|
||||||
super.prepareData();
|
getEmbeddedDocument(embeddedName, id, { invalid = false, strict = false } = {}) {
|
||||||
|
const systemEmbeds = this.system.constructor.metadata.embedded ?? {};
|
||||||
|
if (embeddedName in systemEmbeds) {
|
||||||
|
const path = `system.${systemEmbeds[embeddedName]}.${id}`;
|
||||||
|
return foundry.utils.getProperty(this, path) ?? null;
|
||||||
|
}
|
||||||
|
return super.getEmbeddedDocument(embeddedName, id, { invalid, strict });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -27,10 +33,6 @@ export default class DhpItem extends Item {
|
||||||
return ['weapon', 'armor', 'miscellaneous', 'consumable'].includes(this.type);
|
return ['weapon', 'armor', 'miscellaneous', 'consumable'].includes(this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUpdate(data, options, userId) {
|
|
||||||
super._onUpdate(data, options, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async createDialog(data = {}, { parent = null, pack = null, ...options } = {}) {
|
static async createDialog(data = {}, { parent = null, pack = null, ...options } = {}) {
|
||||||
const documentName = this.metadata.name;
|
const documentName = this.metadata.name;
|
||||||
const types = game.documentTypes[documentName].filter(t => t !== CONST.BASE_DOCUMENT_TYPE);
|
const types = game.documentTypes[documentName].filter(t => t !== CONST.BASE_DOCUMENT_TYPE);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue