Files
yourpart3/frontend/src/views/social/GalleryView.vue

393 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="gallery-page">
<section class="gallery-hero surface-card">
<div>
<span class="gallery-kicker">Bilder und Ordner</span>
<h2>{{ $t('socialnetwork.gallery.title') }}</h2>
<p>Eigene Inhalte organisieren, sichtbar machen und in Ordnern strukturieren.</p>
</div>
</section>
<div class="gallery-view">
<div class="sidebar surface-card">
<h3>{{ $t('socialnetwork.gallery.folders') }}</h3>
<ul class="tree">
<folder-item v-for="folder in [folders]" :key="folder.id" :folder="folder"
:selected-folder="selectedFolder" @select-folder="selectFolder" :isLastItem="true" :depth="0"
:parentsWithChildren="[false]" @edit-folder="openEditFolderDialog" @delete-folder="deleteFolder">
</folder-item>
</ul>
<button @click="openCreateFolderDialog">{{ $t('socialnetwork.gallery.create_folder') }}</button>
</div>
<div class="content">
<div class="upload-section surface-card">
<div class="upload-header" @click="toggleUploadSection">
<span>
<i class="icon-upload-toggle">{{ isUploadVisible ? '&#9650;' : '&#9660;' }}</i>
</span>
<h3>{{ $t('socialnetwork.gallery.upload.title') }}</h3>
</div>
<div v-if="isUploadVisible" class="upload-content">
<form @submit.prevent="handleUpload">
<div class="form-group">
<label for="imageTitle">{{ $t('socialnetwork.gallery.upload.image_title') }}</label>
<input type="text" v-model="imageTitle"
:placeholder="$t('socialnetwork.gallery.upload.image_title')" />
</div>
<div class="form-group">
<label for="imageFile">{{ $t('socialnetwork.gallery.upload.image_file') }}</label>
<input type="file" @change="onFileChange" accept="image/*" required />
<div v-if="imagePreview" class="image-preview">
<img :src="imagePreview" alt="Image Preview"
style="max-width: 150px; max-height: 150px;" />
</div>
</div>
<div class="form-group">
<label for="visibility">{{ $t('socialnetwork.gallery.upload.visibility') }}</label>
<multiselect v-model="selectedVisibilities" :options="visibilityOptions" :multiple="true"
:close-on-select="false" label="description"
:placeholder="$t('socialnetwork.gallery.upload.selectvisibility')" :track-by="'value'">
<template #option="{ option }">
<span v-if="option && option.description">
{{ $t(`socialnetwork.gallery.visibility.${option.description}`) }}
</span>
</template>
<template #tag="{ option, remove }">
<span v-if="option && option.description" class="multiselect__tag">
{{ $t(`socialnetwork.gallery.visibility.${option.description}`) }}
<span @click="remove(option)">×</span>
</span>
</template>
</multiselect>
</div>
<button type="submit" class="upload-button">
{{ $t('socialnetwork.gallery.upload.upload_button') }}
</button>
</form>
</div>
</div>
<div class="image-list surface-card">
<h3>{{ $t('socialnetwork.gallery.images') }}</h3>
<ul v-if="images.length > 0" class="image-grid">
<li v-for="image in images" :key="image.id" @click="openImageDialog(image)">
<img :src="image.url || image.placeholder" alt="Loading..." />
<p>{{ image.title }}</p>
</li>
</ul>
<span v-else>{{ $t('socialnetwork.gallery.noimages') }}</span>
</div>
</div>
</div>
</div>
</template>
<script>
import apiClient from '@/utils/axios.js';
import Multiselect from 'vue-multiselect';
import FolderItem from '../../components/FolderItem.vue';
import 'vue-multiselect/dist/vue-multiselect.min.css';
import { EventBus } from '@/utils/eventBus.js';
export default {
components: {
FolderItem,
Multiselect,
},
data() {
return {
folders: { children: [] },
images: [],
selectedFolder: null,
imageTitle: '',
fileToUpload: null,
isUploadVisible: false,
visibilityOptions: [],
selectedVisibilities: [],
imagePreview: null,
};
},
async mounted() {
await this.loadFolders();
await this.loadImageVisibilities();
if (this.folders) {
this.selectFolder(this.folders);
}
EventBus.on('folderCreated', this.loadFolders);
},
beforeUnmount() {
EventBus.off('folderCreated', this.loadFolders);
},
methods: {
async loadFolders() {
try {
const response = await apiClient.get('/api/socialnetwork/folders');
this.folders = response.data;
} catch (error) {
console.error('Error loading folders:', error);
}
},
async loadImageVisibilities() {
try {
const response = await apiClient.get('/api/socialnetwork/imagevisibilities');
this.visibilityOptions = response.data;
} catch (error) {
console.error('Error loading visibility options:', error);
}
},
async selectFolder(folder) {
this.selectedFolder = folder;
await this.loadImages(folder.id);
},
async loadImages(folderId) {
try {
const response = await apiClient.get(`/api/socialnetwork/folder/${folderId}`);
this.images = response.data.map((image) => ({
...image,
placeholder:
'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"%3E%3C/svg%3E', // Placeholder SVG
url: null,
}));
await this.fetchImages();
} catch (error) {
console.error('Error loading images:', error);
}
},
async fetchImages() {
this.images.forEach((image) => {
this.fetchImage(image);
});
},
openCreateFolderDialog() {
const parentFolder = this.selectedFolder || { id: null, name: this.$t('socialnetwork.gallery.root_folder') };
Object.assign(this.$root.$refs.createFolderDialog, {
parentFolder: parentFolder,
folderId: 0,
});
this.$root.$refs.createFolderDialog.open();
},
async handleFolderCreated() {
await this.loadFolders();
},
onFileChange(event) {
this.fileToUpload = event.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
this.imagePreview = e.target.result;
};
reader.readAsDataURL(this.fileToUpload);
},
async handleUpload() {
if (!this.fileToUpload) return;
const formData = new FormData();
formData.append('image', this.fileToUpload);
formData.append('folderId', this.selectedFolder.id);
formData.append('title', this.imageTitle);
formData.append('visibility', JSON.stringify(this.selectedVisibilities.map((v) => v.id)));
try {
await apiClient.post('/api/socialnetwork/images', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
this.loadImages(this.selectedFolder.id);
this.imageTitle = '';
this.fileToUpload = null;
this.imagePreview = null;
this.selectedVisibilities = [];
} catch (error) {
console.error('Error uploading image:', error);
}
},
async fetchImage(image) {
const userId = localStorage.getItem('userid') || sessionStorage.getItem('userid');
try {
const response = await apiClient.get(`/api/socialnetwork/image/${image.hash}`, {
headers: {
userid: userId,
},
responseType: 'blob',
});
image.url = URL.createObjectURL(response.data);
} catch (error) {
console.error('Error fetching image:', error);
}
},
toggleUploadSection() {
this.isUploadVisible = !this.isUploadVisible;
},
openImageDialog(image) {
this.$root.$refs.editImageDialog.open(image);
},
async saveImage(updatedImage) {
try {
const response = await apiClient.put(`/api/socialnetwork/images/${updatedImage.id}`, {
title: updatedImage.title,
visibilities: updatedImage.visibilities,
});
this.images = response.data.map((image) => ({
...image,
placeholder:
'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"%3E%3C/svg%3E', // Placeholder SVG
url: null,
}));
await this.fetchImages();
} catch (error) {
console.error('Error saving image:', error);
}
},
openEditFolderDialog(folder) {
const parentFolder = folder.parent || { id: null, name: this.$t('socialnetwork.gallery.root_folder') };
Object.assign(this.$root.$refs.createFolderDialog, {
parentFolder: parentFolder,
folderId: folder.id,
});
this.$root.$refs.createFolderDialog.open(folder);
},
async deleteFolder(folder) {
const folderName = folder.name;
const confirmed = await this.$root.$refs.chooseDialog.open({
title: this.$t('socialnetwork.gallery.delete_folder_confirmation_title'),
message: this.$t('socialnetwork.gallery.delete_folder_confirmation_message').replace('%%folderName%%', folderName),
});
if (confirmed) {
const deletedFolderIsCurrentFolder = folder.id === this.selectFolder.id;
try {
await apiClient.delete(`/api/socialnetwork/folders/${folder.id}`);
await this.loadFolders();
} catch (error) {
console.error('Fehler beim Löschen des Ordners:', error);
}
if (deletedFolderIsCurrentFolder) {
this.selectFolder = this.folders[0];
}
}
},
},
};
</script>
<style scoped>
.gallery-page {
max-width: var(--content-max-width);
margin: 0 auto;
padding-bottom: 24px;
}
.gallery-hero {
padding: 24px 26px;
margin-bottom: 16px;
}
.gallery-kicker {
display: inline-block;
margin-bottom: 10px;
padding: 4px 10px;
border-radius: 999px;
background: rgba(120, 195, 138, 0.14);
color: #42634e;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.gallery-hero p {
margin: 0;
color: var(--color-text-secondary);
}
.gallery-view {
display: flex;
gap: 18px;
}
.sidebar {
width: 240px;
margin-right: 0;
padding: 18px;
}
.content {
flex: 1;
min-width: 0;
}
.upload-section {
margin-bottom: 20px;
padding: 18px;
}
.image-list {
display: flex;
flex-direction: column;
flex-wrap: wrap;
padding: 18px;
}
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 14px;
list-style-type: none;
padding: 0;
margin: 0;
}
.image-grid li {
margin: 0;
padding: 12px;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background: rgba(255, 255, 255, 0.72);
cursor: pointer;
}
.image-grid p {
text-align: center;
margin: 0;
}
.image-list li img {
width: 100%;
height: 150px;
object-fit: cover;
border-radius: 12px;
margin-bottom: 10px;
}
.icon-upload-toggle {
cursor: pointer;
}
.multiselect {
display: inline-block;
width: auto;
}
.upload-header {
display: flex;
align-items: center;
gap: 10px;
}
.tree {
padding: 0;
}
@media (max-width: 960px) {
.gallery-view {
flex-direction: column;
}
.sidebar {
width: auto;
}
}
</style>