Gradient-Colorpicker im Dark-Style für Event- und Kalenderfarben
Feste Farb-Swatches durch den HSV-Gradient-Colorpicker ersetzt. Neues Dark-Theme-CSS für den Picker passend zum Rest der App.
This commit is contained in:
@@ -193,6 +193,76 @@ a { color: var(--primary); text-decoration: none; }
|
||||
.color-swatch:hover { transform: scale(1.15); }
|
||||
.color-swatch.active { border-color: var(--text-1); }
|
||||
|
||||
.ev-color-row { display: flex; align-items: center; gap: 8px; }
|
||||
.ev-color-hex {
|
||||
flex: 1; height: 36px; padding: 0 10px;
|
||||
background: var(--bg-hover); border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); color: var(--text-1);
|
||||
font-family: 'Roboto Mono', monospace; font-size: 13px;
|
||||
}
|
||||
.ev-color-hex:focus { outline: none; border-color: var(--primary); }
|
||||
.ev-color-preview {
|
||||
width: 36px; height: 36px; border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border); cursor: pointer;
|
||||
flex-shrink: 0; transition: box-shadow var(--transition);
|
||||
}
|
||||
.ev-color-preview:hover { box-shadow: 0 0 0 2px var(--primary); }
|
||||
|
||||
/* ── Gradient Color Picker (Dark) ──────────────────────── */
|
||||
.gcp {
|
||||
position: fixed; z-index: 600;
|
||||
width: 252px; padding: 16px;
|
||||
background: var(--bg-surface); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); box-shadow: var(--shadow-lg);
|
||||
}
|
||||
.gcp-sv {
|
||||
display: block; width: 100%; height: 160px;
|
||||
border-radius: var(--radius-sm); cursor: crosshair;
|
||||
}
|
||||
.gcp-sv-cursor {
|
||||
position: absolute; width: 14px; height: 14px;
|
||||
border: 2px solid #fff; border-radius: 50%;
|
||||
box-shadow: 0 0 0 1px rgba(0,0,0,.4), inset 0 0 0 1px rgba(0,0,0,.3);
|
||||
transform: translate(-50%, -50%); pointer-events: none;
|
||||
}
|
||||
.gcp-hue-track {
|
||||
position: relative; margin-top: 12px; height: 14px; cursor: pointer;
|
||||
}
|
||||
.gcp-hue {
|
||||
display: block; width: 100%; height: 14px;
|
||||
border-radius: 7px; cursor: pointer;
|
||||
}
|
||||
.gcp-hue-thumb {
|
||||
position: absolute; top: 50%; width: 18px; height: 18px;
|
||||
border: 2px solid #fff; border-radius: 50%;
|
||||
box-shadow: 0 0 0 1px rgba(0,0,0,.3), 0 1px 4px rgba(0,0,0,.4);
|
||||
transform: translate(-50%, -50%); pointer-events: none;
|
||||
background: transparent;
|
||||
}
|
||||
.gcp-bottom {
|
||||
display: flex; align-items: center; gap: 10px; margin-top: 12px;
|
||||
}
|
||||
.gcp-preview {
|
||||
width: 36px; height: 36px; border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border); flex-shrink: 0;
|
||||
}
|
||||
.gcp-hex {
|
||||
flex: 1; height: 36px; padding: 0 10px;
|
||||
background: var(--bg-hover); border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); color: var(--text-1);
|
||||
font-family: 'Roboto Mono', monospace; font-size: 13px;
|
||||
outline: none;
|
||||
}
|
||||
.gcp-hex:focus { border-color: var(--primary); }
|
||||
.gcp-select {
|
||||
display: block; width: 100%; margin-top: 12px;
|
||||
padding: 8px 0; background: var(--primary); color: #fff;
|
||||
border: none; border-radius: var(--radius-sm);
|
||||
font-weight: 600; font-size: 13px; cursor: pointer;
|
||||
transition: opacity var(--transition);
|
||||
}
|
||||
.gcp-select:hover { opacity: .85; }
|
||||
|
||||
/* ── Top Bar ────────────────────────────────────────────── */
|
||||
.topbar {
|
||||
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
|
||||
@@ -321,21 +391,6 @@ a { color: var(--primary); text-decoration: none; }
|
||||
width: 14px; height: 14px; border-radius: 3px; flex-shrink: 0; cursor: pointer;
|
||||
}
|
||||
.cal-item-dot:hover { outline: 2px solid var(--text-2); outline-offset: 1px; }
|
||||
.cal-color-picker {
|
||||
position: fixed; z-index: 500;
|
||||
display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px;
|
||||
padding: 10px;
|
||||
background: var(--bg-surface); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); box-shadow: var(--shadow-lg);
|
||||
}
|
||||
.cal-cp-swatch {
|
||||
width: 28px; height: 28px; border-radius: 50%;
|
||||
cursor: pointer; transition: transform .1s, box-shadow .1s;
|
||||
}
|
||||
.cal-cp-swatch:hover {
|
||||
transform: scale(1.2);
|
||||
box-shadow: 0 0 0 2px var(--bg-surface), 0 0 0 4px currentColor;
|
||||
}
|
||||
.cal-item input[type=checkbox] { accent-color: var(--primary); width: 14px; height: 14px; }
|
||||
.cal-item-name { font-size: 13px; flex: 1; color: var(--text-1); cursor: default; }
|
||||
.cal-rename-input {
|
||||
|
||||
@@ -220,16 +220,9 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Farbe</label>
|
||||
<div class="color-picker" id="ev-color-picker">
|
||||
<div class="color-swatch active" data-color="" style="background:var(--cal-color,#4285f4)" title="Kalenderfarbe"></div>
|
||||
<div class="color-swatch" data-color="#4285f4" style="background:#4285f4"></div>
|
||||
<div class="color-swatch" data-color="#ea4335" style="background:#ea4335"></div>
|
||||
<div class="color-swatch" data-color="#fbbc04" style="background:#fbbc04"></div>
|
||||
<div class="color-swatch" data-color="#34a853" style="background:#34a853"></div>
|
||||
<div class="color-swatch" data-color="#ff6d00" style="background:#ff6d00"></div>
|
||||
<div class="color-swatch" data-color="#46bdc6" style="background:#46bdc6"></div>
|
||||
<div class="color-swatch" data-color="#8e24aa" style="background:#8e24aa"></div>
|
||||
<div class="color-swatch" data-color="#e67c73" style="background:#e67c73"></div>
|
||||
<div class="ev-color-row">
|
||||
<input type="text" id="ev-color-hex" class="ev-color-hex" maxlength="7" placeholder="#4285f4" spellcheck="false" />
|
||||
<div class="ev-color-preview" id="ev-color-preview" title="Farbe wählen"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { applyTheme, isToday, isSameDay, toLocalDatetimeInput, toDateInput, date
|
||||
import { renderMonth } from './views/month.js';
|
||||
import { renderWeek } from './views/week.js';
|
||||
import { renderAgenda } from './views/agenda.js';
|
||||
import { openColorPicker } from './color-picker.js';
|
||||
|
||||
// Fetch avatar image as blob URL (with auth header)
|
||||
function fetchAvatarBlob() {
|
||||
@@ -516,9 +517,10 @@ function toggleAlldayFields(allDay) {
|
||||
|
||||
function resetColorPicker(color) {
|
||||
state.selectedEventColor = color;
|
||||
document.querySelectorAll('#ev-color-picker .color-swatch').forEach(sw => {
|
||||
sw.classList.toggle('active', (sw.dataset.color || '') === color);
|
||||
});
|
||||
const hex = document.getElementById('ev-color-hex');
|
||||
const preview = document.getElementById('ev-color-preview');
|
||||
hex.value = color ? color.toUpperCase() : '';
|
||||
preview.style.background = color || 'var(--primary)';
|
||||
}
|
||||
|
||||
function bindEventModal() {
|
||||
@@ -526,11 +528,22 @@ function bindEventModal() {
|
||||
toggleAlldayFields(e.target.checked);
|
||||
});
|
||||
|
||||
document.querySelectorAll('#ev-color-picker .color-swatch').forEach(sw => {
|
||||
sw.addEventListener('click', () => {
|
||||
state.selectedEventColor = sw.dataset.color || '';
|
||||
resetColorPicker(state.selectedEventColor);
|
||||
});
|
||||
// Color picker: click preview to open gradient picker
|
||||
const evColorPreview = document.getElementById('ev-color-preview');
|
||||
const evColorHex = document.getElementById('ev-color-hex');
|
||||
|
||||
evColorPreview.addEventListener('click', async () => {
|
||||
const current = state.selectedEventColor || '#4285f4';
|
||||
const picked = await openColorPicker(evColorPreview, current);
|
||||
if (picked) resetColorPicker(picked);
|
||||
});
|
||||
|
||||
evColorHex.addEventListener('change', () => {
|
||||
let val = evColorHex.value.trim();
|
||||
if (!val.startsWith('#')) val = '#' + val;
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(val)) {
|
||||
resetColorPicker(val);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('ev-save').onclick = async () => {
|
||||
@@ -943,71 +956,26 @@ function updateTopbarAvatar(hasAvatar) {
|
||||
}
|
||||
|
||||
// ── Calendar Color Picker ─────────────────────────────────
|
||||
const CAL_COLORS = [
|
||||
'#4285f4', '#7986cb', '#8e24aa', '#e67c73',
|
||||
'#f4511e', '#f6bf26', '#33b679', '#0b8043',
|
||||
'#039be5', '#616161', '#3f51b5', '#d50000',
|
||||
'#e4c441', '#009688', '#795548', '#ef6c00',
|
||||
];
|
||||
|
||||
let activeColorPicker = null;
|
||||
|
||||
function openCalColorPicker(anchor, calId) {
|
||||
closeCalColorPicker();
|
||||
|
||||
const rect = anchor.getBoundingClientRect();
|
||||
const picker = document.createElement('div');
|
||||
picker.className = 'cal-color-picker';
|
||||
picker.innerHTML = CAL_COLORS.map(c =>
|
||||
`<div class="cal-cp-swatch" data-color="${c}" style="background:${c}" title="${c}"></div>`
|
||||
).join('');
|
||||
|
||||
picker.style.top = (rect.bottom + 6) + 'px';
|
||||
picker.style.left = rect.left + 'px';
|
||||
|
||||
// Ensure picker stays in viewport
|
||||
document.body.appendChild(picker);
|
||||
const pRect = picker.getBoundingClientRect();
|
||||
if (pRect.right > window.innerWidth - 8) {
|
||||
picker.style.left = (window.innerWidth - pRect.width - 8) + 'px';
|
||||
async function openCalColorPicker(anchor, calId) {
|
||||
// Find current color of the calendar
|
||||
let currentColor = '#4285f4';
|
||||
for (const acc of state.accounts) {
|
||||
for (const cal of acc.calendars) {
|
||||
if (cal.id === calId && cal.color) currentColor = cal.color;
|
||||
}
|
||||
}
|
||||
|
||||
picker.querySelectorAll('.cal-cp-swatch').forEach(sw => {
|
||||
sw.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
const color = sw.dataset.color;
|
||||
await api.put(`/caldav/calendars/${calId}`, { color });
|
||||
for (const acc of state.accounts) {
|
||||
for (const cal of acc.calendars) {
|
||||
if (cal.id === calId) cal.color = color;
|
||||
}
|
||||
}
|
||||
closeCalColorPicker();
|
||||
renderCalendarList();
|
||||
fetchAndRender();
|
||||
});
|
||||
});
|
||||
const picked = await openColorPicker(anchor, currentColor);
|
||||
if (!picked) return;
|
||||
|
||||
activeColorPicker = picker;
|
||||
|
||||
// Close on outside click (next tick to avoid immediate close)
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', closeCalColorPickerOutside);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function closeCalColorPicker() {
|
||||
if (activeColorPicker) {
|
||||
activeColorPicker.remove();
|
||||
activeColorPicker = null;
|
||||
document.removeEventListener('click', closeCalColorPickerOutside);
|
||||
}
|
||||
}
|
||||
|
||||
function closeCalColorPickerOutside(e) {
|
||||
if (activeColorPicker && !activeColorPicker.contains(e.target)) {
|
||||
closeCalColorPicker();
|
||||
await api.put(`/caldav/calendars/${calId}`, { color: picked });
|
||||
for (const acc of state.accounts) {
|
||||
for (const cal of acc.calendars) {
|
||||
if (cal.id === calId) cal.color = picked;
|
||||
}
|
||||
}
|
||||
renderCalendarList();
|
||||
fetchAndRender();
|
||||
}
|
||||
|
||||
// ── Avatar Crop ──────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user