Skip to content

Commit

Permalink
Merge pull request #2926 from League-of-Foundry-Developers/document-t…
Browse files Browse the repository at this point in the history
…ype-field-implementation

v12 Document type field implementation
  • Loading branch information
LukeAbby authored Nov 20, 2024
2 parents 2079f3d + a1057a0 commit 5a9c98c
Show file tree
Hide file tree
Showing 21 changed files with 360 additions and 382 deletions.
29 changes: 27 additions & 2 deletions src/foundry/client/game.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import type { Socket } from "socket.io-client";
import type {
ConfiguredDocumentClassForName,
ConfiguredModule,
GetKey,
ModuleRequiredOrOptional,
} from "../../types/helperTypes.d.mts";
import type { ValueOf } from "../../types/utils.d.mts";
import type { EmptyObject, ValueOf } from "../../types/utils.d.mts";
import type BasePackage from "../common/packages/base-package.d.mts";
import type { Document } from "../common/abstract/module.d.mts";

Expand Down Expand Up @@ -599,6 +600,30 @@ declare global {
get<T extends string>(id: T): (Module & ConfiguredModule<T>) | Exclude<ModuleRequiredOrOptional<T>, undefined>;
}

namespace Model {
/**
* Get the configured core and system type names for a specific document type.
* @typeParam DocumentName - the type of the Document this data is for
*/
type TypeNames<DocumentName extends Document.AnyConstructor> = string & keyof Model[DocumentName];
}

type Model = {
[DocumentType in Document.Type]: {
// The `& string` is helpful even though there should never be any numeric/symbol keys.
// This is because when `keyof Config<...>` is deferred then TypeScript does a bunch of proofs under the assumption that `SystemTypeNames` could be a `string | number` until proven otherwise.
// This causes issues where there shouldn't be, for example it has been observed to obstruct the resolution of the `Actor` class.
[SubType in
| Document.CoreTypesForName<DocumentType>
| keyof GetKey<DataModelConfig, DocumentType, unknown>
| keyof GetKey<SourceConfig, DocumentType, unknown> as SubType & string]: GetKey<
GetKey<SourceConfig, DocumentType>,
SubType,
EmptyObject
>;
};
};

