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) {
|
|
console.log('TVA-Mapper: Starting Image caching.');
|
|
await cacheImages();
|
|
console.log('TVA-Mapper: Caching finished.');
|
|
}
|
|
|
|
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;
|
|
let consoleProcessedTracking = 0;
|
|
if (compendium.documentName === 'Actor') {
|
|
processItem = async function (item) {
|
|
const actor = item;
|
|
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);
|
|
}
|
|
}
|
|
|
|
consoleProcessedTracking++;
|
|
if (consoleProcessedTracking % 100 === 0)
|
|
console.log(`TVA-Mapper: Processed ${consoleProcessedTracking} ${compendium.documentName}s`);
|
|
};
|
|
} else {
|
|
processItem = async function (item) {
|
|
const doc = item;
|
|
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);
|
|
},
|
|
});
|
|
}
|
|
|
|
consoleProcessedTracking++;
|
|
if (consoleProcessedTracking % 100 === 0)
|
|
console.log(`TVA-Mapper: Processed ${consoleProcessedTracking} ${compendium.documentName}s`);
|
|
};
|
|
}
|
|
|
|
console.log(`TVA-Mapper: Starting Batch ${compendium.documentName} load.`);
|
|
const documents = await compendium.getDocuments();
|
|
console.log(`TVA-Mapper: Load finished. Beginning processing.`);
|
|
|
|
if (formData.autoApply) {
|
|
let processing = true;
|
|
let stopProcessing = false;
|
|
let processed = 0;
|
|
let counter = $(`<p>CACHING 0/${documents.length}</p>`);
|
|
let d;
|
|
|
|
const startProcessing = async function () {
|
|
while (processing && processed < documents.length) {
|
|
await new Promise((resolve, reject) => {
|
|
setTimeout(async () => {
|
|
await processItem(documents[processed]);
|
|
resolve();
|
|
}, 10);
|
|
});
|
|
processed++;
|
|
counter.html(`${processed}/${documents.length}`);
|
|
}
|
|
if (stopProcessing || processed === documents.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 = documents.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);
|
|
}
|
|
}
|
|
}
|