added multiple pic viewer and additional fixes
This commit is contained in:
@@ -30,19 +30,20 @@
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
display: flex;
|
||||
flex-direction: column; /* Allow content to stack */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
cursor: zoom-out; /* Indicate it's closable */
|
||||
/* Ensure it can receive keyboard events for 'esc' */
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.tt-fullscreen-image {
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
max-height: 80%; /* Give space for gallery */
|
||||
object-fit: contain;
|
||||
cursor: default; /* Change cursor back to default when over the image */
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.tt-fullscreen-close-btn {
|
||||
@@ -61,4 +62,130 @@
|
||||
|
||||
.tt-fullscreen-close-btn:hover {
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
.tt-fullscreen-gallery-nav {
|
||||
position: absolute;
|
||||
bottom: 20px; /* Position at the bottom */
|
||||
width: 90%; /* Match image width */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
z-index: 10000;
|
||||
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent background for readability */
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.tt-fullscreen-gallery-nav .nav-arrow {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.tt-fullscreen-gallery-nav .nav-arrow:hover {
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
.tt-fullscreen-gallery-nav .gallery-thumbnails {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
overflow-x: auto; /* Allow horizontal scrolling for many thumbnails */
|
||||
padding: 5px 0;
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.tt-fullscreen-gallery-nav .gallery-thumbnails::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, Opera */
|
||||
}
|
||||
|
||||
|
||||
.tt-fullscreen-gallery-nav .thumbnail {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
object-fit: cover;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.tt-fullscreen-gallery-nav .thumbnail:hover {
|
||||
border-color: #fff;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.tt-fullscreen-gallery-nav .thumbnail.active {
|
||||
border-color: #3b82f6; /* Highlight active thumbnail */
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Modal gallery grid for uploaded images */
|
||||
.asset-gallery-upload-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); /* Responsive grid */
|
||||
gap: 15px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.asset-gallery-upload-grid .gallery-item {
|
||||
position: relative;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
padding-bottom: 100%; /* Creates a square aspect ratio */
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.asset-gallery-upload-grid .gallery-item img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.asset-gallery-upload-grid .gallery-item.is-main::before {
|
||||
content: '\f005'; /* FontAwesome star icon */
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-weight: 900;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
color: gold;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 3px;
|
||||
padding: 2px 5px;
|
||||
font-size: 14px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.asset-gallery-upload-grid .gallery-item-actions {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 5px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.asset-gallery-upload-grid .gallery-item:hover .gallery-item-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.asset-gallery-upload-grid .gallery-item-actions .btn {
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
padding: .25rem .5rem;
|
||||
}
|
||||
@@ -36,11 +36,11 @@ Vue.component('asset-management', {
|
||||
|
||||
<template v-slot:assetdetails="{ row }">
|
||||
<div class="d-flex align-items-center">
|
||||
<tt-asset-image :image-id="row.mainImageId" class="mr-3"/>
|
||||
<tt-asset-image :image-id="row.mainImageId" :all-image-ids="row.imageIds" class="mr-3"/>
|
||||
<div>
|
||||
<strong>{{ row.name }}</strong><br>
|
||||
<strong v-html="row.name.replace(/\\n/g, '<br>')"></strong><br>
|
||||
<small class="text-muted">{{ row.assetNumber }}</small><br>
|
||||
<small v-if="row.description">{{ row.description }}</small>
|
||||
<small v-if="row.description" v-html="row.description.replace(/\\n/g, '<br>')"></small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -103,15 +103,20 @@ Vue.component('asset-management', {
|
||||
Vue.component('tt-asset-image', {
|
||||
props: {
|
||||
imageId: Number,
|
||||
allImageIds: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isFullScreen: false,
|
||||
currentFullScreenImageId: null,
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div class="asset-image-container" :style="{ cursor: imageId ? 'pointer' : 'default' }">
|
||||
<img v-if="imageId" :src="'/File/show?id=' + imageId" @error="onImageError" @click="openFullScreen" class="asset-image"/>
|
||||
<img v-if="imageId" :src="'/File/show?id=' + imageId" @error="onImageError" @click="openFullScreen(imageId)" class="asset-image"/>
|
||||
<div v-else class="asset-image-placeholder">
|
||||
<i class="fas fa-camera"></i>
|
||||
</div>
|
||||
@@ -122,11 +127,29 @@ Vue.component('tt-asset-image', {
|
||||
@keydown.esc="closeFullScreen"
|
||||
tabindex="-1"
|
||||
ref="fullScreenOverlay">
|
||||
<img :src="'/File/show?id=' + imageId"
|
||||
<img :src="'/File/show?id=' + currentFullScreenImageId"
|
||||
class="tt-fullscreen-image"
|
||||
@click.stop /> <button class="tt-fullscreen-close-btn" @click="closeFullScreen">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
@click.stop />
|
||||
<button class="tt-fullscreen-close-btn" @click="closeFullScreen">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
|
||||
<div v-if="allImageIds.length > 1" class="tt-fullscreen-gallery-nav">
|
||||
<button class="nav-arrow left" @click.stop="prevImage">
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
</button>
|
||||
<div class="gallery-thumbnails">
|
||||
<img v-for="imgId in allImageIds"
|
||||
:key="imgId"
|
||||
:src="'/File/show?id=' + imgId"
|
||||
:class="{'active': imgId === currentFullScreenImageId}"
|
||||
@click.stop="setFullScreenImage(imgId)"
|
||||
class="thumbnail" />
|
||||
</div>
|
||||
<button class="nav-arrow right" @click.stop="nextImage">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
@@ -134,18 +157,42 @@ Vue.component('tt-asset-image', {
|
||||
onImageError(event) {
|
||||
event.target.src = 'https://placehold.co/60x60/eee/ccc?text=Error'; // Fallback placeholder
|
||||
},
|
||||
openFullScreen() {
|
||||
openFullScreen(initialImageId) {
|
||||
if (this.imageId) { // Only open if an image exists
|
||||
this.isFullScreen = true;
|
||||
this.currentFullScreenImageId = initialImageId;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.fullScreenOverlay) {
|
||||
this.$refs.fullScreenOverlay.focus(); // Focus the overlay to capture keydown events
|
||||
}
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
});
|
||||
}
|
||||
},
|
||||
closeFullScreen() {
|
||||
this.isFullScreen = false;
|
||||
this.currentFullScreenImageId = null;
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
},
|
||||
setFullScreenImage(imageId) {
|
||||
this.currentFullScreenImageId = imageId;
|
||||
},
|
||||
prevImage() {
|
||||
const currentIndex = this.allImageIds.indexOf(this.currentFullScreenImageId);
|
||||
const prevIndex = (currentIndex - 1 + this.allImageIds.length) % this.allImageIds.length;
|
||||
this.currentFullScreenImageId = this.allImageIds[prevIndex];
|
||||
},
|
||||
nextImage() {
|
||||
const currentIndex = this.allImageIds.indexOf(this.currentFullScreenImageId);
|
||||
const nextIndex = (currentIndex + 1) % this.allImageIds.length;
|
||||
this.currentFullScreenImageId = this.allImageIds[nextIndex];
|
||||
},
|
||||
handleKeyDown(event) {
|
||||
if (event.key === 'ArrowLeft') {
|
||||
this.prevImage();
|
||||
} else if (event.key === 'ArrowRight') {
|
||||
this.nextImage();
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -365,7 +412,7 @@ Vue.component('asset-management-modal', {
|
||||
<div v-if="!asset.imageIds || asset.imageIds.length === 0" class="text-muted text-center p-3">
|
||||
Noch keine Bilder hochgeladen.
|
||||
</div>
|
||||
<div v-else class="asset-gallery">
|
||||
<div v-else class="asset-gallery-upload-grid">
|
||||
<div v-for="imgId in asset.imageIds" :key="imgId"
|
||||
class="gallery-item"
|
||||
:class="{ 'is-main': imgId === asset.mainImageId }">
|
||||
@@ -413,6 +460,8 @@ Vue.component('asset-management-modal', {
|
||||
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/AssetManagement/getById`, { params: { id: this.id } });
|
||||
if (!response.data.imageIds) {
|
||||
response.data.imageIds = [];
|
||||
} else if (typeof response.data.imageIds === 'string') {
|
||||
response.data.imageIds = JSON.parse(response.data.imageIds);
|
||||
}
|
||||
this.asset = response.data;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user