';
+ 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);
+
window.UgcLinks = {
// A badge for the UGC server's state; a failure's reason as its tooltip
state: function (item) {
@@ -18,7 +87,19 @@
return '';
},
- // {modelId: item} for the player-built models on a property
+ /**
+ * 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 = {};
diff --git a/dDashboardServer/templates/property-3d.jinja2 b/dDashboardServer/templates/property-3d.jinja2
index 0d3080b60..aaf94bf88 100644
--- a/dDashboardServer/templates/property-3d.jinja2
+++ b/dDashboardServer/templates/property-3d.jinja2
@@ -217,7 +217,22 @@ function showSelection(model, index) {
'
'
+ : 'Download LXFML');
+ if (!showcase && brickBuilt(model)) {
+ const item = ugc[model.id];
+ const placed = property && property.models.find((m) => String(m.id) === String(model.id));
+ UgcLinks.details(item ? item.ugcId : placed && placed.ugc_id, 'property=' + propertyId, model.id).then((html) => {
+ const box = document.getElementById('ugcDetails');
+ if (box && selectedIndex === index) { box.className = ''; box.innerHTML = html; }
+ });
+ }
+}
+
+// Brick-built models (their blueprint is on the UGC server); cars and rockets are modular and drawn by the client
+function brickBuilt(model) {
+ const item = ugc[model.id];
+ return item ? item.kind === 'model' : model.lot === 14;
}
// A player-built model's UGC server state, what it is drawn from, and a link to it in the UGC viewer
diff --git a/dDashboardServer/templates/property-view.jinja2 b/dDashboardServer/templates/property-view.jinja2
index 03cf8bb53..12f74852c 100644
--- a/dDashboardServer/templates/property-view.jinja2
+++ b/dDashboardServer/templates/property-view.jinja2
@@ -217,6 +217,12 @@ function ugcState(m) {
return '
UGC server: ' + UgcLinks.state(m.ugc) + (m.ugc.link ? ' Open in UGC viewer' : '') + '
';
}
+// A brick-built model's full UGC data (icons, stats, links), loaded when opened (ugc-links.js)
+function ugcDetails(m) {
+ return '' +
+ 'UGC details