]> _ Git - fluidbook-html5.git/commitdiff
fix #3054 @7
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Thu, 26 Sep 2019 12:39:54 +0000 (14:39 +0200)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Thu, 26 Sep 2019 12:39:54 +0000 (14:39 +0200)
images/interface.svg
js/libs/fluidbook/fluidbook.links.js
js/libs/fluidbook/fluidbook.share.js
js/libs/fluidbook/links/fluidbook.links.zoom.js [new file with mode: 0644]
style/fluidbook.less

index aa0b0aeb5b8d2aa8741163b05e2994c0392fde71..e80f8558e99511a46baab97db376f1d059736776 100644 (file)
     <symbol id="interface-minus" viewBox="0 0 50 50">
         <rect x="16.5" y="23.5" width="17" height="2.9"/>
     </symbol>
+    <symbol id="icon-site" viewBox="0 0 25 25">
+        <path d="M18.5,20.6H10c-0.4,0-0.7,0.3-0.7,0.7S9.6,22,10,22h8.5c0.4,0,0.7-0.3,0.7-0.7S18.9,20.6,18.5,20.6z"/>
+        <path d="M21.8,3H5.4c-1.2,0-2.2,1-2.2,2.2v7.1c0,0.4,0.3,0.7,0.7,0.7s0.7-0.3,0.7-0.7V5.1c0-0.4,0.3-0.8,0.8-0.8h16.4
+               c0.4,0,0.8,0.3,0.8,0.8V15h-11c-0.4,0-0.7,0.3-0.7,0.7s0.3,0.7,0.7,0.7h11v0.8c0,0.4-0.3,0.8-0.8,0.8H10.6c-0.4,0-0.7,0.3-0.7,0.7
+               s0.3,0.7,0.7,0.7h11.2c1.2,0,2.2-1,2.2-2.2V5.1C23.9,3.9,22.9,3,21.8,3z"/>
+        <path d="M11.5,10.2c-0.2-0.2-0.5-0.2-0.8-0.1L1.4,15c-0.3,0.1-0.4,0.4-0.4,0.7s0.2,0.5,0.5,0.6l1.9,0.5l-1.7,2.1
+               c-0.2,0.3-0.2,0.7,0.1,1l1.7,1.5c0.1,0.1,0.3,0.2,0.5,0.2c0,0,0,0,0,0c0.2,0,0.4-0.1,0.5-0.3l1.7-2.1L7.2,21
+               c0.1,0.3,0.4,0.4,0.7,0.4c0.3,0,0.5-0.2,0.6-0.5l3.3-10C11.8,10.6,11.7,10.3,11.5,10.2z M7.7,18.8l-0.5-1.1
+               c-0.1-0.2-0.3-0.4-0.5-0.4c-0.2,0-0.5,0.1-0.6,0.2l-2,2.4l-0.7-0.6l2-2.4c0.2-0.2,0.2-0.4,0.1-0.7C5.4,16,5.2,15.8,5,15.8l-1.3-0.4
+               l6.2-3.3L7.7,18.8z"/>
+    </symbol>
     <symbol id="interface-next-simple" viewBox="0 0 12 22">
         <path d="M11.89,10.71L1.49,0.12c-0.15-0.15-0.4-0.15-0.55,0L0.13,0.95c-0.15,0.15-0.15,0.4,0,0.56l9.34,9.52
                l-9.35,9.54c-0.15,0.15-0.15,0.4,0,0.56l0.82,0.84c0.15,0.15,0.4,0.15,0.55,0l10.39-10.6c0.08-0.09,0.11-0.2,0.1-0.31
index ca916d6c8a6a65f3ffd5bc2f87dbc3dbd0cda8d8..e1559db796e1f236f01fe2eff7690216e93b2ada 100644 (file)
@@ -6,6 +6,11 @@
 
 function FluidbookLinks(fluidbook) {
     this.fluidbook = fluidbook;
+    try {
+        this.zoom = new FluidbookLinksZoom(fluidbook);
+    }catch (e) {
+        
+    }
     this.initInlineSlideshowsIntervals = [];
     this.lowdef = false;
     this.init();
@@ -31,14 +36,6 @@ FluidbookLinks.prototype = {
 
         });
 
-        // ToDo: consider re-using existing popinOverlay div?
-        $('body').append('<div id="zoomPopupOverlay"></div><div id="zoomPopupGroupWrapper"></div>');
-
-        $(document).on('click', '.zoomPopup', function (e) {
-            e.preventDefault();
-            $this.zoomLink(this);
-            return false;
-        });
 
         $(document).on('mouseenter', '#links a.image_rollover', function () {
             var id = $(this).closest('[data-id]').data('id');
@@ -52,23 +49,11 @@ FluidbookLinks.prototype = {
             $this.rolloverLeave(iid);
         });
 
-        $(this.fluidbook).on('fluidbook.zoom.in.end', function () {
-            $this.zoomLinkReset(true);
-            return true;
-        });
-
-        $(this.fluidbook).on('fluidbook.resize.orientation', function () {
-            $this.zoomLinkReset(true);
-        });
 
         $(this.fluidbook).on('fluidbook.resize', function () {
             $this.resize();
         });
 
-        $(document).on('click', '#zoomPopupOverlay, .zoomPopupWrapper, .zoomPopupClose', function (e) {
-            $this.zoomLinkReset();
-            return false;
-        });
 
         $(document).on('click', 'a', function () {
             if ($(this).is('#wopen')) {
@@ -140,7 +125,7 @@ FluidbookLinks.prototype = {
             // Ensure that mobile menu closes if it is open
             $this.fluidbook.nav.closeMenu();
 
-            if (action == 'share') {
+            if (action === 'share') {
                 // Let share class handle this
                 return true;
             }
@@ -149,7 +134,7 @@ FluidbookLinks.prototype = {
                 action = map[action];
             }
 
-            if (action == 'chapters' && extra) {
+            if (action === 'chapters' && extra) {
                 window.location.hash = '#/chapters/' + extra;
                 return false;
             }
@@ -249,7 +234,7 @@ FluidbookLinks.prototype = {
     },
 
     animateContentLink: function (link) {
-        link=$(link);
+        link = $(link);
         var linkElement = $(link).get(0);
         var animation = link.data('animation');
         if (animation.type === undefined || animation.type === '') {
@@ -497,301 +482,6 @@ FluidbookLinks.prototype = {
         }, delay + additionalDelay);
     },
 
-    zoomLink: function (link) {
-
-        var $this = this,
-            $link = $(link),
-            links = [],
-            zoomMargin = 50,
-            zoomZonesGap = 30, // Space between grouped zoom links
-            gapsTotal,
-            availableWidth = $(window).width() - (2 * zoomMargin),
-            availableHeight = $(window).height() - (2 * zoomMargin),
-            maxZoom = parseFloat($(link).data('maxzoom')) || 2;
-
-        // If the interface is zoomed in, we must zoom out first
-        if (this.fluidbook.zoom.zoom > 1) {
-
-            // Zoom out
-            this.fluidbook.zoom.resetZoom();
-
-            // Wait for clickZoom out to finish before trying again to open zoom link
-            $(this.fluidbook).one('fluidbook.zoom.out.end', function () {
-                $this.zoomLink(link);
-            });
-
-            return false;
-        }
-
-        // Add clicked zoom link to the collection
-        links.push($link);
-
-        // If there is a zoom group, we need to find the other zone it is linked to
-        // For now, only 2 zones maximum can be linked together so if we have more
-        // than one group (eg. for a shared zone), we should treat it as a standalone
-        // zone when clicked.
-        if ($link.data('group-count') == 1) {
-            // Find other zones in the same group
-            var $others = $('.zoom-group-' + $link.data('group')).not('#' + $link.attr('id'));
-
-            if ($others.length >= 1) {
-                links.push($others.first()); // Take the first because we only support 1 linked zone currently
-            }
-        }
-
-        //===============
-
-        // Calculate space taken by between blocks (always 1 less gap than total blocks)
-        gapsTotal = (links.length - 1) * zoomZonesGap;
-
-        // Calculate positions and scaling for all zoomLink blocks
-        // First, calculate stacked height of all zoomLink blocks
-        var stackedHeight = gapsTotal + links.reduce(function (sum, zoomLink) {
-            return sum + (zoomLink.data('height') * maxZoom);
-        }, 0);
-
-        // Calculate side-by-side width of all zoomLink blocks
-        var sideBySideWidth = gapsTotal + links.reduce(function (sum, zoomLink) {
-            return sum + (zoomLink.data('width') * maxZoom);
-        }, 0);
-
-        // Find widest element in collection
-        var widestLink = links.reduce(function (maxWidth, zoomLink) {
-            var width = (zoomLink.data('width') * maxZoom);
-            return (width > maxWidth) ? width : maxWidth;
-        }, 0);
-
-        // Find tallest element in collection
-        var tallestLink = links.reduce(function (maxHeight, zoomLink) {
-            var height = (zoomLink.data('height') * maxZoom);
-            return (height > maxHeight) ? height : maxHeight;
-        }, 0);
-
-        // Compare scaling required for each layout of blocks
-        var stackedScale = Math.min(availableHeight / stackedHeight, availableWidth / widestLink, 1),
-            sideBySideScale = Math.min(availableHeight / tallestLink, availableWidth / sideBySideWidth, 1),
-            groupScale = (stackedScale > sideBySideScale) ? stackedScale : sideBySideScale,
-            layout = (stackedScale > sideBySideScale) ? 'stacked' : 'side-by-side';
-
-        // Apply scaling for the group so it is accounted for in later calculations
-        stackedHeight *= groupScale;
-        sideBySideWidth *= groupScale;
-
-        // Sort links so they are displayed in a natural order when zooming
-        // When stacked: highest link on page will come first
-        // When side-by-side: leftmost link on page will come first
-        links.sort(function (a, b) {
-            if (layout == 'stacked') {
-                return a.data('y') - b.data('y'); // Lowest Y co-ordinates first
-            } else {
-                return a.offset().left - b.offset().left; // Lowest X co-ordinates first
-            }
-        });
-
-
-        links.forEach(function (zoomLink, index) {
-            // console.log(index, 'Found link with ID: ' + zoomLink.attr('id'));
-
-            var zoomID = zoomLink.attr('id'),
-                $groupWrapper = $('#zoomPopupGroupWrapper');
-
-            // Add holder for each zoom zone
-            $groupWrapper.append('<div class="zoomPopupWrapper" id="zoomPopup_' + zoomID + '"><a href="#" class="zoomPopupClose">' + getSpriteIcon('interface-close') + '</a></div>');
-
-            var z = $('#zoomPopup_' + zoomID),
-                box = zoomLink[0].getBoundingClientRect(), // Should return full values without rounding
-                parent = zoomLink.closest('.link'),
-                baseWidth = parseInt(zoomLink.data('width')), // Width of the original link from the editor
-                baseHeight = parseInt(zoomLink.data('height')), // Height of the original link from the editor
-                maxZoom = parseFloat(zoomLink.data('maxzoom')) || 2, // The default value for this should match that of the compiler in zoomLink::generateImage()
-                zoomX,
-                zoomY,
-                zoomWidth,
-                zoomHeight,
-                zoomScale;
-
-            if ($(parent).length == 0) {
-                return;
-            }
-
-            var linkId = $(parent).attr('id').split('_', 2)[1];
-            var zoomImage = 'data/links/zoom_' + linkId + '.jpg';
-
-            // TODO: In the compiler we should look at generating higher-res images to cater for hiDPI displays
-            // TODO: It should be clearer what the sizes in the link editor really mean - due to screen size and scaling, a 100x100 link could appear as 150x150 in the player so if it we set a max zoom level of 2, it will only appear at 200x200, which is not much bigger than the embedded size. It's hard to handle max zoom levels while also working with a player that always scales the content to fit the screen. The most pragmatic approach probably is to generate higher res images.
-
-
-            // Due to the scaling done by the Fluidbook interface, the actual link size is likely to be different
-            // to the original link editor width and height. Since the zoom image is generated based on the link editor
-            // dimensions multiplied by the max zoom level, we need to be able to take that into account so we don't exceed
-            // the resolution of the image in the popup. We can't use the fluidbook.resize.bookScale variable because it
-            // measures the scaling of the high-res images. Instead, we work out the relative scaling by taking the
-            // link editor width (baseWidth) and comparing it to the actual link width.
-            //maxZoom = maxZoom / (box.width / baseWidth); // Adjusted maxZoom level...
-
-            // Apply any necessary group scaling to the individual element...
-            baseHeight *= groupScale;
-            baseWidth *= groupScale;
-
-            // Then calculate best scale factor to fit and also to honour the maxZoom level
-            zoomScale = Math.min((availableWidth / baseWidth), (availableHeight / baseHeight), maxZoom);
-
-            // Dimensions of the final popup
-            zoomWidth = baseWidth * zoomScale;
-            zoomHeight = baseHeight * zoomScale;
-
-            // Max zoom of first clicked link
-            var firstMaxZoom = parseFloat(links[0].data('maxzoom')) || 2;
-
-            //=========
-            // Position elements based on the layout
-            // There are two possible layouts: stacked or side-by-side
-            if (layout == 'stacked') {
-
-                $groupWrapper.addClass('layout-stacked');
-
-                // Calculate translate co-ordinates so image is centred correctly
-                // Values are rounded so we don't end up with image being positioned between pixels, which causes blurring
-                zoomX = Math.round((availableWidth / 2) - parent.offset().left - (zoomWidth / 2) + zoomMargin);
-
-                // Y position of first block (so both blocks are vertically centred)
-                var groupY = Math.round((availableHeight - stackedHeight) / 2);
-
-                // If this is the first / only block, use calculated groupY position
-                if (index == 0) {
-                    zoomY = groupY - parent.offset().top + zoomMargin;
-
-                    // Otherwise, calculate Y position based on first element position
-                } else {
-                    zoomY = Math.round(groupY + links[0].data('height') * firstMaxZoom * groupScale + zoomZonesGap + zoomMargin - parent.offset().top);
-                }
-            } else {
-                // Side-by-side layout
-                $groupWrapper.addClass('layout-side-by-side');
-
-                // Vertically centre each block
-                zoomY = Math.round((availableHeight / 2) - parent.offset().top - (zoomHeight / 2) + zoomMargin);
-
-                // X position of first block (so both blocks are horizontally centred)
-                var groupX = Math.round((availableWidth - sideBySideWidth) / 2);
-
-                // If this is the first / only block, use calculated groupX position
-                if (index == 0) {
-                    zoomX = groupX - parent.offset().left + zoomMargin;
-
-                    // Otherwise, calculate X position based on first element position
-                } else {
-                    zoomX = Math.round(groupX + (links[0].data('width') * firstMaxZoom * groupScale) + zoomZonesGap + zoomMargin - parent.offset().left);
-                }
-            }
-
-            // Keep starting scale with zoom element so it can be used when zooming back out
-            z.data('starting-scale', box.width / zoomWidth);
-
-            // Initial position of the zoom box - should sit over the link and match
-            // size and position so that the element appears to zoom from the link
-            z.css({
-                transform: 'translateX(0) translateY(0) scale(' + z.data('starting-scale') + ')',
-                width: zoomWidth,
-                height: zoomHeight,
-                left: Math.round(box.left),
-                top: Math.round(box.top)
-            });
-
-            // Load image before running zoom up animation
-            this.fluidbook.displayLoader();
-            loadImage(zoomImage, function (img) {
-
-                // Image is set as a background for better scaling / fitting via CSS
-                z.css('background-image', 'url(' + img.src + ')');
-                this.fluidbook.hideLoader();
-                z.show();
-                $this.showOverlay();
-
-                // Trigger zoom up animation just after showing zoom element
-                setTimeout(function () {
-                    z.css({
-                        boxShadow: '0 0 100px rgba(0,0,0,0.3)',
-                        transform: 'translateX(' + zoomX + 'px) translateY(' + zoomY + 'px) scale(1)'
-                    });
-                }, 50);
-
-                // Hide close button initially so it only shows when zoom finishes.
-                $('.zoomPopupClose').css('opacity', 0);
-
-                $this.fluidbook.stats.track(2, $this.fluidbook.currentPage);
-
-                // Display close button after zoom animation has finished
-                setTimeout(function () {
-                    $('.zoomPopupClose').css('opacity', 1);
-                }, 500);
-            });
-
-        });
-    },
-
-    zoomLinkClose: function (immediate) {
-        return this.zoomLinkReset(immediate);
-    },
-
-    zoomLinkReset: function (immediate) {
-
-        var $this = this;
-
-        if ($('.zoomPopupWrapper:visible').length == 0) {
-            return;
-        }
-
-        if (immediate == undefined) {
-            immediate = false;
-        }
-
-        var $wrapper = $('#zoomPopupGroupWrapper');
-
-        // Close each popup that is open
-        $wrapper.find('.zoomPopupWrapper').each(function () {
-            var z = $(this);
-            z.find('.zoomPopupClose').css('opacity', '0');
-
-            if (immediate) {
-                $('.zoomPopupWrapper').hide();
-                $this.hideOverlay(1);
-            }
-
-            z.css({
-                transform: 'translate(0,0) scale(' + z.data('starting-scale') + ')',
-                boxShadow: '0 0 0 rgba(0,0,0,0.3)',
-            });
-        });
-
-        // Hide popup after transition completes
-        // ToDo: use CSS transition end event to do this without needing a timeout value (or use Web Animation API)
-        // ToDo: see https://davidwalsh.name/css-animation-callback
-        this.hideOverlay(500);
-        setTimeout(function () {
-            $('.zoomPopupWrapper').hide();
-            $wrapper.html(''); // Empty group wrapper
-            $wrapper.removeClass(); // Remove all classes (stacked / side-by-side layout)
-        }, 500);
-
-        return false;
-    },
-
-    hideOverlay: function (delay) {
-        if (delay == undefined) {
-            delay = 500;
-        }
-        $("#zoomPopupOverlay").css('opacity', 0);
-        setTimeout(function () {
-            $("#zoomPopupOverlay").hide();
-        }, delay);
-    },
-    showOverlay: function () {
-        $("#zoomPopupOverlay").css('opacity', 0).show();
-        setTimeout(function () {
-            $("#zoomPopupOverlay").css('opacity', 1)
-        }, 10)
-    },
     triggerLinkById: function (id) {
         var a = $('.link[data-id="' + id + '"] a:eq(0)');
         a.get(0).click();
index 4359abedc80e9bfccea4a32d9d08c3bb885d601f..4a52d6969d8475d67738845876b927899489a219 100644 (file)
@@ -9,14 +9,16 @@ function FluidbookShare(fluidbook) {
                 if (url === undefined || url === null || url === 'undefined') {
                     url = '';
                 }
-                $this[f](url);
+                var context = $(this).data('context') === null ? 'publication' : $(this).data('context');
+
+                $this[f](url, context);
                 $(this).closest('.mview').find('.back').click();
                 return false;
             });
         }
 
         $(document).on('click touchend', '[data-action="share"]', function () {
-            $this.fluidbook.menu.openView("share", $(this).data('extra'));
+            $this.fluidbook.menu.openView("share", $(this).data('extra'), $(this).data('context'));
             return false;
         });
 
@@ -63,7 +65,6 @@ FluidbookShare.prototype = {
     },
 
     getShareURL: function (url) {
-
         if (url == undefined || url == 'undefined' || url == null || url == false) {
             url = '';
         }
@@ -103,20 +104,28 @@ FluidbookShare.prototype = {
         return this.fluidbook.datas.title;
     },
 
-    getEmailSubject: function () {
-        if (this.fluidbook.datas.email_title == '') {
-            return this.fluidbook.datas.title;
+    getEmailSubject: function (context) {
+        if (context === 'publication') {
+            if (this.fluidbook.datas.email_title === '') {
+                return this.fluidbook.datas.title;
+            }
+            return this.fluidbook.datas.email_title;
+        } else if (context === 'product') {
+            return this.fluidbook.datas.product_email_title;
         }
-        return this.fluidbook.datas.email_title;
     },
 
-    getEmailBody: function (url) {
+    getEmailBody: function (url, context) {
         var body;
         var u = this.getShareURL(url);
-        if (this.fluidbook.datas.email_body == '') {
-            body = this.fluidbook.l10n.__('Veuillez cliquer sur le lien suivant pour ouvrir %title%\\n%link%');
-        } else {
-            body = this.fluidbook.datas.email_body;
+        if (context === 'publication') {
+            if (this.fluidbook.datas.email_body === '') {
+                body = this.fluidbook.l10n.__('Veuillez cliquer sur le lien suivant pour ouvrir %title%\\n%link%');
+            } else {
+                body = this.fluidbook.datas.email_body;
+            }
+        } else if (context === 'product') {
+            body = this.fluidbook.datas.product_email_body;
         }
         body = body.trim();
         body = body.replace(/\%title\%/g, this.fluidbook.datas.title);
@@ -137,12 +146,12 @@ FluidbookShare.prototype = {
         return this.fluidbook.datas.seoArticles[title];
     },
 
-    getShareLinks: function (hideLabels, url) {
+    getShareLinks: function (hideLabels, url, context) {
         var shareLinks = {},
             shareHTML = '';
 
-        if (url == undefined || url == 'undefined') {
-            url == '';
+        if (url === undefined || url === null || url === 'undefined') {
+            url = '';
         }
 
         hideLabels = hideLabels || false;
@@ -172,7 +181,7 @@ FluidbookShare.prototype = {
         // Generate links
         for (var shareType in shareLinks) {
             if (shareLinks.hasOwnProperty(shareType)) { // Ensure we don't get inherited properties
-                shareHTML += '<li data-level="0"><a href="#" data-service="' + shareType + '" data-url="' + url + '" class="share level0">';
+                shareHTML += '<li data-level="0"><a href="#" data-service="' + shareType + '" data-url="' + url + '" data-context="'+context+'" class="share level0">';
                 shareHTML += getSpriteIcon('share-' + shareType);
                 if (!hideLabels) {
                     shareHTML += ' ' + shareLinks[shareType];
@@ -185,15 +194,19 @@ FluidbookShare.prototype = {
         return '<ul class="chapters shareList">' + shareHTML + '</ul>';
     },
 
-    openShare: function (url, p2, callback) {
+    openShare: function (url, context, callback) {
+
         var view;
         if (url === undefined || url === null || url === 'undefined' || !url) {
             url = '';
         }
+        if (context === undefined || context === null) {
+            context = 'publication';
+        }
 
         view = '<div class="caption">' + this.fluidbook.menu.closeButton() + '<h2>' + this.fluidbook.l10n.__('share') + '</h2></div>';
         view += '<div class="content">';
-        view += this.getShareLinks(false, url);
+        view += this.getShareLinks(false, url, context);
         view += '</div>';
 
         $("#view").append('<div class="mview" data-menu="share">' + view + '</div>');
@@ -214,7 +227,7 @@ FluidbookShare.prototype = {
         body = body.replace(/\r/g, "\n");
         body = body.replace(/\n/g, "\r\n");
 
-        if (this.fluidbook.datas.phonegap == 'android') {
+        if (this.fluidbook.datas.phonegap === 'android') {
             var extras = {};
             extras[window.plugins.webintent.EXTRA_SUBJECT] = subject;
             extras[window.plugins.webintent.EXTRA_TEXT] = body;
@@ -231,13 +244,23 @@ FluidbookShare.prototype = {
         this.fluidbook.stats.track(5);
     },
 
+    getTweetContent: function (url, context) {
+        var tweet;
+        if (context === 'publication') {
+            tweet = this.fluidbook.datas.twitter_description;
+
+        } else if (context === 'product') {
+            tweet = this.fluidbook.datas.product_tweet;
+        }
+        return tweet.replace('%title%', this.getShareTitle(url));
+    },
 
-    sendEmail: function (url) {
-        window.location = 'mailto:?subject=' + encodeURIComponent(this.getEmailSubject()) + '&body=' + encodeURIComponent(this.getEmailBody(url));
+    sendEmail: function (url, context) {
+        window.location = 'mailto:?subject=' + encodeURIComponent(this.getEmailSubject(context)) + '&body=' + encodeURIComponent(this.getEmailBody(url, context));
         this.fluidbook.stats.track(5);
     },
-    sendTwitter: function (url) {
-        var tweet = this.fluidbook.datas.twitter_description.replace('%title%', this.getShareTitle(url));
+    sendTwitter: function (url, context) {
+        var tweet = this.getTweetContent(url, context);
         var hasUrlInTweet = tweet.indexOf('%url%') >= 0 || tweet.indexOf('%short%') >= 0;
 
         url = this.getShareURL(url);
@@ -253,24 +276,24 @@ FluidbookShare.prototype = {
         this.fluidbook.wopen('http://twitter.com/intent/tweet?source=webclient' + urlshare + '&text=' + encodeURIComponent(tweet), 'share_twitter', 'width=650,height=400');
         this.fluidbook.stats.track(13);
     },
-    sendFacebook: function (url) {
+    sendFacebook: function (url, context) {
         this.fluidbook.wopen('https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent(this.getShareURL(url)), 'share_facebook', 'width=650,height=400');
         this.fluidbook.stats.track(12);
     },
-    sendGoogleplus: function (url) {
+    sendGoogleplus: function (url, context) {
         this.fluidbook.wopen('https://plus.google.com/share?url=' + encodeURIComponent(this.getShareURL(url)), 'share_googleplus', 'width=600,height=600');
         this.fluidbook.stats.track(12);
     },
-    sendLinkedin: function (url) {
+    sendLinkedin: function (url, context) {
         this.fluidbook.wopen('https://www.linkedin.com/cws/share?url=' + encodeURIComponent(this.getShareURL(url)) + '&isFramed=true&_ts=' + Date.now(), 'share_linkedin', 'width=650,height=400');
         this.fluidbook.stats.track(12);
     },
-    sendViadeo: function (url) {
+    sendViadeo: function (url, context) {
         this.fluidbook.wopen('http://www.viadeo.com/shareit/share/?url=' + encodeURIComponent(this.getShareURL(url)), 'share_viadeo', 'width=650,height=400');
         this.fluidbook.stats.track(12);
     },
-    sendPinterest: function (url) {
-        this.fluidbook.wopen('http://pinterest.com/pin/create/button/?url=' + encodeURIComponent(this.getShareURL(url)) + '&media=' + encodeURIComponent('https://workshop.fluidbook.com/services/facebook_thumbnail?id=' + this.fluidbook.datas.id + '&j=' + Date.now()) + '"','width=650,height=400');
+    sendPinterest: function (url, context) {
+        this.fluidbook.wopen('http://pinterest.com/pin/create/button/?url=' + encodeURIComponent(this.getShareURL(url)) + '&media=' + encodeURIComponent('https://workshop.fluidbook.com/services/facebook_thumbnail?id=' + this.fluidbook.datas.id + '&j=' + Date.now()) + '"', 'width=650,height=400');
         this.fluidbook.stats.track(12);
     }
 };
\ No newline at end of file
diff --git a/js/libs/fluidbook/links/fluidbook.links.zoom.js b/js/libs/fluidbook/links/fluidbook.links.zoom.js
new file mode 100644 (file)
index 0000000..81b2efd
--- /dev/null
@@ -0,0 +1,379 @@
+function FluidbookLinksZoom(fluidbook) {
+    this.fluidbook = fluidbook;
+    this.resizeZoomRunning = false;
+    this.init();
+}
+
+FluidbookLinksZoom.prototype = {
+
+    init: function () {
+        var $this = this;
+        // ToDo: consider re-using existing popinOverlay div?
+        $('body').append('<div id="zoomPopupOverlay"></div><div id="zoomPopupGroupWrapper"></div>');
+
+        $(document).on('click', '.zoomPopup', function (e) {
+            e.preventDefault();
+            $this.zoomLink(this);
+            return false;
+        });
+
+        $(this.fluidbook).on('fluidbook.zoom.in.end', function () {
+            $this.zoomLinkReset(true);
+            return true;
+        });
+
+        $(this.fluidbook).on('fluidbook.resize.orientation', function () {
+            $this.zoomLinkReset(true);
+        });
+
+        $(document).on('click', '#zoomPopupOverlay, .zoomPopupWrapper, .zoomPopupClose,  #zoomPopupBackground .bg', function (e) {
+            $this.zoomLinkReset();
+            return false;
+        });
+    },
+
+    zoomLink: function (link) {
+
+        var $this = this,
+            $link = $(link),
+            links = [],
+            zoomMargin = 50,
+            zoomZonesGap = 30, // Space between grouped zoom links
+            gapsTotal,
+            availableWidth = this.fluidbook.resize.ww - (2 * zoomMargin),
+            availableHeight = this.fluidbook.resize.hh - (2 * zoomMargin),
+            maxZoom = parseFloat($(link).data('maxzoom')) || 2;
+
+        // If the interface is zoomed in, we must zoom out first
+        if (this.fluidbook.zoom.zoom > 1) {
+
+            // Zoom out
+            this.fluidbook.zoom.resetZoom();
+
+            // Wait for clickZoom out to finish before trying again to open zoom link
+            $(this.fluidbook).one('fluidbook.zoom.out.end', function () {
+                $this.zoomLink(link);
+            });
+
+            return false;
+        }
+
+        // Add clicked zoom link to the collection
+        links.push($link);
+
+        // If there is a zoom group, we need to find the other zone it is linked to
+        // For now, only 2 zones maximum can be linked together so if we have more
+        // than one group (eg. for a shared zone), we should treat it as a standalone
+        // zone when clicked.
+        if ($link.data('group-count') === 1) {
+            // Find other zones in the same group
+            var $others = $('.zoom-group-' + $link.data('group')).not('#' + $link.attr('id'));
+
+            if ($others.length >= 1) {
+                links.push($others.first()); // Take the first because we only support 1 linked zone currently
+            }
+        }
+
+        //===============
+
+        // Calculate space taken by between blocks (always 1 less gap than total blocks)
+        gapsTotal = (links.length - 1) * zoomZonesGap;
+
+        // Calculate positions and scaling for all zoomLink blocks
+        // First, calculate stacked height of all zoomLink blocks
+        var stackedHeight = gapsTotal + links.reduce(function (sum, zoomLink) {
+            return sum + (zoomLink.data('height') * maxZoom);
+        }, 0);
+
+        // Calculate side-by-side width of all zoomLink blocks
+        var sideBySideWidth = gapsTotal + links.reduce(function (sum, zoomLink) {
+            return sum + (zoomLink.data('width') * maxZoom);
+        }, 0);
+
+        // Find widest element in collection
+        var widestLink = links.reduce(function (maxWidth, zoomLink) {
+            var width = (zoomLink.data('width') * maxZoom);
+            return (width > maxWidth) ? width : maxWidth;
+        }, 0);
+
+        // Find tallest element in collection
+        var tallestLink = links.reduce(function (maxHeight, zoomLink) {
+            var height = (zoomLink.data('height') * maxZoom);
+            return (height > maxHeight) ? height : maxHeight;
+        }, 0);
+
+        // Compare scaling required for each layout of blocks
+        var stackedScale = Math.min(availableHeight / stackedHeight, availableWidth / widestLink, 1),
+            sideBySideScale = Math.min(availableHeight / tallestLink, availableWidth / sideBySideWidth, 1),
+            groupScale = (stackedScale > sideBySideScale) ? stackedScale : sideBySideScale,
+            layout = (stackedScale > sideBySideScale) ? 'stacked' : 'side-by-side';
+
+        // Apply scaling for the group so it is accounted for in later calculations
+        stackedHeight *= groupScale;
+        sideBySideWidth *= groupScale;
+
+        // Sort links so they are displayed in a natural order when zooming
+        // When stacked: highest link on page will come first
+        // When side-by-side: leftmost link on page will come first
+        links.sort(function (a, b) {
+            if (layout === 'stacked') {
+                return a.data('y') - b.data('y'); // Lowest Y co-ordinates first
+            } else {
+                return a.offset().left - b.offset().left; // Lowest X co-ordinates first
+            }
+        });
+
+
+        links.forEach(function (zoomLink, index) {
+            // console.log(index, 'Found link with ID: ' + zoomLink.attr('id'));
+
+            var zoomID = zoomLink.attr('id'),
+                $groupWrapper = $('#zoomPopupGroupWrapper');
+
+            // Add holder for each zoom zone
+            $groupWrapper.append('<div class="zoomPopupWrapper" id="zoomPopup_' + zoomID + '"></div>');
+            if ($groupWrapper.find('.zoomPopupClose').length === 0) {
+                var menu = '<div id="zoomPopupMenuWrapper"><div id="zoomPopupMenu">';
+                if (zoomLink.data('shareurl') !== undefined) {
+                    menu += '<a href="#" class="button nolabel" data-action="share" data-extra="' + zoomLink.data('shareurl') + '" data-context="product">' + getSpriteIcon('nav-share') + '</a>';
+                }
+                if (zoomLink.data('producturl') !== undefined) {
+                    menu += '<a href="' + zoomLink.data('producturl') + '" target="_blank" class="button">' + getSpriteIcon('icon-site') + '<span>' + this.fluidbook.l10n.__('see on the website') + '</span></a>';
+                }
+                menu += '</div></div>';
+                $groupWrapper.append('<div id="zoomPopupBackground"><div class="bg"></div>' + menu + '</div>');
+                $groupWrapper.append('<a href="#" class="zoomPopupClose">' + getSpriteIcon('interface-close') + '</a>');
+            }
+
+            var z = $('#zoomPopup_' + zoomID),
+                box = zoomLink[0].getBoundingClientRect(), // Should return full values without rounding
+                parent = zoomLink.closest('.link'),
+                baseWidth = parseInt(zoomLink.data('width')), // Width of the original link from the editor
+                baseHeight = parseInt(zoomLink.data('height')), // Height of the original link from the editor
+                maxZoom = parseFloat(zoomLink.data('maxzoom')) || 2, // The default value for this should match that of the compiler in zoomLink::generateImage()
+                zoomX,
+                zoomY,
+                zoomWidth,
+                zoomHeight,
+                zoomScale;
+
+            if ($(parent).length === 0) {
+                return;
+            }
+
+            var linkId = $(parent).attr('id').split('_', 2)[1];
+            var zoomImage = 'data/links/zoom_' + linkId + '.jpg';
+
+            // TODO: In the compiler we should look at generating higher-res images to cater for hiDPI displays
+            // TODO: It should be clearer what the sizes in the link editor really mean - due to screen size and scaling, a 100x100 link could appear as 150x150 in the player so if it we set a max zoom level of 2, it will only appear at 200x200, which is not much bigger than the embedded size. It's hard to handle max zoom levels while also working with a player that always scales the content to fit the screen. The most pragmatic approach probably is to generate higher res images.
+
+
+            // Due to the scaling done by the Fluidbook interface, the actual link size is likely to be different
+            // to the original link editor width and height. Since the zoom image is generated based on the link editor
+            // dimensions multiplied by the max zoom level, we need to be able to take that into account so we don't exceed
+            // the resolution of the image in the popup. We can't use the fluidbook.resize.bookScale variable because it
+            // measures the scaling of the high-res images. Instead, we work out the relative scaling by taking the
+            // link editor width (baseWidth) and comparing it to the actual link width.
+            //maxZoom = maxZoom / (box.width / baseWidth); // Adjusted maxZoom level...
+
+            // Apply any necessary group scaling to the individual element...
+            baseHeight *= groupScale;
+            baseWidth *= groupScale;
+
+            // Then calculate best scale factor to fit and also to honour the maxZoom level
+            zoomScale = Math.min((availableWidth / baseWidth), (availableHeight / baseHeight), maxZoom);
+
+            // Dimensions of the final popup
+            zoomWidth = baseWidth * zoomScale;
+            zoomHeight = baseHeight * zoomScale;
+
+            // Max zoom of first clicked link
+            var firstMaxZoom = parseFloat(links[0].data('maxzoom')) || 2;
+
+            //=========
+            // Position elements based on the layout
+            // There are two possible layouts: stacked or side-by-side
+            if (layout === 'stacked') {
+
+                $groupWrapper.addClass('layout-stacked');
+
+                // Calculate translate co-ordinates so image is centred correctly
+                // Values are rounded so we don't end up with image being positioned between pixels, which causes blurring
+                zoomX = Math.round((availableWidth / 2) - parent.offset().left - (zoomWidth / 2) + zoomMargin);
+
+                // Y position of first block (so both blocks are vertically centred)
+                var groupY = Math.round((availableHeight - stackedHeight) / 2);
+
+                // If this is the first / only block, use calculated groupY position
+                if (index === 0) {
+                    zoomY = groupY - parent.offset().top + zoomMargin;
+
+                    // Otherwise, calculate Y position based on first element position
+                } else {
+                    zoomY = Math.round(groupY + links[0].data('height') * firstMaxZoom * groupScale + zoomZonesGap + zoomMargin - parent.offset().top);
+                }
+            } else {
+                // Side-by-side layout
+                $groupWrapper.addClass('layout-side-by-side');
+
+                // Vertically centre each block
+                zoomY = Math.round((availableHeight / 2) - parent.offset().top - (zoomHeight / 2) + zoomMargin);
+
+                // X position of first block (so both blocks are horizontally centred)
+                var groupX = Math.round((availableWidth - sideBySideWidth) / 2);
+
+                // If this is the first / only block, use calculated groupX position
+                if (index === 0) {
+                    zoomX = groupX - parent.offset().left + zoomMargin;
+
+                    // Otherwise, calculate X position based on first element position
+                } else {
+                    zoomX = Math.round(groupX + (links[0].data('width') * firstMaxZoom * groupScale) + zoomZonesGap + zoomMargin - parent.offset().left);
+                }
+            }
+
+            // Keep starting scale with zoom element so it can be used when zooming back out
+            z.data('starting-scale', box.width / zoomWidth);
+
+            // Initial position of the zoom box - should sit over the link and match
+            // size and position so that the element appears to zoom from the link
+            z.css({
+                transform: 'translateX(0) translateY(0) scale(' + z.data('starting-scale') + ')',
+                width: zoomWidth,
+                height: zoomHeight,
+                left: Math.round(box.left),
+                top: Math.round(box.top)
+            });
+
+            // Load image before running zoom up animation
+            this.fluidbook.displayLoader();
+            loadImage(zoomImage, function (img) {
+
+                // Image is set as a background for better scaling / fitting via CSS
+                z.css('background-image', 'url(' + img.src + ')');
+                $this.fluidbook.hideLoader();
+                z.show();
+                $this.showOverlay();
+
+                // Trigger zoom up animation just after showing zoom element
+                setTimeout(function () {
+                    z.css({
+                        boxShadow: '0 0 100px rgba(0,0,0,0.3)',
+                        transform: 'translateX(' + zoomX + 'px) translateY(' + zoomY + 'px) scale(1)'
+                    });
+
+                    setTimeout(function () {
+                        $this.resizeZoomLinkBackground(true);
+                    }, 600);
+
+                }, 50);
+
+
+                $this.fluidbook.stats.track(2, $this.fluidbook.currentPage);
+            });
+
+        });
+    },
+
+    resizeZoomLinkBackground: function (show) {
+        var top, left, bottom, right, width, height, padding;
+
+        var wrappers = $('.zoomPopupWrapper');
+        if ($('html').hasClass('menu-burger')) {
+            top = 0;
+            left = 0;
+            width = this.fluidbook.resize.ww;
+            height = this.fluidbook.resize.hh;
+        } else if (wrappers.length === 1) {
+            var box = $(wrappers).get(0).getBoundingClientRect();
+            top = box.top;
+            left = box.left;
+            width = box.width;
+            height = box.height;
+        } else {
+            top = Number.MAX_VALUE;
+            left = Number.MAX_VALUE;
+            bottom = Number.MIN_VALUE;
+            right = Number.MIN_VALUE;
+            wrappers.each(function () {
+                var box = $(this).get(0).getBoundingClientRect();
+                top = Math.min(top, box.top);
+                left = Math.min(left, box.left);
+                right = Math.max(right, box.right);
+                bottom = Math.max(bottom, box.bottom);
+            });
+            padding = 20;
+            width = right - left + 2 * padding;
+            height = bottom - top + 2 * padding;
+            top -= padding;
+            left -= padding;
+        }
+
+        $("#zoomPopupBackground").css({left: left, top: top, width: width, height: height, opacity: show ? 1 : 0});
+    },
+
+    zoomLinkClose: function (immediate) {
+        return this.zoomLinkReset(immediate);
+    },
+
+    zoomLinkReset: function (immediate) {
+
+        var $this = this;
+
+        if ($('.zoomPopupWrapper:visible').length === 0) {
+            return;
+        }
+
+        if (immediate === undefined) {
+            immediate = false;
+        }
+
+        var $wrapper = $('#zoomPopupGroupWrapper');
+
+        // Close each popup that is open
+        $wrapper.find('.zoomPopupWrapper').each(function () {
+            var z = $(this);
+            z.find('.zoomPopupClose').css('opacity', '0');
+
+            if (immediate) {
+                $('.zoomPopupWrapper').hide();
+                $this.hideOverlay(1);
+            }
+
+            z.css({
+                transform: 'translate(0,0) scale(' + z.data('starting-scale') + ')',
+                boxShadow: '0 0 0 rgba(0,0,0,0.3)',
+            });
+        });
+
+        this.resizeZoomLinkBackground(0);
+
+        // Hide popup after transition completes
+        // ToDo: use CSS transition end event to do this without needing a timeout value (or use Web Animation API)
+        // ToDo: see https://davidwalsh.name/css-animation-callback
+        this.hideOverlay(500);
+        setTimeout(function () {
+            $('.zoomPopupWrapper').hide();
+            $wrapper.html(''); // Empty group wrapper
+            $wrapper.removeClass(); // Remove all classes (stacked / side-by-side layout)
+        }, 500);
+
+        return false;
+    },
+
+    hideOverlay: function (delay) {
+        if (delay === undefined) {
+            delay = 500;
+        }
+        $("#zoomPopupOverlay").css('opacity', 0);
+        setTimeout(function () {
+            $("#zoomPopupOverlay").hide();
+        }, delay);
+    },
+    showOverlay: function () {
+        $("#zoomPopupOverlay").css('opacity', 0).show();
+        setTimeout(function () {
+            $("#zoomPopupOverlay").css('opacity', 1)
+        }, 10)
+    },
+};
\ No newline at end of file
index 0d916a00486cc00da7925b29900c56c6b626f4f5..c9c719a257b3751dd4e03aab1a01abd6329474ff 100644 (file)
@@ -2804,35 +2804,112 @@ ul.chapters {
   z-index: 100;
   display: none;
   .overlayBackground();
+
+  .menu-burger & {
+    background-color: @menu-background;
+  }
 }
 
-// Depending on the layout, hide one of the close buttons
-// We need to define both first and last rules in case there is only one zoom zone
-// In this case, the last written rule will apply
-#zoomPopupGroupWrapper {
-  &.layout-stacked {
-    .zoomPopupWrapper:last-of-type .zoomPopupClose {
-      display: none;
-    }
+.zoomPopupWrapper, .zoomPopupClose, #zoomPopupBackground .bg {
+  cursor: zoom-out !important; // Needed for close link, otherwise pointer cursor is used
+}
 
-    .zoomPopupWrapper:first-of-type .zoomPopupClose {
-      display: block;
-    }
+#zoomPopupBackground {
+  position: absolute;
+  z-index: 100;
+  opacity: 0;
+  transition: opacity 150ms;
+
+  .bg {
+    background-color: @menu-background;
+    width: 100%;
+    height: 100%;
   }
 
-  &.layout-side-by-side {
-    .zoomPopupWrapper:first-of-type .zoomPopupClose {
-      display: none;
+  #zoomPopupMenu {
+    position: absolute;
+    right: 0;
+    bottom: -40px;
+    color: @menu-text;
+
+    .menu-burger & {
+      right: auto;
+      bottom: 0;
+      left: 50%;
+      transform: translateX(-50%);
+      white-space: nowrap;
     }
 
-    .zoomPopupWrapper:last-of-type .zoomPopupClose {
-      display: block;
+    .button {
+      margin-left: 3px;
+      height: 40px;
+      display: inline-block;
+      padding: 7px 17px 7px 7px;
+      background-color: @menu-button-background;
+      width: auto;
+      white-space: nowrap;
+
+      .menu-burger & {
+        padding: 10px 20px 10px 10px;
+        height: 60px;
+      }
+
+      .svg-icon {
+        height: 25px;
+
+        .menu-burger & {
+          height: 40px;
+        }
+      }
+
+      &.nolabel {
+        padding-right: 7px;
+        width: 40px;
+
+        .menu-burger & {
+          padding-right: 10px;
+          width: 60px;
+        }
+      }
+
+      span {
+        text-transform: uppercase;
+        margin-left: 20px;
+        position: relative;
+        top: -5px;
+
+        .menu-burger & {
+          font-size: 20px;
+          top: -13px;
+        }
+      }
     }
   }
 }
 
-.zoomPopupWrapper, .zoomPopupClose {
-  cursor: zoom-out !important; // Needed for close link, otherwise pointer cursor is used
+.zoomPopupClose {
+  @zoom-close-button-size: 60px;
+
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: @zoom-close-button-size;
+  height: @zoom-close-button-size;
+  padding: unit((@zoom-close-button-size/30)*11, px);
+  z-index: 103;
+  background-color: @menu-button-background;
+  color: @menu-text;
+  opacity: 1;
+  transition: opacity 500ms;
+
+  .svg-icon {
+    display: block; // Needed for proper positioning in centre of square
+  }
+
+  .rtl & {
+    right: auto;
+    left: 0;
+  }
 }
 
 .zoomPopupWrapper {
@@ -2841,17 +2918,11 @@ ul.chapters {
   background-repeat: no-repeat;
   background-position: center;
   background-size: cover;
-  transition: all 0.5s ease-in-out;
+  transition: all 0.5s;
   transform-origin: 0 0;
   position: absolute;
   z-index: 101;
 
-  &:hover {
-    .zoomPopupClose {
-      opacity: 1;
-    }
-  }
-
   img {
     position: absolute;
     top: 0;
@@ -2862,30 +2933,7 @@ ul.chapters {
     z-index: 102;
   }
 
-  .zoomPopupClose {
-    @zoom-close-button-size: 30px;
 
-    position: absolute;
-    top: 0;
-    right: -@zoom-close-button-size;
-    width: @zoom-close-button-size;
-    height: @zoom-close-button-size;
-    padding: 11px;
-    z-index: 103;
-    background-color: @menu-button-background;
-    color: @menu-text;
-    opacity: 1;
-    transition: opacity 250ms;
-
-    .svg-icon {
-      display: block; // Needed for proper positioning in centre of square
-    }
-
-    .rtl & {
-      right: auto;
-      left: 0;
-    }
-  }
 }
 
 @import "lib/perfect-scrollbar.less";