type Data = {
activeUsers: string[];
addresses: {
Expand Down Expand Up @@ -661,7 +686,7 @@ declare global {
documentTypes: Record<foundry.CONST.DOCUMENT_TYPES, string[]>;
// TODO: This is also inheriting the configured types,
// but is only filled in if there's `template.json`
model: Record<foundry.CONST.DOCUMENT_TYPES, Record<string, object>>;
model: Model;
userId: string;
world: World["_source"];
} & {
Expand Down
20 changes: 17 additions & 3 deletions src/foundry/common/abstract/document.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ declare abstract class Document<

/**
* The allowed types which may exist for this Document class
* @remarks Document.TYPES is overly generic so subclasses don't cause problems
*/
static get TYPES(): string[];

Expand Down Expand Up @@ -961,8 +962,20 @@ declare namespace Document {
| "Token"
| "Wall";

type CoreTypesForName<Name extends Type> = string &
GetKey<Document.Internal.MetadataFor<ConfiguredInstanceForName<Name>>, "coreTypes", ["base"]>[number];

// TODO: Probably a way to auto-determine this
type SystemType = "Actor" | "Card" | "Cards" | "Item" | "JournalEntryPage";
type SystemType =
| "ActiveEffect"
| "Actor"
| "Card"
| "Cards"
| "ChatMessage"
| "Combat"
| "Combatant"
| "Item"
| "JournalEntryPage";

type EmbeddableNamesFor<ConcreteDocument extends Document.Internal.Instance.Any> = {
[K in keyof ConfiguredDocuments]: IsParentOf<ConcreteDocument, InstanceType<ConfiguredDocuments[K]>> extends true
Expand Down Expand Up @@ -1031,7 +1044,7 @@ declare namespace Document {
foundry.data.fields.SchemaField.InnerAssignmentType<Schema>;

type SystemConstructor = AnyConstructor & {
metadata: { name: SystemType; coreTypes?: readonly string[] | undefined };
metadata: { name: SystemType };
};

type ConfiguredClass<T extends { metadata: Metadata.Any }> = ConfiguredClassForName<T["metadata"]["name"]>;
Expand Down Expand Up @@ -1286,7 +1299,7 @@ declare namespace Document {
indexed?: boolean | undefined;
compendiumIndexFields?: string[] | undefined;
label: string;
coreTypes?: readonly string[] | undefined;
coreTypes: readonly string[];
embedded: Record<string, string>;
permissions: {
create:
Expand Down Expand Up @@ -1320,6 +1333,7 @@ declare namespace Document {
name: "Document";
collection: "documents";
label: "DOCUMENT.Document";
coreTypes: [typeof foundry.CONST.BASE_DOCUMENT_TYPE];
types: [];
embedded: EmptyObject;
hasSystemData: false;
Expand Down
19 changes: 7 additions & 12 deletions src/foundry/common/data/fields.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2790,7 +2790,7 @@ declare namespace ForeignDocumentField {
ConcreteDocument extends Document.AnyConstructor,
Opts extends Options,
> = DataField.DerivedInitializedType<
Opts["idOnly"] extends true ? string : Document.ToConfiguredClass<ConcreteDocument>,
Opts["idOnly"] extends true ? string : Document.ToConfiguredInstance<ConcreteDocument>,
MergedOptions<Opts>
>;

Expand Down Expand Up @@ -3902,28 +3902,23 @@ declare namespace TypeDataField {
/**
* Get the configured core and system type names for a specific document type.
* @typeParam DocumentType - the type of the Document this data is for
* @deprecated Use the Game.Model namespace instead of TypeDataField
*/
type TypeNames<DocumentType extends Document.SystemConstructor> =
| CoreTypeNames<DocumentType>
| SystemTypeNames<DocumentType>;
type TypeNames<DocumentType extends Document.SystemConstructor> = Game.Model.TypeNames<DocumentType>;

/**
* Get the core type names for a specific document type.
* @typeParam DocumentType - the type of the Document this data is for
* @deprecated Use `DocumentType["metadata"]["coreTypes"][number]`
*/
type CoreTypeNames<DocumentType extends Document.SystemConstructor> =
DocumentType["metadata"]["coreTypes"] extends string[]
? DocumentType["metadata"]["coreTypes"][number] | "base"
: "base";
type CoreTypeNames<DocumentType extends Document.SystemConstructor> = DocumentType["metadata"]["coreTypes"][number];

/**
* Get the configured system type names for a specific document type.
* @typeParam DocumentType - the type of the Document this system data is for
* @deprecated Use the Game.Model namespace instead of TypeDataField
*/
// The `& string` is helpful even though there should never be any numeric/symbol keys.
// This is because when `keyof Config<...>` is deferred then TypeScript does a bunch of proofs under the assumption that `SystemTypeNames` could be a `string | number` until proven otherwise.
// This causes issues where there shouldn't be, for example it has been observed to obstruct the resolution of the `Actor` class.
type SystemTypeNames<DocumentType extends Document.SystemConstructor> = keyof Config<DocumentType> & string;
type SystemTypeNames<DocumentType extends Document.SystemConstructor> = Game.Model.SystemTypeNames<DocumentType["metadata"]["name"]>;

/**
* A shorthand for the assignment type of a TypeDataField class.
Expand Down
57 changes: 38 additions & 19 deletions src/foundry/common/documents/active-effect.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type * as fields from "../data/fields.d.mts";
import type * as documents from "./_module.mts";

/**
* The data schema for an ActiveEffect document.
* The ActiveEffect Document.
*/
// Note(LukeAbby): You may wonder why documents don't simply pass the `Parent` generic parameter.
// This pattern evolved from trying to avoid circular loops and even internal tsc errors.
Expand Down Expand Up @@ -44,27 +44,38 @@ declare class BaseActiveEffect extends Document<BaseActiveEffect.Schema, BaseAct
* For type simplicity it is left off. These methods historically have been the source of a large amount of computation from tsc.
*/

protected override _initialize(options?: any): void;

static override migrateData(source: AnyObject): AnyObject;

/**
* @deprecated since v11, will be removed in v13
* @remarks Replaced by name
* @remarks Replaced by `name`
*/
get label(): this["name"];

set label(value);

/**
* @deprecated since v12, will be removed in v14
* @remarks Replaced by `img`
*/
get label(): string;
get icon(): this["img"];

set icon(value);
}

export default BaseActiveEffect;

declare namespace BaseActiveEffect {
type Parent = Actor.ConfiguredInstance | Item.ConfiguredInstance | null;

type TypeNames = Game.Model.TypeNames<typeof BaseActiveEffect>;

type Metadata = Merge<
Document.Metadata.Default,
{
name: "ActiveEffect";
collection: "effects";
hasTypeData: true;
label: string;
labelPlural: string;
schemaVersion: string;
Expand All @@ -84,6 +95,22 @@ declare namespace BaseActiveEffect {
*/
_id: fields.DocumentIdField;

/**
* The name of the ActiveEffect
* @defaultValue `""`
*/
name: fields.StringField<{ required: true; label: "EFFECT.Label" }>;

/**
* An image path used to depict the ActiveEffect as an icon
* @defaultValue `null`
*/
img: fields.FilePathField<{ categories: "IMAGE"[]; label: "EFFECT.Image" }>;

type: fields.DocumentTypeField<typeof BaseActiveEffect, { initial: typeof foundry.CONST.BASE_DOCUMENT_TYPE }>;

system: fields.TypeDataField<typeof BaseActiveEffect>;

/**
* The array of EffectChangeData objects which the ActiveEffect applies
* @defaultValue `[]`
Expand Down Expand Up @@ -180,18 +207,6 @@ declare namespace BaseActiveEffect {
*/
description: fields.HTMLField<{ label: "EFFECT.Description"; textSearch: true }>;

/**
* An icon image path used to depict the ActiveEffect
* @defaultValue `null`
*/
icon: fields.FilePathField<{ categories: "IMAGE"[]; label: "EFFECT.Icon" }>;

/**
* The name of the ActiveEffect
* @defaultValue `""`
*/
name: fields.StringField<{ required: true; label: "EFFECT.Label" }>;

/**
* A UUID reference to the document from which this ActiveEffect originated
* @defaultValue `null`
Expand All @@ -200,9 +215,9 @@ declare namespace BaseActiveEffect {

/**
* A color string which applies a tint to the ActiveEffect icon
* @defaultValue `null`
* @defaultValue `"#ffffff"`
*/
tint: fields.ColorField<{ label: "EFFECT.IconTint" }>;
tint: fields.ColorField<{ nullable: false; initial: "#ffffff"; label: "EFFECT.IconTint" }>;

/**
* Does this ActiveEffect automatically transfer from an Item to an Actor?
Expand All @@ -216,11 +231,15 @@ declare namespace BaseActiveEffect {
*/
statuses: fields.SetField<fields.StringField<{ required: true; blank: false }>>;

sort: fields.IntegerSortField;

/**
* An object of optional key/value flags
* @defaultValue `{}`
*/
flags: fields.ObjectField.FlagsField<"ActiveEffect", InterfaceToObject<CoreFlags>>;

_stats: fields.DocumentStatsField;
}

interface CoreFlags {
Expand Down
2 changes: 1 addition & 1 deletion src/foundry/common/documents/actor-delta.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ declare namespace BaseActorDelta {
type Parent = TokenDocument.ConfiguredInstance | null;

// Note that in places like CONFIG the only eligible type is "base"
type TypeNames = fields.TypeDataField.TypeNames<typeof documents.BaseActor>;
type TypeNames = fields.TypeDataField.TypeNames<"Actor">;
type SchemaField = fields.SchemaField<Schema>;
type ConstructorData = fields.SchemaField.InnerConstructorType<Schema>;
type UpdateData = fields.SchemaField.InnerAssignmentType<Schema>;
Expand Down
Loading

0 comments on commit 5a9c98c

Please sign in to comment.