feat(dashboard): import, reprocess and remove models from the property 3D view

The 3D view gets the property page's model actions: Reprocess all models
(ugc_manage), Import LXFML at the file's positions and Remove all models
(properties_import). Not on the showcase view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-28 10:25:37 -05:00
parent f3922b3bcb
commit 59b9ceb52c

View File

@@ -34,6 +34,14 @@
<span class="small text-body-secondary" id="viewerProgress"></span>
<button class="btn btn-sm btn-outline-secondary" id="resetViewBtn">Reset view</button>
<button class="btn btn-sm btn-outline-secondary" id="resetPropertyBtn" title="Start the property over as it's saved: every model back in its place and not smashed, behaviors stopped, the log and selection cleared, the view framed on the plot">Reset property</button>
{% if not exists("showcase") %}
{% if can.ugc_manage %}<button class="btn btn-sm btn-outline-secondary" id="reprocessModelsBtn" title="Have the UGC server make every model on this property again (mesh and icon), with the current settings">Reprocess all models</button>{% endif %}
{% if can.properties_import %}
<button class="btn btn-sm btn-outline-secondary" id="importModelsBtn" title="Add the models in an .lxfml file at the positions stored in it, owned by the property's owner. Not while the property is loaded in a world">Import LXFML</button>
<input type="file" id="importModelsFile" accept=".lxfml" class="d-none">
<button class="btn btn-sm btn-outline-danger" id="removeModelsBtn" title="Delete every model placed on this property (they don't go back to the owner). Not while the property is loaded in a world">Remove all models</button>
{% endif %}
{% endif %}
<button class="btn btn-sm btn-outline-secondary" id="fullscreenBtn">Full screen</button>
<button class="btn btn-sm btn-outline-primary d-lg-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#viewerPanel" aria-controls="viewerPanel">Panel</button>
</div>
@@ -476,6 +484,30 @@ document.getElementById('resetPropertyBtn').addEventListener('click', () => {
document.getElementById('behaviorLog').innerHTML = '<li class="text-body-secondary">Select a model with behaviors and trigger it.</li>';
load();
});
const reprocessModelsBtn = document.getElementById('reprocessModelsBtn');
if (reprocessModelsBtn) reprocessModelsBtn.addEventListener('click', () => {
reprocessModelsBtn.disabled = true;
api.action('/api/ugc/reprocess', { kind: 'model', property: propertyId }).then((d) => toast(d.message, 'success'))
.catch(() => {}).finally(() => { reprocessModelsBtn.disabled = false; });
});
const importModelsFile = document.getElementById('importModelsFile');
if (importModelsFile) {
document.getElementById('importModelsBtn').addEventListener('click', () => importModelsFile.click());
importModelsFile.addEventListener('change', async () => {
const file = importModelsFile.files[0];
if (!file) return;
const lxfml = await file.text();
importModelsFile.value = '';
api.action('/api/properties/' + propertyId + '/import', { lxfml }).then((d) => { toast(d.message, 'success'); load(); }).catch(() => {});
});
}
const removeModelsBtn = document.getElementById('removeModelsBtn');
if (removeModelsBtn) removeModelsBtn.addEventListener('click', () => {
if (window.prompt('This deletes every model on the property. Type the property ID (' + propertyId + ') to confirm:') !== propertyId) return;
removeModelsBtn.disabled = true;
api.action('/api/properties/' + propertyId + '/remove_models', { confirm: propertyId }).then((d) => { toast(d.message, 'success'); load(); })
.catch(() => {}).finally(() => { removeModelsBtn.disabled = false; });
});
document.getElementById('fullscreenBtn').addEventListener('click', () => {
const wrap = document.getElementById('viewerWrap');
if (document.fullscreenElement) document.exitFullscreen();