178 lines
6.4 KiB
JavaScript
178 lines
6.4 KiB
JavaScript
// portrait-dialog.mjs — Portrait Picker dialog (Foundry v11/v12/v13 compatible)
|
|
|
|
import { MODULE_ID } from './settings.mjs';
|
|
|
|
// The categories available in the picker UI, with display labels.
|
|
// Three broad aesthetic types: human | mutant | animal
|
|
const CATEGORIES = [
|
|
{ value: 'any', label: 'All Portraits (799)' },
|
|
// ── Human-presenting ──────────────────────────────
|
|
{ value: 'human_female', label: 'Human — Female (97)' },
|
|
{ value: 'human_male', label: 'Human — Male (98)' },
|
|
{ value: 'human_unknown', label: 'Human — Unknown gender (367)' },
|
|
// ── Mutant / altered humans ────────────────────────
|
|
{ value: 'mutant_female', label: 'Mutant — Female (41)' },
|
|
{ value: 'mutant_male', label: 'Mutant — Male (41)' },
|
|
{ value: 'mutant_unknown', label: 'Mutant — Unknown gender (57)' },
|
|
// ── Non-human / alien ─────────────────────────────
|
|
{ value: 'animal_female', label: 'Animal / Alien — Female (38)' },
|
|
{ value: 'animal_male', label: 'Animal / Alien — Male (36)' },
|
|
{ value: 'animal_unknown', label: 'Animal / Alien — Unknown (24)' },
|
|
];
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Foundry v13 uses ApplicationV2; v11/v12 uses FormApplication.
|
|
// We detect which is available and subclass accordingly.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function buildPickerClass() {
|
|
// Foundry v13+ — ApplicationV2 available
|
|
if (foundry.applications?.api?.ApplicationV2) {
|
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
|
|
|
return class PortraitPickerDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|
|
|
constructor(actor, manager, opts = {}) {
|
|
super(opts);
|
|
this.actor = actor;
|
|
this.manager = manager;
|
|
this._selectedCategory = game.settings.get(MODULE_ID, 'defaultCategory') ?? 'any';
|
|
}
|
|
|
|
static DEFAULT_OPTIONS = {
|
|
id: 'npc-portrait-picker',
|
|
tag: 'form',
|
|
classes: ['npc-portrait-pack', 'portrait-picker'],
|
|
window: {
|
|
title: 'NPC Portrait Picker',
|
|
resizable: true,
|
|
},
|
|
position: { width: 720, height: 580 },
|
|
actions: {
|
|
selectPortrait: PortraitPickerDialog._onSelectPortrait,
|
|
randomPortrait: PortraitPickerDialog._onRandomPortrait,
|
|
},
|
|
};
|
|
|
|
static PARTS = {
|
|
form: {
|
|
template: `modules/${MODULE_ID}/templates/portrait-picker.hbs`,
|
|
}
|
|
};
|
|
|
|
async _prepareContext(_opts) {
|
|
const portraits = this.manager.filterPortraits({ category: this._selectedCategory });
|
|
return {
|
|
actor: this.actor,
|
|
portraits,
|
|
categories: CATEGORIES,
|
|
selectedCategory: this._selectedCategory,
|
|
};
|
|
}
|
|
|
|
_onRender(context, opts) {
|
|
super._onRender?.(context, opts);
|
|
// Category dropdown change
|
|
this.element.querySelector('.category-filter')?.addEventListener('change', async (ev) => {
|
|
this._selectedCategory = ev.currentTarget.value;
|
|
await this.render();
|
|
});
|
|
}
|
|
|
|
static async _onSelectPortrait(event, target) {
|
|
const path = target.dataset.path;
|
|
await this._applyPortrait(path);
|
|
}
|
|
|
|
static async _onRandomPortrait(_event, _target) {
|
|
const portraits = this.manager.filterPortraits({ category: this._selectedCategory });
|
|
if (!portraits.length) return;
|
|
const chosen = portraits[Math.floor(Math.random() * portraits.length)];
|
|
await this._applyPortrait(chosen.path);
|
|
}
|
|
|
|
async _applyPortrait(path) {
|
|
const setAvatar = game.settings.get(MODULE_ID, 'setAvatar');
|
|
const updateData = { 'prototypeToken.texture.src': path };
|
|
if (setAvatar) updateData.img = path;
|
|
await this.actor.update(updateData);
|
|
ui.notifications.info(
|
|
game.i18n.format('NPC_PORTRAIT_PACK.Notifications.Assigned', { actorName: this.actor.name })
|
|
);
|
|
this.close();
|
|
}
|
|
};
|
|
}
|
|
|
|
// Foundry v11/v12 — FormApplication fallback
|
|
return class PortraitPickerDialog extends FormApplication {
|
|
|
|
constructor(actor, manager) {
|
|
super();
|
|
this.actor = actor;
|
|
this.manager = manager;
|
|
this._category = game.settings.get(MODULE_ID, 'defaultCategory') ?? 'any';
|
|
}
|
|
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
id: 'npc-portrait-picker',
|
|
title: 'NPC Portrait Picker',
|
|
template: `modules/${MODULE_ID}/templates/portrait-picker.hbs`,
|
|
width: 720,
|
|
height: 580,
|
|
resizable: true,
|
|
classes: ['npc-portrait-pack', 'portrait-picker'],
|
|
});
|
|
}
|
|
|
|
getData() {
|
|
const portraits = this.manager.filterPortraits({ category: this._category });
|
|
return {
|
|
actor: this.actor,
|
|
portraits,
|
|
categories: CATEGORIES,
|
|
selectedCategory: this._category,
|
|
};
|
|
}
|
|
|
|
activateListeners(html) {
|
|
super.activateListeners(html);
|
|
|
|
html.find('.portrait-thumb').on('click', async (ev) => {
|
|
const path = ev.currentTarget.dataset.path;
|
|
await this._applyPortrait(path);
|
|
});
|
|
|
|
html.find('.btn-random').on('click', async () => {
|
|
const portraits = this.manager.filterPortraits({ category: this._category });
|
|
if (!portraits.length) return;
|
|
const chosen = portraits[Math.floor(Math.random() * portraits.length)];
|
|
await this._applyPortrait(chosen.path);
|
|
});
|
|
|
|
html.find('.category-filter').on('change', (ev) => {
|
|
this._category = ev.currentTarget.value;
|
|
this.render(true);
|
|
});
|
|
}
|
|
|
|
async _applyPortrait(path) {
|
|
const setAvatar = game.settings.get(MODULE_ID, 'setAvatar');
|
|
const updateData = { 'prototypeToken.texture.src': path };
|
|
if (setAvatar) updateData.img = path;
|
|
await this.actor.update(updateData);
|
|
ui.notifications.info(
|
|
game.i18n.format('NPC_PORTRAIT_PACK.Notifications.Assigned', { actorName: this.actor.name })
|
|
);
|
|
this.close();
|
|
}
|
|
|
|
async _updateObject(_event, _formData) {
|
|
// Handled by activateListeners
|
|
}
|
|
};
|
|
}
|
|
|
|
export const PortraitPickerDialog = buildPickerClass();
|