ebess / advanced-nova-media-library

A Laravel Nova field for displaying, creating, updating and ordering a Spatie Media Library model.
591 stars 295 forks source link

[FEATURE REQUEST] Lightbox preview #240

Open didix16 opened 3 years ago

didix16 commented 3 years ago

Hi there! First of all thanks for your effort doing this great package! However I'm missing something that would be nice. I've a field which is multiple image but when I gonna preview it, it always open in a new browser window instead of showing some kind of modal like lightbox to view the gallery.

So how about using it to preview multiple images, using arrow controls to view the next one?

Thanks in advance!

basserd commented 6 months ago

It's been a few years, have you implemented this somehow? @didix16

didix16 commented 6 months ago

Hi @basserd. Sorry I have not implemented any change. I'm using the packge without the improvements. Hope they apply anytime

basserd commented 6 months ago

@didix16 I've implemented a Javascript method that injects the use of photoswipe for the detail view of the resource. I know it's not the best solution. But it was something that I could quickly come up with.

didix16 commented 6 months ago

So you did a custom nova tool that loads your JavaScript? If so, could you share the code? Thanks in advance!

basserd commented 6 months ago

Well it could be changed to be within a tool. But I just loaded it from within a javascript file from the resources folder.

Added the following to the package.json:

"dependencies": {
    "photoswipe": "^5.4.3"
}

Added a file within the resources folder called photoGallery.js, I've added a few todo's where you should change the naming to match your application. Please don't be judgy about me harcoding these haha, I just didn't feel like making a more proper solution since I wasn't getting paid anymore at this point. It works with observers since I was not sure what events to use. I haven't noticed it having a alot of impact on the performance (for my client it didn't matter anyways).

import PhotoSwipeLightbox from 'photoswipe/lightbox'
import 'photoswipe/style.css'

class PhotoGallery {
    constructor() {
        document.addEventListener( 'DOMContentLoaded', ( event ) => {
            this.observe();
        });
    }

    shouldInit() {
        const currentUrl = window.location.pathname;

        // Check if URL matches the desired pattern and does not contain '/edit'
        // @ToDo: You should change this, or make it dynamic in a way (I did not need this).
        const isMatch = /\/resources\/(projects|work-performeds)\/\d+$/.test(currentUrl);
        const isEditNotInUrl = !currentUrl.includes('/edit');

        return isMatch && isEditNotInUrl;
    }

    observe() {
        const self = this;
        const config = { attributes: false, childList: true, subtree: true }
        const callback = function(mutationsList) {
            if (! self.shouldInit()) {
                return;
            }

            console.log('observe');

            for (let mutation of mutationsList) {
                if (mutation.type === 'childList' && mutation.addedNodes.length) {
                    mutation.addedNodes.forEach(node => {

                        // @ToDo: You should change this, or make it dynamic in a way (I did not need this).
                        if (node.nodeType === 1 && node.matches('div[dusk="work-performeds-detail-component"], div[dusk="projects-detail-component"]')) {
                            self.initiatePhotoswipe();
                        }
                    });
                }
            }
        };

        const observer = new MutationObserver( callback )
        observer.observe( document.body, config )
    }

    initiatePhotoswipe() {
        let self = this;

        // @ToDo: You should change this, or make it dynamic in a way (I did not need this).
        let imageBlocks = document.querySelectorAll(
          'div[dusk="work_performed_images"],' +
          'div[dusk="project_after_images"],' +
          'div[dusk="project_start_images"],' +
          'div[dusk="project_during_images"]' );

        imageBlocks.forEach( function( imageBlock ) {
            if (imageBlock.classList.contains( 'photoswipe-initiated' ) ) {
                return;
            }

            console.log( 'initiated');

            self.transformGalleryItem(imageBlock);
            self.addGalleryLightbox(imageBlock);

            imageBlock.classList.add('photoswipe-initiated');
        });
    }

    transformGalleryItem(block) {
        let draggableDivs = block.querySelectorAll('[data-draggable="true"]');

        draggableDivs.forEach(draggableDiv => {
            let imgElement = draggableDiv.querySelector('.gallery-image');

            if (!imgElement) return;

            let newAnchor = document.createElement('a');

            draggableDiv.querySelectorAll('.gallery-image').forEach(function(imgElement) {
                const image = new Image();
                image.onload = function() {
                    newAnchor.setAttribute('data-pswp-src', image.src);
                    newAnchor.setAttribute('data-pswp-width', image.naturalWidth.toString());
                    newAnchor.setAttribute('data-pswp-height', image.naturalHeight.toString());
                };
                image.src = imgElement.src;
            });

            newAnchor.className = 'gallery-item gallery-item-image mb-3 p-3 mr-3';
            newAnchor.innerHTML = imgElement.outerHTML;

            draggableDiv.parentNode.replaceChild(newAnchor, draggableDiv);
        });
    }

    addGalleryLightbox(block) {
        let galleries = block.querySelectorAll('.gallery .gallery-list');

        galleries.forEach(function(gallery) {
            let lightbox = new PhotoSwipeLightbox({
                gallery: gallery,
                children: '.gallery-item',
                pswpModule: () => import('photoswipe')
            });

            lightbox.init();

            gallery.addEventListener('click', function(event) {
                if (event.target && event.target.matches('.gallery-image')) {
                    const index = Array.from(gallery.querySelectorAll('.gallery-image')).indexOf(event.target);

                    lightbox.preload(index);
                    lightbox.loadAndOpen(index);
                }
            });
        });
    }
}

new PhotoGallery();
didix16 commented 5 months ago

Yeah I think it could be turned into a tool. At the moment I'll try your solution and see how it works. I'll speak with my front-end partner to propperly inject into our project.

Thank you so much @basserd !