Files
Pigmento/js/collection.js
Ferdinand cf31d6bbd2 style: alle font-family auf Poppins umgestellt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 14:00:32 +02:00

425 lines
14 KiB
JavaScript

import { hslToHex } from './converter.js';
const STORAGE_KEY = 'pigmento';
let editSchemaHandler = null;
export function setEditSchemaHandler(fn) { editSchemaHandler = fn; }
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):');
if (name === null) return; // user pressed Cancel — do nothing
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 saveSchema(name, farben, bild, originalName) {
const data = load();
if (originalName && originalName !== name) {
const old = data.schemata.find(s => s.name === originalName);
const preservedParent = old?.parent;
// Kind-Referenzen auf neuen Namen umbiegen
data.schemata.forEach(s => { if (s.parent === originalName) s.parent = name; });
data.schemata = data.schemata.filter(s => s.name !== originalName);
const entry = { name, farben, bild: bild || null };
if (preservedParent) entry.parent = preservedParent;
data.schemata.push(entry);
} else {
const existing = data.schemata.find(s => s.name === name);
if (existing) {
existing.farben = farben;
if (bild !== undefined) existing.bild = bild || null;
// parent bleibt erhalten
} else {
data.schemata.push({ name, farben, bild: bild || null });
}
}
save(data);
renderSammlung();
}
export function deleteSchema(name) {
const data = load();
const schema = data.schemata.find(s => s.name === name);
const parentOfDeleted = schema?.parent || null;
// Kinder auf die Eltern-Ebene des gelöschten Schemas verschieben
data.schemata.forEach(s => {
if (s.parent === name) {
if (parentOfDeleted) { s.parent = parentOfDeleted; } else { delete s.parent; }
}
});
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 url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'pigmento-sammlung.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
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 (!hsl || typeof hsl.h !== 'number' || typeof hsl.s !== 'number' || typeof hsl.l !== 'number') return;
if (!existingFavHexes.has(hslToHex(hsl))) data.favoriten.push(hsl);
});
const existingSchemaNames = new Set(data.schemata.map(s => s.name));
(imported.schemata || []).forEach(s => {
if (!s || typeof s.name !== 'string' || !Array.isArray(s.farben)) return;
if (!existingSchemaNames.has(s.name)) data.schemata.push(s);
});
const existingHistHexes = new Set(data.history.map(hslToHex));
(imported.history || []).forEach(hsl => {
if (!hsl || typeof hsl.h !== 'number' || typeof hsl.s !== 'number' || typeof hsl.l !== 'number') return;
if (!existingHistHexes.has(hslToHex(hsl))) data.history.push(hsl);
});
if (data.history.length > 20) data.history = data.history.slice(0, 20);
save(data);
renderSammlung();
alert('Import erfolgreich.');
} catch {
alert('Ungültige Datei.');
}
};
reader.readAsText(file);
};
input.click();
}
// --- Schema-Hierarchie ---
function buildTree(schemata) {
const nodes = {};
schemata.forEach(s => { nodes[s.name] = { ...s, children: [] }; });
const roots = [];
schemata.forEach(s => {
if (s.parent && nodes[s.parent]) {
nodes[s.parent].children.push(nodes[s.name]);
} else {
roots.push(nodes[s.name]);
}
});
return roots;
}
function isDescendant(schemata, ancestorName, checkName) {
for (const s of schemata) {
if (s.parent === ancestorName) {
if (s.name === checkName || isDescendant(schemata, s.name, checkName)) return true;
}
}
return false;
}
function setSchemaParent(childName, parentName) {
const data = load();
const schema = data.schemata.find(s => s.name === childName);
if (!schema) return;
if (parentName) { schema.parent = parentName; } else { delete schema.parent; }
save(data);
renderSammlung();
}
let draggedSchemaName = null;
function renderSchemaCard(node, container, allSchemata, depth) {
const card = document.createElement('div');
card.className = depth === 0 ? 'schema-card' : 'schema-card schema-card-nested';
card.dataset.schemaName = node.name;
card.draggable = true;
card.addEventListener('dragstart', (e) => {
draggedSchemaName = node.name;
e.dataTransfer.effectAllowed = 'move';
setTimeout(() => card.classList.add('schema-dragging'), 0);
});
card.addEventListener('dragend', () => {
card.classList.remove('schema-dragging');
draggedSchemaName = null;
document.querySelectorAll('.schema-drag-over').forEach(el => el.classList.remove('schema-drag-over'));
});
card.addEventListener('dragover', (e) => {
if (!draggedSchemaName || draggedSchemaName === node.name) return;
if (isDescendant(allSchemata, draggedSchemaName, node.name)) return;
e.preventDefault();
e.stopPropagation();
document.querySelectorAll('.schema-drag-over').forEach(el => el.classList.remove('schema-drag-over'));
card.classList.add('schema-drag-over');
});
card.addEventListener('dragleave', (e) => {
if (!card.contains(e.relatedTarget)) card.classList.remove('schema-drag-over');
});
card.addEventListener('drop', (e) => {
e.preventDefault();
e.stopPropagation();
card.classList.remove('schema-drag-over');
if (!draggedSchemaName || draggedSchemaName === node.name) return;
if (isDescendant(allSchemata, draggedSchemaName, node.name)) return;
setSchemaParent(draggedSchemaName, node.name);
});
const header = document.createElement('div');
header.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem;gap:0.5rem';
const nameRow = document.createElement('div');
nameRow.style.cssText = 'display:flex;align-items:center;gap:0.5rem;min-width:0';
const handle = document.createElement('span');
handle.textContent = '⠿';
handle.title = 'Ziehen um Untergruppe zu setzen';
handle.style.cssText = 'color:#ccc;cursor:grab;font-size:1.1rem;user-select:none;flex-shrink:0';
nameRow.appendChild(handle);
if (node.bild) {
const thumb = document.createElement('div');
thumb.className = 'schema-thumb';
thumb.style.backgroundImage = 'url(' + node.bild + ')';
nameRow.appendChild(thumb);
}
const nameEl = document.createElement('strong');
nameEl.textContent = node.name;
nameRow.appendChild(nameEl);
const btnGroup = document.createElement('div');
btnGroup.style.cssText = 'display:flex;gap:0.4rem;flex-shrink:0';
if (depth > 0) {
const unestBtn = document.createElement('button');
unestBtn.className = 'action-btn';
unestBtn.textContent = '↑ Entnesten';
unestBtn.style.fontSize = '0.75rem';
unestBtn.addEventListener('click', () => setSchemaParent(node.name, null));
btnGroup.appendChild(unestBtn);
}
const editBtn = document.createElement('button');
editBtn.className = 'action-btn';
editBtn.textContent = 'Bearbeiten';
editBtn.style.fontSize = '0.75rem';
editBtn.addEventListener('click', () => editSchemaHandler?.(node));
const delBtn = document.createElement('button');
delBtn.className = 'action-btn';
delBtn.textContent = 'Löschen';
delBtn.style.fontSize = '0.75rem';
delBtn.addEventListener('click', () => {
if (!confirm('"' + node.name + '" wirklich löschen?')) return;
deleteSchema(node.name);
});
btnGroup.appendChild(editBtn);
btnGroup.appendChild(delBtn);
header.appendChild(nameRow);
header.appendChild(btnGroup);
const swatchesDiv = document.createElement('div');
swatchesDiv.style.cssText = 'display:flex;gap:0.5rem;flex-wrap:wrap';
node.farben.forEach(hsl => swatchesDiv.appendChild(makeSwatch(hsl)));
card.appendChild(header);
card.appendChild(swatchesDiv);
if (node.children.length > 0) {
const childrenDiv = document.createElement('div');
childrenDiv.className = 'schema-children';
node.children.forEach(child => renderSchemaCard(child, childrenDiv, allSchemata, depth + 1));
card.appendChild(childrenDiv);
}
container.appendChild(card);
}
function makeSwatch(hsl) {
const hex = hslToHex(hsl);
const wrapper = document.createElement('div');
wrapper.className = 'swatch-cell';
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:Poppins,sans-serif';
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 = makeSwatch(hsl);
const del = document.createElement('button');
del.className = 'action-btn';
del.textContent = 'Löschen';
del.style.fontSize = '0.7rem';
del.addEventListener('click', () => {
if (!confirm('Favorit wirklich löschen?')) return;
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 {
const clearBtn = document.createElement('button');
clearBtn.className = 'action-btn';
clearBtn.textContent = 'Verlauf löschen';
clearBtn.style.cssText = 'font-size:0.75rem;margin-bottom:0.75rem;display:block';
clearBtn.addEventListener('click', () => {
if (!confirm('Verlauf wirklich löschen?')) return;
const d = load();
d.history = [];
save(d);
renderSammlung();
});
histContainer.appendChild(clearBtn);
data.history.forEach(hsl => histContainer.appendChild(makeSwatch(hsl)));
}
}
// 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 {
const tree = buildTree(data.schemata);
tree.forEach(node => renderSchemaCard(node, schemataContainer, data.schemata, 0));
const rootZone = document.createElement('div');
rootZone.className = 'schema-root-dropzone';
rootZone.textContent = 'Hierher ziehen um auf oberste Ebene zu verschieben';
rootZone.addEventListener('dragover', (e) => {
if (!draggedSchemaName) return;
e.preventDefault();
rootZone.classList.add('active');
});
rootZone.addEventListener('dragleave', () => rootZone.classList.remove('active'));
rootZone.addEventListener('drop', (e) => {
e.preventDefault();
rootZone.classList.remove('active');
if (draggedSchemaName) setSchemaParent(draggedSchemaName, null);
});
schemataContainer.appendChild(rootZone);
}
}
}