feat: color picker palette + filtre/tri/duplicate matières
Deploy via Portainer / deploy (push) Successful in 0s
Deploy via Portainer / deploy (push) Successful in 0s
This commit is contained in:
@@ -494,6 +494,25 @@ def fmt_time(s):
|
||||
return f"{s//3600}h{(s%3600)//60:02d}m"
|
||||
app.jinja_env.filters['fmt_time'] = fmt_time
|
||||
|
||||
_FRENCH_COLORS = {
|
||||
'blanc': '#ffffff', 'noir': '#111111', 'gris': '#6b7280', 'argent': '#9ca3af',
|
||||
'rouge': '#ef4444', 'rose': '#ec4899', 'fuchsia': '#d946ef',
|
||||
'orange': '#f97316', 'jaune': '#eab308', 'or': '#d97706',
|
||||
'vert': '#22c55e', 'olive': '#65a30d', 'emeraude': '#10b981',
|
||||
'bleu': '#3b82f6', 'marine': '#1e3a8a', 'ciel': '#38bdf8', 'cyan': '#06b6d4',
|
||||
'violet': '#8b5cf6', 'mauve': '#a855f7', 'indigo': '#6366f1',
|
||||
'marron': '#92400e', 'beige': '#d4b896', 'creme': '#fef3c7',
|
||||
'turquoise': '#14b8a6', 'or rose': '#fb7185',
|
||||
'transparent': 'rgba(200,200,200,0.3)', 'naturel': '#f5f0e8',
|
||||
'translucide': 'rgba(200,200,200,0.3)',
|
||||
}
|
||||
|
||||
def css_color(value):
|
||||
if not value:
|
||||
return value
|
||||
return _FRENCH_COLORS.get(value.strip().lower(), value)
|
||||
app.jinja_env.filters['css_color'] = css_color
|
||||
|
||||
# ─── Dashboard ────────────────────────────────────────────────────────────────
|
||||
|
||||
@app.route('/')
|
||||
|
||||
@@ -35,11 +35,34 @@
|
||||
value="{{ material.brand if material else '' }}"
|
||||
placeholder="Bambu, eSUN, Polymaker…">
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Couleur</label>
|
||||
<input type="text" name="color" class="form-control"
|
||||
value="{{ material.color if material else '' }}"
|
||||
placeholder="Rouge, #FF0000…">
|
||||
<!-- Valeur soumise (hex) -->
|
||||
<input type="hidden" name="color" id="colorValue"
|
||||
value="{{ (material.color | css_color) if material and material.color else '' }}">
|
||||
|
||||
<!-- Swatches preset -->
|
||||
<div id="swatchGrid" class="mb-2 d-flex flex-wrap gap-1">
|
||||
<!-- Générés par JS -->
|
||||
</div>
|
||||
|
||||
<!-- Ligne : preview + picker natif + hex -->
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span id="colorPreview"
|
||||
style="width:32px;height:32px;border-radius:6px;border:2px solid #dee2e6;
|
||||
flex-shrink:0;background:{{ (material.color | css_color) if material and material.color else '#cccccc' }}"></span>
|
||||
<input type="color" id="nativePicker" class="form-control form-control-color p-0 border-0"
|
||||
style="width:36px;height:32px;cursor:pointer"
|
||||
value="{{ (material.color | css_color) if material and material.color else '#cccccc' }}"
|
||||
title="Choisir une couleur personnalisée">
|
||||
<input type="text" id="hexInput" class="form-control form-control-sm font-monospace"
|
||||
style="max-width:110px" placeholder="#rrggbb"
|
||||
value="{{ (material.color | css_color) if material and material.color else '' }}">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" id="clearColor"
|
||||
title="Aucune couleur">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label fw-semibold">Prix (€/kg) *</label>
|
||||
@@ -98,3 +121,126 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
(function () {
|
||||
// ── Palette de preset filaments ──────────────────────────────────────
|
||||
const PRESETS = [
|
||||
// Blancs / Neutres
|
||||
{ hex: '#ffffff', label: 'Blanc' },
|
||||
{ hex: '#f5f0e8', label: 'Naturel' },
|
||||
{ hex: '#e8e0d0', label: 'Crème' },
|
||||
{ hex: '#d4b896', label: 'Beige' },
|
||||
{ hex: '#9ca3af', label: 'Argent' },
|
||||
{ hex: '#6b7280', label: 'Gris' },
|
||||
{ hex: '#374151', label: 'Gris foncé' },
|
||||
{ hex: '#111111', label: 'Noir' },
|
||||
// Rouges / Roses
|
||||
{ hex: '#fca5a5', label: 'Rose clair' },
|
||||
{ hex: '#f87171', label: 'Saumon' },
|
||||
{ hex: '#ef4444', label: 'Rouge' },
|
||||
{ hex: '#dc2626', label: 'Rouge foncé' },
|
||||
{ hex: '#ec4899', label: 'Rose' },
|
||||
{ hex: '#db2777', label: 'Fuchsia' },
|
||||
// Oranges / Jaunes
|
||||
{ hex: '#fb923c', label: 'Orange clair' },
|
||||
{ hex: '#f97316', label: 'Orange' },
|
||||
{ hex: '#ea580c', label: 'Orange foncé' },
|
||||
{ hex: '#fbbf24', label: 'Jaune' },
|
||||
{ hex: '#d97706', label: 'Or' },
|
||||
{ hex: '#eab308', label: 'Jaune vif' },
|
||||
// Verts
|
||||
{ hex: '#86efac', label: 'Vert clair' },
|
||||
{ hex: '#4ade80', label: 'Vert lime' },
|
||||
{ hex: '#22c55e', label: 'Vert' },
|
||||
{ hex: '#16a34a', label: 'Vert foncé' },
|
||||
{ hex: '#15803d', label: 'Forêt' },
|
||||
{ hex: '#65a30d', label: 'Olive' },
|
||||
// Bleus / Cyans
|
||||
{ hex: '#7dd3fc', label: 'Bleu ciel' },
|
||||
{ hex: '#38bdf8', label: 'Ciel' },
|
||||
{ hex: '#06b6d4', label: 'Cyan' },
|
||||
{ hex: '#3b82f6', label: 'Bleu' },
|
||||
{ hex: '#2563eb', label: 'Bleu vif' },
|
||||
{ hex: '#1d4ed8', label: 'Bleu foncé' },
|
||||
{ hex: '#1e3a8a', label: 'Marine' },
|
||||
// Violets / Mauves
|
||||
{ hex: '#c4b5fd', label: 'Lavande' },
|
||||
{ hex: '#a78bfa', label: 'Mauve' },
|
||||
{ hex: '#8b5cf6', label: 'Violet' },
|
||||
{ hex: '#7c3aed', label: 'Violet foncé' },
|
||||
{ hex: '#6366f1', label: 'Indigo' },
|
||||
{ hex: '#d946ef', label: 'Magenta' },
|
||||
// Marrons
|
||||
{ hex: '#b45309', label: 'Marron clair' },
|
||||
{ hex: '#92400e', label: 'Marron' },
|
||||
{ hex: '#78350f', label: 'Marron foncé' },
|
||||
// Spéciaux
|
||||
{ hex: 'rgba(200,200,200,0.3)', label: 'Translucide', css: 'repeating-linear-gradient(45deg,#ccc 0,#ccc 3px,#fff 3px,#fff 8px)' },
|
||||
];
|
||||
|
||||
const colorValue = document.getElementById('colorValue');
|
||||
const colorPreview = document.getElementById('colorPreview');
|
||||
const nativePicker = document.getElementById('nativePicker');
|
||||
const hexInput = document.getElementById('hexInput');
|
||||
const swatchGrid = document.getElementById('swatchGrid');
|
||||
|
||||
// ── Construire les swatches ──────────────────────────────────────────
|
||||
PRESETS.forEach(p => {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.title = p.label;
|
||||
btn.dataset.hex = p.hex;
|
||||
btn.style.cssText = [
|
||||
'width:24px', 'height:24px', 'border-radius:5px', 'border:2px solid transparent',
|
||||
`background:${p.css || p.hex}`, 'cursor:pointer', 'padding:0', 'flex-shrink:0',
|
||||
'transition:transform .1s,border-color .1s'
|
||||
].join(';');
|
||||
btn.addEventListener('mouseenter', () => { btn.style.transform = 'scale(1.3)'; });
|
||||
btn.addEventListener('mouseleave', () => { if (btn.dataset.hex !== colorValue.value) btn.style.transform = ''; });
|
||||
btn.addEventListener('click', () => setColor(p.hex));
|
||||
swatchGrid.appendChild(btn);
|
||||
});
|
||||
|
||||
// ── Fonction centrale ────────────────────────────────────────────────
|
||||
function setColor(hex) {
|
||||
colorValue.value = hex;
|
||||
hexInput.value = hex;
|
||||
colorPreview.style.background = hex;
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(hex)) nativePicker.value = hex;
|
||||
// Highlight swatch actif
|
||||
swatchGrid.querySelectorAll('button').forEach(b => {
|
||||
const active = b.dataset.hex === hex;
|
||||
b.style.borderColor = active ? '#ff6b35' : 'transparent';
|
||||
b.style.transform = active ? 'scale(1.3)' : '';
|
||||
});
|
||||
}
|
||||
|
||||
// ── Native color picker ──────────────────────────────────────────────
|
||||
nativePicker.addEventListener('input', () => setColor(nativePicker.value));
|
||||
|
||||
// ── Saisie hex manuelle ──────────────────────────────────────────────
|
||||
hexInput.addEventListener('input', function () {
|
||||
const v = this.value.trim();
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(v)) setColor(v);
|
||||
else { colorValue.value = v; colorPreview.style.background = v; }
|
||||
});
|
||||
|
||||
// ── Effacer ──────────────────────────────────────────────────────────
|
||||
document.getElementById('clearColor').addEventListener('click', () => {
|
||||
colorValue.value = '';
|
||||
hexInput.value = '';
|
||||
colorPreview.style.background = '#e5e7eb';
|
||||
swatchGrid.querySelectorAll('button').forEach(b => {
|
||||
b.style.borderColor = 'transparent';
|
||||
b.style.transform = '';
|
||||
});
|
||||
});
|
||||
|
||||
// ── Init ─────────────────────────────────────────────────────────────
|
||||
const init = colorValue.value;
|
||||
if (init) setColor(init);
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
<td>
|
||||
{% if m.color %}
|
||||
<span class="d-inline-flex align-items-center gap-1">
|
||||
<span style="width:14px;height:14px;border-radius:50%;background:{{ m.color }};border:1px solid #ccc;display:inline-block"></span>
|
||||
<span style="width:14px;height:14px;border-radius:50%;background:{{ m.color | css_color }};border:1px solid #ccc;display:inline-block"></span>
|
||||
{{ m.color }}
|
||||
</span>
|
||||
{% else %}—{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user