All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

460 lines
16 KiB

import { showArtSelect } from '../token-variants.mjs';
import {
BASE_IMAGE_CATEGORIES,
SEARCH_TYPE,
updateActorImage,
updateTokenImage,
userRequiresImageCache,
} from '../scripts/utils.js';
import { addToQueue, ArtSelect, renderFromQueue } from './artSelect.js';
import { getSearchOptions, TVA_CONFIG, updateSettings } from '../scripts/settings.js';
import ConfigureSettings from './configureSettings.js';
import MissingImageConfig from './missingImageConfig.js';
import { cacheImages, doImageSearch } from '../scripts/search.js';
async function autoApply(actor, image1, image2, formData, typeOverride) {
let portraitFound = formData.ignorePortrait;
let tokenFound = formData.ignoreToken;
if (formData.diffImages) {
let results = [];
if (!formData.ignorePortrait) {
results = await doImageSearch(actor.name, {
searchType: typeOverride ?? SEARCH_TYPE.PORTRAIT,
simpleResults: true,
searchOptions: formData.searchOptions,
});
if ((results ?? []).length != 0) {
portraitFound = true;
await updateActorImage(actor, results[0], false, formData.compendium);
}
}
if (!formData.ignoreToken) {
results = await doImageSearch(actor.prototypeToken.name, {
searchType: SEARCH_TYPE.TOKEN,
simpleResults: true,
searchOptions: formData.searchOptions,
});
if ((results ?? []).length != 0) {
tokenFound = true;
updateTokenImage(results[0], {
actor: actor,
pack: formData.compendium,
applyDefaultConfig: false,
});
}
}
} else {
let results = await doImageSearch(actor.name, {
searchType: typeOverride ?? SEARCH_TYPE.PORTRAIT_AND_TOKEN,
simpleResults: true,
searchOptions: formData.searchOptions,
});
if ((results ?? []).length != 0) {
portraitFound = tokenFound = true;
updateTokenImage(results[0], {
actor: actor,
actorUpdate: { img: results[0] },
pack: formData.compendium,
applyDefaultConfig: false,
});
}
}
if (!(tokenFound && portraitFound) && formData.autoDisplayArtSelect) {
addToArtSelectQueue(actor, image1, image2, formData, typeOverride);
}
}
function addToArtSelectQueue(actor, image1, image2, formData, typeOverride) {
if (formData.diffImages) {
if (!formData.ignorePortrait && !formData.ignoreToken) {
addToQueue(actor.name, {
searchType: typeOverride ?? SEARCH_TYPE.PORTRAIT,
object: actor,
preventClose: true,
image1: image1,
image2: image2,
displayMode: ArtSelect.IMAGE_DISPLAY.PORTRAIT,
searchOptions: formData.searchOptions,
callback: async function (imgSrc, _) {
await updateActorImage(actor, imgSrc);
showArtSelect(actor.prototypeToken.name, {
searchType: typeOverride ?? SEARCH_TYPE.TOKEN,
object: actor,
force: true,
image1: imgSrc,
image2: image2,
displayMode: ArtSelect.IMAGE_DISPLAY.TOKEN,
searchOptions: formData.searchOptions,
callback: (imgSrc, name) =>
updateTokenImage(imgSrc, {
actor: actor,
imgName: name,
applyDefaultConfig: false,
}),
});
},
});
} else if (formData.ignorePortrait) {
addToQueue(actor.name, {
searchType: typeOverride ?? SEARCH_TYPE.TOKEN,
object: actor,
image1: image1,
image2: image2,
displayMode: ArtSelect.IMAGE_DISPLAY.TOKEN,
searchOptions: formData.searchOptions,
callback: async function (imgSrc, name) {
updateTokenImage(imgSrc, {
actor: actor,
imgName: name,
applyDefaultConfig: false,
});
},
});
} else if (formData.ignoreToken) {
addToQueue(actor.name, {
searchType: typeOverride ?? SEARCH_TYPE.PORTRAIT,
object: actor,
image1: image1,
image2: image2,
displayMode: ArtSelect.IMAGE_DISPLAY.PORTRAIT,
searchOptions: formData.searchOptions,
callback: async function (imgSrc, name) {
await updateActorImage(actor, imgSrc);
},
});
}
} else {
addToQueue(actor.name, {
searchType: typeOverride ?? SEARCH_TYPE.PORTRAIT_AND_TOKEN,
object: actor,
image1: image1,
image2: image2,
displayMode: ArtSelect.IMAGE_DISPLAY.PORTRAIT_TOKEN,
searchOptions: formData.searchOptions,
callback: async function (imgSrc, name) {
await updateActorImage(actor, imgSrc);
updateTokenImage(imgSrc, {
actor: actor,
imgName: name,
applyDefaultConfig: false,
});
},
});
}
}
export default class CompendiumMapConfig extends FormApplication {
constructor() {
super({}, {});
this.searchOptions = deepClone(getSearchOptions());
mergeObject(this.searchOptions, deepClone(TVA_CONFIG.compendiumMapper.searchOptions));
this._fixSearchPaths();
}
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
id: 'token-variants-compendium-map-config',
classes: ['sheet'],
template: 'modules/token-variants/templates/compendiumMap.html',
resizable: false,
minimizable: false,
title: game.i18n.localize('token-variants.settings.compendium-mapper.Name'),
width: 500,
});
}
async getData(options) {
let data = super.getData(options);
data = mergeObject(data, TVA_CONFIG.compendiumMapper);
const supportedPacks = ['Actor', 'Cards', 'Item', 'Macro', 'RollTable'];
data.supportedPacks = supportedPacks.join(', ');
const packs = [];
game.packs.forEach((pack) => {
if (!pack.locked && supportedPacks.includes(pack.documentName)) {
packs.push({ title: pack.title, id: pack.collection, type: pack.documentName });
}
});
data.compendiums = packs;
data.compendium = TVA_CONFIG.compendiumMapper.compendium;
data.categories = BASE_IMAGE_CATEGORIES.concat(TVA_CONFIG.customImageCategories);
data.category = TVA_CONFIG.compendiumMapper.category;
return data;
}
/**
* @param {JQuery} html
*/
activateListeners(html) {
super.activateListeners(html);
html.find('.token-variants-override-category').change(this._onCategoryOverride).trigger('change');
html.find('.token-variants-auto-apply').change(this._onAutoApply);
html.find('.token-variants-diff-images').change(this._onDiffImages);
html.find(`.token-variants-search-options`).on('click', this._onSearchOptions.bind(this));
html.find(`.token-variants-missing-images`).on('click', this._onMissingImages.bind(this));
$(html).find('[name="compendium"]').change(this._onCompendiumSelect.bind(this)).trigger('change');
}
async _onAutoApply(event) {
$(event.target).closest('form').find('.token-variants-auto-art-select').prop('disabled', !event.target.checked);
}
async _onCategoryOverride(event) {
$(event.target).closest('form').find('.token-variants-category').prop('disabled', !event.target.checked);
}
async _onDiffImages(event) {
$(event.target).closest('form').find('.token-variants-tp-ignore').prop('disabled', !event.target.checked);
}
async _onCompendiumSelect(event) {
const compendium = game.packs.get($(event.target).val());
if (compendium) {
$(event.target)
.closest('form')
.find('.token-specific')
.css('visibility', compendium.documentName === 'Actor' ? 'visible' : 'hidden');
}
}
_fixSearchPaths() {
if (!this.searchOptions.searchPaths?.length) {
this.searchOptions.searchPaths = deepClone(TVA_CONFIG.searchPaths);
}
}
async _onSearchOptions(event) {
this._fixSearchPaths();
new ConfigureSettings(this.searchOptions, {
searchPaths: true,
searchFilters: true,
searchAlgorithm: true,
randomizer: false,
features: false,
popup: false,
permissions: false,
worldHud: false,
misc: false,
activeEffects: false,
}).render(true);
}
async _onMissingImages(event) {
new MissingImageConfig().render(true);
}
async startMapping(formData) {
if (formData.diffImages && formData.ignoreToken && formData.ignorePortrait) {
return;
}
const originalSearchPaths = TVA_CONFIG.searchPaths;
if (formData.searchOptions.searchPaths?.length) {
TVA_CONFIG.searchPaths = formData.searchOptions.searchPaths;
}
if (formData.cache || !userRequiresImageCache() || formData.searchOptions.searchPaths?.length) {
await cacheImages();
}
const endMapping = function () {
if (formData.searchOptions.searchPaths?.length) {
TVA_CONFIG.searchPaths = originalSearchPaths;
cacheImages();
}
};
const compendium = game.packs.get(formData.compendium);
let missingImageList = TVA_CONFIG.compendiumMapper.missingImages
.filter((mi) => mi.document === 'all' || mi.document === compendium.documentName)
.map((mi) => mi.image);
const typeOverride = formData.overrideCategory ? formData.category : null;
let artSelectDisplayed = false;
let processItem;
if (compendium.documentName === 'Actor') {
processItem = async function (item) {
const actor = await compendium.getDocument(item._id);
if (actor.name === '#[CF_tempEntity]') return; // Compendium Folders module's control entity
let hasPortrait = actor.img !== CONST.DEFAULT_TOKEN && !missingImageList.includes(actor.img);
let hasToken =
actor.prototypeToken.texture.src !== CONST.DEFAULT_TOKEN &&
!missingImageList.includes(actor.prototypeToken.texture.src);
if (formData.syncImages && hasPortrait !== hasToken) {
if (hasPortrait) {
await updateTokenImage(actor.img, { actor: actor, applyDefaultConfig: false });
} else {
await updateActorImage(actor, actor.prototypeToken.texture.src);
}
hasPortrait = hasToken = true;
}
let includeThisActor = !(formData.missingOnly && hasPortrait) && !formData.ignorePortrait;
let includeThisToken = !(formData.missingOnly && hasToken) && !formData.ignoreToken;
const image1 = formData.showImages ? actor.img : '';
const image2 = formData.showImages ? actor.prototypeToken.texture.src : '';
if (includeThisActor || includeThisToken) {
if (formData.autoApply) {
await autoApply(actor, image1, image2, formData, typeOverride);
} else {
artSelectDisplayed = true;
addToArtSelectQueue(actor, image1, image2, formData, typeOverride);
}
}
};
} else {
processItem = async function (item) {
const doc = await compendium.getDocument(item._id);
if (doc.name === '#[CF_tempEntity]') return; // Compendium Folders module's control entity
let defaultImg = '';
if (doc.schema.fields.img || doc.schema.fields.texture) {
defaultImg = (doc.schema.fields.img ?? doc.schema.fields.texture).initial();
}
const hasImage = doc.img != null && doc.img !== defaultImg && !missingImageList.includes(doc.img);
let imageFound = false;
if (formData.missingOnly && hasImage) return;
if (formData.autoApply) {
let results = await doImageSearch(doc.name, {
searchType: typeOverride ?? compendium.documentName,
simpleResults: true,
searchOptions: formData.searchOptions,
});
if ((results ?? []).length != 0) {
imageFound = true;
await updateActorImage(doc, results[0], false, formData.compendium);
}
}
if (!formData.autoApply || (formData.autoDisplayArtSelect && !imageFound)) {
artSelectDisplayed = true;
addToQueue(doc.name, {
searchType: typeOverride ?? compendium.documentName,
object: doc,
image1: formData.showImages ? doc.img : '',
displayMode: ArtSelect.IMAGE_DISPLAY.IMAGE,
searchOptions: formData.searchOptions,
callback: async function (imgSrc, name) {
await updateActorImage(doc, imgSrc);
},
});
}
};
}
const allItems = [];
compendium.index.forEach((k) => {
allItems.push(k);
});
if (formData.autoApply) {
let processing = true;
let stopProcessing = false;
let processed = 0;
let counter = $(`<p>CACHING 0/${allItems.length}</p>`);
let d;
const startProcessing = async function () {
while (processing && processed < allItems.length) {
await new Promise((resolve, reject) => {
setTimeout(async () => {
await processItem(allItems[processed]);
resolve();
}, 10);
});
processed++;
counter.html(`${processed}/${allItems.length}`);
}
if (stopProcessing || processed === allItems.length) {
d?.close(true);
addToQueue('DUMMY', { execute: endMapping });
renderFromQueue();
}
};
d = new Dialog({
title: `Mapping: ${compendium.title}`,
content: `
<div style="text-align:center;" class="fa-3x"><i class="fas fa-spinner fa-pulse"></i></div>
<div style="text-align:center;" class="counter"></div>
<button style="width:100%;" class="pause"><i class="fas fa-play-circle"></i> Pause/Start</button>`,
buttons: {
cancel: {
icon: '<i class="fas fa-stop-circle"></i>',
label: 'Cancel',
},
},
default: 'cancel',
render: (html) => {
html.find('.counter').append(counter);
const spinner = html.find('.fa-spinner');
html.find('.pause').on('click', () => {
if (processing) {
processing = false;
spinner.removeClass('fa-pulse');
} else {
processing = true;
startProcessing();
spinner.addClass('fa-pulse');
}
});
setTimeout(async () => startProcessing(), 1000);
},
close: () => {
if (!stopProcessing) {
stopProcessing = true;
if (!processing) startProcessing();
else processing = false;
}
},
});
d.render(true);
} else {
const tasks = allItems.map(processItem);
Promise.all(tasks).then(() => {
addToQueue('DUMMY', { execute: endMapping });
renderFromQueue();
if (formData.missingOnly && !artSelectDisplayed) {
ui.notifications.warn('Token Variant Art: No documents found containing missing images.');
}
});
}
}
/**
* @param {Event} event
* @param {Object} formData
*/
async _updateObject(event, formData) {
// If search paths are the same, remove them from searchOptions
if (
!this.searchOptions.searchPaths?.length ||
isEmpty(diffObject(this.searchOptions.searchPaths, TVA_CONFIG.searchPaths))
) {
this.searchOptions.searchPaths = [];
}
formData.searchOptions = this.searchOptions;
await updateSettings({ compendiumMapper: formData });
if (formData.compendium) {
this.startMapping(formData);
}
}
}