@graphcommerce/magento-product 10.1.0-canary.21 → 10.1.0-canary.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 10.1.0-canary.22
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2627](https://github.com/graphcommerce-org/graphcommerce/pull/2627) [`95c188f`](https://github.com/graphcommerce-org/graphcommerce/commit/95c188fcd0dc6cb4ca3edc9877d203d45fe18bb0) - Add `YoutubeEmbed` component — a lightweight lazy-loading YouTube player that defers iframe creation until the user clicks the poster. Uses preconnect on hover for fast playback start and is styled with MUI sx, so no external CSS is required. Supports playlists, no-cookie mode, custom aspect ratios and ad-network preconnect hints.
|
|
8
|
+
|
|
9
|
+
`ProductVideo` (used by `ProductPageGallery`) now delegates YouTube playback to `YoutubeEmbed`, so any product whose Magento `media_gallery` contains a YouTube video entry gets the new lazy-loading player on its product page. Vimeo and self-hosted video paths are unchanged. The Magento preview image is passed as the YoutubeEmbed `thumbnail` so the visible poster stays consistent with the rest of the gallery.
|
|
10
|
+
|
|
11
|
+
Fix `SidebarGallery` so it forwards the `Additional` and `slotProps` from each image to `MotionImageAspect`. Before this fix the gallery silently dropped both props, which meant any `Additional` overlay configured by `ProductPageGallery` (the `<ProductVideo>` overlay with its `PlayCircle` and the new `YoutubeEmbed`) never reached the DOM. That was a latent regression that made all video-gallery entries render as static images, with or without this PR's YouTube changes.
|
|
12
|
+
|
|
13
|
+
Fix `playwright.config.ts` so `npx playwright test` actually loads. The config previously imported `examples/magento-graphcms/next.config.ts`, which transitively pulled `@graphcommerce/next-config`'s ESM build into a CJS context and crashed with `ReferenceError: exports is not defined`. Replaced with an opt-in `PLAYWRIGHT_LOCALES` env var for the multi-locale projects that the next.config import was meant to drive. ([@paales](https://github.com/paales))
|
|
14
|
+
|
|
3
15
|
## 10.1.0-canary.21
|
|
4
16
|
|
|
5
17
|
## 10.1.0-canary.20
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MotionImageAspectPropsAdditional } from '@graphcommerce/framer-scroller'
|
|
2
|
-
import { Fab, iconPlay, IconSvg, sxx, type FabProps } from '@graphcommerce/next-ui'
|
|
2
|
+
import { Fab, iconPlay, IconSvg, sxx, YoutubeEmbed, type FabProps } from '@graphcommerce/next-ui'
|
|
3
3
|
import { Avatar, Box, IconButton, styled, type SxProps, type Theme } from '@mui/material'
|
|
4
4
|
import { m, type MotionProps, type MotionStyle } from 'framer-motion'
|
|
5
5
|
import React, { useState } from 'react'
|
|
@@ -90,6 +90,11 @@ function getEmbedUrl(regularUrl: string, noCookie: boolean = true, muted: boolea
|
|
|
90
90
|
return null
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
function extractYoutubeId(src: string): string | null {
|
|
94
|
+
const match = src.match(youtubeRegExp)
|
|
95
|
+
return match?.[1] ?? null
|
|
96
|
+
}
|
|
97
|
+
|
|
93
98
|
export function ProductVideo(props: ProductVideoProps) {
|
|
94
99
|
const { video, autoplay, iframeProps, videoProps, sx, style, layout, width, height } = props
|
|
95
100
|
|
|
@@ -102,6 +107,33 @@ export function ProductVideo(props: ProductVideoProps) {
|
|
|
102
107
|
const src = videoContent.video_url
|
|
103
108
|
const title = videoContent.video_title || undefined
|
|
104
109
|
|
|
110
|
+
const youtubeId = extractYoutubeId(src)
|
|
111
|
+
if (youtubeId) {
|
|
112
|
+
return (
|
|
113
|
+
<YoutubeEmbed
|
|
114
|
+
id={youtubeId}
|
|
115
|
+
title={title ?? ''}
|
|
116
|
+
thumbnail={video?.url ?? undefined}
|
|
117
|
+
aspectWidth={width ?? 16}
|
|
118
|
+
aspectHeight={height ?? 9}
|
|
119
|
+
sx={sxx(
|
|
120
|
+
{
|
|
121
|
+
position: 'absolute',
|
|
122
|
+
top: '50%',
|
|
123
|
+
left: '50%',
|
|
124
|
+
transform: 'translate(-50%, -50%)',
|
|
125
|
+
width: '100%',
|
|
126
|
+
height: 'auto',
|
|
127
|
+
maxWidth: '99.6%',
|
|
128
|
+
maxHeight: '100%',
|
|
129
|
+
aspectRatio: width && height ? `${width} / ${height}` : '16 / 9',
|
|
130
|
+
},
|
|
131
|
+
sx,
|
|
132
|
+
)}
|
|
133
|
+
/>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
105
137
|
const baseSx: SxProps<Theme> = (theme) => ({
|
|
106
138
|
display: 'block',
|
|
107
139
|
maxWidth: '99.6%',
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@graphcommerce/magento-product",
|
|
3
3
|
"homepage": "https://www.graphcommerce.org/",
|
|
4
4
|
"repository": "github:graphcommerce-org/graphcommerce",
|
|
5
|
-
"version": "10.1.0-canary.
|
|
5
|
+
"version": "10.1.0-canary.22",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
8
8
|
"eslintConfig": {
|
|
@@ -18,18 +18,18 @@
|
|
|
18
18
|
"typescript": "5.9.3"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"@graphcommerce/ecommerce-ui": "^10.1.0-canary.
|
|
22
|
-
"@graphcommerce/eslint-config-pwa": "^10.1.0-canary.
|
|
23
|
-
"@graphcommerce/framer-next-pages": "^10.1.0-canary.
|
|
24
|
-
"@graphcommerce/framer-scroller": "^10.1.0-canary.
|
|
25
|
-
"@graphcommerce/graphql": "^10.1.0-canary.
|
|
26
|
-
"@graphcommerce/graphql-mesh": "^10.1.0-canary.
|
|
27
|
-
"@graphcommerce/image": "^10.1.0-canary.
|
|
28
|
-
"@graphcommerce/magento-cart": "^10.1.0-canary.
|
|
29
|
-
"@graphcommerce/magento-store": "^10.1.0-canary.
|
|
30
|
-
"@graphcommerce/next-ui": "^10.1.0-canary.
|
|
31
|
-
"@graphcommerce/prettier-config-pwa": "^10.1.0-canary.
|
|
32
|
-
"@graphcommerce/typescript-config-pwa": "^10.1.0-canary.
|
|
21
|
+
"@graphcommerce/ecommerce-ui": "^10.1.0-canary.22",
|
|
22
|
+
"@graphcommerce/eslint-config-pwa": "^10.1.0-canary.22",
|
|
23
|
+
"@graphcommerce/framer-next-pages": "^10.1.0-canary.22",
|
|
24
|
+
"@graphcommerce/framer-scroller": "^10.1.0-canary.22",
|
|
25
|
+
"@graphcommerce/graphql": "^10.1.0-canary.22",
|
|
26
|
+
"@graphcommerce/graphql-mesh": "^10.1.0-canary.22",
|
|
27
|
+
"@graphcommerce/image": "^10.1.0-canary.22",
|
|
28
|
+
"@graphcommerce/magento-cart": "^10.1.0-canary.22",
|
|
29
|
+
"@graphcommerce/magento-store": "^10.1.0-canary.22",
|
|
30
|
+
"@graphcommerce/next-ui": "^10.1.0-canary.22",
|
|
31
|
+
"@graphcommerce/prettier-config-pwa": "^10.1.0-canary.22",
|
|
32
|
+
"@graphcommerce/typescript-config-pwa": "^10.1.0-canary.22",
|
|
33
33
|
"@lingui/core": "^5",
|
|
34
34
|
"@lingui/macro": "^5",
|
|
35
35
|
"@lingui/react": "^5",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validates that a product whose `media_gallery` contains a YouTube video
|
|
5
|
+
* renders the lazy-loading `YoutubeEmbed` component in the product gallery,
|
|
6
|
+
* shows the Magento preview image as the poster, and swaps in the YouTube
|
|
7
|
+
* iframe on click.
|
|
8
|
+
*
|
|
9
|
+
* Backend prerequisite: the configured Magento backend must have a product
|
|
10
|
+
* at the URL below with at least one `ProductVideo` entry pointing at a
|
|
11
|
+
* YouTube URL. Override with `PRODUCT_URL` and `EXPECTED_YOUTUBE_ID` env
|
|
12
|
+
* vars when running against a different backend.
|
|
13
|
+
*/
|
|
14
|
+
const PRODUCT_URL = process.env.PRODUCT_URL ?? '/p/spooky-girl-gc-1-sock'
|
|
15
|
+
const EXPECTED_YOUTUBE_ID = process.env.EXPECTED_YOUTUBE_ID ?? 'u_pe6qAhz5U'
|
|
16
|
+
|
|
17
|
+
test.describe('YoutubeEmbed in product media gallery', () => {
|
|
18
|
+
test('renders the YouTube poster, lazy-loads the iframe on click', async ({ page }) => {
|
|
19
|
+
await page.goto(PRODUCT_URL, { waitUntil: 'domcontentloaded' })
|
|
20
|
+
|
|
21
|
+
// Wait for the gallery to mount. We don't depend on a specific slide order
|
|
22
|
+
// — the YouTube slide may be 2nd, 3rd, etc. depending on the product.
|
|
23
|
+
await page.waitForSelector('[class*="SidebarGallery-root"]', { timeout: 30_000 })
|
|
24
|
+
|
|
25
|
+
const youtubeEmbed = page.locator('[class*="YoutubeEmbed-root"]').first()
|
|
26
|
+
await expect(youtubeEmbed).toBeAttached({ timeout: 15_000 })
|
|
27
|
+
|
|
28
|
+
// Magento preview image is forwarded as the YoutubeEmbed `thumbnail` prop,
|
|
29
|
+
// so the background should be the configured Magento media URL — not the
|
|
30
|
+
// YouTube auto-generated thumbnail at i.ytimg.com.
|
|
31
|
+
const initial = await youtubeEmbed.evaluate((el) => {
|
|
32
|
+
const styles = getComputedStyle(el)
|
|
33
|
+
return {
|
|
34
|
+
dataTitle: el.getAttribute('data-title'),
|
|
35
|
+
backgroundImage: styles.backgroundImage,
|
|
36
|
+
hasIframe: !!el.querySelector('iframe'),
|
|
37
|
+
playButtonLabel: el.querySelector('button[type="button"]')?.getAttribute('aria-label'),
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
expect(initial.hasIframe).toBe(false)
|
|
41
|
+
expect(initial.dataTitle).toBeTruthy()
|
|
42
|
+
expect(initial.playButtonLabel).toContain('Watch')
|
|
43
|
+
expect(initial.backgroundImage).not.toContain('i.ytimg.com')
|
|
44
|
+
expect(initial.backgroundImage).toContain('http')
|
|
45
|
+
|
|
46
|
+
// Hovering preconnects to youtube-nocookie.com so the iframe load is fast.
|
|
47
|
+
await youtubeEmbed.hover()
|
|
48
|
+
await expect(
|
|
49
|
+
page.locator('link[rel="preconnect"][href*="youtube"]'),
|
|
50
|
+
).toHaveCount(1, { timeout: 5_000 })
|
|
51
|
+
|
|
52
|
+
// Click swaps the poster for the actual YouTube iframe with autoplay=1.
|
|
53
|
+
await youtubeEmbed.click()
|
|
54
|
+
const iframe = youtubeEmbed.locator('iframe')
|
|
55
|
+
await expect(iframe).toBeAttached({ timeout: 5_000 })
|
|
56
|
+
|
|
57
|
+
const src = await iframe.getAttribute('src')
|
|
58
|
+
expect(src).toContain(EXPECTED_YOUTUBE_ID)
|
|
59
|
+
expect(src).toContain('autoplay=1')
|
|
60
|
+
expect(src).toMatch(/youtube(-nocookie)?\.com/)
|
|
61
|
+
})
|
|
62
|
+
})
|