Skip to content

Commit

Permalink
Update user config overrides for v12 (foundryvtt#14535)
Browse files Browse the repository at this point in the history
  • Loading branch information
CarlosFdez committed May 29, 2024
1 parent 35a2029 commit 49c6024
Show file tree
Hide file tree
Showing 15 changed files with 455 additions and 228 deletions.
60 changes: 54 additions & 6 deletions src/module/user/sheet.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,62 @@
import { htmlQueryAll } from "@util";
import type { UserPF2e } from "./document.ts";

/** Player-specific settings, stored as flags on each User */
export class UserConfigPF2e<TUser extends UserPF2e> extends foundry.applications.sheets.UserConfig<TUser> {
override async getData(options: DocumentSheetOptions): Promise<UserConfigData<TUser>> {
const data = await super.getData(options);
data.actors = data.actors.filter((a) => a.type !== "party");
return data;
static override PARTS = {
tabs: {
template: "templates/generic/tab-navigation.hbs",
},
// Add a new main part, which embeds the original form part
main: {
template: "systems/pf2e/templates/user/sheet.hbs",
},

...fu.deepClone(super.PARTS),
};

override tabGroups = {
primary: "core",
};

#getTabs() {
const DEFAULTS = { group: "primary" as const, active: false, cssClass: "" };
const tabs = [
{ ...DEFAULTS, id: "core", icon: "fa-solid fa-user", label: "Core" },
{ ...DEFAULTS, id: "pf2e", icon: "fa-solid fa-dice", label: "System" },
];
for (const tab of tabs) {
tab.active = this.tabGroups[tab.group] === tab.id;
tab.cssClass = tab.active ? "active" : "";
}
return tabs;
}

override get template(): string {
return "systems/pf2e/templates/user/sheet.hbs";
override async _prepareContext(options: DocumentSheetRenderOptions): Promise<UserConfigDataPF2e<TUser>> {
const data = await super._prepareContext(options);

// Remove party actors from the selection
function createAdjustedCharacterWidget(...args: unknown[]) {
const widget = data.characterWidget(...args);
for (const option of htmlQueryAll(widget, "option")) {
const actor = game.actors.get(option.value);
if (actor?.isOfType("party")) {
option.remove();
}
}
return widget;
}

return {
...data,
tabGroups: this.tabGroups,
tabs: this.#getTabs(),
characterWidget: createAdjustedCharacterWidget,
};
}
}

interface UserConfigDataPF2e<TUser extends UserPF2e> extends UserConfigData<TUser> {
tabs: Partial<ApplicationTab>[];
tabGroups: Record<string, string>;
}
107 changes: 39 additions & 68 deletions static/templates/user/sheet.hbs
Original file line number Diff line number Diff line change
@@ -1,76 +1,47 @@
<form autocomplete="off">
<div class="form-group">
<label>{{localize "PLAYERS.PlayerAvatar"}}</label>
<img class="avatar" src="{{user.avatar}}" data-edit="avatar" title="{{actor.name}}" height="64" width="64"/>
</div>
<div>
<section class="tab {{#if (eq tabGroups.primary "core")}}active{{/if}}" data-tab="core" data-group="primary">
<div data-application-part="form"></div>
</section>

<div class="form-group">
<label>{{localize "PLAYERS.PlayerColor"}}</label>
<div class="form-fields">
{{colorPicker name="color" value=user.color}}
{{!-- PF2e Settings --}}
<section class="tab {{#if (eq tabGroups.primary "pf2e")}}active{{/if}}" data-tab="pf2e" data-group="primary">
<div class="form-group">
<label for="{{user.uuid}}-showEffectPanel">{{localize "PF2E.SETTINGS.User.EffectPanel.Name"}}</label>
<div class="form-fields">
<input id="{{user.uuid}}-showEffectPanel" type="checkbox" name="flags.pf2e.settings.showEffectPanel" {{checked user.flags.pf2e.settings.showEffectPanel}} />
</div>
<p class="hint">{{localize "PF2E.SETTINGS.User.EffectPanel.Hint"}}</p>
</div>
</div>

<div class="form-group">
<label>{{localize 'PLAYERS.PlayerPronouns'}}</label>
<div class="form-fields">
<input type="text" name="pronouns" value="{{user.pronouns}}">
<div class="form-group">
<label for="{{user.uuid}}-showCheckDialogs">{{localize "PF2E.SettingsQuickChecksLabel"}}</label>
<div class="form-fields">
<input id="{{user.uuid}}-showCheckDialogs" type="checkbox" name="flags.pf2e.settings.showCheckDialogs" {{checked user.flags.pf2e.settings.showCheckDialogs}} />
</div>
<p class="hint">{{localize "PF2E.SettingsQuickChecksNotes"}}</p>
</div>
</div>

{{!-- PF2e Settings --}}
<div class="form-group">
<label>{{localize "PF2E.SETTINGS.User.EffectPanel.Name"}}</label>
<input type="checkbox" name="flags.pf2e.settings.showEffectPanel" {{checked user.flags.pf2e.settings.showEffectPanel}} />
<p class="notes">{{localize "PF2E.SETTINGS.User.EffectPanel.Hint"}}</p>
</div>

<div class="form-group">
<label>{{localize "PF2E.SettingsQuickChecksLabel"}}</label>
<input type="checkbox" name="flags.pf2e.settings.showCheckDialogs" {{checked user.flags.pf2e.settings.showCheckDialogs}} />
<p class="notes">{{localize "PF2E.SettingsQuickChecksNotes"}}</p>
</div>

<div class="form-group">
<label>{{localize "PF2E.SettingsQuickDamageLabel"}}</label>
<input type="checkbox" name="flags.pf2e.settings.showDamageDialogs" {{checked user.flags.pf2e.settings.showDamageDialogs}} />
<p class="notes">{{localize "PF2E.SettingsQuickDamageNotes"}}</p>
</div>
<div class="form-group">
<label for="{{user.uuid}}-showDamageDialogs">{{localize "PF2E.SettingsQuickDamageLabel"}}</label>
<div class="form-fields">
<input id="{{user.uuid}}-showDamageDialogs" type="checkbox" name="flags.pf2e.settings.showDamageDialogs" {{checked user.flags.pf2e.settings.showDamageDialogs}} />
</div>
<p class="hint">{{localize "PF2E.SettingsQuickDamageNotes"}}</p>
</div>

<div class="form-group">
<label>{{localize "PF2E.SETTINGS.User.MonochromeDarkvision.Name"}}</label>
<input type="checkbox" name="flags.pf2e.settings.monochromeDarkvision" {{checked user.flags.pf2e.settings.monochromeDarkvision}} />
<p class="notes">{{localize "PF2E.SETTINGS.User.MonochromeDarkvision.Hint"}}</p>
</div>
{{!-- /PF2e Settings --}}
<div class="form-group">
<label for="{{user.uuid}}-monochromeDarkvision">{{localize "PF2E.SETTINGS.User.MonochromeDarkvision.Name"}}</label>
<div class="form-fields">
<input id="{{user.uuid}}-monochromeDarkvision" type="checkbox" name="flags.pf2e.settings.monochromeDarkvision" {{checked user.flags.pf2e.settings.monochromeDarkvision}} />
</div>
<p class="hint">{{localize "PF2E.SETTINGS.User.MonochromeDarkvision.Hint"}}</p>
</div>

<div class="form-group stacked directory">
{{#if user.character}}
<label>{{localize "PLAYERS.CharSelected"}}</label>
<button type="reset" name="release">
<i class="fa-solid fa-ban"></i> {{localize "PLAYERS.CharRelease"}}
<footer class="form-footer">
<button type="submit">
<i class="fas fa-save"></i>
<label>{{localize "USER.SHEET.BUTTONS.SAVE"}}</label>
</button>
<ul id="characters" class="directory-list">
<li class="actor directory-item flexrow" data-actor-id="{{user.character.id}}">
<img class="profile actor-profile" src="{{user.character.img}}" title="{{user.character.name}}" />
<h3 class="document-name noborder"><a>{{user.character.name}}</a></h3>
</li>
</ul>
{{else}}
<label>{{localize "PLAYERS.CharSelect"}}</label>
<ul id="characters" class="directory-list">
{{#each actors}}
<li class="actor directory-item flexrow" data-actor-id="{{this.id}}">
<img class="profile actor-profile" src="{{this.img}}" title="{{this.name}}" />
<h3 class="document-name noborder"><a>{{this.name}}</a></h3>
</li>
{{/each}}
</ul>
<input type="hidden" name="character" value="" />
{{/if}}
</div>

<button type="submit">
<i class="fa-solid fa-save"></i> {{localize "PLAYERS.SaveConfig"}}
</button>
</form>
</footer>
</section>
</div>
14 changes: 10 additions & 4 deletions types/foundry/build/generate-client-base-mixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ const genClientBase = (
const tParentOrBlank = typeParamName ? "<TParent>" : "";
const tParentOrNull = typeParamName ? "TParent" : "null";
console.log(String.raw`${declareOrExportClientBase} class ${clientBaseName}${typeParams} extends foundry.documents.Base${className}${tParentOrBlank} {
protected _sheet: FormApplication<this> | null;
protected _sheet: DocumentSheet<this> | null;
/**
* A collection of Application instances which should be re-rendered whenever this document is updated.
* The keys of this object are the application ids and the values are Application instances. Each
* Application in this object will have its render method called by {@link Document#render}.
* @see {@link Document#render}
*/
apps: { [K in number]?: Application };
apps: { [K in number]?: Application | ApplicationV2 };
constructor(data: object, context?: DocumentConstructionContext<${tParentOrNull}>);
Expand Down Expand Up @@ -73,7 +73,7 @@ const genClientBase = (
get permission(): DocumentOwnershipLevel;
/** Lazily obtain a FormApplication instance used to configure this Document, or null if no sheet is available. */
get sheet(): ${hasSheet ? "FormApplication<this>" : "null"};
get sheet(): ${hasSheet ? "DocumentSheet<this> | DocumentSheetV2<this> " : "null"};
/** A Universally Unique Identifier (uuid) for this Document instance. */
get uuid(): DocumentUUID;
Expand Down Expand Up @@ -292,7 +292,7 @@ const genClientBase = (
* @param [options] Positioning and sizing options for the resulting dialog
* @return A Promise which resolves to the deleted Document
*/
deleteDialog(options?: Record<string, unknown>): Promise<this>;
deleteDialog(options?: ConfirmDialogParameters): Promise<this>;
/**
* Export document data to a JSON file which can be saved by the client and later imported into a different session.
Expand Down Expand Up @@ -470,6 +470,12 @@ const clientDocs: Record<string, { hasSheet?: boolean; isCanvasDoc?: boolean; pa
Wall: { isCanvasDoc: true },
};

// imports
console.log(
String.raw`import type { ApplicationV2, DocumentSheetV2 } from "../../../client-esm/applications/api/module.d.ts";
`,
);
for (const [className, data] of Object.entries(clientDocs)) {
genClientBase(className, data);
}
2 changes: 1 addition & 1 deletion types/foundry/client-esm/applications/api/application.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export default class ApplicationV2<
* @returns The result of HTML rendering may be implementation specific.
* Whatever value is returned here is passed to _replaceHTML
*/
_renderHTML(context: ApplicationRenderContext, options: TRenderOptions): Promise<unknown>;
protected _renderHTML(context: ApplicationRenderContext, options: TRenderOptions): Promise<unknown>;

/**
* Replace the HTML of the application with the result provided by the rendering backend.
Expand Down
139 changes: 139 additions & 0 deletions types/foundry/client-esm/applications/api/handlebars-application.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import DocumentSheetV2 from "./document-sheet.js";

/** Augment an Application class with [Handlebars](https://handlebarsjs.com) template rendering behavior. */
declare function HandlebarsApplicationMixin<
TDocument extends foundry.abstract.Document,
TConfig extends DocumentSheetConfiguration = DocumentSheetConfiguration,
TRenderOptions extends DocumentSheetRenderOptions = DocumentSheetRenderOptions,
>(
app: DocumentSheetV2<TDocument, TConfig, TRenderOptions>,
): ConstructorOf<HandlebarsApplicationDocumentSheet<TDocument, TConfig, TRenderOptions>>;

export default HandlebarsApplicationMixin;

export class HandlebarsApplicationDocumentSheet<
TDocument extends foundry.abstract.Document,
TConfig extends DocumentSheetConfiguration = DocumentSheetConfiguration,
TRenderOptions extends DocumentSheetRenderOptions = DocumentSheetRenderOptions,
> extends DocumentSheetV2<TDocument, TConfig, TRenderOptions> {
static PARTS: Record<string, unknown>;

/**
* A record of all rendered template parts.
* @returns {Record<string, HTMLElement>}
*/
get parts(): Record<string, HTMLElement>;

/**
* Render each configured application part using Handlebars templates.
* @param context Context data for the render operation
* @param options Options which configure application rendering behavior
* @returns A single rendered HTMLElement for each requested part
*/
protected override _renderHTML(
context: ApplicationRenderContext,
options: TRenderOptions & HandlebarsRenderOptions,
): Promise<Record<string, HTMLElement>>;

/**
* Prepare context that is specific to only a single rendered part.
*
* It is recommended to augment or mutate the shared context so that downstream methods like _onRender have
* visibility into the data that was used for rendering. It is acceptable to return a different context object
* rather than mutating the shared context at the expense of this transparency.
*
* @param partId The part being rendered
* @param context Shared context provided by _prepareContext
* @returns Context data for a specific part
*/
protected _preparePartContext(partId: string, context: ApplicationRenderContext): Promise<ApplicationRenderContext>;

/**
* Replace the HTML of the application with the result provided by Handlebars rendering.
* @param result The result from Handlebars template rendering
* @param content The content element into which the rendered result must be inserted
* @param options Options which configure application rendering behavior
*/
protected override _replaceHTML(
result: Record<string, HTMLElement>,
content: HTMLElement,
options: TRenderOptions & HandlebarsRenderOptions,
): void;

/**
* Prepare data used to synchronize the state of a template part.
* @param partId The id of the part being rendered
* @param newElement The new rendered HTML element for the part
* @param priorElement The prior rendered HTML element for the part
* @param state A state object which is used to synchronize after replacement
*/
protected _preSyncPartState(
partId: string,
newElement: HTMLElement,
priorElement: HTMLElement,
state: object,
): void;

/**
* Synchronize the state of a template part after it has been rendered and replaced in the DOM.
* @param partId The id of the part being rendered
* @param newElement The new rendered HTML element for the part
* @param priorElement The prior rendered HTML element for the part
* @param state A state object which is used to synchronize after replacement
* @protected
*/
protected _syncPartState(partId: string, newElement: HTMLElement, priorElement: HTMLElement, state: object): void;

/* -------------------------------------------- */
/* Event Listeners and Handlers */
/* -------------------------------------------- */

/**
* Attach event listeners to rendered template parts.
* @param partId The id of the part being rendered
* @param htmlElement The rendered HTML element for the part
* @param options Rendering options passed to the render method
*/
protected _attachPartListeners(
partId: string,
htmlElement: HTMLElement,
options: TRenderOptions & HandlebarsRenderOptions,
): void;
}

declare global {
interface HandlebarsTemplatePart {
/** The template entry-point for the part */
template: string;

/**
* A CSS id to assign to the top-level element of the rendered part.
* This id string is automatically prefixed by the application id.
*/
id?: string;

/** An array of CSS classes to apply to the top-level element of the rendered part. */
classes?: string[];

/**
* An array of templates that are required to render the part.
* If omitted, only the entry-point is inferred as required.
*/
templates?: string[];

/**
* An array of selectors within this part whose scroll positions should
* be persisted during a re-render operation. A blank string is used
* to denote that the root level of the part is scrollable.
*/
scrollable?: boolean[];

/** A registry of forms selectors and submission handlers. */
forms?: Record<string, ApplicationFormConfiguration>;
}

interface HandlebarsRenderOptions {
/** An array of named template parts to render */
parts: string[];
}
}
1 change: 1 addition & 0 deletions types/foundry/client-esm/applications/api/module.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as ApplicationV2 } from "./application.ts";
export { default as DocumentSheetV2 } from "./document-sheet.ts";
export { default as HandlebarsApplicationMixin } from "./handlebars-application.ts";
2 changes: 1 addition & 1 deletion types/foundry/client-esm/applications/module.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type ApplicationV2 from "./api/application.d.ts";

export * as sheets from "../../client/foundry/applications/sheets.ts";
export * as api from "./api/module.ts";
export * as elements from "./elements/module.ts";
export * as fields from "./forms/fields.ts";
export * as sheets from "./sheets/module.ts";

export const instances: Map<number, ApplicationV2>;

Expand Down
1 change: 1 addition & 0 deletions types/foundry/client-esm/applications/sheets/module.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as UserConfig } from "./user-config.ts";
Loading

0 comments on commit 49c6024

Please sign in to comment.