mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-15 05:01:08 +01:00
FEAT: add TypedPseudoDocument
REFACTOR: PreudoDocument FIX: Typos Bug
This commit is contained in:
parent
a0b0411a48
commit
88bf26dad0
21 changed files with 559 additions and 447 deletions
1
daggerheart.d.ts
vendored
1
daggerheart.d.ts
vendored
|
|
@ -1,4 +1,3 @@
|
||||||
import './module/_types';
|
|
||||||
import '@client/global.mjs';
|
import '@client/global.mjs';
|
||||||
import Canvas from '@client/canvas/board.mjs';
|
import Canvas from '@client/canvas/board.mjs';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ globalThis.SYSTEM = SYSTEM;
|
||||||
|
|
||||||
Hooks.once('init', () => {
|
Hooks.once('init', () => {
|
||||||
CONFIG.daggerheart = SYSTEM;
|
CONFIG.daggerheart = SYSTEM;
|
||||||
|
|
||||||
game.system.api = {
|
game.system.api = {
|
||||||
applications,
|
applications,
|
||||||
models,
|
models,
|
||||||
|
|
|
||||||
1
module/_types.d.ts
vendored
1
module/_types.d.ts
vendored
|
|
@ -1 +0,0 @@
|
||||||
import './data/pseudo-documents/_types';
|
|
||||||
7
module/config/pseudoConfig.mjs
Normal file
7
module/config/pseudoConfig.mjs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { pseudoDocuments } from "../data/_module.mjs";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
feature: {
|
||||||
|
weapon: pseudoDocuments.feature.WeaponFeature,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -5,6 +5,7 @@ import * as ITEM from './itemConfig.mjs';
|
||||||
import * as SETTINGS from './settingsConfig.mjs';
|
import * as SETTINGS from './settingsConfig.mjs';
|
||||||
import * as EFFECTS from './effectConfig.mjs';
|
import * as EFFECTS from './effectConfig.mjs';
|
||||||
import * as ACTIONS from './actionConfig.mjs';
|
import * as ACTIONS from './actionConfig.mjs';
|
||||||
|
import pseudoDocuments from "./pseudoConfig.mjs";
|
||||||
|
|
||||||
export const SYSTEM_ID = 'daggerheart';
|
export const SYSTEM_ID = 'daggerheart';
|
||||||
|
|
||||||
|
|
@ -16,5 +17,6 @@ export const SYSTEM = {
|
||||||
ITEM,
|
ITEM,
|
||||||
SETTINGS,
|
SETTINGS,
|
||||||
EFFECTS,
|
EFFECTS,
|
||||||
ACTIONS
|
ACTIONS,
|
||||||
|
pseudoDocuments
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,5 @@ 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";
|
export * as fields from './fields/_module.mjs';
|
||||||
export * as PseudoDocuments from "./pseudo-documents/_module.mjs";
|
export * as pseudoDocuments from './pseudo-documents/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
export { default as FormulaField } from "./formulaField.mjs";
|
export { default as FormulaField } from './formulaField.mjs';
|
||||||
export { default as ForeignDocumentUUIDField } from "./foreignDocumentUUIDField.mjs";
|
export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs';
|
||||||
export { default as PseudoDocumentsField } from "./pseudoDocumentsField.mjs";
|
export { default as PseudoDocumentsField } from './pseudoDocumentsField.mjs';
|
||||||
|
|
|
||||||
|
|
@ -16,78 +16,78 @@
|
||||||
* Special case StringField which represents a formula.
|
* Special case StringField which represents a formula.
|
||||||
*/
|
*/
|
||||||
export default class FormulaField extends foundry.data.fields.StringField {
|
export default class FormulaField extends foundry.data.fields.StringField {
|
||||||
|
/**
|
||||||
|
* @param {FormulaFieldOptions} [options] - Options which configure the behavior of the field
|
||||||
|
* @param {foundry.data.types.DataFieldContext} [context] - Additional context which describes the field
|
||||||
|
*/
|
||||||
|
constructor(options, context) {
|
||||||
|
super(options, context);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/** @inheritDoc */
|
||||||
* @param {FormulaFieldOptions} [options] - Options which configure the behavior of the field
|
static get _defaults() {
|
||||||
* @param {foundry.data.types.DataFieldContext} [context] - Additional context which describes the field
|
return foundry.utils.mergeObject(super._defaults, {
|
||||||
*/
|
deterministic: false
|
||||||
constructor(options, context) {
|
});
|
||||||
super(options, context);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritDoc */
|
/* -------------------------------------------- */
|
||||||
static get _defaults() {
|
|
||||||
return foundry.utils.mergeObject(super._defaults, {
|
|
||||||
deterministic: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @inheritDoc */
|
||||||
|
_validateType(value) {
|
||||||
|
const roll = new Roll(value.replace(/@([a-z.0-9_-]+)/gi, '1'));
|
||||||
|
roll.evaluateSync({ strict: false });
|
||||||
|
if (this.options.deterministic && !roll.isDeterministic)
|
||||||
|
throw new Error(`must not contain dice terms: ${value}`);
|
||||||
|
super._validateType(value);
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/* -------------------------------------------- */
|
||||||
_validateType(value) {
|
/* Active Effect Integration */
|
||||||
const roll = new Roll(value.replace(/@([a-z.0-9_-]+)/gi, "1"));
|
/* -------------------------------------------- */
|
||||||
roll.evaluateSync({ strict: false });
|
|
||||||
if (this.options.deterministic && !roll.isDeterministic) throw new Error(`must not contain dice terms: ${value}`);
|
|
||||||
super._validateType(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @override */
|
||||||
/* Active Effect Integration */
|
_castChangeDelta(delta) {
|
||||||
/* -------------------------------------------- */
|
return this._cast(delta).trim();
|
||||||
|
}
|
||||||
|
|
||||||
/** @override */
|
/* -------------------------------------------- */
|
||||||
_castChangeDelta(delta) {
|
|
||||||
return this._cast(delta).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @override */
|
||||||
|
_applyChangeAdd(value, delta, model, change) {
|
||||||
|
if (!value) return delta;
|
||||||
|
const operator = delta.startsWith('-') ? '-' : '+';
|
||||||
|
delta = delta.replace(/^[+-]/, '').trim();
|
||||||
|
return `${value} ${operator} ${delta}`;
|
||||||
|
}
|
||||||
|
|
||||||
/** @override */
|
/* -------------------------------------------- */
|
||||||
_applyChangeAdd(value, delta, model, change) {
|
|
||||||
if (!value) return delta;
|
|
||||||
const operator = delta.startsWith("-") ? "-" : "+";
|
|
||||||
delta = delta.replace(/^[+-]/, "").trim();
|
|
||||||
return `${value} ${operator} ${delta}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @override */
|
||||||
|
_applyChangeMultiply(value, delta, model, change) {
|
||||||
|
if (!value) return delta;
|
||||||
|
const terms = new Roll(value).terms;
|
||||||
|
if (terms.length > 1) return `(${value}) * ${delta}`;
|
||||||
|
return `${value} * ${delta}`;
|
||||||
|
}
|
||||||
|
|
||||||
/** @override */
|
/* -------------------------------------------- */
|
||||||
_applyChangeMultiply(value, delta, model, change) {
|
|
||||||
if (!value) return delta;
|
|
||||||
const terms = new Roll(value).terms;
|
|
||||||
if (terms.length > 1) return `(${value}) * ${delta}`;
|
|
||||||
return `${value} * ${delta}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @override */
|
||||||
|
_applyChangeUpgrade(value, delta, model, change) {
|
||||||
|
if (!value) return delta;
|
||||||
|
const terms = new Roll(value).terms;
|
||||||
|
if (terms.length === 1 && terms[0].fn === 'max') return value.replace(/\)$/, `, ${delta})`);
|
||||||
|
return `max(${value}, ${delta})`;
|
||||||
|
}
|
||||||
|
|
||||||
/** @override */
|
/* -------------------------------------------- */
|
||||||
_applyChangeUpgrade(value, delta, model, change) {
|
|
||||||
if (!value) return delta;
|
|
||||||
const terms = new Roll(value).terms;
|
|
||||||
if ((terms.length === 1) && (terms[0].fn === "max")) return value.replace(/\)$/, `, ${delta})`);
|
|
||||||
return `max(${value}, ${delta})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @override */
|
||||||
|
_applyChangeDowngrade(value, delta, model, change) {
|
||||||
/** @override */
|
if (!value) return delta;
|
||||||
_applyChangeDowngrade(value, delta, model, change) {
|
const terms = new Roll(value).terms;
|
||||||
if (!value) return delta;
|
if (terms.length === 1 && terms[0].fn === 'min') return value.replace(/\)$/, `, ${delta})`);
|
||||||
const terms = new Roll(value).terms;
|
return `min(${value}, ${delta})`;
|
||||||
if ((terms.length === 1) && (terms[0].fn === "min")) return value.replace(/\)$/, `, ${delta})`);
|
}
|
||||||
return `min(${value}, ${delta})`;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,44 @@
|
||||||
import { BasePseudoDocument } from "../pseudo-documents/_types";
|
import PseudoDocument from '../pseudo-documents/base/pseudoDocument.mjs';
|
||||||
export default class PseudoDocumentsField extends foundry.data.fields.TypedObjectField {
|
|
||||||
|
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 = {}) {
|
constructor(model, options = {}, context = {}) {
|
||||||
options.validateKey ||= ((key) => foundry.data.validators.isValidId(key));
|
options.validateKey ||= key => foundry.data.validators.isValidId(key);
|
||||||
if (!(model instanceof BasePseudoDocument)) throw new Error("The model must be a PseudoDocument");
|
if (!foundry.utils.isSubclass(model, PseudoDocument)) throw new Error('The model must be a PseudoDocument');
|
||||||
const field = new foundry.data.fields.EmbeddedDataField(model);
|
|
||||||
|
const allTypes = foundry.utils.duplicate(model.TYPES);
|
||||||
|
options.validTypes ??= Object.keys(allTypes);
|
||||||
|
const filteredTypes = {};
|
||||||
|
for (const typeName of options.validTypes) {
|
||||||
|
if (typeName in allTypes) {
|
||||||
|
filteredTypes[typeName] = allTypes[typeName];
|
||||||
|
} else {
|
||||||
|
console.warn(`Document type "${typeName}" is not found in model.TYPES`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const field = new TypedSchemaField(filteredTypes);
|
||||||
super(field, options, context);
|
super(field, options, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static get _defaults() {
|
static get _defaults() {
|
||||||
return Object.assign(super._defaults, {
|
return Object.assign(super._defaults, {
|
||||||
max: Infinity
|
max: Infinity,
|
||||||
|
validTypes: []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,4 +47,12 @@ export default class PseudoDocumentsField extends foundry.data.fields.TypedObjec
|
||||||
if (Object.keys(value).length > this.max) throw new Error(`cannot have more than ${this.max} elements`);
|
if (Object.keys(value).length > this.max) throw new Error(`cannot have more than ${this.max} elements`);
|
||||||
return super._validateType(value, options);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,19 @@
|
||||||
import BaseDataItem from "./base.mjs";
|
import BaseDataItem from './base.mjs';
|
||||||
import FormulaField from "../fields/formulaField.mjs";
|
import FormulaField from '../fields/formulaField.mjs';
|
||||||
|
import PseudoDocumentsField from '../fields/pseudoDocumentsField.mjs';
|
||||||
|
import BaseFeatureData from '../pseudo-documents/feature/baseFeatureData.mjs';
|
||||||
|
|
||||||
export default class DHWeapon extends BaseDataItem {
|
export default class DHWeapon extends BaseDataItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static get metadata() {
|
static get metadata() {
|
||||||
return foundry.utils.mergeObject(super.metadata, {
|
return foundry.utils.mergeObject(super.metadata, {
|
||||||
label: "TYPES.Item.weapon",
|
label: 'TYPES.Item.weapon',
|
||||||
type: "weapon",
|
type: 'weapon',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
isQuantifiable: true,
|
isQuantifiable: true,
|
||||||
|
embedded: {
|
||||||
|
feature: 'featureTest'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,6 +40,12 @@ export default class DHWeapon extends BaseDataItem {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
feature: new fields.StringField({ choices: SYSTEM.ITEM.weaponFeatures, blank: true }),
|
feature: new fields.StringField({ choices: SYSTEM.ITEM.weaponFeatures, blank: true }),
|
||||||
|
featureTest: new PseudoDocumentsField(BaseFeatureData, {
|
||||||
|
required: true,
|
||||||
|
nullable: true,
|
||||||
|
max: 1,
|
||||||
|
validTypes: ['weapon']
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,2 @@
|
||||||
import BasePseudoDocument from "./base.mjs";
|
export { default as base } from './base/pseudoDocument.mjs';
|
||||||
import PseudoDocument from "./pseudoDocument.mjs";
|
export * as feature from './feature/_module.mjs';
|
||||||
|
|
||||||
export {
|
|
||||||
BasePseudoDocument,
|
|
||||||
PseudoDocument
|
|
||||||
}
|
|
||||||
|
|
|
||||||
36
module/data/pseudo-documents/_types.d.ts
vendored
36
module/data/pseudo-documents/_types.d.ts
vendored
|
|
@ -1,36 +0,0 @@
|
||||||
import ApplicationV2 from '@client/applications/api/application.mjs';
|
|
||||||
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?: ApplicationV2;
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data model for pseudo-documents.
|
|
||||||
*/
|
|
||||||
declare class PseudoDocument extends BasePseudoDocument {}
|
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
/** @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]}.${id}`;
|
|
||||||
return foundry.utils.getProperty(this, path) ?? 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
213
module/data/pseudo-documents/base/base.mjs
Normal file
213
module/data/pseudo-documents/base/base.mjs
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
/**
|
||||||
|
* @typedef {object} PseudoDocumentMetadata
|
||||||
|
* @property {string} name - The document name of this pseudo-document
|
||||||
|
* @property {Record<string, string>} 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: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @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) {
|
||||||
|
return foundry.utils.getProperty(this, embeds[embeddedName]).get(id) ?? 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
module/data/pseudo-documents/base/pseudoDocument.mjs
Normal file
48
module/data/pseudo-documents/base/pseudoDocument.mjs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import BasePseudoDocument from './base.mjs';
|
||||||
|
import SheetManagementMixin from './sheetManagementMixin.mjs';
|
||||||
|
|
||||||
|
/** @extends BasePseudoDocument */
|
||||||
|
export default class PseudoDocument extends SheetManagementMixin(BasePseudoDocument) {
|
||||||
|
static get TYPES() {
|
||||||
|
return (this._TYPES ??= Object.freeze(CONFIG.daggerheart.pseudoDocuments[this.metadata.name]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static _TYPES;
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of this shape.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
static TYPE = '';
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
120
module/data/pseudo-documents/base/sheetManagementMixin.mjs
Normal file
120
module/data/pseudo-documents/base/sheetManagementMixin.mjs
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
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 (!ApplicationV2.isPrototypeOf(cls)) {
|
||||||
|
return void ui.notifications.error(
|
||||||
|
'Daggerheart | Error on PseudoDocument | sheetClass must be ApplicationV2'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sheet = new cls({ document: this });
|
||||||
|
this._sheet = sheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Static Properties */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of apps what should be re-render.
|
||||||
|
* @type {Set<ApplicationV2>}
|
||||||
|
* @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<PseudoDocument|null>}
|
||||||
|
*/
|
||||||
|
static async createDialog(data = {}, { parent, types = null, ...options } = {}) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Present a Dialog form to confirm deletion of this PseudoDocument.
|
||||||
|
* @param {object} [options] Positioning and sizing options for the resulting dialog.
|
||||||
|
* @returns {Promise<PseudoDocument>} A Promise which resolves to the deleted PseudoDocument.
|
||||||
|
*/
|
||||||
|
async deleteDialog(options = {}) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PseudoDocumentWithSheets;
|
||||||
|
}
|
||||||
2
module/data/pseudo-documents/feature/_module.mjs
Normal file
2
module/data/pseudo-documents/feature/_module.mjs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as BaseFeatureData } from './baseFeatureData.mjs';
|
||||||
|
export { default as WeaponFeature } from './weaponFeature.mjs';
|
||||||
25
module/data/pseudo-documents/feature/baseFeatureData.mjs
Normal file
25
module/data/pseudo-documents/feature/baseFeatureData.mjs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import PseudoDocument from '../base/pseudoDocument.mjs';
|
||||||
|
|
||||||
|
export default class BaseFeatureData extends PseudoDocument {
|
||||||
|
/**@inheritdoc */
|
||||||
|
static get metadata() {
|
||||||
|
return foundry.utils.mergeObject(
|
||||||
|
super.metadata,
|
||||||
|
{
|
||||||
|
name: 'feature',
|
||||||
|
label: 'DAGGERHEART.Feature.Label',
|
||||||
|
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' })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
11
module/data/pseudo-documents/feature/weaponFeature.mjs
Normal file
11
module/data/pseudo-documents/feature/weaponFeature.mjs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import BaseFeatureData from './baseFeatureData.mjs';
|
||||||
|
|
||||||
|
export default class WeaponFeature extends BaseFeatureData {
|
||||||
|
/**@override */
|
||||||
|
static TYPE = 'weapon';
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
static get metadata() {
|
||||||
|
return foundry.utils.mergeObject(super.metadata, {}, { inplace: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
/** @import {PseudoDocument} from "./_types" */
|
|
||||||
import BasePseudoDocument from "./base.mjs";
|
|
||||||
|
|
||||||
|
|
||||||
const { ApplicationV2 } = foundry.applications.api;
|
|
||||||
|
|
||||||
/** @implements {PseudoDocument}*/
|
|
||||||
export default class PseudoDocument extends BasePseudoDocument {
|
|
||||||
/**
|
|
||||||
* 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 (!ApplicationV2.isPrototypeOf(cls)) {
|
|
||||||
return void ui.notifications.error("Daggerheart | Error on PseudoDocument | sheetClass must be ApplicationV2");
|
|
||||||
}
|
|
||||||
|
|
||||||
const sheet = new cls({ document: this });
|
|
||||||
this._sheet = sheet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
/* Static Properties */
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set of apps what should be re-render.
|
|
||||||
* @type {Set<ApplicationV2>}
|
|
||||||
* @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<PseudoDocument|null>}
|
|
||||||
*/
|
|
||||||
static async createDialog(data = {}, { parent, types = null, ...options } = {}) { }
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Present a Dialog form to confirm deletion of this PseudoDocument.
|
|
||||||
* @param {object} [options] Positioning and sizing options for the resulting dialog.
|
|
||||||
* @returns {Promise<PseudoDocument>} A Promise which resolves to the deleted PseudoDocument.
|
|
||||||
*/
|
|
||||||
async deleteDialog(options = {}) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,8 +3,8 @@ export default class DhpItem extends Item {
|
||||||
getEmbeddedDocument(embeddedName, id, { invalid = false, strict = false } = {}) {
|
getEmbeddedDocument(embeddedName, id, { invalid = false, strict = false } = {}) {
|
||||||
const systemEmbeds = this.system.constructor.metadata.embedded ?? {};
|
const systemEmbeds = this.system.constructor.metadata.embedded ?? {};
|
||||||
if (embeddedName in systemEmbeds) {
|
if (embeddedName in systemEmbeds) {
|
||||||
const path = `system.${systemEmbeds[embeddedName]}.${id}`;
|
const path = `system.${systemEmbeds[embeddedName]}`;
|
||||||
return foundry.utils.getProperty(this, path) ?? null;
|
return foundry.utils.getProperty(this, path).get(id) ?? null;
|
||||||
}
|
}
|
||||||
return super.getEmbeddedDocument(embeddedName, id, { invalid, strict });
|
return super.getEmbeddedDocument(embeddedName, id, { invalid, strict });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue