Refactor member gallery dialog in DiaryView for improved functionality and user experience
This commit replaces the existing BaseDialog for the member gallery with a new MemberGalleryDialog component, streamlining the dialog's functionality. The new component integrates props for current club, date, and participant status, enhancing interactivity. Additionally, redundant gallery loading logic and state management have been removed, simplifying the codebase and improving maintainability.
This commit is contained in:
356
frontend/src/components/MemberGalleryDialog.vue
Normal file
356
frontend/src/components/MemberGalleryDialog.vue
Normal file
@@ -0,0 +1,356 @@
|
||||
<template>
|
||||
<BaseDialog
|
||||
:model-value="modelValue"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
:title="date && date !== 'new' ? 'Mitglieder-Galerie - Klicken Sie auf ein Bild, um als Teilnehmer hinzuzufügen' : 'Mitglieder-Galerie'"
|
||||
size="large"
|
||||
:close-on-overlay="true"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="gallery-dialog-content">
|
||||
<div class="gallery-controls">
|
||||
<label for="gallery-size">Bildgröße:</label>
|
||||
<select id="gallery-size" v-model="gallerySize" @change="loadGalleryMembers" :disabled="galleryLoading">
|
||||
<option :value="100">100x100 px</option>
|
||||
<option :value="150">150x150 px</option>
|
||||
<option :value="200">200x200 px</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="galleryLoading" class="gallery-loading">Galerie wird geladen…</div>
|
||||
<div v-else-if="galleryMembers.length > 0" class="gallery-members-grid" :style="{ gridTemplateColumns: 'repeat(auto-fill, ' + gallerySize + 'px)' }">
|
||||
<div
|
||||
v-for="member in galleryMembers"
|
||||
:key="member.memberId"
|
||||
class="gallery-member-item"
|
||||
:class="{ 'is-participant': date && date !== 'new' && isParticipant(member.memberId) }"
|
||||
:style="{ width: gallerySize + 'px', minWidth: gallerySize + 'px', height: gallerySize + 'px' }"
|
||||
@click="handleGalleryMemberClick(member)"
|
||||
>
|
||||
<img
|
||||
v-if="member.imageUrl"
|
||||
:src="member.imageUrl"
|
||||
:alt="member.fullName"
|
||||
class="gallery-member-image"
|
||||
@error="handleImageError(member)"
|
||||
@load="handleImageLoad(member)"
|
||||
/>
|
||||
<div v-else class="gallery-member-placeholder">Kein Bild</div>
|
||||
<div class="gallery-member-name">{{ member.fullName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="gallery-error">
|
||||
{{ galleryError || 'Keine Mitglieder mit Bildern gefunden.' }}
|
||||
</div>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseDialog from './BaseDialog.vue';
|
||||
import apiClient from '../apiClient.js';
|
||||
|
||||
export default {
|
||||
name: 'MemberGalleryDialog',
|
||||
components: {
|
||||
BaseDialog
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
currentClub: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
date: {
|
||||
type: [Object, String],
|
||||
default: null
|
||||
},
|
||||
isParticipant: {
|
||||
type: Function,
|
||||
default: () => false
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'member-click'],
|
||||
data() {
|
||||
return {
|
||||
galleryLoading: false,
|
||||
galleryImageUrl: null,
|
||||
galleryError: '',
|
||||
gallerySize: 200,
|
||||
galleryMembers: []
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
modelValue(newVal) {
|
||||
if (newVal) {
|
||||
this.loadGalleryMembers();
|
||||
} else {
|
||||
this.handleClose();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateGallerySizeBasedOnCount() {
|
||||
const count = this.galleryMembers.length;
|
||||
if (count < 11) {
|
||||
this.gallerySize = 200;
|
||||
} else if (count < 22) {
|
||||
this.gallerySize = 150;
|
||||
} else {
|
||||
this.gallerySize = 100;
|
||||
}
|
||||
},
|
||||
async loadGalleryMembers() {
|
||||
if (!this.currentClub || this.galleryLoading) {
|
||||
return;
|
||||
}
|
||||
this.galleryLoading = true;
|
||||
this.galleryError = '';
|
||||
this.galleryImageUrl = null;
|
||||
this.revokeGalleryImage();
|
||||
try {
|
||||
const response = await apiClient.get(`/clubmembers/gallery/${this.currentClub}?format=json&size=${this.gallerySize}`);
|
||||
const members = response.data.members || [];
|
||||
|
||||
// Setze Größe basierend auf Anzahl der Mitglieder (vor dem Laden der Bilder)
|
||||
this.galleryMembers = members; // Temporär setzen für count
|
||||
this.updateGallerySizeBasedOnCount();
|
||||
|
||||
// Lade Bilder als Blobs und erstelle ObjectURLs
|
||||
this.galleryMembers = await Promise.all(members.map(async (member) => {
|
||||
try {
|
||||
const imageUrl = await this.loadMemberImageAsBlob(member.memberId);
|
||||
console.log(`[loadGalleryMembers] Member ${member.memberId} (${member.fullName}): imageUrl =`, imageUrl ? 'OK' : 'null');
|
||||
return {
|
||||
...member,
|
||||
imageUrl: imageUrl || null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`[loadGalleryMembers] Fehler beim Laden des Bildes für Mitglied ${member.memberId}:`, error);
|
||||
return {
|
||||
...member,
|
||||
imageUrl: null
|
||||
};
|
||||
}
|
||||
}));
|
||||
|
||||
// Sortiere nach Vorname, dann Nachname
|
||||
this.galleryMembers.sort((a, b) => {
|
||||
const firstNameA = (a.firstName || '').toLowerCase();
|
||||
const firstNameB = (b.firstName || '').toLowerCase();
|
||||
const lastNameA = (a.lastName || '').toLowerCase();
|
||||
const lastNameB = (b.lastName || '').toLowerCase();
|
||||
|
||||
if (firstNameA !== firstNameB) {
|
||||
return firstNameA.localeCompare(firstNameB);
|
||||
}
|
||||
return lastNameA.localeCompare(lastNameB);
|
||||
});
|
||||
|
||||
console.log('[loadGalleryMembers] Geladene Mitglieder:', this.galleryMembers.map(m => ({ id: m.memberId, name: m.fullName, hasImage: !!m.imageUrl })));
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Galerie:', error);
|
||||
this.galleryError = error?.response?.data?.error || 'Galerie konnte nicht geladen werden.';
|
||||
this.galleryMembers = [];
|
||||
} finally {
|
||||
this.galleryLoading = false;
|
||||
}
|
||||
},
|
||||
async loadMemberImageAsBlob(memberId) {
|
||||
try {
|
||||
const response = await apiClient.get(`/clubmembers/image/${this.currentClub}/${memberId}`, { responseType: 'blob' });
|
||||
if (!response.data || response.data.size === 0) {
|
||||
console.warn(`[loadMemberImageAsBlob] Leeres Blob für Mitglied ${memberId}`);
|
||||
return null;
|
||||
}
|
||||
const objectUrl = URL.createObjectURL(response.data);
|
||||
console.log(`[loadMemberImageAsBlob] ObjectURL erstellt für Mitglied ${memberId}:`, objectUrl.substring(0, 50) + '...');
|
||||
return objectUrl;
|
||||
} catch (error) {
|
||||
// 404 ist OK - Mitglied hat kein Bild
|
||||
if (error?.response?.status === 404) {
|
||||
console.log(`[loadMemberImageAsBlob] Kein Bild für Mitglied ${memberId} (404)`);
|
||||
return null;
|
||||
}
|
||||
console.error(`[loadMemberImageAsBlob] Fehler beim Laden des Bildes für Mitglied ${memberId}:`, error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
revokeGalleryImage() {
|
||||
if (this.galleryImageUrl) {
|
||||
URL.revokeObjectURL(this.galleryImageUrl);
|
||||
this.galleryImageUrl = null;
|
||||
}
|
||||
},
|
||||
handleClose() {
|
||||
this.revokeGalleryImage();
|
||||
// Revoke all object URLs
|
||||
this.galleryMembers.forEach(member => {
|
||||
if (member.imageUrl) {
|
||||
URL.revokeObjectURL(member.imageUrl);
|
||||
}
|
||||
});
|
||||
this.galleryMembers = [];
|
||||
this.$emit('update:modelValue', false);
|
||||
},
|
||||
handleGalleryMemberClick(member) {
|
||||
this.$emit('member-click', member);
|
||||
},
|
||||
handleImageError(member) {
|
||||
console.error(`[handleImageError] Bild konnte nicht geladen werden für Mitglied ${member.memberId} (${member.fullName}), URL:`, member.imageUrl);
|
||||
// Setze imageUrl auf null, damit der Placeholder angezeigt wird
|
||||
member.imageUrl = null;
|
||||
},
|
||||
handleImageLoad(member) {
|
||||
console.log(`[handleImageLoad] Bild erfolgreich geladen für Mitglied ${member.memberId} (${member.fullName})`);
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.handleClose();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gallery-dialog-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding: 0;
|
||||
min-height: 60vh;
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.gallery-controls {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--border-color, #ddd);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: #f9f9f9;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.gallery-controls label {
|
||||
font-weight: 500;
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.gallery-controls select {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--border-color, #ddd);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gallery-controls select:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.gallery-loading,
|
||||
.gallery-error {
|
||||
font-size: 1rem;
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.gallery-members-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, max-content);
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
align-items: start;
|
||||
box-sizing: border-box;
|
||||
justify-items: start;
|
||||
}
|
||||
|
||||
.gallery-member-item {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gallery-member-item:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.gallery-member-item.is-participant {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.gallery-member-image {
|
||||
object-fit: cover;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
pointer-events: none; /* Prevent image from blocking clicks on parent */
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.gallery-member-name {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
color: #ff6b6b;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
padding: 4px 8px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(0, 0, 0, 0.5) !important;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.gallery-member-item.is-participant .gallery-member-name {
|
||||
color: #51cf66;
|
||||
}
|
||||
|
||||
.gallery-member-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 0;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -502,49 +502,13 @@
|
||||
/>
|
||||
|
||||
<!-- Mitglieder-Galerie Dialog -->
|
||||
<BaseDialog
|
||||
<MemberGalleryDialog
|
||||
v-model="showGalleryDialog"
|
||||
:title="date && date !== 'new' ? 'Mitglieder-Galerie - Klicken Sie auf ein Bild, um als Teilnehmer hinzuzufügen' : 'Mitglieder-Galerie'"
|
||||
size="large"
|
||||
:close-on-overlay="true"
|
||||
@close="closeGalleryDialog"
|
||||
>
|
||||
<div class="gallery-dialog-content">
|
||||
<div class="gallery-controls">
|
||||
<label for="gallery-size">Bildgröße:</label>
|
||||
<select id="gallery-size" v-model="gallerySize" @change="loadGalleryMembers" :disabled="galleryLoading">
|
||||
<option :value="100">100x100 px</option>
|
||||
<option :value="150">150x150 px</option>
|
||||
<option :value="200">200x200 px</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="galleryLoading" class="gallery-loading">Galerie wird geladen…</div>
|
||||
<div v-else-if="galleryMembers.length > 0" class="gallery-members-grid" :style="{ gridTemplateColumns: 'repeat(auto-fill, ' + gallerySize + 'px)' }">
|
||||
<div
|
||||
v-for="member in galleryMembers"
|
||||
:key="member.memberId"
|
||||
class="gallery-member-item"
|
||||
:class="{ 'is-participant': date && date !== 'new' && isParticipant(member.memberId) }"
|
||||
:style="{ width: gallerySize + 'px', minWidth: gallerySize + 'px', height: gallerySize + 'px' }"
|
||||
@click="handleGalleryMemberClick(member)"
|
||||
>
|
||||
<img
|
||||
v-if="member.imageUrl"
|
||||
:src="member.imageUrl"
|
||||
:alt="member.fullName"
|
||||
class="gallery-member-image"
|
||||
@error="handleImageError(member)"
|
||||
@load="handleImageLoad(member)"
|
||||
/>
|
||||
<div v-else class="gallery-member-placeholder">Kein Bild</div>
|
||||
<div class="gallery-member-name">{{ member.fullName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="gallery-error">
|
||||
{{ galleryError || 'Keine Mitglieder mit Bildern gefunden.' }}
|
||||
</div>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
:current-club="currentClub"
|
||||
:date="date"
|
||||
:is-participant="isParticipant"
|
||||
@member-click="handleGalleryMemberClick"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Info Dialog -->
|
||||
@@ -585,6 +549,7 @@ import TagHistoryDialog from '../components/TagHistoryDialog.vue';
|
||||
import MemberActivityStatsDialog from '../components/MemberActivityStatsDialog.vue';
|
||||
import AccidentFormDialog from '../components/AccidentFormDialog.vue';
|
||||
import QuickAddMemberDialog from '../components/QuickAddMemberDialog.vue';
|
||||
import MemberGalleryDialog from '../components/MemberGalleryDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'DiaryView',
|
||||
@@ -600,7 +565,8 @@ export default {
|
||||
TagHistoryDialog,
|
||||
MemberActivityStatsDialog,
|
||||
AccidentFormDialog,
|
||||
QuickAddMemberDialog
|
||||
QuickAddMemberDialog,
|
||||
MemberGalleryDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -690,11 +656,6 @@ export default {
|
||||
editingActivityId: null, // ID der Aktivität, die gerade bearbeitet wird
|
||||
// Suche für Inline-Edit
|
||||
showGalleryDialog: false,
|
||||
galleryLoading: false,
|
||||
galleryImageUrl: null,
|
||||
galleryError: '',
|
||||
gallerySize: 200,
|
||||
galleryMembers: [],
|
||||
editShowDropdown: false,
|
||||
editSearchResults: [],
|
||||
editSearchForId: null,
|
||||
@@ -807,104 +768,11 @@ export default {
|
||||
}
|
||||
this.confirmDialog.isOpen = false;
|
||||
},
|
||||
revokeGalleryImage() {
|
||||
if (this.galleryImageUrl) {
|
||||
URL.revokeObjectURL(this.galleryImageUrl);
|
||||
this.galleryImageUrl = null;
|
||||
}
|
||||
},
|
||||
async openGalleryDialog() {
|
||||
if (!this.currentClub || this.galleryLoading) {
|
||||
if (!this.currentClub) {
|
||||
return;
|
||||
}
|
||||
this.showGalleryDialog = true;
|
||||
await this.loadGalleryMembers();
|
||||
},
|
||||
updateGallerySizeBasedOnCount() {
|
||||
const count = this.galleryMembers.length;
|
||||
if (count < 11) {
|
||||
this.gallerySize = 200;
|
||||
} else if (count < 22) {
|
||||
this.gallerySize = 150;
|
||||
} else {
|
||||
this.gallerySize = 100;
|
||||
}
|
||||
},
|
||||
async loadGalleryMembers() {
|
||||
if (!this.currentClub || this.galleryLoading) {
|
||||
return;
|
||||
}
|
||||
this.galleryLoading = true;
|
||||
this.galleryError = '';
|
||||
this.galleryImageUrl = null;
|
||||
this.revokeGalleryImage();
|
||||
try {
|
||||
const response = await apiClient.get(`/clubmembers/gallery/${this.currentClub}?format=json&size=${this.gallerySize}`);
|
||||
const members = response.data.members || [];
|
||||
|
||||
// Setze Größe basierend auf Anzahl der Mitglieder (vor dem Laden der Bilder)
|
||||
this.galleryMembers = members; // Temporär setzen für count
|
||||
this.updateGallerySizeBasedOnCount();
|
||||
|
||||
// Lade Bilder als Blobs und erstelle ObjectURLs
|
||||
this.galleryMembers = await Promise.all(members.map(async (member) => {
|
||||
try {
|
||||
const imageUrl = await this.loadMemberImageAsBlob(member.memberId);
|
||||
console.log(`[loadGalleryMembers] Member ${member.memberId} (${member.fullName}): imageUrl =`, imageUrl ? 'OK' : 'null');
|
||||
return {
|
||||
...member,
|
||||
imageUrl: imageUrl || null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`[loadGalleryMembers] Fehler beim Laden des Bildes für Mitglied ${member.memberId}:`, error);
|
||||
return {
|
||||
...member,
|
||||
imageUrl: null
|
||||
};
|
||||
}
|
||||
}));
|
||||
console.log('[loadGalleryMembers] Geladene Mitglieder:', this.galleryMembers.map(m => ({ id: m.memberId, name: m.fullName, hasImage: !!m.imageUrl })));
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Galerie:', error);
|
||||
this.galleryError = error?.response?.data?.error || 'Galerie konnte nicht geladen werden.';
|
||||
this.galleryMembers = [];
|
||||
} finally {
|
||||
this.galleryLoading = false;
|
||||
}
|
||||
},
|
||||
async loadMemberImageAsBlob(memberId) {
|
||||
try {
|
||||
const response = await apiClient.get(`/clubmembers/image/${this.currentClub}/${memberId}`, { responseType: 'blob' });
|
||||
if (!response.data || response.data.size === 0) {
|
||||
console.warn(`[loadMemberImageAsBlob] Leeres Blob für Mitglied ${memberId}`);
|
||||
return null;
|
||||
}
|
||||
const objectUrl = URL.createObjectURL(response.data);
|
||||
console.log(`[loadMemberImageAsBlob] ObjectURL erstellt für Mitglied ${memberId}:`, objectUrl.substring(0, 50) + '...');
|
||||
return objectUrl;
|
||||
} catch (error) {
|
||||
// 404 ist OK - Mitglied hat kein Bild
|
||||
if (error?.response?.status === 404) {
|
||||
console.log(`[loadMemberImageAsBlob] Kein Bild für Mitglied ${memberId} (404)`);
|
||||
return null;
|
||||
}
|
||||
console.error(`[loadMemberImageAsBlob] Fehler beim Laden des Bildes für Mitglied ${memberId}:`, error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async regenerateGallery() {
|
||||
await this.loadGalleryMembers();
|
||||
},
|
||||
closeGalleryDialog() {
|
||||
this.showGalleryDialog = false;
|
||||
this.revokeGalleryImage();
|
||||
// Revoke all object URLs
|
||||
this.galleryMembers.forEach(member => {
|
||||
if (member.imageUrl) {
|
||||
URL.revokeObjectURL(member.imageUrl);
|
||||
}
|
||||
});
|
||||
this.galleryMembers = [];
|
||||
},
|
||||
async handleGalleryMemberClick(member) {
|
||||
if (!this.date || this.date === 'new') {
|
||||
@@ -913,14 +781,6 @@ export default {
|
||||
console.log('[handleGalleryMemberClick] Clicked member:', member);
|
||||
await this.toggleParticipant(member.memberId);
|
||||
},
|
||||
handleImageError(member) {
|
||||
console.error(`[handleImageError] Bild konnte nicht geladen werden für Mitglied ${member.memberId} (${member.fullName}), URL:`, member.imageUrl);
|
||||
// Setze imageUrl auf null, damit der Placeholder angezeigt wird
|
||||
member.imageUrl = null;
|
||||
},
|
||||
handleImageLoad(member) {
|
||||
console.log(`[handleImageLoad] Bild erfolgreich geladen für Mitglied ${member.memberId} (${member.fullName})`);
|
||||
},
|
||||
|
||||
hasActivityVisual(pa) {
|
||||
if (!pa) return false;
|
||||
@@ -2527,7 +2387,6 @@ export default {
|
||||
if (this.timeChecker) {
|
||||
clearInterval(this.timeChecker);
|
||||
}
|
||||
this.revokeGalleryImage();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -2585,165 +2444,6 @@ h3 {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.gallery-dialog-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding: 0;
|
||||
min-height: 60vh;
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.gallery-controls {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--border-color, #ddd);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: #f9f9f9;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.gallery-controls label {
|
||||
font-weight: 500;
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.gallery-controls select {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--border-color, #ddd);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gallery-controls select:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.gallery-image-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.gallery-dialog-image {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
|
||||
position: static !important;
|
||||
left: auto !important;
|
||||
top: auto !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.gallery-loading,
|
||||
.gallery-error {
|
||||
font-size: 1rem;
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.gallery-members-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, max-content);
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
align-items: start;
|
||||
box-sizing: border-box;
|
||||
justify-items: start;
|
||||
}
|
||||
|
||||
.gallery-member-item {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gallery-member-item:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.gallery-member-item.is-participant {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.gallery-member-image {
|
||||
object-fit: cover;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
pointer-events: none; /* Prevent image from blocking clicks on parent */
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.gallery-member-name {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
color: #ff6b6b;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
padding: 4px 8px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(0, 0, 0, 0.5) !important;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.gallery-member-item.is-participant .gallery-member-name {
|
||||
color: #51cf66;
|
||||
}
|
||||
|
||||
.gallery-member-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 0;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.column:first-child {
|
||||
flex: 1;
|
||||
overflow: visible;
|
||||
|
||||
Reference in New Issue
Block a user