@shopify/shop-minis-react 0.0.32 → 0.0.34
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/dist/_virtual/index2.js +4 -4
- package/dist/_virtual/index3.js +4 -4
- package/dist/_virtual/index4.js +2 -2
- package/dist/_virtual/index5.js +3 -2
- package/dist/_virtual/index5.js.map +1 -1
- package/dist/_virtual/index6.js +2 -2
- package/dist/_virtual/index7.js +2 -3
- package/dist/_virtual/index7.js.map +1 -1
- package/dist/_virtual/index8.js +2 -2
- package/dist/_virtual/index9.js +2 -2
- package/dist/components/atoms/image.js +52 -0
- package/dist/components/atoms/image.js.map +1 -0
- package/dist/components/commerce/merchant-card.js +188 -245
- package/dist/components/commerce/merchant-card.js.map +1 -1
- package/dist/components/commerce/product-card.js +11 -11
- package/dist/components/commerce/product-card.js.map +1 -1
- package/dist/components/content/image-content-wrapper.js +29 -22
- package/dist/components/content/image-content-wrapper.js.map +1 -1
- package/dist/hooks/content/useCreateImageContent.js +16 -22
- package/dist/hooks/content/useCreateImageContent.js.map +1 -1
- package/dist/hooks/storage/useImageUpload.js +36 -37
- package/dist/hooks/storage/useImageUpload.js.map +1 -1
- package/dist/index.js +252 -246
- package/dist/shop-minis-platform/src/types/content.js.map +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.1.6_react@19.1.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/@videojs_xhr@2.7.0/node_modules/@videojs/xhr/lib/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/@xmldom_xmldom@0.8.10/node_modules/@xmldom/xmldom/lib/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/color-string@1.9.1/node_modules/color-string/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/mpd-parser@1.3.1/node_modules/mpd-parser/dist/mpd-parser.es.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/querystringify@2.2.0/node_modules/querystringify/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/video.js@8.23.3/node_modules/video.js/dist/video.es.js +1 -1
- package/dist/utils/colors.js +1 -1
- package/dist/utils/image.js +45 -9
- package/dist/utils/image.js.map +1 -1
- package/package.json +2 -2
- package/src/components/atoms/{thumbhash-image.tsx → image.tsx} +14 -14
- package/src/components/commerce/merchant-card.tsx +224 -225
- package/src/components/commerce/product-card.tsx +2 -2
- package/src/components/content/image-content-wrapper.tsx +9 -2
- package/src/components/index.ts +1 -1
- package/src/hooks/content/useCreateImageContent.ts +1 -7
- package/src/hooks/storage/useImageUpload.ts +22 -20
- package/src/stories/MerchantCard.stories.tsx +0 -3
- package/src/utils/image.ts +72 -0
- package/src/utils/index.ts +1 -1
- package/dist/components/atoms/thumbhash-image.js +0 -54
- package/dist/components/atoms/thumbhash-image.js.map +0 -1
- package/dist/utils/imageToDataUri.js +0 -10
- package/dist/utils/imageToDataUri.js.map +0 -1
- package/src/utils/imageToDataUri.ts +0 -8
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import {useCallback} from 'react'
|
|
2
2
|
|
|
3
3
|
import {useShopActions} from '../../internal/useShopActions'
|
|
4
|
+
import {fileToDataUri} from '../../utils'
|
|
4
5
|
|
|
5
6
|
import type {UploadTarget} from '@shopify/shop-minis-platform/actions'
|
|
6
7
|
|
|
7
8
|
export interface UploadImageParams {
|
|
9
|
+
/**
|
|
10
|
+
* The file to upload.
|
|
11
|
+
*/
|
|
12
|
+
image: File
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ProcessedImage {
|
|
8
16
|
/**
|
|
9
17
|
* The MIME type of the image.
|
|
10
18
|
*/
|
|
@@ -12,11 +20,11 @@ export interface UploadImageParams {
|
|
|
12
20
|
/**
|
|
13
21
|
* The size of the image in bytes.
|
|
14
22
|
*/
|
|
15
|
-
fileSize
|
|
23
|
+
fileSize: number
|
|
16
24
|
/**
|
|
17
|
-
* The
|
|
25
|
+
* The file blob of the image.
|
|
18
26
|
*/
|
|
19
|
-
|
|
27
|
+
fileBlob: Blob
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
export interface UploadedImage {
|
|
@@ -36,28 +44,27 @@ export interface UploadedImage {
|
|
|
36
44
|
|
|
37
45
|
interface UseImageUploadReturns {
|
|
38
46
|
/**
|
|
39
|
-
* Upload an image attached to the current user.
|
|
47
|
+
* Upload an image which will be attached to the current user.
|
|
40
48
|
*/
|
|
41
|
-
uploadImage: (
|
|
49
|
+
uploadImage: (image: File) => Promise<UploadedImage[]>
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
// Fetch file data and detect file sizes if not provided
|
|
45
53
|
// Works with file://, data:, and http(s):// URIs
|
|
46
|
-
const processFileData = async (image:
|
|
47
|
-
const
|
|
54
|
+
const processFileData = async (image: File): Promise<ProcessedImage> => {
|
|
55
|
+
const uri = await fileToDataUri(image)
|
|
56
|
+
|
|
57
|
+
const response = await fetch(uri)
|
|
48
58
|
const blob = await response.blob()
|
|
49
59
|
|
|
50
60
|
return {
|
|
51
|
-
|
|
52
|
-
fileSize: image.
|
|
61
|
+
mimeType: image.type,
|
|
62
|
+
fileSize: image.size ?? blob.size,
|
|
53
63
|
fileBlob: blob,
|
|
54
64
|
}
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
const uploadFileToGCS = async (
|
|
58
|
-
image: UploadImageParams & {fileSize: number; fileBlob: Blob},
|
|
59
|
-
target: UploadTarget
|
|
60
|
-
) => {
|
|
67
|
+
const uploadFileToGCS = async (image: ProcessedImage, target: UploadTarget) => {
|
|
61
68
|
const formData = new FormData()
|
|
62
69
|
target.parameters.forEach(({name, value}: {name: string; value: string}) => {
|
|
63
70
|
formData.append(name, value)
|
|
@@ -84,13 +91,8 @@ export const useImageUpload = (): UseImageUploadReturns => {
|
|
|
84
91
|
const {createImageUploadLink, completeImageUpload} = useShopActions()
|
|
85
92
|
|
|
86
93
|
const uploadImage = useCallback(
|
|
87
|
-
async (
|
|
88
|
-
|
|
89
|
-
throw new Error('Multiple image upload is not supported yet')
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const imageParams = params[0]
|
|
93
|
-
const processedImageParams = await processFileData(imageParams)
|
|
94
|
+
async (image: File) => {
|
|
95
|
+
const processedImageParams = await processFileData(image)
|
|
94
96
|
|
|
95
97
|
const links = await createImageUploadLink({
|
|
96
98
|
input: [
|
package/src/utils/image.ts
CHANGED
|
@@ -16,3 +16,75 @@ export function getThumbhashDataURL(thumbhash?: string): string | undefined {
|
|
|
16
16
|
console.warn('Failed to decode thumbhash to data URL', error)
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
/** Converts a file to a data URI
|
|
21
|
+
* @param file The file to convert
|
|
22
|
+
* @returns A promise that resolves to the data URI string
|
|
23
|
+
*/
|
|
24
|
+
export function fileToDataUri(file: File): Promise<string> {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const reader = new FileReader()
|
|
27
|
+
reader.onloadend = () => resolve(reader.result as string)
|
|
28
|
+
reader.onerror = reject
|
|
29
|
+
reader.readAsDataURL(file)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const ImageSizes = {
|
|
34
|
+
xxsUrl: 32,
|
|
35
|
+
xsUrl: 64,
|
|
36
|
+
sUrl: 128,
|
|
37
|
+
xxsmUrl: 256,
|
|
38
|
+
xsmUrl: 384,
|
|
39
|
+
smUrl: 512,
|
|
40
|
+
mUrl: 640,
|
|
41
|
+
lUrl: 1080,
|
|
42
|
+
xlUrl: 2048,
|
|
43
|
+
} as const
|
|
44
|
+
|
|
45
|
+
type Key = keyof typeof ImageSizes
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Acceptable offset for image sizes. An image could use the size that is within this offset.
|
|
49
|
+
*/
|
|
50
|
+
const offsetPercentage = 0.05
|
|
51
|
+
|
|
52
|
+
const sortedImageSizes = Object.entries(ImageSizes).sort(
|
|
53
|
+
([, firstSize], [, secondSize]) => firstSize - secondSize
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const getImageSizeKeyWithSize = (size: number): Key => {
|
|
57
|
+
for (const [key, imgSize] of sortedImageSizes) {
|
|
58
|
+
const upperBoundSize = imgSize + imgSize * offsetPercentage
|
|
59
|
+
if (size <= upperBoundSize) return key as Key
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return 'xlUrl'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resizeImage = (imageUrl: string, width: number) => {
|
|
66
|
+
const pattern = new RegExp(/\?+/g)
|
|
67
|
+
const delimiter = pattern.test(imageUrl) ? '&' : '?'
|
|
68
|
+
return `${imageUrl}${delimiter}width=${width}`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Optimizes Shopify CDN image URLs by adding a width parameter based on screen size
|
|
73
|
+
* @param url The image URL to optimize
|
|
74
|
+
* @returns The optimized URL with width parameter if it's a Shopify CDN image, otherwise returns the original URL
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
export const getResizedImageUrl = (url?: string): string => {
|
|
78
|
+
if (!url) return ''
|
|
79
|
+
|
|
80
|
+
// Only process Shopify CDN images
|
|
81
|
+
if (!url.startsWith('https://cdn.shopify.com')) {
|
|
82
|
+
return url
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const width = window.innerWidth ?? screen.width
|
|
86
|
+
|
|
87
|
+
const key = getImageSizeKeyWithSize(width)
|
|
88
|
+
|
|
89
|
+
return resizeImage(url, ImageSizes[key])
|
|
90
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { jsx as t } from "react/jsx-runtime";
|
|
2
|
-
import { memo as p, useState as g, useMemo as L, useCallback as v } from "react";
|
|
3
|
-
import { cn as s } from "../../lib/utils.js";
|
|
4
|
-
import { getThumbhashDataURL as k } from "../../utils/image.js";
|
|
5
|
-
const j = p(function(r) {
|
|
6
|
-
const {
|
|
7
|
-
src: c,
|
|
8
|
-
alt: m,
|
|
9
|
-
thumbhash: o,
|
|
10
|
-
onLoad: a,
|
|
11
|
-
className: l,
|
|
12
|
-
style: i,
|
|
13
|
-
aspectRatio: n = "auto",
|
|
14
|
-
...u
|
|
15
|
-
} = r, [d, h] = g(!1), e = L(
|
|
16
|
-
() => k(o ?? void 0),
|
|
17
|
-
[o]
|
|
18
|
-
), b = v(
|
|
19
|
-
(f) => {
|
|
20
|
-
h(!0), a?.(f);
|
|
21
|
-
},
|
|
22
|
-
[a]
|
|
23
|
-
);
|
|
24
|
-
return /* @__PURE__ */ t(
|
|
25
|
-
"div",
|
|
26
|
-
{
|
|
27
|
-
className: s("relative w-full ", l),
|
|
28
|
-
style: {
|
|
29
|
-
...i,
|
|
30
|
-
aspectRatio: n,
|
|
31
|
-
backgroundImage: e ? `url(${e})` : void 0,
|
|
32
|
-
backgroundSize: "cover",
|
|
33
|
-
backgroundPosition: "center"
|
|
34
|
-
},
|
|
35
|
-
children: /* @__PURE__ */ t(
|
|
36
|
-
"img",
|
|
37
|
-
{
|
|
38
|
-
className: s(
|
|
39
|
-
"absolute inset-0 w-full h-full opacity-0 object-cover",
|
|
40
|
-
d && "opacity-100"
|
|
41
|
-
),
|
|
42
|
-
src: c,
|
|
43
|
-
alt: m,
|
|
44
|
-
onLoad: b,
|
|
45
|
-
...u
|
|
46
|
-
}
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
);
|
|
50
|
-
});
|
|
51
|
-
export {
|
|
52
|
-
j as ThumbhashImage
|
|
53
|
-
};
|
|
54
|
-
//# sourceMappingURL=thumbhash-image.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"thumbhash-image.js","sources":["../../../src/components/atoms/thumbhash-image.tsx"],"sourcesContent":["/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */\nimport {ImgHTMLAttributes, useCallback, useMemo, memo, useState} from 'react'\n\nimport {cn} from '../../lib/utils'\nimport {getThumbhashDataURL} from '../../utils/image'\n\ntype ThumbhashImageProps = ImgHTMLAttributes<HTMLImageElement> & {\n src: string\n thumbhash: string\n alt?: string | null\n aspectRatio?: number | string\n}\n\nexport const ThumbhashImage = memo(function ThumbhashImage(\n props: ThumbhashImageProps\n) {\n const {\n src,\n alt,\n thumbhash,\n onLoad,\n className,\n style,\n aspectRatio = 'auto',\n ...restProps\n } = props\n\n const [isLoaded, setIsLoaded] = useState(false)\n\n const dataURL = useMemo(\n () => getThumbhashDataURL(thumbhash ?? undefined),\n [thumbhash]\n )\n\n const handleLoad = useCallback(\n (event: React.SyntheticEvent<HTMLImageElement, Event>) => {\n setIsLoaded(true)\n onLoad?.(event)\n },\n [onLoad]\n )\n\n return (\n <div\n className={cn('relative w-full ', className)}\n style={{\n ...style,\n aspectRatio,\n backgroundImage: dataURL ? `url(${dataURL})` : undefined,\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n }}\n >\n <img\n className={cn(\n 'absolute inset-0 w-full h-full opacity-0 object-cover',\n isLoaded && 'opacity-100'\n )}\n src={src}\n alt={alt}\n onLoad={handleLoad}\n {...restProps}\n />\n </div>\n )\n})\n"],"names":["ThumbhashImage","memo","props","src","alt","thumbhash","onLoad","className","style","aspectRatio","restProps","isLoaded","setIsLoaded","useState","dataURL","useMemo","getThumbhashDataURL","handleLoad","useCallback","event","jsx","cn"],"mappings":";;;;AAaO,MAAMA,IAAiBC,EAAK,SACjCC,GACA;AACM,QAAA;AAAA,IACJ,KAAAC;AAAA,IACA,KAAAC;AAAA,IACA,WAAAC;AAAA,IACA,QAAAC;AAAA,IACA,WAAAC;AAAA,IACA,OAAAC;AAAA,IACA,aAAAC,IAAc;AAAA,IACd,GAAGC;AAAA,EAAA,IACDR,GAEE,CAACS,GAAUC,CAAW,IAAIC,EAAS,EAAK,GAExCC,IAAUC;AAAA,IACd,MAAMC,EAAoBX,KAAa,MAAS;AAAA,IAChD,CAACA,CAAS;AAAA,EACZ,GAEMY,IAAaC;AAAA,IACjB,CAACC,MAAyD;AACxD,MAAAP,EAAY,EAAI,GAChBN,IAASa,CAAK;AAAA,IAChB;AAAA,IACA,CAACb,CAAM;AAAA,EACT;AAGE,SAAA,gBAAAc;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,EAAG,oBAAoBd,CAAS;AAAA,MAC3C,OAAO;AAAA,QACL,GAAGC;AAAA,QACH,aAAAC;AAAA,QACA,iBAAiBK,IAAU,OAAOA,CAAO,MAAM;AAAA,QAC/C,gBAAgB;AAAA,QAChB,oBAAoB;AAAA,MACtB;AAAA,MAEA,UAAA,gBAAAM;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAWC;AAAA,YACT;AAAA,YACAV,KAAY;AAAA,UACd;AAAA,UACA,KAAAR;AAAA,UACA,KAAAC;AAAA,UACA,QAAQa;AAAA,UACP,GAAGP;AAAA,QAAA;AAAA,MAAA;AAAA,IACN;AAAA,EACF;AAEJ,CAAC;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"imageToDataUri.js","sources":["../../src/utils/imageToDataUri.ts"],"sourcesContent":["export function fileToDataUri(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onloadend = () => resolve(reader.result as string)\n reader.onerror = reject\n reader.readAsDataURL(file)\n })\n}\n"],"names":["fileToDataUri","file","resolve","reject","reader"],"mappings":"AAAO,SAASA,EAAcC,GAA6B;AACzD,SAAO,IAAI,QAAQ,CAACC,GAASC,MAAW;AAChC,UAAAC,IAAS,IAAI,WAAW;AAC9B,IAAAA,EAAO,YAAY,MAAMF,EAAQE,EAAO,MAAgB,GACxDA,EAAO,UAAUD,GACjBC,EAAO,cAAcH,CAAI;AAAA,EAAA,CAC1B;AACH;"}
|