Skip to content

Commit

Permalink
Merge pull request #2 from League-of-Foundry-Developers:break-actors-…
Browse files Browse the repository at this point in the history
…and-items

Add antificial types to Actor and Item
  • Loading branch information
zeel01 authored Aug 19, 2021
2 parents abc40bd + 68ccaf9 commit 2a1fe21
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 24 deletions.
94 changes: 91 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,55 @@ Once this library is active (it should be activated at the start of the `init` h
- Playlist
- Scene
- User
- ~~Folder~~ (excluded for complexity)
- Folder

### Hooks

This library provides two hooks:
```js
Hooks.on("preDocumentSheetRegistrarInit", (settings) => {});
Hooks.on("documentSheetRegistrarInit", (documentTypes) => {});
```

The `preDocumentSheetRegistrarInit` hook passes an object of boolean "settings", you must set the setting corresponding to the document type that you wish to register a sheet for to `true`. If you do not do this, the registration method will not be created which will produce an error when you call it.

The `documentSheetRegistrarInit` hook indicates that the initialization process has been completed, and it is now safe to register your sheets. This hook also passes an object of data about any documents for which this library has been enabled.

### Settings

From the `preDocumentSheetRegistrarInit` hook you can choose to enable this library for particular document types. The hook passes a `settings` parameter which is an object like so:

```js
{
Actor: false
Folder: false
Item: false
JournalEntry: false
Macro: false
Playlist: false
RollTable: false
Scene: false
User: false
}
```

You need only to set the appropriate value to `true` for the document type you wish to register a sheet for.

Example:

```js
Hooks.on("preDocumentSheetRegistrarInit", (settings) => {
settings["JournalEntry"] = true;
});
```

This will enable `Journal.registerSheet`.

### `DocType.registerSheet`

Register a sheet class as a candidate which can be used to display Journal Entries.
Register a sheet class as a candidate which can be used to display this document.

You must enable your chosen document type in the `preDocumentSheetRegistrarInit` hook for this method to be available.

#### Parameters

Expand All @@ -44,7 +88,7 @@ Register a sheet class as a candidate which can be used to display Journal Entri
| sheetClass | `Application` | A defined Application class used to render the sheet |   |
| options | `Object` | Additional options used for sheet registration |   |
| options.label | `string` | A human readable label for the sheet name, which will be localized | *Optional* |
| options.types | `Array.<string>` | An array of entity types for which this sheet should be used | *Optional* |
| options.types | `Array.<string>` | An array of entity types for which this sheet should be used. When not specified, all types will be used. That does *not* include artificial types, if you are using artificial types you must specify them here. | *Optional* |
| options.makeDefault | `boolean` | Whether to make this sheet the default for provided types | *Optional* |

#### Examples
Expand All @@ -58,8 +102,11 @@ Journal.registerSheet?.("myModule", SheetApplicationClass, {
```
### `DocType.unregisterSheet`
Unregister a Journal Entry sheet class, removing it from the list of available Applications to use for Journal Entries.
You must enable your chosen document type in the `preDocumentSheetRegistrarInit` hook for this method to be available.
#### Parameters
| Name | Type | Description | |
Expand All @@ -86,3 +133,44 @@ It can be set in [any of the ways a flag can be set](https://foundryvtt.wiki/en/
```js
someJournalEntry.setFlag('core', 'sheetClass', 'my-module.MyModuleSheetClassName');
```
### Types
This library introduces the ability to give documents "types" even for documents that did not support types before. The `object.type` property is supported on many documents in Core such as Actor (character, npc, vehicle), Item, Macro (script, chat), and others. This allows a document to have type-specific sheets such as NPC sheets vs. character sheets. With Document Sheet Registrar, we can add "artificial" types to any of the following nine do documents:
- Actor
- Item
- JournalEntry
- RollTable
- Macro
- Playlist
- Scene
- User
- Folder
There are two steps to adding a new artificial type. First, you must register a new sheet and pass your custom type as part of the `types` array:
```javascript
DocType.registerSheet?.("myModule", SheetApplicationClass, {
types: ["my-type"],
makeDefault: false,
label: "My document sheet"
});
```
When you register a sheet in this way, the sheet will only be available to documents with the specified type. Since the `object.data.type` property is part of the official schema of the document, we can not add this property to documents that don't already support it, or give it a custom value. Instead, DSR uses a `type` flag in the `_document-sheet-registrar` scope to specify the artificial type.
```js
document.setFlag("_document-sheet-registrar", "type", "my-type")
```
This will cause the `object.type` getter on the document to return the value stored in this flag, resulting in a different selection of sheets which are specific to that `type`.
Note that if no sheet is registered to handle a given document and type, an error will occur. When this happens with the library enabled, a UI warning is displayed. When the library is disabled, the default sheet for that document will render.
## The Sheet Config Dialog
In order to give the user control of how their documents are rendered, documents that have multiple registered sheets will now have a "⚙ sheet" button in the header of their sheet application. This button opens the same sheet dialog that is used by Actor and Item.
If for some reason you need to prevent users from modifying this, you can hide the button with CSS by targeting the `.configure-sheet` class on the element.
If it is important that documents created for your module only be rendered using sheets provided by your module, you may also want to restrict which sheets are available by setting a particular `type` for those sheets. You can specify an existing type for documents that support it, e.g. "script" Macros, or you can use the artificial types system discussed in the API section above. The sheet config dialog will only give the user the option to select a sheet that is valid for the type of the document being configured.
4 changes: 2 additions & 2 deletions module.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"name": "_document-sheet-registrar",
"title": "Lib: Document Sheet Registrar",
"description": "A library module which enables the registration of alternative document sheets for all document types that don't normally have this capacity.",
"version": "0.5.0",
"version": "0.6.1",
"library": true,
"manifestPlusVersion": "1.1.0",
"minimumCoreVersion": "0.8.8",
"compatibleCoreVersion": "0.8.8",
"compatibleCoreVersion": "0.8.9",
"languages": [
{
"lang": "en",
Expand Down
124 changes: 105 additions & 19 deletions scripts/document-sheet-registrar.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,44 @@ export default class DocumentSheetRegistrar {
*/
static get name() { return "_document-sheet-registrar"; }


/**
* A function to filter the CONFIG...Document object for only
* documents that have either the sheetClass or sheetClasses property
* and have a collection.
*
* Since these properties can be getters, it can be dangerous to run the
* getters this early in the init process. Instead, we use
* Object.getOwnPropertyDescriptors to check if the properties exist.
*
* @static
* @param {[string, object]} [key, config] - The key and config object for the document type
* @return {boolean} True if the document fits the criteria, false otherwise
* @memberof DocumentSheetRegistrar
*/
static filterDocs([key, config]) {
return (
Object.getOwnPropertyDescriptor(config, "sheetClass") ||
Object.getOwnPropertyDescriptor(config, "sheetClasses")
) && config.collection;
}


/**
* A list of booleans for each document type that indicates whether
* or not the sheet registration is enabled.
*
* @type {object<string, boolean>}
*
* @static
* @memberof DocumentSheetRegistrar
*/
static settings = Object.fromEntries(
Object.entries(CONFIG)
.filter(this.filterDocs)
.map(([key, config]) => [key, false])
);

/**
* @typedef {object} DocumentMap A map of document name, class, and collection
* @property {string} name - The name of the document
Expand All @@ -42,13 +80,14 @@ export default class DocumentSheetRegistrar {
*/
static get documentTypes() {
return Object.entries(CONFIG)
.filter(([key, config]) => config.sheetClass && config.collection)
.filter(this.filterDocs)
.map(([key, config]) => {
/** @return {DocumentMap} */
return {
name: key,
class: config.documentClass,
collection: config.collection
collection: config.collection,
enabled: this.settings[key]
}
});
}
Expand Down Expand Up @@ -89,20 +128,28 @@ export default class DocumentSheetRegistrar {


/**
* Initialize all of the document sheet registrars.
* Handles the init hook
*
* Initializes all of the document sheet registrars,
* then sets up some wrapper functions.
*
* Calls a pre-init hook to allow modules to request certain
* sheet registration options.
*
* Finally calls a post-init hook to alert modules that the
* document sheet registrar has been initialized.
*
* @static
* @memberof DocumentSheetRegistrar
*/
static initializeDocumentSheets() {
console.log(game.i18n.localize("_document-sheet-registrar.console.log.init"));
static init() {
console.log(game.i18n.localize("Document Sheet Registrar: initializing..."));

for (let doc of this.documentTypes) {
// Skip any collection that already has a sheet registration method
if (doc.collection.registerSheet) continue;
// Call settings hook for this module
Hooks.callAll("preDocumentSheetRegistrarInit", this.settings);

this.initializeDocumentSheet(doc);
}
// Initialize all of the document sheet registrars
this.initializeDocumentSheets();

// Add a sheet config event handler for header buttons on DocumentSheet
DocumentSheet.prototype._onConfigureSheet = this._onConfigureSheet;
Expand All @@ -115,6 +162,27 @@ export default class DocumentSheetRegistrar {
this.object.data.type = this.object.type;
return wrapped(...args);
}, "WRAPPER");

console.log(game.i18n.localize("Document Sheet Registrar: ...ready!"));

// Call the init hook to alert modules that the registrar is ready
Hooks.callAll("documentSheetRegistrarInit", Object.fromEntries(
this.documentTypes.filter(doc => doc.enabled).map(doc => [doc.name, doc])
));
}


/**
* Initialize all of the document sheet registrars.
*
* @static
* @memberof DocumentSheetRegistrar
*/
static initializeDocumentSheets() {
for (let doc of this.documentTypes) {
// Skip documents that aren't enabled
if (doc.enabled) this.initializeDocumentSheet(doc);
}
}


Expand Down Expand Up @@ -170,7 +238,8 @@ export default class DocumentSheetRegistrar {
* @memberof DocumentSheetRegistrar
*/
static configureSheetClasses(doc) {
CONFIG[doc.name].sheetClasses = { };
if (!CONFIG[doc.name]?.sheetClasses)
CONFIG[doc.name].sheetClasses = { };

if (doc.class.metadata.types.length) {
for (let type of doc.class.metadata.types) {
Expand All @@ -193,6 +262,9 @@ export default class DocumentSheetRegistrar {
* @memberof DocumentSheetRegistrar
*/
static configureSheetClassessByType(doc, type) {
// If this config already exists, do nothing
if (CONFIG[doc.name].sheetClasses[type]) return;

CONFIG[doc.name].sheetClasses[type] = {
[doc.name]: { // Register the default sheet
id: doc.name,
Expand Down Expand Up @@ -240,7 +312,7 @@ export default class DocumentSheetRegistrar {
* @param {Application} sheetClass A defined Application class used to render the sheet
* @param {Object} options Additional options used for sheet registration
* @param {string} [options.label] A human readable label for the sheet name, which will be localized
* @param {string[]} [options.types] An array of entity types for which this sheet should be used
* @param {string[]} [options.types] An array of entity types for which this sheet should be used. When not specified, all types will be used. That does *not* include artificial types, if you are using artificial types you must specify them here.
* @param {boolean} [options.makeDefault] Whether to make this sheet the default for provided types
*
* @example
Expand Down Expand Up @@ -283,8 +355,18 @@ export default class DocumentSheetRegistrar {
}


/*********************************************************************************************/

/*********************************************************************************************
* This section contains code copied from the Foundry core software and modified for
* this library.
*
* Foundry Virtual Tabletop © Copyright 2021, Foundry Gaming, LLC.
*
* This code is used in accordance with the Foundry Virtual Tabletop
* LIMITED LICENSE AGREEMENT FOR MODULE DEVELOPMENT.
*
* https://foundryvtt.com/article/license/
*
*********************************************************************************************/

/**
* Retrieve the sheet class for the document. @see Actor._getSheetClass
Expand Down Expand Up @@ -326,10 +408,7 @@ export default class DocumentSheetRegistrar {
*/
static updateDefaultSheets(setting = {}) {
if (!Object.keys(setting).length) return;
const documents = [
"Actor", "Item",
...DocumentSheetRegistrar.documentTypes.map(doc => doc.name)
];
const documents = DocumentSheetRegistrar.documentTypes.filter(doc => doc.enabled).map(doc => doc.name);
for (let documentName of documents) {
const cfg = CONFIG[documentName];
const classes = cfg.sheetClasses;
Expand All @@ -351,10 +430,17 @@ export default class DocumentSheetRegistrar {
}
}
}

/*********************************************************************************************
*
* END OF SECTION COPIED FROM FOUNDRY CORE SOFTWARE
*
*********************************************************************************************/
}


// On init, create the nessesary configs and methods to enable the sheet config API
Hooks.once("init", DocumentSheetRegistrar.initializeDocumentSheets.bind(DocumentSheetRegistrar));
Hooks.once("init", DocumentSheetRegistrar.init.bind(DocumentSheetRegistrar));

// When a doc sheet is rendered, add a header button for sheet configuration
Hooks.on("getDocumentSheetHeaderButtons", DocumentSheetRegistrar.getDocumentSheetHeaderButtons.bind(DocumentSheetRegistrar));

0 comments on commit 2a1fe21

Please sign in to comment.