From 1f633354ab4e74f64c29446329afc92bc37c01dd Mon Sep 17 00:00:00 2001 From: Matthew Oliveira Date: Thu, 11 Jan 2024 18:03:55 -0500 Subject: [PATCH] [lightbox-media-viewer]: Auto-open and play media from targeted link (#11253) ### Related Ticket(s) Closes https://github.com/carbon-design-system/carbon-for-ibm-dotcom/issues/11213 [Jira ticket](https://jsw.ibm.com/browse/ADCMS-4360) ### Description Enables the `CTAMixin` to check the URL fragment for a particular pattern, `cta-video-[video-id]`, and if present, kick off the CTA trigger, which in the case of a video CTA, is opening the corresponding lightbox and auto-playing video. The effect is that visiting any page with a video CTA for a specific video embedded in it (eg. `0_ibuqxqbe`), with a URL fragment like `#cta-video-0_ibuqxqbe`, will automatically open the lightbox modal and play the video (provided [browser auto-play policies to not block it](https://developer.chrome.com/blog/autoplay/)) ### Testing instructions - [ ] Navigate to any page with a CTA component that allows you to configure it as a video component. Examples include: [Link with icon](https://ibmdotcom-webcomponents.s3.us-east.cloud-object-storage.appdomain.cloud/deploy-previews/11253/index.html?path=/story/components-link-with-icon--default) - [ ] Set the CTA type (cta-type) to Video (video) - [ ] Take note of the configured Video ID (eg. `0_ibuqxqbe`). - [ ] Click Open canvas in new tab - [ ] The component should work without any regressions - [ ] Append a URL fragment to the URL like `#cta-video-[video_id]` - [ ] When loading the page with the URL fragment, the lightbox should automatically open and begin playback of the video (subject to [browser auto-play policies](https://developer.chrome.com/blog/autoplay/)). Note that mosy likely, the video _won't_ autoplay b/c you probably haven't built up enough of Media Engagement Index (Chrome speak) against the Storybook deploy preview URL. We don't have control over this. ### Changelog **New** - `CTAMixin`, where `ctaType === CTA_TYPE.VIDEO`, will check for a specifically crafted URL fragment (`cta-video-[video-id]`) to trigger it's run action. --- .../web-components/IMPLEMENTATION_NOTES.md | 6 +++ .../src/component-mixins/cta/cta.ts | 51 ++++++++++++++++++- .../expressive-modal/expressive-modal.ts | 4 +- .../integration/link-with-icon/default.e2e.js | 51 ++++++++++++++----- 4 files changed, 97 insertions(+), 15 deletions(-) diff --git a/packages/web-components/IMPLEMENTATION_NOTES.md b/packages/web-components/IMPLEMENTATION_NOTES.md index beca75342a1..79574a95aad 100644 --- a/packages/web-components/IMPLEMENTATION_NOTES.md +++ b/packages/web-components/IMPLEMENTATION_NOTES.md @@ -202,6 +202,12 @@ There are some common behaviors in CTA components that are implemented by attribute of `` for `external` CTA types. - [Use a hash link](https://github.com/carbon-design-system/carbon-for-ibm-dotcom/blob/v1.15.0-rc.0/packages/web-components/src/component-mixins/cta/cta.ts#L113-L122) for `video` CTA types. +- Trigger a CTA video on page load using a `#cta-video-[video-id]` URL fragment. + For any page that includes a CTA component composed with `CTAMixin`, the CTA + will look for a URL fragment following the pattern `#cta-video-[video-id]`, + where `[video-id]` is the video id configured for the component. If there is a + match, the CTA will automatically be triggered, which in most cases will open + a lightbox and begin video playback (subject to browser auto-play policies). ### Video CTA diff --git a/packages/web-components/src/component-mixins/cta/cta.ts b/packages/web-components/src/component-mixins/cta/cta.ts index 8aaa313a822..31d7c5cad95 100644 --- a/packages/web-components/src/component-mixins/cta/cta.ts +++ b/packages/web-components/src/component-mixins/cta/cta.ts @@ -1,7 +1,7 @@ /** * @license * - * Copyright IBM Corp. 2020, 2023 + * Copyright IBM Corp. 2020, 2024 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. @@ -29,6 +29,7 @@ import { formatVideoCaption, formatVideoDuration, } from '../../internal/vendor/@carbon/ibmdotcom-utilities/utilities/formatVideoCaption/formatVideoCaption.js'; +import root from 'window-or-global'; const { prefix, stablePrefix: c4dPrefix } = settings; @@ -201,6 +202,14 @@ const CTAMixin = >(Base: T) => { `; } + firstUpdated() { + const { ctaType, href } = this; + // Check for the URL trigger meant to fire eventRunAction. + if (ctaType === CTA_TYPE.VIDEO && href) { + this._checkUrlVideoTrigger(); + } + } + /** * Handles `.updated()` method of `lit-element`. */ @@ -324,6 +333,46 @@ const CTAMixin = >(Base: T) => { } } + /** + * Check the URL for a fragment including the video id. + * + * If we find a URL fragment that includes the video id, we trigger the + * eventRunAction event, which for video will open the video and start + * playback in a lightbox. This is the same thing that happens when the user + * clicks on the CTA. + */ + _checkUrlVideoTrigger() { + const { ctaType, disabled, href, videoDescription, videoName } = this; + // Without a video id, or if the button is disabled, there is nothing to + // do here. + if (ctaType !== CTA_TYPE.VIDEO || !href || disabled) { + return; + } + // Only trigger for the first CTA with the video id in the page. + if (this.ownerDocument.querySelector(`[href='${href}']`) !== this) { + return; + } + const { eventRunAction } = this.constructor as typeof CTAMixinImpl; + const hash = root.location.hash; + const urlTrigger = `cta-video-${href}`; + + if (hash === `#${urlTrigger}`) { + this.dispatchEvent( + new CustomEvent(eventRunAction, { + bubbles: true, + cancelable: true, + composed: true, + detail: { + href, + ctaType, + videoName, + videoDescription, + }, + }) + ); + } + } + /** * Updates video thumbnail url to match card width. */ diff --git a/packages/web-components/src/components/expressive-modal/expressive-modal.ts b/packages/web-components/src/components/expressive-modal/expressive-modal.ts index dfd56b21708..801c4198b9a 100644 --- a/packages/web-components/src/components/expressive-modal/expressive-modal.ts +++ b/packages/web-components/src/components/expressive-modal/expressive-modal.ts @@ -1,7 +1,7 @@ /** * @license * - * Copyright IBM Corp. 2020, 2023 + * Copyright IBM Corp. 2020, 2024 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. @@ -530,7 +530,7 @@ class C4DExpressiveModal extends StableSelectorMixin( static get selectorPrimaryFocus() { return ` [data-modal-primary-focus], - ${c4dPrefix}-expressive-modal-footer ${c4dPrefix}-button[kind="primary"], + ${c4dPrefix}-expressive-modal-footer ${c4dPrefix}-button[kind="primary"] `; } diff --git a/packages/web-components/tests/e2e-storybook/cypress/integration/link-with-icon/default.e2e.js b/packages/web-components/tests/e2e-storybook/cypress/integration/link-with-icon/default.e2e.js index b8fcc74e0d0..686344d4b6f 100644 --- a/packages/web-components/tests/e2e-storybook/cypress/integration/link-with-icon/default.e2e.js +++ b/packages/web-components/tests/e2e-storybook/cypress/integration/link-with-icon/default.e2e.js @@ -1,5 +1,5 @@ /** - * Copyright IBM Corp. 2021, 2022 + * Copyright IBM Corp. 2021, 2023 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. @@ -15,13 +15,19 @@ */ const _path = '/iframe.html?id=components-link-with-icon--default'; +const _videoId = '0_ibuqxqbe'; + +const _videoPath = `/iframe.html?&id=components-link-with-icon--default&knob-CTA%20type%20(cta-type)=video&knob-Icon%20Position%20(icon-placement):=right&knob-Video%20ID%20(href)=${_videoId}`; + /** * Defines the component selector. * * @type {string} * @private */ -const _selector = '[data-autoid="cds--link-with-icon"]'; +const _selector = '[data-autoid="c4d--link-with-icon"]'; + +const _lightboxVideoPlayerSelector = 'c4d-lightbox-video-player'; /** * Collection of test scenarios. @@ -47,7 +53,9 @@ const _tests = [ .then(([copy]) => { defaultCopy = copy.innerText.trim(); }) - .visit(`${_path}&knob-Link%20text%20(unnamed%20slot)=${customCopyInput}`) + .visit( + `${_path}&knob-Link%20text%20(unnamed%20slot)=${customCopyInput}` + ) .get(_selector) .should(([copy]) => { customCopyOutput = copy.innerText.trim(); @@ -66,16 +74,18 @@ const _tests = [ .get(_selector) .shadow() .find('a') - .should($link => { + .should(($link) => { defaultHref = $link.prop('href'); expect($link.prop('href')).not.to.be.empty; }) - .visit(`${_path}&knob-Link%20href%20(href)=${customHrefInput}`) + .visit( + `${_path}&knob-Content%20link%20href%20(href)=${customHrefInput}` + ) .get(_selector) .shadow() .find('a') - .should($link => { + .should(($link) => { customHrefOutput = $link.prop('href'); expect(customHrefOutput).to.be.eq(customHrefInput); @@ -94,16 +104,18 @@ const _tests = [ }, () => { it('should check icon placements', () => { - ['left', 'right'].forEach(placement => { + ['left', 'right'].forEach((placement) => { let $svg; - cy.visit(`${_path}&knob-Icon%20Position%20(icon-placement):=${placement}`) + cy.visit( + `${_path}&knob-Icon%20Position%20(icon-placement):=${placement}` + ) .get(_selector) - .then($elem => { + .then(($elem) => { $svg = $elem.find('svg'); }) .shadow() .find('a') - .should($link => { + .should(($link) => { const svgPosition = $svg[0].getBoundingClientRect(); const textPosition = $link.find('span')[0].getBoundingClientRect(); @@ -123,6 +135,21 @@ const _tests = [ }); }); }, + () => { + it('should replace the button title with the video title for a video cta type', () => { + cy.visit(_videoPath); + cy.get(_selector) + .shadow() + .find('a > span') + .should('contain.text', 'Mombo'); + cy.get(_lightboxVideoPlayerSelector).should('not.exist'); + }); + + it('should trigger the lightbox for video when using the right URL fragment', () => { + cy.visit(`${_videoPath}#cta-video-${_videoId}`); + cy.get(_lightboxVideoPlayerSelector).should('exist').and('be.visible'); + }); + }, ]; describe('cds-link-with-icon | default (desktop)', () => { @@ -130,7 +157,7 @@ describe('cds-link-with-icon | default (desktop)', () => { cy.viewport(1280, 780); }); - _tests.forEach(test => test()); + _tests.forEach((test) => test()); }); describe('cds-link-with-icon | default (mobile)', () => { @@ -138,5 +165,5 @@ describe('cds-link-with-icon | default (mobile)', () => { cy.viewport(375, 720); }); - _tests.forEach(test => test()); + _tests.forEach((test) => test()); });