/** * The Vanity page, Preview tab: what the world servers would load with the scheduled events that are on at some time, * or with events picked here. The server loads it as the worlds do (VanityEvents::LoadWorld): which vanity files are * read and why (as the files say, or switched by an event), the events in the order they are laid on, the NPCs, the * conflicts between events and anything that couldn't be read. #preview:ID previews that event on its own. */ (function () { 'use strict'; var events = []; var pending = null; // an event id from the hash, previewed once the events are known function get(id) { return document.getElementById(id); } function toInput(ts) { var d = new Date(ts * 1000); d.setMinutes(d.getMinutes() - d.getTimezoneOffset()); return d.toISOString().slice(0, 16); } function renderEvents() { var chosen = {}; Array.prototype.forEach.call(document.querySelectorAll('[data-vp-event]:checked'), function (c) { chosen[c.value] = true; }); if (pending) chosen[pending] = true; get('vpEvents').innerHTML = events.map(function (e) { return '
' + '
'; }).join('') || 'No event changes the vanity NPCs yet.'; } // Why a file is or isn't read function why(f) { if (f.switchedBy && f.loaded) return f.includedBy ? 'switched on by ' + esc(f.switchedBy) + ' (off in ' + esc(f.includedBy) + ')' : 'switched on by ' + esc(f.switchedBy) + ''; if (f.switchedBy) return 'switched off by ' + esc(f.switchedBy) + '' + (f.includedBy ? ' (' + (f.enabled ? 'on' : 'off') + ' in ' + esc(f.includedBy) + ')' : ''); if (!f.includedBy) return f.loaded ? 'where the worlds start' : 'couldn\'t be read'; if (!f.enabled) return 'off in ' + esc(f.includedBy); return f.loaded ? 'on in ' + esc(f.includedBy) : 'on in ' + esc(f.includedBy) + ', but couldn\'t be read'; } function filesTable(d) { var base = {}; d.baseFiles.forEach(function (f) { base[f.name] = f.loaded; }); return '
' + d.files.map(function (f) { var changed = !!base[f.name] !== f.loaded; return '' + '' + ''; }).join('') + '
Vanity fileLoadedWhy
' + esc(f.name) + '' + (f.loaded ? 'loaded' : 'not loaded') + (changed ? ' (' + (f.loaded ? 'not' : 'loaded') + ' without events)' : '') + '' + why(f) + '
'; } function eventLine(e) { var p = e.parts.map(function (part) { var bits = Object.keys(part.fileSwitches).map(function (f) { return f + (part.fileSwitches[f] ? ' on' : ' off'); }); if (part.file) bits.push('overlay ' + part.file); if (part.removals.length) bits.push('takes out ' + part.removals.join(', ')); return bits.join('; '); }).join(' / '); return '
  • ' + esc(e.name) + ' (priority ' + esc(e.priority) + ')' + (p ? ': ' + esc(p) : '') + '
  • '; } function run(body) { var out = get('vpOut'); out.innerHTML = '

    Loading…

    '; api.post('/api/vanity/preview', body).then(function (d) { if (!d.success) { out.innerHTML = '
    ' + esc(d.error || 'Failed') + '
    '; return; } var problems = d.conflicts.map(function (c) { return 'NPC ' + esc(c.npc) + ' is changed by ' + esc(c.events.join(', ')) + '; ' + esc(c.winner) + ' wins.'; }) .concat(d.fileConflicts.map(function (c) { return 'File ' + esc(c.file) + ' is switched by ' + esc(c.switches.map(function (s) { return s.event + ' (' + (s.on ? 'on' : 'off') + ')'; }).join(', ')) + '; ' + esc(c.winner) + ' wins.'; })); out.innerHTML = '
    ' + '

    ' + ('at' in body || !body.ids ? 'At ' + esc(fmt.unix(d.at)) + ': ' : '') + (d.events.length ? d.events.length + ' event(s) with vanity changes, laid on in this order:' : 'no event with vanity changes.') + '

    ' + (d.events.length ? '
      ' + d.events.map(eventLine).join('') + '
    ' : '') + '

    ' + esc(d.npcs) + ' NPCs and props: ' + esc(d.fileNpcs) + ' from the vanity files that are loaded, then the overlays and removals. ' + 'Without any event: ' + esc(d.baseNpcs) + '.

    ' + (problems.length ? '
    ' + problems.join('
    ') + '
    ' : '') + (d.warnings.length ? '
    ' + d.warnings.map(esc).join('
    ') + '
    ' : '') + '

    Vanity files

    ' + filesTable(d) + '
    Highlighted: loaded or not because of an event.
    ' + '
    The merged vanity XML
    ' + '
    ' + '
    '; out.querySelector('pre').textContent = d.xml; get('vpCopy').addEventListener('click', function () { navigator.clipboard.writeText(d.xml).then(function () { toast('Copied', 'success'); }); }); }); } function runFromForm() { if (get('vpChosen').checked) { return run({ ids: Array.prototype.map.call(document.querySelectorAll('[data-vp-event]:checked'), function (c) { return Number(c.value); }) }); } var value = get('vpTime').value; run(value ? { at: Math.floor(new Date(value).getTime() / 1000) } : {}); } document.addEventListener('vanity:data', function (e) { events = e.detail.events || []; renderEvents(); if (pending) { get('vpChosen').checked = true; run({ ids: [Number(pending)] }); pending = null; } }); get('vpNow').addEventListener('click', function () { get('vpTime').value = toInput(Math.floor(Date.now() / 1000)); get('vpAt').checked = true; }); get('vpTime').addEventListener('change', function () { get('vpAt').checked = true; }); get('vpEvents').addEventListener('change', function () { get('vpChosen').checked = true; }); get('vpRun').addEventListener('click', runFromForm); // A file in the table opens it on the other tab get('vpOut').addEventListener('click', function (e) { var link = e.target.closest('[data-vp-file]'); if (!link) return; e.preventDefault(); bootstrap.Tab.getOrCreateInstance(get('vanityTabFiles')).show(); var item = document.querySelector('#vanityFiles [data-select-file="' + CSS.escape(link.dataset.vpFile) + '"]'); if (item) item.click(); }); // The first time the tab is shown, preview now get('vanityTabPreview').addEventListener('shown.bs.tab', function () { if (!get('vpOut').querySelector('.card')) runFromForm(); }); get('vpTime').value = toInput(Math.floor(Date.now() / 1000)); var hash = decodeURIComponent(location.hash.slice(1)); if (hash.indexOf('preview:') === 0) pending = hash.slice(8); })();