Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Classical amp-inline-gallery-captions (0.1) #32075

Merged
merged 10 commits into from
Feb 24, 2021
2 changes: 2 additions & 0 deletions build-system/compile/bundles.config.extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
Expand Down
21 changes: 21 additions & 0 deletions extensions/amp-inline-gallery/0.1/amp-inline-gallery-captions.css
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit surprised by it. Does it mean we are actually not allowing any links in captions? How do "more" and "less" buttons work?

Copy link
Contributor Author

@caroqliu caroqliu Feb 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's because this implementation doesn't actually have any interactivity or interactive descendants in the amp-inline-gallery-captions element, which is effectively just a fixed height block of space that gets covered by captions elements in a different tree (amp-inline-gallery-slide). Agreed it is a bit unconventional and likely not necessary, but it should not be harmful.

<amp-inline-gallery layout="container">
    <amp-base-carousel>
      <amp-inline-gallery-slide>
        <amp-img></amp-img>
        <!-- clickable and slotted into an amp-truncate-text element added to amp-inline-gallery-slide -->
        <span class="caption" slot="caption"> 
          The first slide's caption.
        </span>
        <span class="attribution" slot="attribution">This is an attribution (not truncated).</span>
      </amp-inline-gallery-slide>
      ... 
    </amp-base-carousel>
    <amp-inline-gallery-captions layout="fixed-height" height="3.75em">
    </amp-inline-gallery-captions>
  </amp-inline-gallery>

padding-top: var(--caption-margin-top);
margin-top: calc(-1 * var(--i-amphtml-caption-height, 0));
}
91 changes: 91 additions & 0 deletions extensions/amp-inline-gallery/0.1/amp-inline-gallery-captions.js
Original file line number Diff line number Diff line change
@@ -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) {
caroqliu marked this conversation as resolved.
Show resolved Hide resolved
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<!Element>} 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<!Element>} 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',
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,9 @@ export class AmpInlineGalleryPagination extends AMP.BaseElement {
* @param {number} total
* @param {number} index
* @param {number} offset
* @param {!Array<!Element>} unusedSlides
*/
updateProgress(total, index, offset) {
updateProgress(total, index, offset, unusedSlides) {
this.mutateElement(() => {
this.updateTotal_(total);
this.updateDots_(index, offset);
Expand Down
86 changes: 86 additions & 0 deletions extensions/amp-inline-gallery/0.1/amp-inline-gallery-slide.css
Original file line number Diff line number Diff line change
@@ -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 <figure> */
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%;
}
121 changes: 121 additions & 0 deletions extensions/amp-inline-gallery/0.1/amp-inline-gallery-slide.js
Original file line number Diff line number Diff line change
@@ -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_() {
caroqliu marked this conversation as resolved.
Show resolved Hide resolved
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`
<figure class="i-amphtml-inline-gallery-slide-container">
<div class="i-amphtml-inline-gallery-slide-content-slot"></div>
<figcaption class="i-amphtml-inline-gallery-slide-caption">
<amp-truncate-text layout="fill">
<span class="i-amphtml-inline-gallery-slide-caption-slot"></span>
<button
class="i-amphtml-inline-gallery-slide-see-more"
slot="collapsed"
>
See more
</button>
<div
class="i-amphtml-inline-gallery-slide-persistent-slot"
slot="persistent"
></div>
</amp-truncate-text>
</figcaption>
</figure>
`;
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);
}
}