bloombase/plants/templates/plants/identify_upload.html
Stephan Kerkman 99fd95ee80 chore: rename PlantDB to BloomBase
Rename Django project module plantdb/ → bloombase/, update all
references in manage.py, docker-compose.yml, wsgi/asgi/settings.
Replace PlantDB with BloomBase in all template titles and navbar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 12:36:20 +02:00

118 lines
4.7 KiB
HTML

{% extends "plants/base.html" %}
{% block title %}Identify plant — BloomBase{% endblock %}
{% block content %}
<div class="d-flex align-items-center gap-2 mb-3">
<a href="{% url 'plant_list' %}" class="btn btn-sm btn-outline-secondary"></a>
<h5 class="mb-0">Identify plant</h5>
</div>
<p class="text-muted small mb-3">
Add one or more photos, tag what each shows, then tap Identify.
</p>
{% if error %}
<div class="alert alert-warning py-2 small mb-3">{{ error }}</div>
{% endif %}
<form method="post" enctype="multipart/form-data" id="identify-form">
{% csrf_token %}
<input type="file" name="images" accept="image/jpeg,image/png" class="d-none" id="file-picker">
<div id="photo-rows" class="mb-3"></div>
<div id="add-btn-wrap" class="mb-3">
<label class="btn btn-outline-secondary w-100 py-3" for="file-picker" id="add-photo-label">
📷 Add photo <span id="photo-count" class="text-muted small"></span>
</label>
</div>
<button type="submit" class="btn btn-success w-100" id="identify-btn" disabled>
Identify →
</button>
</form>
<div class="text-center mt-3">
<a href="{% url 'plant_add' %}" class="text-muted small">Or enter name manually</a>
</div>
<div id="identify-spinner" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(255,255,255,.88); z-index:9999; align-items:center; justify-content:center; flex-direction:column; gap:12px;">
<div class="spinner-border text-success" role="status"><span class="visually-hidden">Identifying…</span></div>
<p class="text-muted small mb-0">Identifying plant…</p>
</div>
<script>
const ORGANS = [
{val: 'flower', label: '🌸 flower'},
{val: 'leaf', label: '🍃 leaf'},
{val: 'habit', label: '🌿 habit'},
{val: 'fruit', label: '🍎 fruit'},
{val: 'bark', label: '🪵 bark'},
{val: 'auto', label: '✨ auto'},
];
const selected = []; // {file, organ, objectUrl}
const picker = document.getElementById('file-picker');
picker.addEventListener('change', function () {
if (!this.files[0] || selected.length >= 5) return;
selected.push({file: this.files[0], organ: 'auto', objectUrl: URL.createObjectURL(this.files[0])});
this.value = '';
render();
});
function setOrgan(idx, organ) {
selected[idx].organ = organ;
render();
}
function removePhoto(idx) {
URL.revokeObjectURL(selected[idx].objectUrl);
selected.splice(idx, 1);
render();
}
function render() {
const rows = document.getElementById('photo-rows');
rows.innerHTML = '';
selected.forEach((item, i) => {
const row = document.createElement('div');
row.className = 'd-flex gap-2 align-items-start mb-3';
row.innerHTML = `
<div style="position:relative;flex-shrink:0;">
<img src="${item.objectUrl}" style="width:72px;height:72px;object-fit:cover;border-radius:8px;" alt="">
<button type="button" onclick="removePhoto(${i})"
style="position:absolute;top:2px;right:2px;background:rgba(0,0,0,.5);color:#fff;border:none;border-radius:50%;width:20px;height:20px;font-size:10px;cursor:pointer;padding:0;line-height:1;">✕</button>
</div>
<div style="flex:1;">
<div class="text-muted small mb-1">What's shown?</div>
<div class="d-flex flex-wrap gap-1">
${ORGANS.map(o => `
<span onclick="setOrgan(${i}, '${o.val}')"
style="cursor:pointer; padding:2px 8px; border-radius:10px; font-size:11px; ${item.organ === o.val ? 'background:#52b788;color:#fff;' : 'background:#e9ecef;color:#6c757d;'}">
${o.label}
</span>
`).join('')}
</div>
<input type="hidden" name="organs" value="${item.organ}" id="organ-${i}">
</div>`;
rows.appendChild(row);
});
document.querySelectorAll('input[name="organs"]').forEach((el, i) => {
if (selected[i]) el.value = selected[i].organ;
});
document.getElementById('photo-count').textContent = selected.length ? `(${selected.length}/5)` : '';
document.getElementById('add-btn-wrap').style.display = selected.length >= 5 ? 'none' : '';
document.getElementById('identify-btn').disabled = selected.length === 0;
}
document.getElementById('identify-form').addEventListener('submit', function (e) {
if (selected.length === 0) { e.preventDefault(); return; }
const dt = new DataTransfer();
selected.forEach(s => dt.items.add(s.file));
picker.files = dt.files;
document.getElementById('identify-spinner').style.display = 'flex';
});
</script>
{% endblock %}