From f85fee12eb1ca628f50f12f7b85e6bd87c6afb70 Mon Sep 17 00:00:00 2001 From: Vincent Vanwaelscappel Date: Fri, 20 Nov 2020 20:03:17 +0100 Subject: [PATCH] wip #4083 @3 --- js/libs/fluidbook/fluidbook.js | 4 +- js/libs/fluidbook/fluidbook.menu.js | 151 +------------ js/libs/fluidbook/fluidbook.slideshow.js | 61 ----- .../slideshow/fluidbook.slideshow.dummy.js | 8 + .../slideshow/fluidbook.slideshow.js | 43 ++++ .../slideshow/fluidbook.slideshow.splide.js | 211 ++++++++++++++++++ .../slideshow/fluidbook.slideshow.vacheron.js | 8 + 7 files changed, 278 insertions(+), 208 deletions(-) delete mode 100644 js/libs/fluidbook/fluidbook.slideshow.js create mode 100644 js/libs/fluidbook/slideshow/fluidbook.slideshow.dummy.js create mode 100644 js/libs/fluidbook/slideshow/fluidbook.slideshow.js create mode 100644 js/libs/fluidbook/slideshow/fluidbook.slideshow.splide.js create mode 100644 js/libs/fluidbook/slideshow/fluidbook.slideshow.vacheron.js diff --git a/js/libs/fluidbook/fluidbook.js b/js/libs/fluidbook/fluidbook.js index ad63f3b8..3e43737c 100644 --- a/js/libs/fluidbook/fluidbook.js +++ b/js/libs/fluidbook/fluidbook.js @@ -83,7 +83,9 @@ Fluidbook.prototype = { this.tooltip = new FluidbookTooltip(this); this.accessibility = new FluidbookAccessibility(this); this.sound = new FluidbookSound(this); - this.slideshow = new FluidbookSlideshow(this); + if (window.FluidbookSlideshow) { + this.slideshow = new FluidbookSlideshow(this); + } this.printing = new FluidbookPrint(this); this.posad = new FluidbookPOSAd(this); this.notes = new FluidbookNotes(this); diff --git a/js/libs/fluidbook/fluidbook.menu.js b/js/libs/fluidbook/fluidbook.menu.js index 191f235e..214dc634 100644 --- a/js/libs/fluidbook/fluidbook.menu.js +++ b/js/libs/fluidbook/fluidbook.menu.js @@ -313,7 +313,7 @@ FluidbookMenu.prototype = { // TODO: check which type should be passed to fluidbook.stats.track() ??? // this.fluidbook.stats.track(11); - this.fluidbook.slideshow.initSlideshow('slideshow_' + slideshow); + this.fluidbook.slideshow.initPopupSlideshow('slideshow_' + slideshow); // var $this = this; // var times = [250, 500, 750, 1000, 1250]; @@ -909,151 +909,10 @@ FluidbookMenu.prototype = { }).css('text-align', 'left'); break; case 'slideshow': - - // The slideshow should scale to the optimium size: no larger than 80% of screen width or height - // but also only as large as necessary for the included images (which are scaled to fit the screen ratio) - - // When the resize function fires, the slideshow might not yet be fully ready so the heights - // can be zero or negative. This causes an ugly jump with the thumbnails and a content redraw - // so we make it invisible until it is ready (resize function runs more than once) - if (fluidbook.slideshow.$wrapper.height() <= 0) { - m.addClass('hidden'); - } else { - m.removeClass('hidden'); - } - - var horizontalPadding = fluidbook.slideshow.$slideshow.outerWidth() - fluidbook.slideshow.$slideshow.width(); - var verticalPadding = fluidbook.slideshow.$wrapper.outerHeight() - fluidbook.slideshow.$wrapper.height(); - - // var lcss = 'color:#fff;padding:0.5em;background:'; - // console.log('%cWRAPPER: '+ fluidbook.slideshow.$wrapper.height() +' | ' + fluidbook.slideshow.$wrapper.outerHeight(), lcss+'dodgerblue'); - // console.log('%cVPad: '+ verticalPadding + ' | HPad: '+ horizontalPadding, lcss+'green'); - - var headerHeight = m.find('.caption').outerHeight(); // Height of title section that also contains close button - var thumbnailsPresent = false; - var thumbnailsHeight = 0; - if (m.find('.fb-slideshow-thumbnails').length > 0) { - thumbnailsPresent = true; - thumbnailsHeight = m.find('.fb-slideshow-thumbnails').outerHeight(); // Thumbnail slider height - } - - // On small/short screens, thumbnails are hidden and close icon / header is reduced - var optimizeHeight = (h < 600); - - if (optimizeHeight) { - fluidbook.slideshow.$thumbnails.hide(); - thumbnailsHeight = 0; - m.find('.caption, .back, .fb-slideshow-wrapper').addClass('small'); - } else { - fluidbook.slideshow.$thumbnails.show(); - m.find('.caption, .back, .fb-slideshow-wrapper').removeClass('small'); - } - - if (optimizeHeight) { - // ToDo: test this more with and without captions activated. Needs refactoring for better transparency of logic - //var contentHeight = h; - var contentHeight = hh; - } - - // On larger screens, the modal is sized based on a best fit for all images so that they are - // as large as possible but not so large that the modal exceeds 80% of the screen width and height. - // The modal should never be larger than necessary for the images - var maxModalWidth = Math.round(ww * 0.8); // Hard limit for modal width - var maxModalHeight = Math.round(hh * 0.8); // Hard limit for modal height - var maxImageWidth = maxModalWidth - horizontalPadding; // Hard limit for slideshow image width - var maxImageHeight = maxModalHeight - verticalPadding - headerHeight - thumbnailsHeight; // Hard limit for slideshow image height - var targetRatio = maxImageWidth / maxImageHeight; - var slideMaxWidth = 0; // Stores the largest scaled image width found - var slideMaxHeight = 0; // Stores the largest scaled image height found - - // console.log('%cmaxImgW: '+ maxImageWidth +' | maxImageH: '+ maxImageHeight, lcss+'blueviolet') - - // Process each image to determine dimensions and how big it will be once scaled onto the screen - fluidbook.slideshow.$slideshow.find('img').each(function () { - var meta = $(this).data('meta'); - var scaledWidth = 0; - var scaledHeight = 0; - - if (meta.width > maxImageWidth || meta.height > maxImageHeight) { // Does it need scaling? - // Scale to fit depending on ratio (best fit for box) - if (targetRatio > meta.ratio) { - scaledWidth = Math.round(meta.width * maxImageHeight / meta.height); - scaledHeight = maxImageHeight - } else { - scaledWidth = maxImageWidth; - scaledHeight = Math.round(meta.height * maxImageWidth / meta.width); - } - - } else { - scaledWidth = meta.width; - scaledHeight = meta.height; - } - - // console.log('%cscaledW: '+ scaledWidth +' | ScaledH: '+ scaledHeight, lcss+'crimson') - - slideMaxWidth = Math.max(scaledWidth, slideMaxWidth); - slideMaxHeight = Math.max(scaledHeight, slideMaxHeight); - }); - - w = slideMaxWidth + horizontalPadding; - h = slideMaxHeight + headerHeight + thumbnailsHeight + verticalPadding; - - - // On smaller screens, modal covers full available space - if (ww < 800 || hh < 500) { - w = ww; - h = hh; - fullscreen = true; - slideMaxWidth = w - horizontalPadding; - slideMaxHeight = h - headerHeight - thumbnailsHeight - verticalPadding; - $('.fb-slideshow, .fb-slideshow-thumbnails').addClass('fb-slideshow-fullscreen'); - } else { - $('.fb-slideshow, .fb-slideshow-thumbnails').removeClass('fb-slideshow-fullscreen'); - } - - // console.log('%cMODAL W: '+ w +' | H: '+ h, lcss+'orangered'); - // console.log('%cslideMaxWidth: '+ slideMaxWidth +' %cslideMaxHeight: '+ slideMaxHeight, lcss+'teal', lcss+'dodgerblue'); - - // console.log('%cslideMaxHeight: '+ slideMaxHeight +' %cheaderHeight: '+ headerHeight +' %cthumbnailsHeight: '+thumbnailsHeight, lcss+'teal', lcss+'blueviolet', lcss+'crimson'); - // console.log('%cMain slideshow height: '+ $('.fb-slideshow').height(), lcss+'hotpink'); - - // Process each slide and calculate optimal height for image and slide container - fluidbook.slideshow.$slideshow.find('.fb-slideshow-slide').each(function () { - // Get slide's caption height, if present - var captionHeight = $(this).find('.fb-slideshow-slide-caption').outerHeight() || 0; - - // Allow image to take up whatever height is left after accounting for the caption - // This is only the max-height so the image might not end up being this tall... - var slideHeight = $(this).outerHeight(); - $(this).find('.fb-slideshow-slide-image').css({ - 'max-height': slideMaxHeight - captionHeight - }); - }); - - // Don't attempt to interact with slider if it's not ready (resize function may run early) - if (!fluidbook.slideshow.primarySlider) break; - - fluidbook.slideshow.primarySlider.options.fixedHeight = slideMaxHeight; - fluidbook.slideshow.primarySlider.options.fixedWidth = slideMaxWidth; - - // If thumbnail slider content is wider than available space, enable dragging and focus:center mode - if (thumbnailsPresent) { - if (fluidbook.slideshow.$thumbnails.find('.splide__list').width() >= slideMaxWidth) { - fluidbook.slideshow.thumbSlider.options = { - drag: true, - focus: 'center', - } - } else { - fluidbook.slideshow.thumbSlider.options = { - drag: false, - focus: false, - } - } - fluidbook.slideshow.thumbSlider.refresh(); - } - - fluidbook.slideshow.primarySlider.refresh(); - + var res = this.fluidbook.slideshow.popupInstance.resize(ww, hh, m); + w = res.w; + h = res.h; + fullscreen = res.fullscreen; break; case 'print': case 'download': diff --git a/js/libs/fluidbook/fluidbook.slideshow.js b/js/libs/fluidbook/fluidbook.slideshow.js deleted file mode 100644 index 609ce906..00000000 --- a/js/libs/fluidbook/fluidbook.slideshow.js +++ /dev/null @@ -1,61 +0,0 @@ -function FluidbookSlideshow(fluidbook) { - this.fluidbook = fluidbook; -} - -FluidbookSlideshow.prototype = { - initSlideshow: function (id) { - - var $this = this; - this.slideshowID = '#' + id; - this.$slideshow = $(this.slideshowID); - this.$wrapper = this.$slideshow.parent(); - this.thumbnailsID = this.slideshowID + '_thumbnails'; - this.$thumbnails = $(this.thumbnailsID); - this.showThumbnails = this.$slideshow.attr('data-thumbnails') === '1'; - - - if (this.$slideshow.length === 0) { - console.warn('Slideshow not found: ' + this.slideshowID); - return false; - } - - // Open slideshow at a specific image - // Ref: https://team.cubedesigners.com/redmine/issues/3131 - var openIndex = parseInt(this.$slideshow.attr('data-open-index')); - if (isNaN(openIndex)) { - openIndex = 1; - } - openIndex--; // Convert to zero-based index - - // Height and width are set by fluidbook.menu resize function - // Note: certain settings are set via the data-splide attribute, generated on the server-side - var slideshowSettings = { - type : 'loop', // slide | loop | fade - start : openIndex, - pagination : false, - arrows : true, - }; - - this.primarySlider = new Splide(this.slideshowID, slideshowSettings); - - - if (this.showThumbnails) { - - this.thumbSlider = new Splide(this.thumbnailsID, { - autoWidth : true, // Height is handled by the CSS - start : openIndex, - gap : 10, - cover : false, - isNavigation: true, - arrows : false, - pagination : false, - drag : false, // Will be enabled along with focus:center if thumbs are wider than available space - } ); - - this.thumbSlider.mount(); - this.primarySlider.sync(this.thumbSlider); - } - - this.primarySlider.mount(); - } -}; diff --git a/js/libs/fluidbook/slideshow/fluidbook.slideshow.dummy.js b/js/libs/fluidbook/slideshow/fluidbook.slideshow.dummy.js new file mode 100644 index 00000000..eb8423b5 --- /dev/null +++ b/js/libs/fluidbook/slideshow/fluidbook.slideshow.dummy.js @@ -0,0 +1,8 @@ +function FluidbookVacheronSlideshow(fluidbook) { + this.fluidbook = fluidbook; +} + +FluidbookVacheronSlideshow.prototype = { + initSlideshow: function (id) { + } +}; diff --git a/js/libs/fluidbook/slideshow/fluidbook.slideshow.js b/js/libs/fluidbook/slideshow/fluidbook.slideshow.js new file mode 100644 index 00000000..962f54af --- /dev/null +++ b/js/libs/fluidbook/slideshow/fluidbook.slideshow.js @@ -0,0 +1,43 @@ +function FluidbookSlideshow(fluidbook) { + this.fluidbook = fluidbook; + this.instances = {inline: null, popup: null}; + + var $this = this; + + $.each(this.instances, function (k, v) { + var s = (k === 'inline' ? $this.fluidbook.settings.slideshowInlineLibrary : $this.fluidbook.settings.slideshowPopupLibrary); + switch (s) { + case 'dummy': + $this.instances[k] = new FluidbookDummySlideshow($this.fluidbook); + case 'vacheron': + $this.instances[k] = new FluidbookVacheronSlideshow($this.fluidbook); + break; + case undefined: + case null: + case 'splide': + default: + $this.instances[k] = new FluidbookSplideSlideshow($this.fluidbook); + break; + } + }); + + this.popupInstance = $this.instances['popup']; + this.inlineInstance = $this.instances['inline']; +} + +FluidbookSlideshow.prototype = { + initPopupSlideshow: function (id) { + this.popupInstance.initSlideshow(id); + }, + + initInlineSlideshow: function (id) { + this.inlineInstance.initSlideshow(id); + }, + initSlideshow: function (id, context) { + if (context === 'popup') { + this.initPopupSlideshow(id); + } else if (context === 'inline') { + this.initInlineSlideshow(id); + } + } +}; diff --git a/js/libs/fluidbook/slideshow/fluidbook.slideshow.splide.js b/js/libs/fluidbook/slideshow/fluidbook.slideshow.splide.js new file mode 100644 index 00000000..66b3bfb5 --- /dev/null +++ b/js/libs/fluidbook/slideshow/fluidbook.slideshow.splide.js @@ -0,0 +1,211 @@ +function FluidbookSplideSlideshow(fluidbook) { + this.fluidbook = fluidbook; +} + +FluidbookSplideSlideshow.prototype = { + initSlideshow: function (id) { + this.slideshowID = '#' + id; + this.$slideshow = $(this.slideshowID); + this.$wrapper = this.$slideshow.parent(); + this.thumbnailsID = this.slideshowID + '_thumbnails'; + this.$thumbnails = $(this.thumbnailsID); + this.showThumbnails = this.$slideshow.attr('data-thumbnails') === '1'; + + if (this.$slideshow.length === 0) { + console.warn('Slideshow not found: ' + this.slideshowID); + return false; + } + + // Open slideshow at a specific image + // Ref: https://team.cubedesigners.com/redmine/issues/3131 + var openIndex = parseInt(this.$slideshow.attr('data-open-index')); + if (isNaN(openIndex)) { + openIndex = 1; + } + openIndex--; // Convert to zero-based index + + // Height and width are set by fluidbook.menu resize function + // Note: certain settings are set via the data-splide attribute, generated on the server-side + var slideshowSettings = { + type: 'loop', // slide | loop | fade + start: openIndex, + pagination: false, + arrows: true, + }; + + this.primarySlider = new Splide(this.slideshowID, slideshowSettings); + + if (this.showThumbnails) { + this.thumbSlider = new Splide(this.thumbnailsID, { + autoWidth: true, // Height is handled by the CSS + start: openIndex, + gap: 10, + cover: false, + isNavigation: true, + arrows: false, + pagination: false, + drag: false, // Will be enabled along with focus:center if thumbs are wider than available space + }); + + this.thumbSlider.mount(); + this.primarySlider.sync(this.thumbSlider); + } + + this.primarySlider.mount(); + }, + + resize: function (ww, hh, m) { + var w = ww * 0.8; + var h = hh * 0.8; + var fullscreen = false; + + // The slideshow should scale to the optimium size: no larger than 80% of screen width or height + // but also only as large as necessary for the included images (which are scaled to fit the screen ratio) + + // When the resize function fires, the slideshow might not yet be fully ready so the heights + // can be zero or negative. This causes an ugly jump with the thumbnails and a content redraw + // so we make it invisible until it is ready (resize function runs more than once) + if (this.$wrapper === undefined) { + return {w: w, h: h, fullscreen: fullscreen}; + } + if (this.$wrapper.height() <= 0) { + m.addClass('hidden'); + } else { + m.removeClass('hidden'); + } + + var horizontalPadding = this.$slideshow.outerWidth() - this.$slideshow.width(); + var verticalPadding = this.$wrapper.outerHeight() - this.$wrapper.height(); + + // var lcss = 'color:#fff;padding:0.5em;background:'; + // console.log('%cWRAPPER: '+ this.$wrapper.height() +' | ' + this.$wrapper.outerHeight(), lcss+'dodgerblue'); + // console.log('%cVPad: '+ verticalPadding + ' | HPad: '+ horizontalPadding, lcss+'green'); + + var headerHeight = m.find('.caption').outerHeight(); // Height of title section that also contains close button + var thumbnailsPresent = false; + var thumbnailsHeight = 0; + if (m.find('.fb-slideshow-thumbnails').length > 0) { + thumbnailsPresent = true; + thumbnailsHeight = m.find('.fb-slideshow-thumbnails').outerHeight(); // Thumbnail slider height + } + + // On small/short screens, thumbnails are hidden and close icon / header is reduced + var optimizeHeight = (h < 600); + + if (optimizeHeight) { + this.$thumbnails.hide(); + thumbnailsHeight = 0; + m.find('.caption, .back, .fb-slideshow-wrapper').addClass('small'); + } else { + this.$thumbnails.show(); + m.find('.caption, .back, .fb-slideshow-wrapper').removeClass('small'); + } + + if (optimizeHeight) { + // ToDo: test this more with and without captions activated. Needs refactoring for better transparency of logic + //var contentHeight = h; + var contentHeight = hh; + } + + // On larger screens, the modal is sized based on a best fit for all images so that they are + // as large as possible but not so large that the modal exceeds 80% of the screen width and height. + // The modal should never be larger than necessary for the images + var maxModalWidth = Math.round(ww * 0.8); // Hard limit for modal width + var maxModalHeight = Math.round(hh * 0.8); // Hard limit for modal height + var maxImageWidth = maxModalWidth - horizontalPadding; // Hard limit for slideshow image width + var maxImageHeight = maxModalHeight - verticalPadding - headerHeight - thumbnailsHeight; // Hard limit for slideshow image height + var targetRatio = maxImageWidth / maxImageHeight; + var slideMaxWidth = 0; // Stores the largest scaled image width found + var slideMaxHeight = 0; // Stores the largest scaled image height found + + // console.log('%cmaxImgW: '+ maxImageWidth +' | maxImageH: '+ maxImageHeight, lcss+'blueviolet') + + // Process each image to determine dimensions and how big it will be once scaled onto the screen + this.$slideshow.find('img').each(function () { + var meta = $(this).data('meta'); + var scaledWidth = 0; + var scaledHeight = 0; + + if (meta.width > maxImageWidth || meta.height > maxImageHeight) { // Does it need scaling? + // Scale to fit depending on ratio (best fit for box) + if (targetRatio > meta.ratio) { + scaledWidth = Math.round(meta.width * maxImageHeight / meta.height); + scaledHeight = maxImageHeight + } else { + scaledWidth = maxImageWidth; + scaledHeight = Math.round(meta.height * maxImageWidth / meta.width); + } + + } else { + scaledWidth = meta.width; + scaledHeight = meta.height; + } + + // console.log('%cscaledW: '+ scaledWidth +' | ScaledH: '+ scaledHeight, lcss+'crimson') + + slideMaxWidth = Math.max(scaledWidth, slideMaxWidth); + slideMaxHeight = Math.max(scaledHeight, slideMaxHeight); + }); + + w = slideMaxWidth + horizontalPadding; + h = slideMaxHeight + headerHeight + thumbnailsHeight + verticalPadding; + + // On smaller screens, modal covers full available space + if (ww < 800 || hh < 500) { + w = ww; + h = hh; + fullscreen = true; + slideMaxWidth = w - horizontalPadding; + slideMaxHeight = h - headerHeight - thumbnailsHeight - verticalPadding; + $('.fb-slideshow, .fb-slideshow-thumbnails').addClass('fb-slideshow-fullscreen'); + } else { + $('.fb-slideshow, .fb-slideshow-thumbnails').removeClass('fb-slideshow-fullscreen'); + } + + // console.log('%cMODAL W: '+ w +' | H: '+ h, lcss+'orangered'); + // console.log('%cslideMaxWidth: '+ slideMaxWidth +' %cslideMaxHeight: '+ slideMaxHeight, lcss+'teal', lcss+'dodgerblue'); + + // console.log('%cslideMaxHeight: '+ slideMaxHeight +' %cheaderHeight: '+ headerHeight +' %cthumbnailsHeight: '+thumbnailsHeight, lcss+'teal', lcss+'blueviolet', lcss+'crimson'); + // console.log('%cMain slideshow height: '+ $('.fb-slideshow').height(), lcss+'hotpink'); + + // Process each slide and calculate optimal height for image and slide container + this.$slideshow.find('.fb-slideshow-slide').each(function () { + // Get slide's caption height, if present + var captionHeight = $(this).find('.fb-slideshow-slide-caption').outerHeight() || 0; + + // Allow image to take up whatever height is left after accounting for the caption + // This is only the max-height so the image might not end up being this tall... + var slideHeight = $(this).outerHeight(); + $(this).find('.fb-slideshow-slide-image').css({ + 'max-height': slideMaxHeight - captionHeight + }); + }); + + // Don't attempt to interact with slider if it's not ready (resize function may run early) + if (!this.primarySlider) { + return {w: w, h: h, fullscreen: fullscreen}; + } + + this.primarySlider.options.fixedHeight = slideMaxHeight; + this.primarySlider.options.fixedWidth = slideMaxWidth; + + // If thumbnail slider content is wider than available space, enable dragging and focus:center mode + if (thumbnailsPresent) { + if (this.$thumbnails.find('.splide__list').width() >= slideMaxWidth) { + this.thumbSlider.options = { + drag: true, + focus: 'center', + } + } else { + this.thumbSlider.options = { + drag: false, + focus: false, + } + } + this.thumbSlider.refresh(); + } + + this.primarySlider.refresh(); + return {w: w, h: h, fullscreen: fullscreen}; + } +}; diff --git a/js/libs/fluidbook/slideshow/fluidbook.slideshow.vacheron.js b/js/libs/fluidbook/slideshow/fluidbook.slideshow.vacheron.js new file mode 100644 index 00000000..10178567 --- /dev/null +++ b/js/libs/fluidbook/slideshow/fluidbook.slideshow.vacheron.js @@ -0,0 +1,8 @@ +function FluidbookDummySlideshow(fluidbook) { + this.fluidbook = fluidbook; +} + +FluidbookDummySlideshow.prototype = { + initSlideshow: function (id) { + } +}; -- 2.39.5