Einige kleine verbesserungen #1
@@ -1840,31 +1840,9 @@ a { color: var(--primary); text-decoration: none; }
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--text-1);
|
color: var(--text-1);
|
||||||
}
|
}
|
||||||
/* Per-member filter bar under the group banner (hide individual people). */
|
/* Group-member rows in the sidebar (colour dot = per-user colour, checkbox =
|
||||||
.group-members-filter {
|
show/hide). Reuse the calendar-list item styling. */
|
||||||
display: flex;
|
.gm-row .gm-dot { cursor: pointer; }
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
background: color-mix(in srgb, var(--accent) 7%, var(--bg-app));
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
.gmf-chip {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: 999px;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: var(--bg-surface);
|
|
||||||
color: var(--text-1);
|
|
||||||
font-size: 12.5px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity var(--transition), background var(--transition);
|
|
||||||
}
|
|
||||||
.gmf-chip:hover { background: var(--bg-hover); }
|
|
||||||
.gmf-chip.gmf-off { opacity: 0.4; text-decoration: line-through; }
|
|
||||||
.gmf-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }
|
|
||||||
|
|
||||||
.group-item-active {
|
.group-item-active {
|
||||||
background: var(--bg-surface);
|
background: var(--bg-surface);
|
||||||
|
|||||||
@@ -179,6 +179,14 @@
|
|||||||
<div class="mini-cal-days" id="mini-days"></div>
|
<div class="mini-cal-days" id="mini-days"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Group members (shown only in the group overlay; hide/show people) -->
|
||||||
|
<div class="cal-list hidden" id="group-members">
|
||||||
|
<div class="cal-list-header">
|
||||||
|
<span data-i18n="group_members">Mitglieder</span>
|
||||||
|
</div>
|
||||||
|
<div id="group-members-items"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Calendar List -->
|
<!-- Calendar List -->
|
||||||
<div class="cal-list" id="cal-list">
|
<div class="cal-list" id="cal-list">
|
||||||
<div class="cal-list-header">
|
<div class="cal-list-header">
|
||||||
|
|||||||
@@ -280,6 +280,22 @@ function ownerColor(ownerId) {
|
|||||||
return OWNER_PALETTE[(Number(ownerId) >>> 0) % OWNER_PALETTE.length];
|
return OWNER_PALETTE[(Number(ownerId) >>> 0) % OWNER_PALETTE.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-user (per-device) member-colour overrides for the group overlay, so each
|
||||||
|
// viewer can recolour members just for their own view. Kept in localStorage,
|
||||||
|
// keyed by group id then member key ("<userId>" or "gc" for the group calendar).
|
||||||
|
function loadGroupColors() {
|
||||||
|
try { return JSON.parse(localStorage.getItem('groupMemberColors') || '{}'); } catch (e) { return {}; }
|
||||||
|
}
|
||||||
|
function groupColorOverride(groupId, key) {
|
||||||
|
return (loadGroupColors()[groupId] || {})[key] || null;
|
||||||
|
}
|
||||||
|
function setGroupColorOverride(groupId, key, hex) {
|
||||||
|
const all = loadGroupColors();
|
||||||
|
all[groupId] = all[groupId] || {};
|
||||||
|
if (hex) all[groupId][key] = hex; else delete all[groupId][key];
|
||||||
|
try { localStorage.setItem('groupMemberColors', JSON.stringify(all)); } catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchAndRender(force = false, silent = false) {
|
async function fetchAndRender(force = false, silent = false) {
|
||||||
const { start, end } = getViewRange();
|
const { start, end } = getViewRange();
|
||||||
|
|
||||||
@@ -312,8 +328,11 @@ async function fetchAndRender(force = false, silent = false) {
|
|||||||
title = ev.title;
|
title = ev.title;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Server-defined colour (member colour / group colour) so web + apps match.
|
// Colour: this viewer's local override first, else the server-defined
|
||||||
const color = ev.display_color || ownerColor(ownerId) || ev.color;
|
// member/group colour, else a palette fallback.
|
||||||
|
const ownerKey = ev.is_group_event ? 'gc' : (ownerId != null ? String(ownerId) : null);
|
||||||
|
const override = ownerKey ? groupColorOverride(state.activeGroupId, ownerKey) : null;
|
||||||
|
const color = override || ev.display_color || ownerColor(ownerId) || ev.color;
|
||||||
return { ...ev, title, color };
|
return { ...ev, title, color };
|
||||||
});
|
});
|
||||||
eventCache.start = null; eventCache.end = null; // invalidate normal cache
|
eventCache.start = null; eventCache.end = null; // invalidate normal cache
|
||||||
@@ -2353,41 +2372,53 @@ function exitGroupView() {
|
|||||||
fetchAndRender(true);
|
fetchAndRender(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-member filter bar shown under the group banner: tap a member (or the
|
// Group members live in the sidebar (like the calendar list): each row has a
|
||||||
// group calendar) to hide/show their events in the combined overlay.
|
// colour dot (tap to recolour that member just for your view) and a checkbox to
|
||||||
|
// hide/show them in the combined overlay. The personal calendar list is hidden
|
||||||
|
// while a group is active.
|
||||||
function renderGroupMemberFilter() {
|
function renderGroupMemberFilter() {
|
||||||
const banner = document.getElementById('group-view-banner');
|
const section = document.getElementById('group-members');
|
||||||
if (!banner) return;
|
const items = document.getElementById('group-members-items');
|
||||||
let bar = document.getElementById('group-members-filter');
|
const calList = document.getElementById('cal-list');
|
||||||
if (!state.activeGroupId || !state.activeGroupDetail) {
|
const active = !!(state.activeGroupId && state.activeGroupDetail);
|
||||||
if (bar) bar.remove();
|
if (section) section.classList.toggle('hidden', !active);
|
||||||
return;
|
if (calList) calList.classList.toggle('hidden', !!state.activeGroupId);
|
||||||
}
|
if (!active || !items) return;
|
||||||
if (!bar) {
|
|
||||||
bar = document.createElement('div');
|
|
||||||
bar.id = 'group-members-filter';
|
|
||||||
bar.className = 'group-members-filter';
|
|
||||||
banner.insertAdjacentElement('afterend', bar);
|
|
||||||
}
|
|
||||||
const g = state.activeGroupDetail;
|
const g = state.activeGroupDetail;
|
||||||
const chip = (key, name, color) => {
|
const gid = state.activeGroupId;
|
||||||
const off = state.hiddenGroupMembers.has(key);
|
const row = (numKey, colorKey, name, baseColor) => {
|
||||||
return `<button class="gmf-chip${off ? ' gmf-off' : ''}" data-gmf="${key}">
|
const hidden = state.hiddenGroupMembers.has(numKey);
|
||||||
<span class="gmf-dot" style="background:${color || '#4285f4'}"></span>${escHtml(name)}</button>`;
|
const color = groupColorOverride(gid, colorKey) || baseColor || '#4285f4';
|
||||||
|
return `<div class="cal-item gm-row">
|
||||||
|
<input type="checkbox" class="gm-vis" ${hidden ? '' : 'checked'} data-k="${colorKey}" />
|
||||||
|
<div class="cal-item-dot gm-dot" style="background:${color}" data-k="${colorKey}" data-color="${color}" title="${t('change_color')}"></div>
|
||||||
|
<span class="cal-item-name">${escHtml(name)}</span>
|
||||||
|
</div>`;
|
||||||
};
|
};
|
||||||
const parts = (g.members || []).map(m => chip(String(m.id), m.display_name || '—', m.color));
|
const parts = (g.members || []).map(m => row(m.id, String(m.id), m.display_name || '—', m.color));
|
||||||
parts.push(chip('gc', t('group_calendar'), g.group_calendar_color));
|
parts.push(row('gc', 'gc', t('group_calendar'), g.group_calendar_color));
|
||||||
bar.innerHTML = parts.join('');
|
items.innerHTML = parts.join('');
|
||||||
bar.querySelectorAll('[data-gmf]').forEach(b => {
|
|
||||||
b.addEventListener('click', () => {
|
items.querySelectorAll('.gm-vis').forEach(cb => {
|
||||||
const raw = b.dataset.gmf;
|
cb.addEventListener('change', () => {
|
||||||
const key = raw === 'gc' ? 'gc' : parseInt(raw);
|
const ck = cb.dataset.k;
|
||||||
if (state.hiddenGroupMembers.has(key)) state.hiddenGroupMembers.delete(key);
|
const key = ck === 'gc' ? 'gc' : parseInt(ck);
|
||||||
|
if (cb.checked) state.hiddenGroupMembers.delete(key);
|
||||||
else state.hiddenGroupMembers.add(key);
|
else state.hiddenGroupMembers.add(key);
|
||||||
renderGroupMemberFilter();
|
|
||||||
renderView();
|
renderView();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
items.querySelectorAll('.gm-dot').forEach(dot => {
|
||||||
|
dot.addEventListener('click', async () => {
|
||||||
|
const picked = await openColorPicker(dot, dot.dataset.color || '#4285f4');
|
||||||
|
if (picked) {
|
||||||
|
setGroupColorOverride(gid, dot.dataset.k, picked);
|
||||||
|
renderGroupMemberFilter();
|
||||||
|
fetchAndRender(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the group modal in create mode (no id) or manage mode (existing group).
|
// Open the group modal in create mode (no id) or manage mode (existing group).
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Increment APP_VERSION with every code change
|
// Increment APP_VERSION with every code change
|
||||||
export const APP_VERSION = 'v43';
|
export const APP_VERSION = 'v44';
|
||||||
|
|||||||
Reference in New Issue
Block a user