diff --git a/index.html b/index.html index 579a34d..46da92d 100644 --- a/index.html +++ b/index.html @@ -77,8 +77,22 @@
-

Sammlung kommt hier

-
+

Sammlung

+ +
+ + +
+ +

Favoriten

+
+ +

Verlauf (letzte 20)

+
+ +

Farbschemata

+
+ diff --git a/js/app.js b/js/app.js index db257ae..10ac70a 100644 --- a/js/app.js +++ b/js/app.js @@ -1,8 +1,8 @@ import { initEingabe } from './eingabe.js'; import { initPicker } from './picker.js'; import { initHarmonien } from './harmonien.js'; +import { addFavorit, addColorToSchema, addToHistory, renderSammlung, exportCollection, importCollection } from './collection.js'; -// Globaler State — aktive Farbe als { h, s, l } (HSL, 0-360, 0-100, 0-100) // state.color is read-only from outside — always use setColor() to update, // so that the colorChanged event is dispatched to all listening modules. export const state = { @@ -26,9 +26,14 @@ document.querySelectorAll('.tab-btn').forEach(btn => { }); }); -function addFavorit(hsl) { console.log('addFavorit', hsl); } -function addColorToSchema(hsl) { console.log('addColorToSchema', hsl); } +// Every color change goes into history +document.addEventListener('colorChanged', (e) => addToHistory(e.detail)); initEingabe(addFavorit, addColorToSchema); initPicker(addFavorit, addColorToSchema); initHarmonien(addFavorit, addColorToSchema); + +document.getElementById('sammlung-export-btn').addEventListener('click', exportCollection); +document.getElementById('sammlung-import-btn').addEventListener('click', importCollection); + +renderSammlung(); diff --git a/js/collection.js b/js/collection.js new file mode 100644 index 0000000..f620fb0 --- /dev/null +++ b/js/collection.js @@ -0,0 +1,232 @@ +import { hslToHex } from './converter.js'; + +const STORAGE_KEY = 'farbhelfer'; +const HISTORY_MAX = 20; + +function load() { + try { + return JSON.parse(localStorage.getItem(STORAGE_KEY)) || { favoriten: [], history: [], schemata: [] }; + } catch { + return { favoriten: [], history: [], schemata: [] }; + } +} + +function save(data) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); +} + +export function addFavorit(hsl) { + const data = load(); + const hex = hslToHex(hsl); + if (!data.favoriten.some(f => hslToHex(f) === hex)) { + data.favoriten.push(hsl); + save(data); + renderSammlung(); + } +} + +export function removeFavorit(hex) { + const data = load(); + data.favoriten = data.favoriten.filter(f => hslToHex(f) !== hex); + save(data); + renderSammlung(); +} + +export function addToHistory(hsl) { + const data = load(); + const hex = hslToHex(hsl); + data.history = data.history.filter(h => hslToHex(h) !== hex); + data.history.unshift(hsl); + if (data.history.length > HISTORY_MAX) data.history = data.history.slice(0, HISTORY_MAX); + save(data); +} + +export function addColorToSchema(hsl) { + const name = prompt('Schema-Name (leer = letztes Schema):'); + const data = load(); + if (name) { + const existing = data.schemata.find(s => s.name === name); + if (existing) { + if (existing.farben.length < 4) { + existing.farben.push(hsl); + save(data); + renderSammlung(); + } else { + alert('Schema hat bereits 4 Farben.'); + } + } else { + data.schemata.push({ name, farben: [hsl] }); + save(data); + renderSammlung(); + } + } else if (data.schemata.length > 0) { + const last = data.schemata[data.schemata.length - 1]; + if (last.farben.length < 4) { + last.farben.push(hsl); + save(data); + renderSammlung(); + } else { + alert('Letztes Schema hat bereits 4 Farben.'); + } + } else { + alert('Kein Schema vorhanden. Bitte Name eingeben.'); + } +} + +export function deleteSchema(name) { + const data = load(); + data.schemata = data.schemata.filter(s => s.name !== name); + save(data); + renderSammlung(); +} + +export function exportCollection() { + const data = load(); + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = 'farbhelfer-sammlung.json'; + a.click(); +} + +export function importCollection() { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.json'; + input.onchange = (e) => { + const file = e.target.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = (ev) => { + try { + const imported = JSON.parse(ev.target.result); + const data = load(); + const existingFavHexes = new Set(data.favoriten.map(hslToHex)); + (imported.favoriten || []).forEach(hsl => { + if (!existingFavHexes.has(hslToHex(hsl))) data.favoriten.push(hsl); + }); + const existingSchemaNames = new Set(data.schemata.map(s => s.name)); + (imported.schemata || []).forEach(s => { + if (!existingSchemaNames.has(s.name)) data.schemata.push(s); + }); + save(data); + renderSammlung(); + alert('Import erfolgreich.'); + } catch { + alert('Ungultige Datei.'); + } + }; + reader.readAsText(file); + }; + input.click(); +} + +function makeSwatch(hsl) { + const hex = hslToHex(hsl); + + const wrapper = document.createElement('div'); + wrapper.style.cssText = 'display:inline-flex;flex-direction:column;align-items:center;gap:0.25rem'; + + const swatch = document.createElement('div'); + swatch.className = 'swatch'; + swatch.style.background = hex; + swatch.title = hex; + + const label = document.createElement('span'); + label.style.cssText = 'font-size:0.75rem;font-family:monospace'; + label.textContent = hex; + + wrapper.appendChild(swatch); + wrapper.appendChild(label); + return wrapper; +} + +export function renderSammlung() { + const data = load(); + + // Favoriten + const favContainer = document.getElementById('sammlung-favoriten'); + if (favContainer) { + favContainer.textContent = ''; + if (data.favoriten.length === 0) { + const msg = document.createElement('p'); + msg.style.cssText = 'color:#999;font-size:0.85rem'; + msg.textContent = 'Noch keine Favoriten.'; + favContainer.appendChild(msg); + } else { + data.favoriten.forEach(hsl => { + const hex = hslToHex(hsl); + const cell = document.createElement('div'); + cell.style.cssText = 'display:inline-flex;flex-direction:column;align-items:center;gap:0.25rem;margin:0.25rem'; + cell.appendChild(makeSwatch(hsl)); + + const del = document.createElement('button'); + del.className = 'action-btn'; + del.textContent = 'Loschen'; + del.style.fontSize = '0.7rem'; + del.addEventListener('click', () => removeFavorit(hex)); + cell.appendChild(del); + favContainer.appendChild(cell); + }); + } + } + + // Historie + const histContainer = document.getElementById('sammlung-history'); + if (histContainer) { + histContainer.textContent = ''; + if (data.history.length === 0) { + const msg = document.createElement('p'); + msg.style.cssText = 'color:#999;font-size:0.85rem'; + msg.textContent = 'Noch kein Verlauf.'; + histContainer.appendChild(msg); + } else { + data.history.forEach(hsl => { + const cell = document.createElement('div'); + cell.style.cssText = 'display:inline-block;margin:0.25rem'; + cell.appendChild(makeSwatch(hsl)); + histContainer.appendChild(cell); + }); + } + } + + // Schemata + const schemataContainer = document.getElementById('sammlung-schemata'); + if (schemataContainer) { + schemataContainer.textContent = ''; + if (data.schemata.length === 0) { + const msg = document.createElement('p'); + msg.style.cssText = 'color:#999;font-size:0.85rem'; + msg.textContent = 'Noch keine Schemata.'; + schemataContainer.appendChild(msg); + } else { + data.schemata.forEach(schema => { + const card = document.createElement('div'); + card.style.cssText = 'border:1px solid #ddd;border-radius:8px;padding:1rem;margin-bottom:0.75rem;background:#fff'; + + const header = document.createElement('div'); + header.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem'; + + const nameEl = document.createElement('strong'); + nameEl.textContent = schema.name; + + const delBtn = document.createElement('button'); + delBtn.className = 'action-btn'; + delBtn.textContent = 'Schema loschen'; + delBtn.style.fontSize = '0.75rem'; + delBtn.addEventListener('click', () => deleteSchema(schema.name)); + + header.appendChild(nameEl); + header.appendChild(delBtn); + + const swatchesDiv = document.createElement('div'); + swatchesDiv.style.cssText = 'display:flex;gap:0.5rem;flex-wrap:wrap'; + schema.farben.forEach(hsl => swatchesDiv.appendChild(makeSwatch(hsl))); + + card.appendChild(header); + card.appendChild(swatchesDiv); + schemataContainer.appendChild(card); + }); + } + } +}