diff --git a/.changeset/tender-queens-trade.md b/.changeset/tender-queens-trade.md new file mode 100644 index 000000000..183d0664e --- /dev/null +++ b/.changeset/tender-queens-trade.md @@ -0,0 +1,5 @@ +--- +"docs": patch +--- + +Working with images diff --git a/.changeset/thick-poets-tan.md b/.changeset/thick-poets-tan.md new file mode 100644 index 000000000..f8f63af8e --- /dev/null +++ b/.changeset/thick-poets-tan.md @@ -0,0 +1,5 @@ +--- +"@shopware-pwa/types": minor +--- + +Improved media types diff --git a/.changeset/wild-parents-jam.md b/.changeset/wild-parents-jam.md new file mode 100644 index 000000000..a399d9f4f --- /dev/null +++ b/.changeset/wild-parents-jam.md @@ -0,0 +1,5 @@ +--- +"@shopware-pwa/helpers-next": patch +--- + +Proper access for an URL of main product image \ No newline at end of file diff --git a/apps/docs/.vitepress/config.ts b/apps/docs/.vitepress/config.ts index 91bfd6f38..d0abb0295 100644 --- a/apps/docs/.vitepress/config.ts +++ b/apps/docs/.vitepress/config.ts @@ -43,6 +43,7 @@ export const sidebar = [ { text: "Styling", link: "/framework/styling" }, { text: "Context Composables", link: "/framework/context-composables" }, { text: "Shared Composables", link: "/framework/shared-composables" }, + { text: "Images", link: "/framework/images" }, ], }, { @@ -51,6 +52,7 @@ export const sidebar = [ { text: "Testing", link: "/best-practices/testing" }, { text: "Performance", link: "/best-practices/performance" }, { text: "Deployment", link: "/best-practices/deployment" }, + { text: "Images", link: "/best-practices/images" }, ], }, { @@ -80,6 +82,7 @@ export default defineConfigWithTheme({ srcDir: "src", // srcExclude: ["tutorial/**/description.md"], In case we need something to be excluded scrollOffset: "header", + ignoreDeadLinks: true, // remove once MR #294 is merged head: [ [ "link", diff --git a/apps/docs/src/.assets/edit-media-sizes.png b/apps/docs/src/.assets/edit-media-sizes.png new file mode 100644 index 000000000..063790a35 Binary files /dev/null and b/apps/docs/src/.assets/edit-media-sizes.png differ diff --git a/apps/docs/src/.assets/squoosh-app.png b/apps/docs/src/.assets/squoosh-app.png new file mode 100644 index 000000000..e83d6150b Binary files /dev/null and b/apps/docs/src/.assets/squoosh-app.png differ diff --git a/apps/docs/src/best-practices/images.md b/apps/docs/src/best-practices/images.md new file mode 100644 index 000000000..22d38144a --- /dev/null +++ b/apps/docs/src/best-practices/images.md @@ -0,0 +1,142 @@ +--- +head: + - - meta + - name: og:title + content: "Best practices: Images" + - - meta + - name: og:description + content: "Collection of good practices to manage images." + - - meta + - name: og:image + content: "https://frontends-og-image.vercel.app/Best%20practices:%20**Images** 🖼.png?fontSize=110px" +nav: + position: 30 +--- + +# Images + +Best practices for images. + +## Optimization + +Let's have a look on some good practices to help display images efficiently. + +### Image format & Compression + +Compression is the first step, relatively easy to achieve in order to reduce loading time by reducing file size, thus saving network traffic for images. + +**WebP** is a new format for images, developed by Google, in order to add an alternative for _png_ images, but with lossy compression, enhanced by some new techniques allowing to compress with different level selectively within the same image. As it's become fully supported in all modern browsers - it can be recommended. + +You can check how much you can save by using `webp` format instead of others raster-images formats. See how can it help you on [Thumbor](http://thumborize.globo.com/?url=https://frontends-demo.vercel.app). + +:::info Test different formats +There are many image formats, which have different advantages, depending on images purposes. Probably you don't need `webp` files for vector images. Sometimes, when the high image quality is important, using lossy formats may not be a good idea. It always depends on the use case. + +There are many tools to check different image formats, but the great one is [Squoosh](https://squoosh.app/) which allows you to experiment with images interactively: + +![Squoosh screenshot](../.assets/squoosh-app.png) +::: + +### Images hosting on CDN + Image processor + +Using Content Delivery Network platforms (CDN) helps to reduce network distance, by serving resources from the closest server for an user. + +Although it can be a standalone service, some platforms serves images with additional option of resizing on the fly, or being more general: processing the images, depending on provided query parameter, like `?width=400px`. Thanks to this, `` element is more readable. + +```html + +``` + +Examples of open source image processors which can be used as a middleware to serve processed images: +* [thumbor](https://www.thumbor.org/) +* [lovell/sharp](https://github.com/lovell/sharp) +* [imgproxy/imgproxy](https://github.com/imgproxy/imgproxy) + + +## Responsive images + +Utilize [srcset](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-srcset) attribute for `` elements in order to load the image in size what is actually needed at the moment. +Decide what metric (pixel ratio - DPR or width) is more appropriate for your users when defining breakpoints. + +Also, consider using `sizes` attribute which will indicate what image size is best to choose - if your images occupy less than 100% of viewport. The value can be defined in percentage of viewport width (`sizes="80vw"`) or fixed value (`sizes="600px"`) regardless the device size. Read more at [mdn web docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes). + +```html +... + +``` + +If you application serves many image formats and there is a significant part of users with older browsers, you can use [picture](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture) element. + +In this example, browser will decide which image format is available to serve, otherwise the `` will be picked as a fallback. + +```html + + + + Logo Shopware Frontends + +``` + +## Reduce Cumulative Layout Shift (CLS) + +When Images occupy a big amount of space on web pages, they are a common cause of high [CLS](https://web.dev/cls/) scores. + +* Always set `width` and `hight` attributes for your `` elements, with values matching size of image source. So even if they are being loaded, the space of layout will be filled out. +* Define CSS style to override `` attributes (there is a moment when image element is available in DOM, and CSS is not loaded yet): + ```css + img { + max-width: 100%; + height: auto; + } + ``` +* Try to use low-quality placeholders (based on svg, for example) to avoid having empty blank spaces within the layout: +
+
+ +
+
+ + +## Speed up Largest Contentful Paint + +"[LCP](https://web.dev/lcp/) element has an image on around three quarters of pages" says the result of [Web research](https://almanac.httparchive.org/en/2021/media#images). Moreover, on 70.6% mobile pages, LCP element has an image. On desktops, the rate is even bigger: 79.4%. So we can assume, that bad LCP scores are based on low image performance. + +* Never use `loading="lazy"` on `` elements if they are part of what an user see first on they viewport (consider editing the attributes for CMS elements in Shopware Experiences). +* Utilize `fetchpriority="high"` on `` also tells the browser, that the asset (LCP resource is prioritized) is important and should be taken care of as fast as possible. + + + + + + +## Resources + +Collection of useful blog posts and articles about performance related to images. + +* https://web.dev/learn/images/ +* https://austingil.com/better-html-images/ +* https://www.smashingmagazine.com/2023/01/optimizing-image-element-lcp/ +* https://web.dev/top-cwv-2023/ \ No newline at end of file diff --git a/apps/docs/src/framework/images.md b/apps/docs/src/framework/images.md new file mode 100644 index 000000000..bc3abf154 --- /dev/null +++ b/apps/docs/src/framework/images.md @@ -0,0 +1,158 @@ +--- +head: + - - meta + - name: og:title + content: "Working with Images" + - - meta + - name: og:description + content: "How to display images served by API" + - - meta + - name: og:image + content: "https://frontends-og-image.vercel.app/Working%20with%20**Images**.png?fontSize=110px" +nav: + position: 30 +--- + + + +# Working with Images + +This section covers topics related to images, with a focus on what comes from API. + +:::warning Not auto-loaded +Although images are not always contained in API responses, we try to keep the composables logic aware of that and ready to load if they are needed. + +Which means if you need to work with images, ensure the requests contains additional [associations](https://shopware.stoplight.io/docs/store-api/cf710bf73d0cd-search-queries#associations). + +Example of request's payload with media association included, to avoid an empty `media` object within the response: + +```json +{ + "associations": { + "media": {} + } +} +``` +::: + +## Structure of media objects + +Media objects can be used in many places, such as: + +* CMS objects (containing [CmsElementImage](https://github.com/shopware/frontends/blob/main/packages/composables/src/types/cmsElementTypes.ts#L71) element) +* Product (cover image, image gallery, attributes in type media, etc.) +* Category (main image, ...) +* ... + +Regardless the outer container (see [ProductMedia](https://github.com/shopware/frontends/blob/main/packages/types/shopware-6-client/models/content/product/ProductMedia.d.ts#L8) as example) an image object can be wrapped with, the inner structure is reflected in type definition at [Media](https://github.com/shopware/frontends/blob/main/packages/types/shopware-6-client/models/content/media/Media.d.ts#L23) + +Let's have a look what's inside: + +```json +{ + // irrelevant data omitted + ... + "mimeType": "image/webp", // mime-type of media object, supported by the Shopware 6 platform + "fileExtension": "webp", + "fileSize": 492024, + "title": "Frontends Logo", + "metaData": { + "hash": "b795091b0a92b8a0605281f710dc1c28", + "type": 2, + "width": 3505, // original width + "height": 5258 // original height + }, + "alt": "Shopware Frontends", + "url": "http://localhost/media/shopware-frontends-4P8HWu_NRp4-unsplash.jpg", + "fileName": "shopware-frontends-4P8HWu_NRp4-unsplash", + "thumbnails": [ // list of resized images for previously configured ranges + { + "width": 1920, + "height": 1920, + "url": "http://localhost/thumbnail/ainars-cekuls-4P8HWu_NRp4-unsplash_1920x1920.webp", + }, + { + // omitted irrelevant data + "width": 800, + "height": 800, + "url": "http://localhost/thumbnail/ainars-cekuls-4P8HWu_NRp4-unsplash_800x800.webp", + "apiAlias": "media_thumbnail" + }, + ... + ] + ... +} +``` + +The media object, and its `thumbnails` list, contain all required information about the file to be used in the browser like URL and sizes. + +## Thumbnails and resolutions + +By default, every uploaded image is resized to the predefined width and height sizes (in pixels): +* 1920x1920 +* 800x800 +* 400x400 + +In order to change those sizes, or add another one (also the quality, or to keep aspect ratio), the values need to be adjusted in administration panel, for specific media folder. + +![Edit media sizes](../.assets/edit-media-sizes.png) + +:::warning Image processing +While a file is uploaded, it's been automatically resized for the current configuration in Administration > Media section. Thanks to this, the newly uploaded files will be available for all required dimensions. However keep in mind that if your settings have changes, the new dimensions won't be applied automatically for the old images. +::: + +## Helpers + +There are few functions that could be used to extract some crucial information about the media in short way. Browse [Helpers > Media](../packages/helpers/index.html#media) category to see them all. + +Example how to work with Product's main image: + +```ts +import type { Product } from "@shopware-pwa/types"; +import { getMainImageUrl } from "@shopware-pwa/helpers-next"; + + +const coverUrl = getMainImageUrl(product as Product); +// coverUrl is now an URL to the resource (or undefined) +``` + +## Responsive Images + +Having additional information about resized images (see `thumbnails` array in `Media` object), we are able to use them to define [srcset](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-srcset) attribute for ``. + +```vue{8} + + + +``` + +### Live example +Have a look on live example: + +
+ +The example above shows how to use dimension sizes configured in admin panel as ranges for viewport. However it can be adjusted to your needs. + +The `src` attribute points to the main image URL (not resized) as a fallback. + +As long as `thumbnails` array is fulfilled, the same strategy can be applied when we work with every `media` object for each entity available in Shopware 6. + + + diff --git a/examples/responsive-images/.gitignore b/examples/responsive-images/.gitignore new file mode 100644 index 000000000..38adffa64 --- /dev/null +++ b/examples/responsive-images/.gitignore @@ -0,0 +1,28 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/responsive-images/README.md b/examples/responsive-images/README.md new file mode 100644 index 000000000..fbd0d8a68 --- /dev/null +++ b/examples/responsive-images/README.md @@ -0,0 +1,28 @@ +# Blank playground + +This example should help get you started developing [Shopware Frontends](https://github.com/shopware/frontends). + + +## Customization + +- edit [./src/App.vue](./src/App.vue) in order to change the current example +- edit [./src/main.js](./src/main.js) in order to adjust Shopware Frontends plugin + + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Compile and Minify for Production + +```sh +npm run build +``` diff --git a/examples/responsive-images/index.html b/examples/responsive-images/index.html new file mode 100644 index 000000000..94ec23982 --- /dev/null +++ b/examples/responsive-images/index.html @@ -0,0 +1,14 @@ + + + + + + + Frontends examples + + + +
+ + + diff --git a/examples/responsive-images/package.json b/examples/responsive-images/package.json new file mode 100644 index 000000000..e9ce19f6d --- /dev/null +++ b/examples/responsive-images/package.json @@ -0,0 +1,21 @@ +{ + "name": "responsive-images", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@shopware-pwa/api-client": "canary", + "@shopware-pwa/composables-next": "canary", + "js-cookie": "^3.0.1", + "vue": "^3.2.47" + }, + "devDependencies": { + "@shopware-pwa/types": "^0.4.0", + "@vitejs/plugin-vue": "^4.0.0", + "vite": "^4.1.1" + } +} diff --git a/examples/responsive-images/public/favicon.ico b/examples/responsive-images/public/favicon.ico new file mode 100644 index 000000000..5e9ad09a6 Binary files /dev/null and b/examples/responsive-images/public/favicon.ico differ diff --git a/examples/responsive-images/src/App.vue b/examples/responsive-images/src/App.vue new file mode 100644 index 000000000..0aa0b6426 --- /dev/null +++ b/examples/responsive-images/src/App.vue @@ -0,0 +1,23 @@ + + diff --git a/examples/responsive-images/src/components/Image.vue b/examples/responsive-images/src/components/Image.vue new file mode 100644 index 000000000..119a90c2c --- /dev/null +++ b/examples/responsive-images/src/components/Image.vue @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/examples/responsive-images/src/main.ts b/examples/responsive-images/src/main.ts new file mode 100644 index 000000000..5de351b3f --- /dev/null +++ b/examples/responsive-images/src/main.ts @@ -0,0 +1,25 @@ +import { createApp, ref } from "vue"; +import { createShopwareContext } from "@shopware-pwa/composables-next"; +import { createInstance } from "@shopware-pwa/api-client"; +import Cookies from "js-cookie"; + +import App from "./App.vue"; + +const cookieContextToken = Cookies.get("sw-context-token"); +const cookieLanguageId = Cookies.get("sw-language-id"); + +const contextToken = ref(cookieContextToken); +const languageId = ref(cookieLanguageId); + +const app = createApp(App); + +const shopwareContext = createShopwareContext(app, { + apiInstance: createInstance({ + endpoint: "https://demo-frontends.shopware.store", + accessToken: "SWSCBHFSNTVMAWNZDNFKSHLAYW", + contextToken: contextToken.value, + languageId: languageId.value, + }), +}); +app.use(shopwareContext); +app.mount("#app"); diff --git a/examples/responsive-images/tsconfig.json b/examples/responsive-images/tsconfig.json new file mode 100644 index 000000000..9c6b947a2 --- /dev/null +++ b/examples/responsive-images/tsconfig.json @@ -0,0 +1,20 @@ +{ + "include": [ + "src/**/*" + ], + "compilerOptions": { + "paths": { + "@/*": [ + "./src/*" // set path `@/*` as alias of `src/*` + ] + }, + "esModuleInterop": true, + "lib": [ + "esnext", + "DOM" + ], + "types": [ + "vite/client" + ] + } +} \ No newline at end of file diff --git a/examples/responsive-images/vite.config.js b/examples/responsive-images/vite.config.js new file mode 100644 index 000000000..de5cb31c6 --- /dev/null +++ b/examples/responsive-images/vite.config.js @@ -0,0 +1,14 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + } +}) diff --git a/packages/cms-base/components/listing-filters/SwFilterProperties.vue b/packages/cms-base/components/listing-filters/SwFilterProperties.vue index 71ead1006..5847bce20 100644 --- a/packages/cms-base/components/listing-filters/SwFilterProperties.vue +++ b/packages/cms-base/components/listing-filters/SwFilterProperties.vue @@ -61,7 +61,7 @@ const toggle = () => { { const mediaUrl = "https://shopware.test/media/8a/fd/cb/1572351035/msh06-gray_main_2.jpg"; + + it("should contain url from first media gallery as a fallback if cover does not exist", () => { + const product: Product = { + media: [ + { + media: { + url: "fallback-url", + }, + }, + ], + apiAlias: "product", + } as any; + const coverUrl = getMainImageUrl(product); + expect(coverUrl).toEqual("fallback-url"); + }); + it("should contain url in nested media object", () => { - const product: any = { + const product: Product = { cover: { media: { url: mediaUrl, }, }, apiAlias: "product", - }; + } as any; const coverUrl = getMainImageUrl(product); expect(coverUrl).toEqual(mediaUrl); }); - it("should contain url in cover object when media url is blank", () => { - const product: any = { - cover: { - url: mediaUrl, - }, + it("should contain empty string when there is no media gallery or cover", () => { + const product: Product = { apiAlias: "product", - }; + } as any; const coverUrl = getMainImageUrl(product); - expect(coverUrl).toEqual(mediaUrl); + expect(coverUrl).toEqual(""); }); it("Should take the url from the media object first", () => { diff --git a/packages/helpers/src/product/getMainImageUrl.ts b/packages/helpers/src/product/getMainImageUrl.ts index 75583c27f..57a9b862e 100644 --- a/packages/helpers/src/product/getMainImageUrl.ts +++ b/packages/helpers/src/product/getMainImageUrl.ts @@ -15,6 +15,6 @@ export function getMainImageUrl( product: Product | LineItem | OrderLineItem ): string { return isProduct(product) - ? product?.cover?.media?.url || product?.cover?.url || "" + ? product?.cover?.media?.url || product?.media?.[0]?.media.url || "" : ""; } diff --git a/packages/helpers/src/product/getProductMainImageUrl.spec.ts b/packages/helpers/src/product/getProductMainImageUrl.spec.ts index 083dee914..f8524fd98 100644 --- a/packages/helpers/src/product/getProductMainImageUrl.spec.ts +++ b/packages/helpers/src/product/getProductMainImageUrl.spec.ts @@ -23,10 +23,17 @@ describe("Helpers - getMainImageUrl", () => { cover: { url: mediaUrl, }, + media: [ + { + media: { + url: "fallback-url", + }, + }, + ], apiAlias: "product", }; const coverUrl = getMainImageUrl(product); - expect(coverUrl).toEqual(mediaUrl); + expect(coverUrl).toEqual("fallback-url"); }); it("Should return an empty string is the entity is not in type Product", () => { diff --git a/packages/helpers/src/product/getProductMediaGallery.ts b/packages/helpers/src/product/getProductMediaGallery.ts index d02571383..52f617fb4 100644 --- a/packages/helpers/src/product/getProductMediaGallery.ts +++ b/packages/helpers/src/product/getProductMediaGallery.ts @@ -14,15 +14,15 @@ export function getProductMediaGallery({ const smallThumb = media.media && media.media.thumbnails && - media.media.thumbnails.find((thumb) => thumb.width == "400"); + media.media.thumbnails.find((thumb) => thumb.width == 400); const normalThumb = media.media && media.media.thumbnails && - media.media.thumbnails.find((thumb) => thumb.width == "800"); + media.media.thumbnails.find((thumb) => thumb.width == 800); const bigThumb = media.media && media.media.thumbnails && - media.media.thumbnails.find((thumb) => thumb.width == "1920"); + media.media.thumbnails.find((thumb) => thumb.width == 1920); return { icon: { url: smallThumb ? smallThumb.url : media.media.url }, mobile: { url: normalThumb ? normalThumb.url : media.media.url }, diff --git a/packages/types/shopware-6-client/models/content/media/Media.d.ts b/packages/types/shopware-6-client/models/content/media/Media.d.ts index 69b4874e7..50d312562 100644 --- a/packages/types/shopware-6-client/models/content/media/Media.d.ts +++ b/packages/types/shopware-6-client/models/content/media/Media.d.ts @@ -21,42 +21,35 @@ import { Customer } from "../../checkout/customer/Customer"; * @public */ export type Media = { - userId: string | null; - mimeType: string | null; - fileExtension: string | null; - fileSize: number | null; - title: string | null; - /** - * @deprecated removed from 6.5.0 - */ - metaDataRaw: string | null; - metaData: [] | null; - mediaType: MediaType; - uploadedAt: Date | null; - alt: string | null; + mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp" | string; + fileExtension: "jpg" | "png" | "gif" | "webp" | string; + fileSize: number; + title: null | string; + metaData: { + hash: string; + type: number; + width: number; + height: number; + }; + uploadedAt: string; + alt: null | string; url: string; fileName: string; - user: Customer; - translations: MediaTranslation[] | null; - translated: MediaTranslation | null; - categories: Category[] | null; - productManufacturers: ProductManufacturer[] | null; - productMedia: ProductMedia | null; - avatarUser: Customer | null; - thumbnails: MediaThumbnail[] | null; - mediaFolderId: string | null; - mediaFolder: MediaFolder | null; + translations: null | MediaTranslation[]; + thumbnails: MediaThumbnail[]; hasFile: boolean; private: boolean; - propertyGroupOptions: PropertyGroupOption[] | null; - mailTemplateMedia: MailTemplateMedia[] | null; - customFields: CustomField[]; - tags: Tag | null; - thumbnailsRo: string | null; - documentBaseConfigs: DocumentBaseConfig[] | null; - shippingMethods: ShippingMethod[] | null; - paymentMethods: PaymentMethod[] | null; - orderLineItems: OrderLineItem[] | null; - cmsBlocks: CmsBlock[] | null; - documents: Document[] | null; + _uniqueIdentifier: string; + versionId: null | string; + translated: { + alt: null | string; + title: null | string; + customFields: unknown; + }; + createdAt: string; + updatedAt: null | string; + extensions: unknown; + id: string; + customFields: null | unknown; + apiAlias: "media"; }; diff --git a/packages/types/shopware-6-client/models/content/media/MediaThumbnail.d.ts b/packages/types/shopware-6-client/models/content/media/MediaThumbnail.d.ts index 4d659f055..94546a03b 100644 --- a/packages/types/shopware-6-client/models/content/media/MediaThumbnail.d.ts +++ b/packages/types/shopware-6-client/models/content/media/MediaThumbnail.d.ts @@ -1,5 +1,15 @@ export type MediaThumbnail = { - width: string; - height: string; - [x: string]: any; + width: number; + height: number; + url: string; + mediaId: string; + _uniqueIdentifier: string; + versionId: null | string; + translated: unknown; + createdAt: string; + updatedAt: null | string; + extensions: unknown; + id: string; + customFields: null | unknown; + apiAlias: "media_thumbnail"; }; diff --git a/packages/types/shopware-6-client/models/content/product/ProductMedia.d.ts b/packages/types/shopware-6-client/models/content/product/ProductMedia.d.ts index 75b668f38..6ecc29993 100644 --- a/packages/types/shopware-6-client/models/content/product/ProductMedia.d.ts +++ b/packages/types/shopware-6-client/models/content/product/ProductMedia.d.ts @@ -10,7 +10,13 @@ export type ProductMedia = { mediaId: string; position: number; media: Media; - product: Product; - customFields: CustomField[]; - url: string; + customFields: null | CustomField[]; + _uniqueIdentifier: string; + versionId: string; + translated: []; + createdAt: string; + updatedAt: string; + extensions: unknown; + id: string; + apiAlias: "product_media"; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af82ce37d..86a7b510b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -124,6 +124,25 @@ importers: '@vitejs/plugin-vue': 4.0.0_vite@4.1.1+vue@3.2.47 vite: 4.1.1 + examples/responsive-images: + specifiers: + '@shopware-pwa/api-client': canary + '@shopware-pwa/composables-next': canary + '@shopware-pwa/types': ^0.4.0 + '@vitejs/plugin-vue': ^4.0.0 + js-cookie: ^3.0.1 + vite: ^4.1.1 + vue: ^3.2.47 + dependencies: + '@shopware-pwa/api-client': link:../../packages/api-client + '@shopware-pwa/composables-next': link:../../packages/composables + js-cookie: 3.0.1 + vue: 3.2.47 + devDependencies: + '@shopware-pwa/types': link:../../packages/types + '@vitejs/plugin-vue': 4.0.0_vite@4.1.1+vue@3.2.47 + vite: 4.1.1 + examples/use-add-to-cart: specifiers: '@shopware-pwa/api-client': canary @@ -767,13 +786,6 @@ packages: dependencies: '@babel/types': 7.20.7 - /@babel/parser/7.20.3: - resolution: {integrity: sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.20.7 - /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.20.12: resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} engines: {node: '>=6.9.0'} @@ -4030,10 +4042,10 @@ packages: dependencies: '@volar/language-core': 1.0.24 '@volar/source-map': 1.0.24 - '@vue/compiler-dom': 3.2.45 - '@vue/compiler-sfc': 3.2.45 + '@vue/compiler-dom': 3.2.47 + '@vue/compiler-sfc': 3.2.47 '@vue/reactivity': 3.2.45 - '@vue/shared': 3.2.45 + '@vue/shared': 3.2.47 minimatch: 5.1.2 vue-template-compiler: 2.7.14 @@ -4062,14 +4074,6 @@ packages: - '@babel/core' - supports-color - /@vue/compiler-core/3.2.45: - resolution: {integrity: sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==} - dependencies: - '@babel/parser': 7.20.3 - '@vue/shared': 3.2.45 - estree-walker: 2.0.2 - source-map: 0.6.1 - /@vue/compiler-core/3.2.47: resolution: {integrity: sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==} dependencies: @@ -4078,32 +4082,12 @@ packages: estree-walker: 2.0.2 source-map: 0.6.1 - /@vue/compiler-dom/3.2.45: - resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==} - dependencies: - '@vue/compiler-core': 3.2.45 - '@vue/shared': 3.2.45 - /@vue/compiler-dom/3.2.47: resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==} dependencies: '@vue/compiler-core': 3.2.47 '@vue/shared': 3.2.47 - /@vue/compiler-sfc/3.2.45: - resolution: {integrity: sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==} - dependencies: - '@babel/parser': 7.20.3 - '@vue/compiler-core': 3.2.45 - '@vue/compiler-dom': 3.2.45 - '@vue/compiler-ssr': 3.2.45 - '@vue/reactivity-transform': 3.2.45 - '@vue/shared': 3.2.45 - estree-walker: 2.0.2 - magic-string: 0.25.9 - postcss: 8.4.21 - source-map: 0.6.1 - /@vue/compiler-sfc/3.2.47: resolution: {integrity: sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==} dependencies: @@ -4118,12 +4102,6 @@ packages: postcss: 8.4.21 source-map: 0.6.1 - /@vue/compiler-ssr/3.2.45: - resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==} - dependencies: - '@vue/compiler-dom': 3.2.45 - '@vue/shared': 3.2.45 - /@vue/compiler-ssr/3.2.47: resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==} dependencies: @@ -4158,15 +4136,6 @@ packages: - supports-color dev: true - /@vue/reactivity-transform/3.2.45: - resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==} - dependencies: - '@babel/parser': 7.20.13 - '@vue/compiler-core': 3.2.45 - '@vue/shared': 3.2.45 - estree-walker: 2.0.2 - magic-string: 0.25.9 - /@vue/reactivity-transform/3.2.47: resolution: {integrity: sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==} dependencies: