mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-28 23:47:14 +00:00
The icon editor shows a player model or a car or rocket's assembled modules through the icon renderer's own camera (same perspective, framing and model turn), with the icon's square outlined, a sun arrow and a debounced live preview. Dragging moves the camera, turns the model, moves the sun or shifts the icon; the wheel sets the border; every parameter's slider (grouped from the parameter list) stays in step both ways. Save as the type's preset or the item's own, go back to the preset or defaults, draw a type's icons again. A presets section opens each type's editor on an example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
194 lines
13 KiB
Django/Jinja
194 lines
13 KiB
Django/Jinja
{% extends "base.jinja2" %}
|
|
|
|
{% block title %}UGC Server - DarkflameServer{% endblock %}
|
|
|
|
{% block content %}
|
|
<div id="ugcPage">
|
|
<div class="mb-3">
|
|
<h2 class="mb-1">UGC Server</h2>
|
|
<p class="text-body-secondary mb-0">The UGC server makes a mesh and an icon for every model players build, and icons for their cars and rockets,
|
|
and serves them to the game client. New and changed models are picked up by themselves; a model that fails is tried a few times, then left
|
|
as failed until it's made again from here. See <code>docs/UgcServer.md</code>.</p>
|
|
</div>
|
|
|
|
<div class="row g-2 mb-3" id="counts"></div>
|
|
|
|
<div class="card mb-3">
|
|
<div class="card-body py-2 small" id="serverStatus">Asking the UGC server…</div>
|
|
</div>
|
|
|
|
<details class="card mb-3" id="presetsCard">
|
|
<summary class="card-header py-2 small fw-semibold">Icon presets per type</summary>
|
|
<div class="card-body py-2 small">
|
|
<p class="text-body-secondary mb-2">Each type of icon (player models, and each car or rocket build type in the client's data) has a preset over the
|
|
default settings. Open one to set its camera, the model's turn, the light and the border on an example.</p>
|
|
<div class="d-flex flex-wrap gap-2" id="presetsList">Loading…</div>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="card mb-3 d-none" id="cacheCard">
|
|
<summary class="card-header py-2 small fw-semibold">Stored files: delete and purge</summary>
|
|
<div class="card-body py-2 small">
|
|
<p class="text-body-secondary mb-2">Deletes files the UGC server made, of the kind picked below. Afterwards they are made again when a game client
|
|
asks for them, queued to be made now, or left deleted. A car or rocket's icon is shared by every build of the same modules.</p>
|
|
<div class="d-flex flex-wrap gap-2 align-items-end">
|
|
<label>State<select class="form-select form-select-sm" id="purgeState"><option value="">Any</option><option value="done">Made</option><option value="failed">Failed</option><option value="pending">Waiting</option></select></label>
|
|
<label>Owner<input class="form-control form-control-sm" id="purgeOwner" placeholder="Name (part of it)"></label>
|
|
<label>Made more than<div class="input-group input-group-sm"><input type="number" min="0" class="form-control" id="purgeOlder" value="0" style="width:5rem"><span class="input-group-text">days ago</span></div></label>
|
|
<label>Not asked for in<div class="input-group input-group-sm"><input type="number" min="0" class="form-control" id="purgeUnused" value="0" style="width:5rem"><span class="input-group-text">days</span></div></label>
|
|
<label>Then<select class="form-select form-select-sm" id="purgeAfter"><option value="on_demand">Make again when asked for</option><option value="now">Make again now</option><option value="gone">Leave deleted</option></select></label>
|
|
<button type="button" class="btn btn-sm btn-outline-warning" id="purgeButton">Purge matching</button>
|
|
<button type="button" class="btn btn-sm btn-outline-danger" id="purgeAllButton">Purge all…</button>
|
|
</div>
|
|
<div class="mt-2" id="purgeResult"></div>
|
|
</div>
|
|
</details>
|
|
|
|
<div class="d-flex flex-wrap gap-2 align-items-center mb-2">
|
|
<div class="btn-group btn-group-sm" role="group" id="kindButtons">
|
|
<button type="button" class="btn btn-outline-primary active" data-kind="model">Models</button>
|
|
<button type="button" class="btn btn-outline-primary" data-kind="modular">Cars and rockets</button>
|
|
</div>
|
|
<select class="form-select form-select-sm w-auto" id="stateFilter" aria-label="State">
|
|
<option value="">Every state</option><option value="pending">Waiting</option><option value="done">Made</option><option value="failed">Failed</option><option value="empty">Empty (no bricks)</option>
|
|
</select>
|
|
<input type="search" class="form-control form-control-sm w-auto" id="search" placeholder="ID or owner" aria-label="Search by ID or owner">
|
|
<div class="btn-group btn-group-sm" role="group" id="viewButtons">
|
|
<button type="button" class="btn btn-outline-secondary active" data-view="gallery">Icons</button>
|
|
<button type="button" class="btn btn-outline-secondary" data-view="list">List</button>
|
|
</div>
|
|
<div class="ms-auto d-none" id="manageButtons">
|
|
<button type="button" class="btn btn-sm btn-outline-warning" id="retryFailed">Try the failed ones again</button>
|
|
<button type="button" class="btn btn-sm btn-outline-danger" id="remakeAll">Make everything again</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="gallery" class="ugc-gallery mb-2"></div>
|
|
<div class="card d-none" id="listCard">
|
|
<table class="table table-sm align-middle mb-0 table-stack">
|
|
<thead><tr><th>Icon</th><th>ID</th><th>Owner</th><th>State</th><th>Last attempt</th><th>Details</th><th></th></tr></thead>
|
|
<tbody id="rows"></tbody>
|
|
</table>
|
|
</div>
|
|
<div class="d-flex gap-2 mt-2">
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="prevPage">Newer</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="nextPage">Older</button>
|
|
</div>
|
|
|
|
<div class="modal fade" id="previewModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-xl modal-fullscreen-lg-down"><div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="previewTitle">Model</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="d-flex flex-wrap gap-2 align-items-center mb-2 small" id="meshControls">
|
|
<label class="d-flex align-items-center gap-1">Detail
|
|
<select class="form-select form-select-sm w-auto" id="lodSelect"></select></label>
|
|
<label class="d-flex align-items-center gap-1">Version
|
|
<select class="form-select form-select-sm w-auto" id="versionSelect">
|
|
<option value="current">Now</option><option value="previous">Before it was made again</option>
|
|
</select></label>
|
|
<div class="form-check form-switch mb-0"><input class="form-check-input" type="checkbox" id="wireframeSwitch"><label class="form-check-label" for="wireframeSwitch">Wireframe</label></div>
|
|
<div class="form-check form-switch mb-0"><input class="form-check-input" type="checkbox" id="colorsSwitch" checked><label class="form-check-label" for="colorsSwitch">Vertex colors</label></div>
|
|
<div class="form-check form-switch mb-0"><input class="form-check-input" type="checkbox" id="aoSwitch" checked><label class="form-check-label" for="aoSwitch">Baked lighting</label></div>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="reframeButton">Reset view</button>
|
|
</div>
|
|
<div class="row g-3">
|
|
<div class="col-lg-5" id="nifColumn">
|
|
<div class="small fw-semibold mb-1">Generated mesh (.nif)</div>
|
|
<div id="nifViewer" class="rounded border" style="height: 420px"></div>
|
|
<div class="small text-body-secondary mt-1" id="nifStats"></div>
|
|
</div>
|
|
<div class="col-lg-4" id="lxfmlColumn">
|
|
<div class="small fw-semibold mb-1">As built (LXFML)</div>
|
|
<div id="lxfmlViewer" class="rounded border" style="height: 420px"></div>
|
|
<div class="small text-body-secondary mt-1">Every brick, before hidden faces are removed.</div>
|
|
</div>
|
|
<div class="col-lg-3">
|
|
<div class="small fw-semibold mb-1">Icon</div>
|
|
<div class="d-flex gap-2 flex-wrap">
|
|
<figure class="text-center mb-0"><img id="previewIcon" alt="Icon" class="border rounded bg-body-tertiary ugc-checker" width="128" height="128">
|
|
<figcaption class="small text-body-secondary">Now</figcaption></figure>
|
|
<figure class="text-center mb-0 d-none" id="previousIconBox"><img id="previousIcon" alt="Icon before" class="border rounded bg-body-tertiary ugc-checker" width="128" height="128">
|
|
<figcaption class="small text-body-secondary">Before</figcaption></figure>
|
|
</div>
|
|
<div class="small mt-2" id="previewLinks"></div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3" id="statsBox"></div>
|
|
<div class="card mt-3 d-none" id="framingCard">
|
|
<div class="card-header py-2 small fw-semibold d-flex flex-wrap align-items-center gap-2">Icon editor
|
|
<span class="text-body-secondary fw-normal" id="framingKind"></span>
|
|
<span class="ms-auto text-body-secondary fw-normal" id="framingState">Preview drawn by the UGC server, not stored.</span></div>
|
|
<div class="card-body py-2 small">
|
|
<div class="row g-3">
|
|
<div class="col-lg-8">
|
|
<div class="d-flex flex-wrap gap-2 align-items-center mb-1">
|
|
<div class="btn-group btn-group-sm" role="group" aria-label="What dragging changes" id="poseModes">
|
|
<button type="button" class="btn btn-outline-secondary active" data-mode="camera" title="Drag: the camera around the model">Camera</button>
|
|
<button type="button" class="btn btn-outline-secondary" data-mode="model" title="Drag: turn the model (Ctrl+drag in any mode; Alt+wheel rolls it)">Turn model</button>
|
|
<button type="button" class="btn btn-outline-secondary" data-mode="sun" title="Drag: the sun's direction (Alt+drag in any mode)">Sun</button>
|
|
<button type="button" class="btn btn-outline-secondary" data-mode="shift" title="Drag: move the model in the icon (Shift+drag or right drag in any mode)">Shift</button>
|
|
</div>
|
|
<div class="form-check form-switch mb-0"><input class="form-check-input" type="checkbox" id="sunArrowSwitch" checked><label class="form-check-label" for="sunArrowSwitch">Sun arrow</label></div>
|
|
<span class="text-body-secondary">Wheel: border. The dashed square is the icon.</span>
|
|
</div>
|
|
<div id="poseView" class="rounded border" style="height: 420px"></div>
|
|
<div class="text-body-secondary mt-1" id="poseStats"></div>
|
|
</div>
|
|
<div class="col-lg-4 text-center">
|
|
<div class="d-flex justify-content-center gap-2 flex-wrap">
|
|
<figure class="mb-0"><img id="framingPreview" alt="Icon preview" class="border rounded ugc-checker" width="256" height="256" style="image-rendering: pixelated">
|
|
<figcaption class="text-body-secondary">Preview</figcaption></figure>
|
|
</div>
|
|
<div class="d-flex flex-column gap-1 mt-2">
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="framingPreviewButton">Draw the preview again</button>
|
|
<button type="button" class="btn btn-sm btn-outline-primary ugc-manage" id="framingSaveType">Save as the preset for this type</button>
|
|
<button type="button" class="btn btn-sm btn-outline-primary ugc-manage" id="framingSaveCombo">Save for this item</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="framingResetType">Back to the type's preset</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="framingResetDefaults">Back to the default settings</button>
|
|
<button type="button" class="btn btn-sm btn-outline-danger ugc-manage" id="framingReset">Remove this item's own values</button>
|
|
<button type="button" class="btn btn-sm btn-outline-warning ugc-manage" id="framingRegenerate">Draw all icons of this type again</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row g-2 mt-1" id="framingControls"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div></div>
|
|
</div>
|
|
</div>
|
|
<style>
|
|
.ugc-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: .5rem; }
|
|
.ugc-tile { cursor: pointer; text-align: center; }
|
|
.ugc-tile:hover, .ugc-tile:focus-visible { border-color: var(--bs-primary); }
|
|
.ugc-tile img, .ugc-tile .ugc-noicon { width: 128px; height: 128px; }
|
|
.ugc-noicon { display: inline-flex; align-items: center; justify-content: center; }
|
|
.ugc-checker { background-image: linear-gradient(45deg, rgba(128,128,128,.15) 25%, transparent 25%, transparent 75%, rgba(128,128,128,.15) 75%),
|
|
linear-gradient(45deg, rgba(128,128,128,.15) 25%, transparent 25%, transparent 75%, rgba(128,128,128,.15) 75%);
|
|
background-size: 16px 16px; background-position: 0 0, 8px 8px; }
|
|
.ugc-meter { height: .4rem; }
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script type="importmap">
|
|
{
|
|
"imports": {
|
|
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.min.js",
|
|
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
|
|
},
|
|
"integrity": {
|
|
"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.min.js": "sha384-IDC7sAMAIMB/TZ6dgKKPPAKZ2bXXXP8+FBMBC8cU319eBhKITx+PaalhfDkDNH28",
|
|
"https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/controls/OrbitControls.js": "sha384-aJoe4qqS/DgF2jh9njAuvA6QIveJYoCuYOfYjdFY8P3eawzmc9bEQ40jq2TvOyuP",
|
|
"https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/environments/RoomEnvironment.js": "sha384-9ItPPH2vgKbkvgrqTKq7ptGYe+WuAnqlZtlqleJW4/CJjnM5R7C+efD6iS7ADPJ9"
|
|
}
|
|
}
|
|
</script>
|
|
<script src="/js/ldd-materials.js"></script>
|
|
<script src="/js/ugc-icon-editor.js"></script>
|
|
<script src="/js/ugc.js"></script>
|
|
{% endblock %}
|