Upload files to "frontend/templates/partials"

This commit is contained in:
Thierry 2026-04-01 23:26:17 +02:00
parent c9685c4ce2
commit c4b3c41718
4 changed files with 331 additions and 21 deletions

View file

@ -0,0 +1,157 @@
<!-- crop_section.html — injecté dans #crop-panel (hors de viewer-panel) -->
<div class="card bg-base-100 shadow border border-warning/30" x-data="cropForm()">
<div class="card-body">
<!-- En-tête -->
<div class="flex items-center justify-between mb-2">
<h2 class="card-title text-base">
✂️ Crop du nuage
<span class="badge badge-warning font-mono text-xs">{{ pc_id }}</span>
</h2>
<button
type="button"
class="btn btn-ghost btn-sm"
onclick="document.getElementById('crop-panel').innerHTML = ''"
>✕ Fermer</button>
</div>
<p class="text-sm text-base-content/60 mb-4">
Renseignez les coordonnées de la boîte 3D à découper. Les valeurs sont dans
le système de coordonnées du nuage de points (en mètres ou en unités du fichier source).
</p>
<!-- Schéma visuel de la box -->
<div class="bg-base-200 rounded-lg p-4 mb-4 flex items-center justify-center gap-8">
<div class="text-center">
<div class="text-xs text-base-content/50 mb-1">Vue de dessus (XY)</div>
<svg width="110" height="75" viewBox="0 0 110 75">
<ellipse cx="55" cy="37" rx="50" ry="30" fill="none" stroke="currentColor" stroke-opacity="0.15" stroke-width="1" stroke-dasharray="3 2"/>
<rect x="18" y="14" width="74" height="46" rx="2" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="4 2"/>
<text x="100" y="40" font-size="9" fill="currentColor" opacity="0.4">X</text>
<text x="52" y="9" font-size="9" fill="currentColor" opacity="0.4">Y</text>
</svg>
</div>
<div class="text-center">
<div class="text-xs text-base-content/50 mb-1">Vue de côté (XZ)</div>
<svg width="110" height="75" viewBox="0 0 110 75">
<ellipse cx="55" cy="37" rx="50" ry="25" fill="none" stroke="currentColor" stroke-opacity="0.15" stroke-width="1" stroke-dasharray="3 2"/>
<rect x="18" y="16" width="74" height="42" rx="2" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="4 2"/>
<text x="100" y="40" font-size="9" fill="currentColor" opacity="0.4">X</text>
<text x="52" y="9" font-size="9" fill="currentColor" opacity="0.4">Z</text>
</svg>
</div>
</div>
<!-- Formulaire -->
<form
hx-post="/admin/crop/{{ pc_id }}"
hx-target="#crop-result"
hx-swap="innerHTML"
hx-indicator="#crop-spinner"
>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-4">
<!-- X -->
<div>
<div class="text-xs font-semibold text-base-content/50 uppercase tracking-wider mb-2">Axe X</div>
<div class="flex flex-col gap-2">
<div>
<label class="label py-0 pb-1"><span class="label-text text-xs">Min X</span></label>
<input type="number" step="any" name="minX" x-model="box.minX"
class="input input-bordered input-sm w-full font-mono" placeholder="ex: 100.0" required/>
</div>
<div>
<label class="label py-0 pb-1"><span class="label-text text-xs">Max X</span></label>
<input type="number" step="any" name="maxX" x-model="box.maxX"
class="input input-bordered input-sm w-full font-mono" placeholder="ex: 500.0" required/>
</div>
</div>
</div>
<!-- Y -->
<div>
<div class="text-xs font-semibold text-base-content/50 uppercase tracking-wider mb-2">Axe Y</div>
<div class="flex flex-col gap-2">
<div>
<label class="label py-0 pb-1"><span class="label-text text-xs">Min Y</span></label>
<input type="number" step="any" name="minY" x-model="box.minY"
class="input input-bordered input-sm w-full font-mono" placeholder="ex: 200.0" required/>
</div>
<div>
<label class="label py-0 pb-1"><span class="label-text text-xs">Max Y</span></label>
<input type="number" step="any" name="maxY" x-model="box.maxY"
class="input input-bordered input-sm w-full font-mono" placeholder="ex: 800.0" required/>
</div>
</div>
</div>
<!-- Z -->
<div>
<div class="text-xs font-semibold text-base-content/50 uppercase tracking-wider mb-2">Axe Z (altitude)</div>
<div class="flex flex-col gap-2">
<div>
<label class="label py-0 pb-1"><span class="label-text text-xs">Min Z</span></label>
<input type="number" step="any" name="minZ" x-model="box.minZ"
class="input input-bordered input-sm w-full font-mono" placeholder="ex: 10.0" required/>
</div>
<div>
<label class="label py-0 pb-1"><span class="label-text text-xs">Max Z</span></label>
<input type="number" step="any" name="maxZ" x-model="box.maxZ"
class="input input-bordered input-sm w-full font-mono" placeholder="ex: 50.0" required/>
</div>
</div>
</div>
</div>
<!-- Résumé dimensions en temps réel -->
<div class="bg-base-200 rounded p-3 mb-4 text-xs font-mono" x-show="isValid()">
<span class="text-base-content/60 mr-2">Dimensions :</span>
<span class="text-warning">ΔX=<span x-text="delta('X')"></span></span>
<span class="mx-2 text-base-content/30">|</span>
<span class="text-warning">ΔY=<span x-text="delta('Y')"></span></span>
<span class="mx-2 text-base-content/30">|</span>
<span class="text-warning">ΔZ=<span x-text="delta('Z')"></span></span>
</div>
<div class="flex items-center gap-3">
<button
type="submit"
class="btn btn-warning"
:disabled="!isValid()"
>
✂️ Lancer le crop
</button>
<div id="crop-spinner" class="loading loading-spinner loading-sm htmx-indicator text-warning"></div>
<span class="text-xs text-base-content/50">Traitement PDAL — peut prendre plusieurs minutes</span>
</div>
</form>
<div id="crop-result" class="mt-4"></div>
</div>
</div>
<script>
function cropForm() {
return {
box: { minX: '', minY: '', minZ: '', maxX: '', maxY: '', maxZ: '' },
isValid() {
const keys = ['minX', 'minY', 'minZ', 'maxX', 'maxY', 'maxZ'];
if (keys.some(k => this.box[k] === '' || isNaN(parseFloat(this.box[k])))) return false;
return (
parseFloat(this.box.minX) < parseFloat(this.box.maxX) &&
parseFloat(this.box.minY) < parseFloat(this.box.maxY) &&
parseFloat(this.box.minZ) < parseFloat(this.box.maxZ)
);
},
delta(axis) {
const min = parseFloat(this.box['min' + axis]);
const max = parseFloat(this.box['max' + axis]);
if (isNaN(min) || isNaN(max) || min >= max) return '—';
return (max - min).toFixed(3);
}
}
}
</script>