mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-06 03:47:13 +00:00
feat(dashboard): UGC search, and UGC icons on property and character pages
UGC Search (/ugc_search) finds creations by name, id or LOT with where each one is. Property pages and character inventories show the icon the UGC server made of each creation, its state and a link to /ugc?item=&kind=, for whoever may view the page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -108,6 +108,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="/js/ugc-links.js"></script>
|
||||
<script type="module">
|
||||
const propertyId = document.getElementById('property').getAttribute('data-property-id');
|
||||
const UGC_LOT = 14;
|
||||
@@ -183,7 +184,9 @@ function renderDetails(p) {
|
||||
|
||||
let loaded = null; // the property as last loaded, for the reject dialog
|
||||
function load() {
|
||||
api.get('/api/properties/' + propertyId).then((p) => {
|
||||
// The UGC server's icon and state of each player-built model come with the property
|
||||
Promise.all([api.get('/api/properties/' + propertyId), UgcLinks.forProperty(propertyId)]).then(([p, ugc]) => {
|
||||
(p.models || []).forEach((m) => { m.ugc = ugc[m.id] || null; });
|
||||
if (p.success === false) { setText('propName', p.error); return; }
|
||||
loaded = p;
|
||||
setText('propName', p.name || 'Unnamed property');
|
||||
@@ -208,13 +211,21 @@ function load() {
|
||||
});
|
||||
}
|
||||
|
||||
// What the UGC server made of it, and a link to it in the UGC viewer (for those who may open it)
|
||||
function ugcState(m) {
|
||||
if (!m.ugc) return '';
|
||||
return '<div class="small mt-1">UGC server: ' + UgcLinks.state(m.ugc) + (m.ugc.link ? ' <a href="' + esc(m.ugc.link) + '">Open in UGC viewer</a>' : '') + '</div>';
|
||||
}
|
||||
|
||||
const labels = $('#modelsTable thead th').map(function () { return $(this).text().trim(); }).get();
|
||||
const columns = [
|
||||
{ data: null, render: (m, type) => {
|
||||
const given = playerName(m.model_name);
|
||||
if (type !== 'display') return (given || m.name) + ' ' + (m.model_description || '');
|
||||
return (given ? '<div class="fw-semibold text-break">' + esc(given) + '</div><div class="small text-body-secondary">' + esc(m.name) + '</div>' : '<div>' + esc(m.name) + '</div>') +
|
||||
(m.model_description ? '<div class="model-desc">' + esc(m.model_description) + '</div>' : '');
|
||||
// A player-built model's icon is the one the UGC server made of it (the item's icon until it has)
|
||||
const icon = m.ugc ? UgcLinks.icon(m.ugc, m.lot) : '<img src="/api/icon/' + esc(m.lot) + '" width="40" height="40" class="rounded" alt="" loading="lazy">';
|
||||
return '<div class="d-flex align-items-center gap-2"><div class="flex-shrink-0">' + icon + '</div><div class="min-w-0">' + (given ? '<div class="fw-semibold text-break">' + esc(given) + '</div><div class="small text-body-secondary">' + esc(m.name) + '</div>' : '<div>' + esc(m.name) + '</div>') +
|
||||
(m.model_description ? '<div class="model-desc">' + esc(m.model_description) + '</div>' : '') + '</div></div>';
|
||||
} },
|
||||
{ data: 'lot' },
|
||||
{ data: 'behaviors', render: (d, type) => type === 'display' ? (d.length ? d.length + ': ' + d.map((b) => esc(b.name || 'Behavior')).join(', ') : muted('-')) : d.length },
|
||||
@@ -225,13 +236,13 @@ const columns = [
|
||||
{ data: null, render: (m, type) => {
|
||||
if (m.lxfml_size === null || m.lxfml_size === undefined) {
|
||||
if (type !== 'display') return m.ldf_config || '';
|
||||
return m.ldf_config ? '<span class="small">Modular: <code>' + esc(m.ldf_config) + '</code></span>' : muted(m.lot === UGC_LOT ? 'Missing' : 'Prebuilt');
|
||||
return m.ldf_config ? '<span class="small">Modular: <code>' + esc(m.ldf_config) + '</code></span>' + ugcState(m) : muted(m.lot === UGC_LOT ? 'Missing' : 'Prebuilt');
|
||||
}
|
||||
if (type !== 'display') return m.lxfml_size;
|
||||
return '<span class="small">' + esc(bytes(m.lxfml_size)) + '</span> ' +
|
||||
(m.is_optimized ? fmt.badge('Optimized', 'info') + ' ' : '') + (m.bake_ao ? fmt.badge('Baked AO', 'secondary') + ' ' : '') +
|
||||
(m.ldf_config ? '<div class="small">Modular: <code>' + esc(m.ldf_config) + '</code></div>' : '') +
|
||||
'<div class="small text-body-secondary">UGC ' + esc(m.ugc_id) + (m.filename ? ' · ' + esc(m.filename) : '') + '</div>';
|
||||
'<div class="small text-body-secondary">UGC ' + esc(m.ugc_id) + (m.filename ? ' · ' + esc(m.filename) : '') + '</div>' + ugcState(m);
|
||||
} },
|
||||
{ data: 'position', orderable: false, render: (d) => '<span class="small text-nowrap">' + d.map((v) => v.toFixed(1)).join(', ') + '</span>' },
|
||||
{ data: 'id', orderable: false, className: 'actions', render: (d) =>
|
||||
|
||||
Reference in New Issue
Block a user