/**
* Players' creations on the property and character pages: the icon the UGC server made of a model, car or rocket in
* place of the item's own icon, its state and a link to it on the /ugc page (for those who may open it). The routes
* (/api/ugc_links/...) let whoever may view the page see the icons of the creations on it.
*/
(function () {
var STATES = { done: ['Made', 'success'], pending: ['Waiting', 'secondary'], failed: ['Failed', 'danger'] };
function row(label, value) { return value === '' || value === null || value === undefined ? '' : '
' + esc(label) + '
' + value + '
'; }
// Triangles before and after hidden surfaces were removed, per LOD
function triangles(stats) {
return (stats.lods || []).map(function (l) {
return 'LOD ' + esc(l.lod) + ': ' + esc(l.opaqueBefore) + ' → ' + esc(l.opaqueAfter) + (l.transparent ? ' + ' + esc(l.transparent) + ' transparent' : '') +
' (' + esc(l.vertices) + ' vertices)';
}).join(' ');
}
function timings(ms) {
if (!ms) return '';
var parts = [['build', 'build'], ['hiddenSurfaces', 'hidden faces'], ['ambientOcclusion', 'AO'], ['icon', 'icon']]
.filter(function (p) { return ms[p[0]] !== undefined; }).map(function (p) { return esc(p[1]) + ' ' + esc(ms[p[0]]); });
return esc(ms.total) + ' ms' + (parts.length ? ' (' + parts.join(', ') + ')' : '');
}
function renderDetails(d, modelId) {
var placement = (d.placements || []).find(function (p) { return p.modelId === String(modelId); }) || (d.placements || [])[0];
var name = placement && placement.name && !/^Objects_\d+_name$/.test(placement.name) ? placement.name : '';
var s = d.stats, prev = d.previousStats;
var waiting = d.state === 'pending' && d.processAfter > Date.now() / 1000;
var html = '
';
var buttons = '';
if (d.link) buttons += 'Open in UGC viewer';
if (d.canManage) buttons += '';
if (d.state === 'done') buttons += 'Download NIF';
if (modelId) buttons += 'Download LXFML';
return html + (buttons ? '
' + buttons + '
' : '') + '
';
}
// "Make again" in any UGC block
document.addEventListener('click', function (e) {
var button = e.target.closest && e.target.closest('[data-ugc-remake]');
if (!button) return;
button.disabled = true;
api.post('/api/ugc/reprocess', { kind: 'model', id: button.dataset.ugcRemake }).then(function (d) {
toast(d.success ? 'The UGC server will make it again' : (d.error || 'Failed'), d.success ? 'success' : 'danger');
}).catch(function () { toast('Failed', 'danger'); }).then(function () { button.disabled = false; });
});
// Expandable blocks () load when first opened
document.addEventListener('toggle', function (e) {
var box = e.target;
if (!box.matches || !box.matches('details[data-ugc-details]') || !box.open || box.dataset.loaded) return;
box.dataset.loaded = '1';
var body = box.querySelector('.ugc-details-body');
UgcLinks.details(box.dataset.ugcDetails, box.dataset.via, box.dataset.model).then(function (html) { body.innerHTML = html; });
}, true);
var linkCache = {}; // character id -> promise of its item links
window.UgcLinks = {
// A badge for the UGC server's state; a failure's reason as its tooltip
state: function (item) {
var s = STATES[item.state] || [item.state, 'secondary'];
return '' + esc(s[0]) + '';
},
// An of the creation's icon that falls back to the item's own icon until the UGC server made one
icon: function (item, lot, size) {
size = size || 40;
return '';
},
/**
* The UGC block for a brick-built model on the property pages: its icons (and the previous version's), state,
* owner, the name the player gave it, the UGC server's stats and links. `via` is the page's ?property= / ?character=
* query that lets its viewer see it; `modelId` (a placed model) picks its name and LXFML download.
*/
details: function (ugcId, via, modelId) {
if (!ugcId || ugcId === '0') return Promise.resolve('
' + esc((e && e.message) || 'Could not load the UGC data') + '
'; });
},
// {modelId: item} for the player-built models on a property // {modelId: item} for the player-built models on a property
forProperty: function (propertyId) {
return api.get('/api/ugc_links/property/' + propertyId).then(function (d) {
var map = {};
((d && d.items) || []).forEach(function (item) { map[item.modelId] = item; });
return map;
}).catch(function () { return {}; });
},
// Shows the creations among a character page's items (.inv-item[data-id]) with their own icon and a link
// Itemid -> the UGC link of each of a character's player-built items (fetched once per character)
inventoryLinks: function (characterId) {
if (!linkCache[characterId]) {
linkCache[characterId] = api.get('/api/ugc_links/character/' + characterId).then(function (d) {
var map = {};
((d && d.items) || []).forEach(function (item) { map[item.itemId] = item; });
return map;
});
}
return linkCache[characterId];
},
// Shows a player-built item's UGC icon and a link to the UGC viewer on one inventory item element
decorateItem: function (el, item) {
var img = el.querySelector('img');
if (img) {
var own = img.src;
img.onerror = function () { img.onerror = null; img.src = own; };
img.src = item.icon;
}
el.classList.add('inv-ugc');
el.title = (el.title ? el.title + ' - ' : '') + 'Player-built ' + (item.kind === 'modular' ? 'car or rocket' : 'model') + ' (UGC ' + item.ugcId + ', ' + item.state + ')';
if (item.link && !el.querySelector('.inv-ugc-link')) {
// Items may sit inside a link already (the item trace), so this is a button, not a nested link
var a = document.createElement('span');
a.className = 'inv-ugc-link';
a.setAttribute('role', 'link');
a.tabIndex = 0;
a.textContent = 'UGC';
a.title = 'Open in the UGC viewer';
var open = function (e) { e.preventDefault(); e.stopPropagation(); window.location.href = item.link; };
a.addEventListener('click', open);
a.addEventListener('keydown', function (e) { if (e.key === 'Enter') open(e); });
el.appendChild(a);
}
},
// Decorates the inventory items already on the page
decorateInventory: function (characterId, root) {
var self = this;
return this.inventoryLinks(characterId).then(function (map) {
Object.keys(map).forEach(function (id) {
(root || document).querySelectorAll('.inv-item[data-id="' + id + '"]').forEach(function (el) { self.decorateItem(el, map[id]); });
});
}).catch(function () {});
}
};
})();