import { CORE_TEMPLATES } from '../scripts/mappingTemplates.js'; import { TVA_CONFIG, updateSettings } from '../scripts/settings.js'; import { showMappingSelectDialog, showUserTemplateCreateDialog } from './dialogs.js'; export class Templates extends FormApplication { constructor({ mappings = null, callback = null } = {}) { super({}, {}); this.mappings = mappings; this.callback = callback; } static get defaultOptions() { return mergeObject(super.defaultOptions, { id: 'token-variants-templates', classes: ['sheet'], template: 'modules/token-variants/templates/templates.html', resizable: false, minimizable: false, title: 'Mapping Templates', width: 500, height: 'auto', }); } async getData(options) { const data = super.getData(options); if (!this.category) this.category = TVA_CONFIG.templateMappings?.length ? 'user' : 'core'; if (this.category === 'user') { this.templates = TVA_CONFIG.templateMappings; } else if (this.category === 'core') { this.templates = CORE_TEMPLATES; } else { this.templates = await communityTemplates(); } for (const template of this.templates) { template.hint = template.hint?.replace(/(\r\n|\n|\r)/gm, '
'); } data.category = this.category; data.templates = this.templates; data.allowDelete = this.category === 'user'; data.allowCreate = this.category === 'user'; data.allowCopy = this.category === 'community' || this.category === 'core'; return data; } /** * @param {JQuery} html */ activateListeners(html) { super.activateListeners(html); // Position tooltip const appWindow = html.closest('#token-variants-templates'); html.find('.template').on('mouseover', (event) => { const template = $(event.target).closest('.template'); const pos = template.position(); const tooltip = template.find('.tooltiptext'); const windowPos = appWindow.position(); tooltip.css('top', windowPos.top + pos.top).css('left', windowPos.left + pos.left); // Lazy load image const img = template.find('img'); if (!img.attr('src')) img.attr('src', img.data('src')); }); if (this.callback) { html.find('.template').on('click', async (event) => { const li = $(event.target).closest('.template'); const id = li.data('id'); const url = li.data('url'); let mappings; let templateName; if (url) { const template = await getTemplateFromFileURL(url); if (template) mappings = template.mappings; } else if (id) { const template = this.templates.find((t) => t.id === id); if (template) { templateName = template.name; mappings = template.mappings; } } if (mappings) this.callback(templateName, mappings); }); } html.find('.search').on('input', () => { const filter = html.find('.search').val().trim().toLowerCase(); html.find('.template-list li').each(function () { const li = $(this); const description = li.find('.description').text().trim().toLowerCase(); const name = li.data('name').trim().toLowerCase(); const createdBy = li.data('creator').trim().toLowerCase(); if (name.includes(filter) || description.includes(filter) || createdBy.includes(filter)) li.show(); else li.hide(); }); }); html.find('[name="category"]').on('change', (event) => { this.category = event.target.value; this.render(true); }); html.find('.delete').on('click', async (event) => { event.preventDefault(); event.stopPropagation(); const id = $(event.target).closest('.template').data('id'); if (id) { await updateSettings({ templateMappings: TVA_CONFIG.templateMappings.filter((m) => m.id !== id), }); this.render(true); } }); html.find('.copy').on('click', async (event) => { event.preventDefault(); event.stopPropagation(); const id = $(event.target).closest('.template').data('id'); if (id) { let template; if (this.category === 'core') { template = deepClone(CORE_TEMPLATES.find((t) => t.id === id)); } else { const fileURL = $(event.target).closest('.template').data('url'); if (fileURL) template = await getTemplateFromFileURL(fileURL); } if (template) { TVA_CONFIG.templateMappings.push(template); await updateSettings({ templateMappings: TVA_CONFIG.templateMappings, }); ui.notifications.info(`Template {${template.name}} copied to User templates.`); this.render(true); } } }); html.find('.create').on('click', () => { showMappingSelectDialog(this.mappings, { title1: 'Create Template', callback: (selectedMappings) => { if (selectedMappings?.length) showUserTemplateCreateDialog(selectedMappings); }, }); }); } _getHeaderButtons() { const buttons = super._getHeaderButtons(); buttons.unshift({ label: 'Upload Template', class: '.token-variants-submit-template', icon: 'fa-solid fa-cloud-arrow-up', onclick: () => { new TemplateSubmissionForm().render(true); }, }); return buttons; } /** * @param {Event} event * @param {Object} formData */ async _updateObject(event, formData) {} } class TemplateSubmissionForm extends FormApplication { constructor() { super({}, {}); } static apiKey = 'AIzaSyCJpwIkpjrG10jaHwcpllvSChxRPawcMXE'; static get pk() { const k2 = 'AIzaSyCJpw'; const k1 = 'IkpjrG10jaHwcpllv'; const k3 = 'SChxRPawcMXE'; return k2 + k1 + k3; } static get defaultOptions() { return mergeObject(super.defaultOptions, { id: 'token-variants-template-submission', classes: ['sheet'], template: 'modules/token-variants/templates/templateSubmission.html', resizable: true, minimizable: false, closeOnSubmit: false, title: 'Upload Template', width: 500, height: 'auto', }); } async getData(options) { const data = super.getData(options); data.systemID = game.system.id; data.systemTitle = game.system.title; data.templates = TVA_CONFIG.templateMappings; data.modules = game.modules .filter((m) => m.active) .map((m) => { return { id: m.name, name: m.title }; }); return data; } /** * @param {Event} event * @param {Object} formData */ async _updateObject(event, formData) { if (!formData.template) return; let template = TVA_CONFIG.templateMappings.find((t) => t.id === formData.template); if (!template) return; const name = formData.name.trim() || template.name; const hint = formData.hint.trim() || template.hint?.trim(); const createdBy = formData.createdBy.trim(); const system = formData.system; if (!(formData.modules instanceof Array)) formData.modules = [formData.modules]; const modules = formData.modules.filter(Boolean).map((id) => { return { id, name: game.modules.get(id).title }; }); const id = randomID(); const img = formData.img.trim(); const result = await submitTemplate({ id, name, hint, img, createdBy, system, modules, mappings: template.mappings, }); if (result) this.close(); } } function _setStringField(template, fields, field) { if (template[field] && template[field] !== '') { fields[field] = { stringValue: template[field] }; } } async function submitTemplate(template) { const fields = {}; ['name', 'hint', 'img', 'id', 'createdBy', 'system'].forEach((field) => _setStringField(template, fields, field)); fields.modules = { stringValue: template.modules.length ? JSON.stringify(template.modules) : '' }; fields.mappings = { stringValue: JSON.stringify(template.mappings) }; fields.createTime = { integerValue: new Date().getTime() }; fields.approved = { booleanValue: false }; const response = await fetch( `https://firestore.googleapis.com/v1/projects/tva---templates/databases/(default)/documents/templates?key=${TemplateSubmissionForm.pk}`, { method: 'POST', body: JSON.stringify({ fields: fields, }), headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, } ); if (response.ok && response.status === 200) { ui.notifications.info('Template submission completed.'); return true; } else { ui.notifications.warn('Template submission failed.'); return false; } } const SEARCH_QUERY = { structuredQuery: { select: { fields: [ { fieldPath: 'id', }, { fieldPath: 'name', }, { fieldPath: 'hint', }, { fieldPath: 'createdBy', }, { fieldPath: 'img', }, { fieldPath: 'system', }, { fieldPath: 'modules', }, ], }, where: { fieldFilter: { field: { fieldPath: 'approved', }, op: 'EQUAL', value: { booleanValue: true, }, }, }, from: [{ collectionId: 'templates' }], orderBy: [ { field: { fieldPath: 'createTime', }, }, ], offset: 0, limit: 50, }, }; async function communityTemplates(search = null) { const response = await fetch( `https://firestore.googleapis.com/v1/projects/tva---templates/databases/(default)/documents:runQuery?key=${TemplateSubmissionForm.pk}`, { method: 'POST', body: JSON.stringify(SEARCH_QUERY), headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, } ); const templates = []; if (response.ok && response.status === 200) { const documents = await response.json(); for (let doc of documents) { if ('document' in doc) templates.push(_docToTemplate(doc.document)); } } else { ui.notifications.warn('Failed to retrieve Community templates.'); } return templates; } function _docToTemplate(doc) { const template = {}; ['id', 'name', 'mappings', 'createdBy', 'img', 'hint', 'system', 'modules'].forEach((f) => { template[f] = doc.fields[f]?.stringValue || ''; }); if (template.mappings) template.mappings = JSON.parse(template.mappings); else template.fileURL = doc.name; if (template.modules) template.modules = JSON.parse(template.modules); if (!template.createdBy) template.createdBy = 'Anonymous'; return template; } async function getTemplateFromFileURL(fileURL) { const response = await fetch(`https://firestore.googleapis.com/v1/${fileURL}?key=${TemplateSubmissionForm.pk}`, { method: 'GET', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, }); if (response.ok && response.status === 200) { const doc = await response.json(); return _docToTemplate(doc); } return null; }