This commit is contained in:
@@ -8,7 +8,7 @@ Aktueller Projektstand
|
||||
- Der Avatar-Proxy liegt unter `public/api/avatar.php`.
|
||||
- Die visuelle Avatar-Konfiguration liegt im Dashboard-Modal unter `partials/landing/account/dashboard.php` und rendert Komponenten-Vorschauen live nach.
|
||||
- Der Generator speichert `avatar_style`, `avatar_seed` und `avatar_config_json` generisch im Profil.
|
||||
- Der Generator zeigt pro Style auch die von DiceBear unterstützten Farbfelder inklusive Fuellung, Stopps und Winkel und speichert diese im gleichen Config-JSON.
|
||||
- Der Generator zeigt pro Style die von DiceBear unterstützten Farbfelder direkt in den passenden Komponentenabschnitten und speichert diese im gleichen Config-JSON.
|
||||
- Gespeicherte Benutzer-Avatare werden lokal unter `public/assets/avatars/users/ptk-<userId>.svg` abgelegt und bei Änderungen überschrieben.
|
||||
- Der Generator besitzt einen Button `Random Profilbild`, der Seed und Varianten des aktiven Styles zufällig neu setzt.
|
||||
- Beim Oeffnen des Modals werden nur die Bilder des aktiven Styles und Tabs geladen; weitere Tabs laden ihre Varianten erst beim Wechsel nach.
|
||||
|
||||
@@ -26,7 +26,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
|
||||
- Standortfunktionen sind relevant für lokale Treffen und werden künftig weiter ausgebaut.
|
||||
- Profilbilder werden aktuell über die Mehr-Style-Architektur serverseitig erzeugt; aktiv sind derzeit `lorelei` und `croodles`.
|
||||
- Stil, Seed und Varianten werden generisch im Profil gespeichert; das fertige SVG wird lokal pro Benutzer unter `ptk-<userId>.svg` abgelegt.
|
||||
- Der Avatar-Editor bietet visuelle Komponenten-Vorschauen statt reiner Text-Dropdowns, laedt nur den aktiven Style/Tab nach und zeigt pro Style die unterstützten Farb-, Fuellungs-, Stopps- und Winkelparameter an.
|
||||
- Der Avatar-Editor bietet visuelle Komponenten-Vorschauen statt reiner Text-Dropdowns, laedt nur den aktiven Style/Tab nach und zeigt die Farbfelder direkt im passenden Komponentenabschnitt an.
|
||||
|
||||
## Navigation / Konto
|
||||
- Eingeloggte Nutzer sehen rechts ein Profil-Menü statt einzelner `Dashboard`-/`Logout`-Buttons.
|
||||
|
||||
@@ -19,9 +19,9 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale
|
||||
- eigener Einstellungsbereich für Standortfreigabe, Browser-Hinweise und Consent-Verwaltung
|
||||
- modernes Avatar-System mit zentraler Mehr-Style-Architektur auf Basis von DiceBear
|
||||
- aktive Avatar-Styles aktuell: `lorelei` und `croodles`; weitere Styles können über `config/avatar.php` freigeschaltet werden
|
||||
- Profilbild-Generator mit visueller Komponenten-Auswahl, generischem Style-/Seed-/Config-Speichermodell, Style-Wechsel und vollständigen Farb-, Füllungs-, Stopps- und Winkeloptionen pro Style
|
||||
- Profilbild-Generator mit visueller Komponenten-Auswahl, generischem Style-/Seed-/Config-Speichermodell, Style-Wechsel und komponentenbezogenen Farbfeldern pro Style
|
||||
- jedes gespeicherte Profilbild wird als lokale Benutzer-SVG unter `public/assets/avatars/users/ptk-<userId>.svg` abgelegt
|
||||
- `config/avatar.php` enthaelt jetzt pro Style auch explizite Beispielpaletten und Beispielprofile als Einbindungsreferenz
|
||||
- `config/avatar.php` enthaelt jetzt pro Style auch explizite Beispielpaletten, Farbgruppen pro Komponente und Beispielprofile als Einbindungsreferenz
|
||||
- Consent-Manager für Analyse und externe Dienste
|
||||
- Navigation aktuell mit `Home`, `Suche`, `Community`
|
||||
- eingeloggte Nutzer sehen rechts ein Profil-Menü mit Direktlinks zu `Profil`, `Kinder`, `Termine`, `Community`, `Einstellungen`
|
||||
|
||||
@@ -23,6 +23,15 @@ return [
|
||||
'label' => 'Lorelei',
|
||||
'class' => \App\Avatar\Lorelei::class,
|
||||
'fixed_colors' => [],
|
||||
'color_groups' => [
|
||||
'eyesVariant' => ['eyesColor', 'skinColor', 'backgroundColor'],
|
||||
'eyebrowsVariant' => ['eyebrowsColor'],
|
||||
'mouthVariant' => ['mouthColor', 'noseColor', 'frecklesColor'],
|
||||
'glassesVariant' => ['glassesColor'],
|
||||
'hairVariant' => ['hairColor', 'hairAccessoriesColor'],
|
||||
'beardVariant' => [],
|
||||
'earringsVariant' => ['earringsColor'],
|
||||
],
|
||||
'component_controls' => [
|
||||
'eyesVariant',
|
||||
'eyebrowsVariant',
|
||||
@@ -38,99 +47,66 @@ return [
|
||||
'default' => '#d6b36b',
|
||||
'samples' => ['#d6b36b', '#b08d57', '#d9d9d9', '#8c6c4a'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'eyebrowsColor' => [
|
||||
'label' => 'Augenbrauen',
|
||||
'default' => '#4a3227',
|
||||
'samples' => ['#4a3227', '#6a4938', '#2c221d', '#8a6651'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'eyesColor' => [
|
||||
'label' => 'Augen',
|
||||
'default' => '#2a2522',
|
||||
'samples' => ['#2a2522', '#3f4f66', '#6b4d35', '#1f3a5f'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'frecklesColor' => [
|
||||
'label' => 'Sommersprossen',
|
||||
'default' => '#b67852',
|
||||
'samples' => ['#b67852', '#a16645', '#c48a66', '#8f5a3e'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'glassesColor' => [
|
||||
'label' => 'Brille',
|
||||
'default' => '#26313f',
|
||||
'samples' => ['#26313f', '#4d5f73', '#1b1f27', '#7a5d42'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'hairColor' => [
|
||||
'label' => 'Haare',
|
||||
'default' => '#4b3128',
|
||||
'samples' => ['#4b3128', '#6f4b38', '#221916', '#b38a52'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'hairAccessoriesColor' => [
|
||||
'label' => 'Haar-Accessoires',
|
||||
'default' => '#b96f54',
|
||||
'samples' => ['#b96f54', '#c98664', '#8d5a9e', '#5e8aa8'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'mouthColor' => [
|
||||
'label' => 'Mund',
|
||||
'default' => '#9f5a56',
|
||||
'samples' => ['#9f5a56', '#b66b66', '#804444', '#c98a7a'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'noseColor' => [
|
||||
'label' => 'Nase',
|
||||
'default' => '#a56f58',
|
||||
'samples' => ['#a56f58', '#bf8569', '#8d5f49', '#d3a17b'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'skinColor' => [
|
||||
'label' => 'Haut',
|
||||
'default' => '#e7ba9a',
|
||||
'samples' => ['#f4d2ba', '#e7ba9a', '#c98f69', '#8a5638'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'backgroundColor' => [
|
||||
'label' => 'Hintergrund',
|
||||
'default' => '#f5efe5',
|
||||
'samples' => ['#f5efe5', '#e9dfd1', '#d7e6ef', '#efe2d2'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
],
|
||||
'example_profile' => [
|
||||
@@ -156,6 +132,15 @@ return [
|
||||
'label' => 'Croodles',
|
||||
'class' => \App\Avatar\Croodles::class,
|
||||
'fixed_colors' => [],
|
||||
'color_groups' => [
|
||||
'headVariant' => ['baseColor', 'backgroundColor'],
|
||||
'eyesVariant' => ['eyepatchColor', 'glassesColor'],
|
||||
'noseVariant' => [],
|
||||
'mouthVariant' => [],
|
||||
'topVariant' => ['topColor'],
|
||||
'beardVariant' => [],
|
||||
'mustacheVariant' => [],
|
||||
],
|
||||
'component_controls' => [
|
||||
'headVariant',
|
||||
'eyesVariant',
|
||||
@@ -171,45 +156,30 @@ return [
|
||||
'default' => '#e7ba9a',
|
||||
'samples' => ['#f4d2ba', '#e7ba9a', '#c98f69', '#8a5638'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'eyepatchColor' => [
|
||||
'label' => 'Augenklappe',
|
||||
'default' => '#283341',
|
||||
'samples' => ['#283341', '#1a222d', '#4a5a6f', '#77583f'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'glassesColor' => [
|
||||
'label' => 'Brille',
|
||||
'default' => '#283341',
|
||||
'samples' => ['#283341', '#4a5a6f', '#1a222d', '#74614a'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'topColor' => [
|
||||
'label' => 'Top-Farbe',
|
||||
'default' => '#4f6ea8',
|
||||
'samples' => ['#4f6ea8', '#6d8fc2', '#36507a', '#6f8c55'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
'backgroundColor' => [
|
||||
'label' => 'Hintergrund',
|
||||
'default' => '#f2eadf',
|
||||
'samples' => ['#f2eadf', '#e6dccd', '#d9e8ef', '#efe0d0'],
|
||||
'allow_custom' => true,
|
||||
'allow_fill' => true,
|
||||
'allow_fill_stops' => true,
|
||||
'allow_angle' => true,
|
||||
],
|
||||
],
|
||||
'example_profile' => [
|
||||
|
||||
@@ -378,22 +378,14 @@ $sectionLinks = [
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php $styleColors = $styleMeta['colors'] ?? []; ?>
|
||||
<?php if ($styleColors): ?>
|
||||
<div class="account-panel" style="margin-top:18px;">
|
||||
<div class="account-panel__head">
|
||||
<h3>Farben</h3>
|
||||
<p class="muted small">Alle aktuell für diesen DiceBear-Style unterstützten Farbfelder.</p>
|
||||
</div>
|
||||
<div class="account-panel__body">
|
||||
<div class="form-grid">
|
||||
<?php foreach ($styleColors as $colorField => $colorMeta): ?>
|
||||
<?php $componentColors = $component['colors'] ?? []; ?>
|
||||
<?php if ($componentColors): ?>
|
||||
<div class="form-grid" style="margin-top:18px;">
|
||||
<?php foreach ($componentColors as $colorField => $colorMeta): ?>
|
||||
<?php
|
||||
$colorValue = (string)($colorMeta['value'] ?? '');
|
||||
$colorDefault = (string)($colorMeta['default'] ?? '#c4a484');
|
||||
$samples = is_array($colorMeta['samples'] ?? null) ? $colorMeta['samples'] : [];
|
||||
?>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="avatarColor<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>"><?= htmlspecialchars((string)($colorMeta['label'] ?? $colorField), ENT_QUOTES) ?></label>
|
||||
@@ -420,68 +412,30 @@ $sectionLinks = [
|
||||
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
||||
data-avatar-color-reset="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
||||
>Standard</button>
|
||||
<span class="muted small" data-avatar-color-label="<?= htmlspecialchars($styleKey . ':' . (string)$colorField, ENT_QUOTES) ?>">
|
||||
<?= $colorValue !== '' ? htmlspecialchars($colorValue, ENT_QUOTES) : 'DiceBear-Standard' ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-grid" style="margin-top:6px;">
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="avatarFill<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>">Füllung</label>
|
||||
<select
|
||||
id="avatarFill<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>"
|
||||
name="avatar_config[<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>][<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>Fill]"
|
||||
class="select"
|
||||
<?php if ($samples): ?>
|
||||
<div class="flex gap-8 center-y" style="flex-wrap:wrap;">
|
||||
<?php foreach ($samples as $sampleColor): ?>
|
||||
<button
|
||||
type="button"
|
||||
class="avatar-color-sample"
|
||||
title="<?= htmlspecialchars((string)$sampleColor, ENT_QUOTES) ?>"
|
||||
aria-label="<?= htmlspecialchars((string)$sampleColor, ENT_QUOTES) ?>"
|
||||
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
||||
data-avatar-color-meta="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
||||
data-avatar-color-meta-type="fill"
|
||||
>
|
||||
<option value="" <?= (($colorMeta['fill'] ?? '') === '') ? 'selected' : '' ?>>Standard</option>
|
||||
<option value="solid" <?= (($colorMeta['fill'] ?? '') === 'solid') ? 'selected' : '' ?>>Solid</option>
|
||||
<option value="linear" <?= (($colorMeta['fill'] ?? '') === 'linear') ? 'selected' : '' ?>>Linear</option>
|
||||
<option value="radial" <?= (($colorMeta['fill'] ?? '') === 'radial') ? 'selected' : '' ?>>Radial</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="avatarStops<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>">Farb-Stopps</label>
|
||||
<input
|
||||
id="avatarStops<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>"
|
||||
type="number"
|
||||
min="2"
|
||||
max="8"
|
||||
step="1"
|
||||
name="avatar_config[<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>][<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>FillStops]"
|
||||
class="input"
|
||||
value="<?= htmlspecialchars((string)($colorMeta['fill_stops'] ?? ''), ENT_QUOTES) ?>"
|
||||
placeholder="Standard"
|
||||
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
||||
data-avatar-color-meta="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
||||
data-avatar-color-meta-type="fillStops"
|
||||
>
|
||||
</div>
|
||||
<div class="stack gap-6">
|
||||
<label class="label" for="avatarAngle<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>">Winkel</label>
|
||||
<input
|
||||
id="avatarAngle<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>"
|
||||
type="number"
|
||||
min="-360"
|
||||
max="360"
|
||||
step="1"
|
||||
name="avatar_config[<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>][<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>Angle]"
|
||||
class="input"
|
||||
value="<?= htmlspecialchars((string)($colorMeta['angle'] ?? ''), ENT_QUOTES) ?>"
|
||||
placeholder="Standard"
|
||||
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
||||
data-avatar-color-meta="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
||||
data-avatar-color-meta-type="angle"
|
||||
>
|
||||
</div>
|
||||
data-avatar-color-sample-field="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
||||
data-avatar-color-sample-value="<?= htmlspecialchars((string)$sampleColor, ENT_QUOTES) ?>"
|
||||
style="width:32px; height:32px; border-radius:10px; border:1px solid var(--color-border); background: <?= htmlspecialchars((string)$sampleColor, ENT_QUOTES) ?>;"
|
||||
></button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
@@ -800,13 +800,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return input ? input.value : '';
|
||||
};
|
||||
|
||||
const readAvatarColorMetaValue = (form, style, field, type) => {
|
||||
const panel = getAvatarStylePanel(form, style);
|
||||
if (!panel) return '';
|
||||
const input = panel.querySelector(`[data-avatar-color-meta="${field}"][data-avatar-color-meta-type="${type}"]`);
|
||||
return input ? input.value : '';
|
||||
};
|
||||
|
||||
const getAvatarStyleConfig = (style) => {
|
||||
if (avatarStyleConfigs[style]) return avatarStyleConfigs[style];
|
||||
return avatarStyleConfigs.lorelei;
|
||||
@@ -845,18 +838,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
: readAvatarColorValue(form, style, field);
|
||||
if (value) {
|
||||
params.set(field, value);
|
||||
const fill = Object.prototype.hasOwnProperty.call(overrides, `${field}Fill`)
|
||||
? overrides[`${field}Fill`]
|
||||
: readAvatarColorMetaValue(form, style, field, 'fill');
|
||||
const fillStops = Object.prototype.hasOwnProperty.call(overrides, `${field}FillStops`)
|
||||
? overrides[`${field}FillStops`]
|
||||
: readAvatarColorMetaValue(form, style, field, 'fillStops');
|
||||
const angle = Object.prototype.hasOwnProperty.call(overrides, `${field}Angle`)
|
||||
? overrides[`${field}Angle`]
|
||||
: readAvatarColorMetaValue(form, style, field, 'angle');
|
||||
if (fill) params.set(`${field}Fill`, fill);
|
||||
if (fillStops) params.set(`${field}FillStops`, fillStops);
|
||||
if (angle) params.set(`${field}Angle`, angle);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -937,13 +918,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const panel = getAvatarStylePanel(form, style);
|
||||
if (!panel || !field) return;
|
||||
const hidden = panel.querySelector(`[data-avatar-color-input="${field}"]`);
|
||||
const label = panel.querySelector(`[data-avatar-color-label="${style}:${field}"]`);
|
||||
if (hidden) {
|
||||
hidden.value = picker.value;
|
||||
}
|
||||
if (label) {
|
||||
label.textContent = picker.value;
|
||||
}
|
||||
ensureAvatarBuilderLoaded(form, style);
|
||||
updateAvatarPreview(form);
|
||||
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
||||
@@ -961,10 +938,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!panel || !field) return;
|
||||
const hidden = panel.querySelector(`[data-avatar-color-input="${field}"]`);
|
||||
const picker = panel.querySelector(`[data-avatar-color-picker="${field}"]`);
|
||||
const label = panel.querySelector(`[data-avatar-color-label="${style}:${field}"]`);
|
||||
const fill = panel.querySelector(`[data-avatar-color-meta="${field}"][data-avatar-color-meta-type="fill"]`);
|
||||
const fillStops = panel.querySelector(`[data-avatar-color-meta="${field}"][data-avatar-color-meta-type="fillStops"]`);
|
||||
const angle = panel.querySelector(`[data-avatar-color-meta="${field}"][data-avatar-color-meta-type="angle"]`);
|
||||
const defaultValue = picker?.getAttribute('data-avatar-color-default') || '#c4a484';
|
||||
if (hidden) {
|
||||
hidden.value = '';
|
||||
@@ -972,28 +945,27 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (picker) {
|
||||
picker.value = defaultValue;
|
||||
}
|
||||
if (label) {
|
||||
label.textContent = 'DiceBear-Standard';
|
||||
}
|
||||
if (fill) fill.value = '';
|
||||
if (fillStops) fillStops.value = '';
|
||||
if (angle) angle.value = '';
|
||||
ensureAvatarBuilderLoaded(form, style);
|
||||
updateAvatarPreview(form);
|
||||
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
||||
});
|
||||
});
|
||||
|
||||
form.querySelectorAll('[data-avatar-color-meta]').forEach((input) => {
|
||||
const syncMeta = () => {
|
||||
const style = input.getAttribute('data-avatar-style') || getCurrentAvatarStyle(form);
|
||||
form.querySelectorAll('[data-avatar-color-sample-field]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
const style = button.getAttribute('data-avatar-style') || getCurrentAvatarStyle(form);
|
||||
const field = button.getAttribute('data-avatar-color-sample-field') || '';
|
||||
const value = button.getAttribute('data-avatar-color-sample-value') || '';
|
||||
const panel = getAvatarStylePanel(form, style);
|
||||
if (!panel || !field || !value) return;
|
||||
const hidden = panel.querySelector(`[data-avatar-color-input="${field}"]`);
|
||||
const picker = panel.querySelector(`[data-avatar-color-picker="${field}"]`);
|
||||
if (hidden) hidden.value = value;
|
||||
if (picker) picker.value = value;
|
||||
ensureAvatarBuilderLoaded(form, style);
|
||||
updateAvatarPreview(form);
|
||||
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
||||
};
|
||||
|
||||
input.addEventListener('input', syncMeta);
|
||||
input.addEventListener('change', syncMeta);
|
||||
});
|
||||
});
|
||||
|
||||
const setActiveComponentPanel = (style, field) => {
|
||||
@@ -1018,7 +990,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
panel.querySelectorAll('[data-avatar-field]').forEach((input) => {
|
||||
input.disabled = !active;
|
||||
});
|
||||
panel.querySelectorAll('[data-avatar-color-input], [data-avatar-color-picker], [data-avatar-color-reset]').forEach((input) => {
|
||||
panel.querySelectorAll('[data-avatar-color-input], [data-avatar-color-picker], [data-avatar-color-reset], [data-avatar-color-sample-field]').forEach((input) => {
|
||||
input.disabled = !active;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -209,6 +209,9 @@ final class AvatarManager
|
||||
$styleClass = (string)$meta['class'];
|
||||
$styleConfig = $currentStyle === $meta['key'] ? $currentConfig : $styleClass::defaultConfig();
|
||||
$components = [];
|
||||
$colorDefinitions = $styleClass::colorDefinitions();
|
||||
$colorControls = is_array($meta['color_controls'] ?? null) ? $meta['color_controls'] : [];
|
||||
$colorGroups = is_array($meta['color_groups'] ?? null) ? $meta['color_groups'] : [];
|
||||
|
||||
foreach ($styleClass::componentDefinitions() as $field => $definition) {
|
||||
$options = [];
|
||||
@@ -221,24 +224,28 @@ final class AvatarManager
|
||||
];
|
||||
}
|
||||
|
||||
$componentColors = [];
|
||||
foreach (($colorGroups[$field] ?? []) as $colorField) {
|
||||
if (!isset($colorDefinitions[$colorField])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$componentColors[$colorField] = [
|
||||
'field' => $colorField,
|
||||
'label' => (string)($colorControls[$colorField]['label'] ?? $colorDefinitions[$colorField]['label'] ?? $colorField),
|
||||
'value' => (string)($styleConfig[$colorField] ?? ''),
|
||||
'default' => (string)($colorControls[$colorField]['default'] ?? $colorDefinitions[$colorField]['default'] ?? '#c4a484'),
|
||||
'samples' => is_array($colorControls[$colorField]['samples'] ?? null) ? $colorControls[$colorField]['samples'] : [],
|
||||
'allow_custom' => (bool)($colorControls[$colorField]['allow_custom'] ?? true),
|
||||
];
|
||||
}
|
||||
|
||||
$components[$field] = [
|
||||
'field' => $field,
|
||||
'label' => (string)($definition['label'] ?? $field),
|
||||
'count' => count(array_filter($options, static fn(array $option): bool => $option['value'] !== '')),
|
||||
'options' => $options,
|
||||
];
|
||||
}
|
||||
|
||||
$colors = [];
|
||||
foreach ($styleClass::colorDefinitions() as $field => $definition) {
|
||||
$colors[$field] = [
|
||||
'field' => $field,
|
||||
'label' => (string)($definition['label'] ?? $field),
|
||||
'value' => (string)($styleConfig[$field] ?? ''),
|
||||
'default' => (string)($definition['default'] ?? '#c4a484'),
|
||||
'fill' => (string)($styleConfig[$field . 'Fill'] ?? ''),
|
||||
'fill_stops' => (string)($styleConfig[$field . 'FillStops'] ?? ''),
|
||||
'angle' => (string)($styleConfig[$field . 'Angle'] ?? ''),
|
||||
'colors' => $componentColors,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -246,7 +253,6 @@ final class AvatarManager
|
||||
'key' => $meta['key'],
|
||||
'label' => $meta['label'],
|
||||
'components' => $components,
|
||||
'colors' => $colors,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user