Upload files to "frontend/templates/partials"
This commit is contained in:
parent
c9685c4ce2
commit
c4b3c41718
4 changed files with 331 additions and 21 deletions
157
frontend/templates/partials/crop_section.html
Normal file
157
frontend/templates/partials/crop_section.html
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue