From 0adf07632cffd3c98e169e1fd3f004f97cc9f992 Mon Sep 17 00:00:00 2001 From: Caroline Liu <10456171+caroqliu@users.noreply.github.com> Date: Wed, 24 Feb 2021 13:08:27 -0500 Subject: [PATCH] Classical amp-inline-gallery-captions (0.1) (#32075) * git cherry-pick 8cfe718 * Fix missed parameter * Lint fixes * gulp presubmit * Add experiments * Pass slides in last Co-authored-by: Sepand Parhami --- .../compile/bundles.config.extensions.json | 2 + .../0.1/amp-inline-gallery-captions.css | 21 +++ .../0.1/amp-inline-gallery-captions.js | 91 +++++++++++++ .../0.1/amp-inline-gallery-pagination.js | 3 +- .../0.1/amp-inline-gallery-slide.css | 86 +++++++++++++ .../0.1/amp-inline-gallery-slide.js | 121 ++++++++++++++++++ .../0.1/amp-inline-gallery.js | 28 +++- .../0.1/service/lightbox-manager-impl.js | 15 ++- test/manual/amp-inline-gallery/captions.html | 96 ++++++++++++++ 9 files changed, 454 insertions(+), 9 deletions(-) create mode 100644 extensions/amp-inline-gallery/0.1/amp-inline-gallery-captions.css create mode 100644 extensions/amp-inline-gallery/0.1/amp-inline-gallery-captions.js create mode 100644 extensions/amp-inline-gallery/0.1/amp-inline-gallery-slide.css create mode 100644 extensions/amp-inline-gallery/0.1/amp-inline-gallery-slide.js create mode 100644 test/manual/amp-inline-gallery/captions.html diff --git a/build-system/compile/bundles.config.extensions.json b/build-system/compile/bundles.config.extensions.json index 5288cc38dc77..bbaa7d3e0553 100644 --- a/build-system/compile/bundles.config.extensions.json +++ b/build-system/compile/bundles.config.extensions.json @@ -220,7 +220,9 @@ "hasCss": true, "cssBinaries": [ "amp-inline-gallery", + "amp-inline-gallery-captions", "amp-inline-gallery-pagination", + "amp-inline-gallery-slide", "amp-inline-gallery-thumbnails" ] } diff --git a/extensions/amp-inline-gallery/0.1/amp-inline-gallery-captions.css b/extensions/amp-inline-gallery/0.1/amp-inline-gallery-captions.css new file mode 100644 index 000000000000..f10886e722e7 --- /dev/null +++ b/extensions/amp-inline-gallery/0.1/amp-inline-gallery-captions.css @@ -0,0 +1,21 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +amp-inline-gallery-captions { + pointer-events: none; + padding-top: var(--caption-margin-top); + margin-top: calc(-1 * var(--i-amphtml-caption-height, 0)); +} diff --git a/extensions/amp-inline-gallery/0.1/amp-inline-gallery-captions.js b/extensions/amp-inline-gallery/0.1/amp-inline-gallery-captions.js new file mode 100644 index 000000000000..ab5fe4ddb761 --- /dev/null +++ b/extensions/amp-inline-gallery/0.1/amp-inline-gallery-captions.js @@ -0,0 +1,91 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {closestAncestorElementBySelector} from '../../../src/dom'; +import {exponentialFalloff} from './amp-inline-gallery-pagination'; +import {isExperimentOn} from '../../../src/experiments'; +import {isLayoutSizeDefined} from '../../../src/layout'; +import {setImportantStyles} from '../../../src/style.js'; +import {userAssert} from '../../../src/log'; + +export class AmpInlineGalleryCaptions extends AMP.BaseElement { + /** @param {!AmpElement} element */ + constructor(element) { + super(element); + } + + /** @override */ + isRelayoutNeeded() { + return true; + } + + /** @override */ + isLayoutSupported(layout) { + userAssert( + isExperimentOn(this.win, 'amp-inline-gallery-captions') || + 'expected "amp-inline-gallery-captions" experiment to be enabled' + ); + return isLayoutSizeDefined(layout); + } + + /** @override */ + layoutCallback() { + const {height} = this./*OK*/ getLayoutBox(); + const parentGallery = closestAncestorElementBySelector( + this.element, + 'amp-inline-gallery' + ); + + setImportantStyles(parentGallery, { + '--i-amphtml-caption-height': `${height}px`, + }); + } + + /** + * @param {number} unusedTotal + * @param {number} index + * @param {number} offset + * @param {!Array} slides + */ + updateProgress(unusedTotal, index, offset, slides) { + this.mutateElement(() => { + this.updateCaptionOpacities_(slides, index, offset); + }); + } + + /** + * Updates the opacities of the captions, based on their distance from the + * current slide. + * @param {!Array} slides + * @param {number} index + * @param {number} offset + */ + updateCaptionOpacities_(slides, index, offset) { + slides.forEach((slide, i) => { + const indexDistance = Math.abs(index + offset - i); + // Want to fall off to zero at the mid way point, the next/prev slide + // will start fading in at the same time. + const falloffDistance = Math.min(2 * indexDistance, 1); + const opacity = exponentialFalloff(falloffDistance, 3); + setImportantStyles(slide, { + '--caption-opacity': opacity, + // Need to prevent pointer events on all other slide's captions so + // that the user can select the caption text, click on links, etc. + 'pointer-events': opacity == 0 ? 'none' : 'all', + }); + }); + } +} diff --git a/extensions/amp-inline-gallery/0.1/amp-inline-gallery-pagination.js b/extensions/amp-inline-gallery/0.1/amp-inline-gallery-pagination.js index 063e30952841..9dabc3790416 100644 --- a/extensions/amp-inline-gallery/0.1/amp-inline-gallery-pagination.js +++ b/extensions/amp-inline-gallery/0.1/amp-inline-gallery-pagination.js @@ -254,8 +254,9 @@ export class AmpInlineGalleryPagination extends AMP.BaseElement { * @param {number} total * @param {number} index * @param {number} offset + * @param {!Array} unusedSlides */ - updateProgress(total, index, offset) { + updateProgress(total, index, offset, unusedSlides) { this.mutateElement(() => { this.updateTotal_(total); this.updateDots_(index, offset); diff --git a/extensions/amp-inline-gallery/0.1/amp-inline-gallery-slide.css b/extensions/amp-inline-gallery/0.1/amp-inline-gallery-slide.css new file mode 100644 index 000000000000..651074201c88 --- /dev/null +++ b/extensions/amp-inline-gallery/0.1/amp-inline-gallery-slide.css @@ -0,0 +1,86 @@ +/** + * Copyright 2021 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +amp-inline-gallery-slide { + /* + * We do not want the slide to be positioned, so the captions can position + * relative to the gallery itself. + */ + position: static !important; + /* + * Do not transform the slide, but rather transform just the content. + */ + transform: none !important; + will-change: auto !important; +} + +amp-inline-gallery-slide.i-amphtml-layout-size-defined { + /* + * Since the content is translated, it may be outside the area of the + * slide itself. + */ + overflow: visible !important; +} + +.i-amphtml-inline-gallery-slide-container { + width: 100%; + height: 100%; + /* Override default from
*/ + margin: 0; +} + +.i-amphtml-inline-gallery-slide-content-slot { + display: flex; + align-items: center; + justify-content: center; + /* Subtract out height for the caption */ + height: calc(100% - calc(var(--i-amphtml-caption-height, 0px))); + transform: var(--content-transform, translateZ(1px)); + will-change: transform; + overflow: hidden; +} + +.i-amphtml-inline-gallery-slide-caption { + position: absolute; + left: 6px; + right: 6px; + margin-top: var(--caption-margin-top); + height: var(--i-amphtml-caption-height, 0); + overflow: hidden; + opacity: var(--caption-opacity); +} + +.i-amphtml-inline-gallery-slide-see-more { + float: right; + padding: 0; + padding-left: 6px; + border: 0; + color: #48f; + background-color: transparent; + outline: none; + font-family: inherit; + font-size: inherit; + line-height: 1.25em; +} + +.i-amphtml-inline-gallery-slide-persistent-slot { + clear: both; +} + +.i-amphtml-inline-gallery-slide-content-slot > * { + height: 100%; + width: 100%; +} diff --git a/extensions/amp-inline-gallery/0.1/amp-inline-gallery-slide.js b/extensions/amp-inline-gallery/0.1/amp-inline-gallery-slide.js new file mode 100644 index 000000000000..742a0516ce05 --- /dev/null +++ b/extensions/amp-inline-gallery/0.1/amp-inline-gallery-slide.js @@ -0,0 +1,121 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Layout} from '../../../src/layout'; +import {Services} from '../../../src/services'; +import {htmlFor} from '../../../src/static-template'; +import {isExperimentOn} from '../../../src/experiments'; +import {toArray} from '../../../src/types'; +import {userAssert} from '../../../src/log'; + +export class AmpInlineGallerySlide extends AMP.BaseElement { + /** + * @return {!Element} + * @private + */ + createDom_() { + userAssert( + isExperimentOn(this.win, 'amp-inline-gallery-captions') || + 'expected "amp-inline-gallery-captions" experiment to be enabled' + ); + const html = htmlFor(this.element); + const content = html` + + `; + const expand = content.querySelector('[slot="collapsed"]'); + expand.addEventListener('click', (e) => { + this.openLightbox(); + e.stopPropagation(); + }); + + return content; + } + + /** @param {!AmpElement} element */ + constructor(element) { + super(element); + } + + /** + * + */ + openLightbox() { + Services.extensionsFor(this.win) + .installExtensionForDoc(this.getAmpDoc(), 'amp-lightbox-gallery') + .then(() => { + const el = document.querySelector('amp-lightbox-gallery'); + return el.getImpl(); + }) + .then((impl) => { + impl.open(this.element, true); + }); + } + + /** @override */ + isLayoutSupported() { + return Layout.FLEX_ITEM; + } + + /** @override */ + buildCallback() { + const dom = this.createDom_(); + const captionSlot = dom.querySelector( + '.i-amphtml-inline-gallery-slide-caption-slot' + ); + const contentSlot = dom.querySelector( + '.i-amphtml-inline-gallery-slide-content-slot' + ); + const attributionSlot = dom.querySelector( + '.i-amphtml-inline-gallery-slide-persistent-slot' + ); + const childNodesArray = toArray(this.element.childNodes); + + childNodesArray + .filter((n) => { + return n.hasAttribute && n.getAttribute('slot') === 'caption'; + }) + .forEach((node) => captionSlot.appendChild(node)); + childNodesArray + .filter((n) => { + return !n.hasAttribute || !n.hasAttribute('slot'); + }) + .forEach((node) => contentSlot.appendChild(node)); + childNodesArray + .filter((n) => { + return n.hasAttribute && n.getAttribute('slot') === 'attribution'; + }) + .forEach((node) => attributionSlot.appendChild(node)); + + this.element.appendChild(dom); + } +} diff --git a/extensions/amp-inline-gallery/0.1/amp-inline-gallery.js b/extensions/amp-inline-gallery/0.1/amp-inline-gallery.js index a4683daea783..8c96ba75f442 100644 --- a/extensions/amp-inline-gallery/0.1/amp-inline-gallery.js +++ b/extensions/amp-inline-gallery/0.1/amp-inline-gallery.js @@ -15,8 +15,12 @@ */ import {CSS as AmpInlineGalleryCSS} from '../../../build/amp-inline-gallery-0.1.css'; +import {AmpInlineGalleryCaptions} from './amp-inline-gallery-captions'; +import {CSS as AmpInlineGalleryCaptionsCSS} from '../../../build/amp-inline-gallery-captions-0.1.css'; import {AmpInlineGalleryPagination} from './amp-inline-gallery-pagination'; import {CSS as AmpInlineGalleryPaginationCSS} from '../../../build/amp-inline-gallery-pagination-0.1.css'; +import {AmpInlineGallerySlide} from './amp-inline-gallery-slide'; +import {CSS as AmpInlineGallerySlideCSS} from '../../../build/amp-inline-gallery-slide-0.1.css'; import {AmpInlineGalleryThumbnails} from './amp-inline-gallery-thumbnails'; import {CSS as AmpInlineGalleryThumbnailsCSS} from '../../../build/amp-inline-gallery-thumbnails-0.1.css'; import {CarouselEvents} from '../../amp-base-carousel/0.1/carousel-events'; @@ -34,7 +38,8 @@ import {toArray} from '../../../src/types'; * The selector of children to update the progress on as the gallery's carousel * changes position. */ -const CHILDREN_FOR_PROGRESS_SELECTOR = 'amp-inline-gallery-pagination'; +const CHILDREN_FOR_PROGRESS_SELECTOR = + 'amp-inline-gallery-pagination, amp-inline-gallery-captions'; /** * The selector for the element to contain the thumbnails. @@ -99,14 +104,15 @@ class AmpInlineGallery extends AMP.BaseElement { * @param {number} total * @param {number} index * @param {number} offset + * @param {!Array} slides * @private */ - updateProgress_(total, index, offset) { + updateProgress_(total, index, offset, slides) { iterateCursor( scopedQuerySelectorAll(this.element, CHILDREN_FOR_PROGRESS_SELECTOR), (el) => { el.getImpl().then((pagination) => { - pagination.updateProgress(total, index, offset); + pagination.updateProgress(total, index, offset, slides); }); } ); @@ -120,8 +126,9 @@ class AmpInlineGallery extends AMP.BaseElement { const detail = getDetail(event); const total = detail['total']; const index = detail['index']; + const slides = detail['slides']; - this.updateProgress_(total, index, 0); + this.updateProgress_(total, index, 0, slides); } /** @@ -133,8 +140,9 @@ class AmpInlineGallery extends AMP.BaseElement { const total = detail['total']; const index = detail['index']; const offset = detail['offset']; + const slides = detail['slides']; - this.updateProgress_(total, index, offset); + this.updateProgress_(total, index, offset, slides); } /** @@ -157,11 +165,21 @@ class AmpInlineGallery extends AMP.BaseElement { } AMP.extension('amp-inline-gallery', '0.1', (AMP) => { + AMP.registerElement( + 'amp-inline-gallery-captions', + AmpInlineGalleryCaptions, + AmpInlineGalleryCaptionsCSS + ); AMP.registerElement( 'amp-inline-gallery-pagination', AmpInlineGalleryPagination, AmpInlineGalleryPaginationCSS ); + AMP.registerElement( + 'amp-inline-gallery-slide', + AmpInlineGallerySlide, + AmpInlineGallerySlideCSS + ); AMP.registerElement( 'amp-inline-gallery-thumbnails', AmpInlineGalleryThumbnails, diff --git a/extensions/amp-lightbox-gallery/0.1/service/lightbox-manager-impl.js b/extensions/amp-lightbox-gallery/0.1/service/lightbox-manager-impl.js index 38ffcaf67e0a..e8a591ae1b95 100644 --- a/extensions/amp-lightbox-gallery/0.1/service/lightbox-manager-impl.js +++ b/extensions/amp-lightbox-gallery/0.1/service/lightbox-manager-impl.js @@ -244,10 +244,19 @@ export class LightboxManager { figure, (child) => child.tagName !== 'FIGCAPTION' ); - if (element) { - element.setAttribute('lightbox', lightboxGroupId); + const isGallerySlide = element.classList.contains( + 'i-amphtml-inline-gallery-slide-content-slot' + ); + // Special handling for gallery slides, needed since they require a + // wrapping div inside of the figure for the content. + const unwrappedElement = isGallerySlide + ? isGallerySlide.firstChild + : element; + + if (unwrappedElement) { + unwrappedElement.setAttribute('lightbox', lightboxGroupId); } - return element; + return unwrappedElement; } /** diff --git a/test/manual/amp-inline-gallery/captions.html b/test/manual/amp-inline-gallery/captions.html new file mode 100644 index 000000000000..88820b45f068 --- /dev/null +++ b/test/manual/amp-inline-gallery/captions.html @@ -0,0 +1,96 @@ + + + + + A Basic inline gallery + + + + + + + + + + + + + +

+ + + + + + The first slide's caption. This is a desert with a single tree in the frame and a mountain in the backdrop. This is some extra text such that it is truncated. Since we have an attribution, that should still show up. + + This is an attribution (not truncated). + + + + + + This is the second slide's caption. It is a close up shot of a blossoming flower. + + This is an attribution (not truncated). + + + + + + The third slide's caption. This is a moon in a cloudy night sky. The arrow buttons should be visible against the background. This is some extra text to force truncation on smaller viewport sizes so that the truncation logic can be tested. + + + + + + + The fourth slide's caption. A sky at sunset. + + + + + + + + + +

Config

+ + +