import { toggleTemplateOnSelected } from '../scripts/hooks/effectMappingHooks.js';
|
|
import { CORE_TEMPLATES } from '../scripts/mappingTemplates.js';
|
|
import { TVA_CONFIG, updateSettings } from '../scripts/settings.js';
|
|
import { BASE_IMAGE_CATEGORIES, uploadTokenImage } from '../scripts/utils.js';
|
|
import { sortMappingsToGroups } from './effectMappingForm.js';
|
|
import { Templates } from './templates.js';
|
|
import TokenCustomConfig from './tokenCustomConfig.js';
|
|
|
|
// Edit overlay configuration as a json string
|
|
export function showOverlayJsonConfigDialog(overlayConfig, callback) {
|
|
const config = deepClone(overlayConfig || {});
|
|
delete config.effect;
|
|
let content = `<div style="height: 300px;" class="form-group stacked command"><textarea style="height: 300px;" class="configJson">${JSON.stringify(
|
|
config,
|
|
null,
|
|
2
|
|
)}</textarea></div>`;
|
|
|
|
new Dialog({
|
|
title: `Overlay Configuration`,
|
|
content: content,
|
|
buttons: {
|
|
yes: {
|
|
icon: "<i class='fas fa-save'></i>",
|
|
label: 'Save',
|
|
callback: (html) => {
|
|
let json = $(html).find('.configJson').val();
|
|
if (json) {
|
|
try {
|
|
json = JSON.parse(json);
|
|
} catch (e) {
|
|
console.warn(`TVA |`, e);
|
|
json = {};
|
|
}
|
|
} else {
|
|
json = {};
|
|
}
|
|
callback(json);
|
|
},
|
|
},
|
|
},
|
|
default: 'yes',
|
|
}).render(true);
|
|
}
|
|
|
|
// Change categories assigned to a path
|
|
export async function showPathSelectCategoryDialog(event) {
|
|
event.preventDefault();
|
|
const typesInput = $(event.target).closest('.path-category').find('input');
|
|
const selectedTypes = typesInput.val().split(',');
|
|
|
|
const categories = BASE_IMAGE_CATEGORIES.concat(TVA_CONFIG.customImageCategories);
|
|
|
|
let content = '<div class="token-variants-popup-settings">';
|
|
|
|
// Split into rows of 4
|
|
const splits = [];
|
|
let currSplit = [];
|
|
for (let i = 0; i < categories.length; i++) {
|
|
if (i > 0 && i + 1 != categories.length && i % 4 == 0) {
|
|
splits.push(currSplit);
|
|
currSplit = [];
|
|
}
|
|
currSplit.push(categories[i]);
|
|
}
|
|
if (currSplit.length) splits.push(currSplit);
|
|
|
|
for (const split of splits) {
|
|
content += '<header class="table-header flexrow">';
|
|
for (const type of split) {
|
|
content += `<label>${type}</label>`;
|
|
}
|
|
content += '</header><ul class="setting-list"><li class="setting form-group"><div class="form-fields">';
|
|
for (const type of split) {
|
|
content += `<input class="category" type="checkbox" name="${type}" data-dtype="Boolean" ${
|
|
selectedTypes.includes(type) ? 'checked' : ''
|
|
}>`;
|
|
}
|
|
content += '</div></li></ul>';
|
|
}
|
|
content += '</div>';
|
|
|
|
new Dialog({
|
|
title: `Image Categories/Filters`,
|
|
content: content,
|
|
buttons: {
|
|
yes: {
|
|
icon: "<i class='fas fa-save'></i>",
|
|
label: 'Apply',
|
|
callback: (html) => {
|
|
const types = [];
|
|
$(html)
|
|
.find('.category')
|
|
.each(function () {
|
|
if ($(this).is(':checked')) {
|
|
types.push($(this).attr('name'));
|
|
}
|
|
});
|
|
typesInput.val(types.join(','));
|
|
},
|
|
},
|
|
},
|
|
default: 'yes',
|
|
}).render(true);
|
|
}
|
|
|
|
// Change configs assigned to a path
|
|
export async function showPathSelectConfigForm(event) {
|
|
event.preventDefault();
|
|
const configInput = $(event.target).closest('.path-config').find('input');
|
|
let config = {};
|
|
try {
|
|
config = JSON.parse(configInput.val());
|
|
} catch (e) {}
|
|
|
|
const setting = game.settings.get('core', DefaultTokenConfig.SETTING);
|
|
const data = new foundry.data.PrototypeToken(setting);
|
|
const token = new TokenDocument(data, { actor: null });
|
|
new TokenCustomConfig(
|
|
token,
|
|
{},
|
|
null,
|
|
null,
|
|
(conf) => {
|
|
if (!conf) conf = {};
|
|
if (conf.flags == null || isEmpty(conf.flags)) delete conf.flags;
|
|
configInput.val(JSON.stringify(conf));
|
|
const cog = configInput.siblings('.select-config');
|
|
if (isEmpty(conf)) cog.removeClass('active');
|
|
else cog.addClass('active');
|
|
},
|
|
config
|
|
).render(true);
|
|
}
|
|
|
|
export async function showTokenCaptureDialog(token) {
|
|
if (!token) return;
|
|
let content = `<form>
|
|
<div class="form-group">
|
|
<label>Image Name</label>
|
|
<input type="text" name="name" value="${token.name}">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Image Path</label>
|
|
<div class="form-fields">
|
|
<input type="text" name="path" value="modules/token-variants/">
|
|
<button type="button" class="file-picker" data-type="folder" data-target="path" title="Browse Folders" tabindex="-1">
|
|
<i class="fas fa-file-import fa-fw"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="form-group slim">
|
|
<label>Width <span class="units">(pixels)</span></label>
|
|
<div class="form-fields">
|
|
<input type="number" step="1" name="width" value="${token.mesh.texture.width}">
|
|
</div>
|
|
</div>
|
|
<div class="form-group slim">
|
|
<label>Height <span class="units">(pixels)</span></label>
|
|
<div class="form-fields">
|
|
<input type="number" step="1" name="height" value="${token.mesh.texture.height}">
|
|
</div>
|
|
</div>
|
|
<div class="form-group slim">
|
|
<label>Scale</label>
|
|
<div class="form-fields">
|
|
<input type="number" step="any" name="scale" value="3">
|
|
</div>
|
|
</div>
|
|
</form>`;
|
|
|
|
new Dialog({
|
|
title: `Save Token/Overlay Image`,
|
|
content: content,
|
|
buttons: {
|
|
yes: {
|
|
icon: "<i class='fas fa-save'></i>",
|
|
label: 'Save',
|
|
callback: (html) => {
|
|
const options = {};
|
|
$(html)
|
|
.find('[name]')
|
|
.each(function () {
|
|
let val = parseFloat(this.value);
|
|
if (isNaN(val)) val = this.value;
|
|
options[this.name] = val;
|
|
});
|
|
uploadTokenImage(token, options);
|
|
},
|
|
},
|
|
},
|
|
render: (html) => {
|
|
html.find('.file-picker').click(() => {
|
|
new FilePicker({
|
|
type: 'folder',
|
|
current: html.find('[name="path"]').val(),
|
|
callback: (path) => {
|
|
html.find('[name="path"]').val(path);
|
|
},
|
|
}).render();
|
|
});
|
|
},
|
|
default: 'yes',
|
|
}).render(true);
|
|
}
|
|
|
|
export function showMappingSelectDialog(
|
|
mappings,
|
|
{ title1 = 'Mappings', title2 = 'Select Mappings', buttonTitle = 'Confirm', callback = null } = {}
|
|
) {
|
|
if (!mappings || !mappings.length) return;
|
|
|
|
let content = `<form style="overflow-y: scroll; height:400px;"><h2>${title2}</h2>`;
|
|
|
|
const [_, mappingGroups] = sortMappingsToGroups(mappings);
|
|
for (const [group, obj] of Object.entries(mappingGroups)) {
|
|
if (obj.list.length) {
|
|
content += `<h4 style="text-align:center;"><b>${group}</b></h4>`;
|
|
for (const mapping of obj.list) {
|
|
content += `
|
|
<div class="form-group">
|
|
<label>${mapping.label}</label>
|
|
<div class="form-fields">
|
|
<input type="checkbox" name="${mapping.id}" data-dtype="Boolean">
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
}
|
|
|
|
content += `</form><div class="form-group"><button type="button" class="select-all">Select all</div>`;
|
|
|
|
new Dialog({
|
|
title: title1,
|
|
content: content,
|
|
buttons: {
|
|
Ok: {
|
|
label: buttonTitle,
|
|
callback: async (html) => {
|
|
if (!callback) return;
|
|
const selectedMappings = [];
|
|
html.find('input[type="checkbox"]').each(function () {
|
|
if (this.checked) {
|
|
const mapping = mappings.find((m) => m.id === this.name);
|
|
if (mapping) {
|
|
const cMapping = deepClone(mapping);
|
|
selectedMappings.push(cMapping);
|
|
delete cMapping.targetActors;
|
|
}
|
|
}
|
|
});
|
|
callback(selectedMappings);
|
|
},
|
|
},
|
|
},
|
|
close: () => callback(null),
|
|
render: (html) => {
|
|
html.find('.select-all').click(() => {
|
|
html.find('input[type="checkbox"]').prop('checked', true);
|
|
});
|
|
},
|
|
}).render(true);
|
|
}
|
|
|
|
export function showUserTemplateCreateDialog(mappings) {
|
|
let content = `
|
|
<div class="form-group">
|
|
<label>Template Name</label>
|
|
<div class="form-fields">
|
|
<input type="text" name="templateName" data-dtype="String" value="">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Hover Text (optional)</label>
|
|
<div class="form-fields">
|
|
<input type="text" name="templateHint" data-dtype="String" value="">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Hover Image (optional)</label>
|
|
<div class="form-fields">
|
|
<input type="text" name="img" data-dtype="String" value="">
|
|
</div>
|
|
</div>`;
|
|
|
|
let dialog;
|
|
dialog = new Dialog({
|
|
title: 'Mapping Templates',
|
|
content,
|
|
buttons: {
|
|
create: {
|
|
label: 'Create Template',
|
|
callback: (html) => {
|
|
const name = html.find('[name="templateName"]').val().trim();
|
|
const hint = html.find('[name="templateHint"]').val().trim();
|
|
const img = html.find('[name="img"]').val().trim();
|
|
if (name.trim()) {
|
|
TVA_CONFIG.templateMappings.push({ id: randomID(), name, hint, img, mappings: deepClone(mappings) });
|
|
updateSettings({ templateMappings: TVA_CONFIG.templateMappings });
|
|
}
|
|
},
|
|
},
|
|
cancel: {
|
|
label: 'Cancel',
|
|
},
|
|
},
|
|
default: 'cancel',
|
|
});
|
|
dialog.render(true);
|
|
}
|
|
|
|
export function toggleTemplateDialog() {
|
|
new Templates({ callback: (templateName, mappings) => toggleTemplateOnSelected(templateName, mappings) }).render(
|
|
true
|
|
);
|
|
}
|