@windrun-huaiin/third-ui 5.11.5 → 5.12.1
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/main/index.d.mts +7 -5
- package/dist/main/index.d.ts +7 -5
- package/dist/main/index.js +275 -254
- package/dist/main/index.js.map +1 -1
- package/dist/main/index.mjs +275 -254
- package/dist/main/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/main/gallery.tsx +79 -20
package/package.json
CHANGED
package/src/main/gallery.tsx
CHANGED
|
@@ -1,37 +1,89 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { globalLucideIcons as icons } from "@base-ui/components/global-icon"
|
|
4
|
+
import { cn } from '@lib/utils'
|
|
4
5
|
import { useTranslations } from 'next-intl'
|
|
5
6
|
import Image from "next/image"
|
|
6
|
-
import {
|
|
7
|
-
import { cn } from '@lib/utils';
|
|
8
|
-
import { useState } from 'react';
|
|
7
|
+
import { useState } from 'react'
|
|
9
8
|
|
|
10
9
|
interface GalleryItem {
|
|
11
10
|
url: string;
|
|
12
11
|
altMsg: string;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
interface GalleryProps {
|
|
15
|
+
sectionClassName?: string;
|
|
16
|
+
button?: React.ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function Gallery({ sectionClassName, button }: GalleryProps) {
|
|
16
20
|
const t = useTranslations('gallery');
|
|
17
21
|
const galleryItems = t.raw('prompts') as GalleryItem[];
|
|
18
22
|
const defaultImgUrl = t.raw('defaultImgUrl') as string;
|
|
19
23
|
const [imageErrors, setImageErrors] = useState<Set<number>>(new Set());
|
|
24
|
+
const [downloadingItems, setDownloadingItems] = useState<Set<number>>(new Set());
|
|
25
|
+
const cdnProxyUrl = process.env.NEXT_PUBLIC_STYLE_CDN_PROXY_URL!;
|
|
20
26
|
|
|
21
27
|
const handleDownload = async (item: GalleryItem, index: number) => {
|
|
28
|
+
// prevent duplicate clicks
|
|
29
|
+
if (downloadingItems.has(index)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// set download status
|
|
34
|
+
setDownloadingItems(prev => new Set(prev).add(index));
|
|
35
|
+
|
|
22
36
|
try {
|
|
23
|
-
|
|
37
|
+
// use R2 proxy to download directly, no need to fetch
|
|
38
|
+
// convert original R2 URL to proxy URL
|
|
39
|
+
const originalUrl = new URL(item.url);
|
|
40
|
+
const filename = originalUrl.pathname.substring(1);
|
|
41
|
+
|
|
42
|
+
// build proxy download URL
|
|
43
|
+
const proxyUrl = `${cdnProxyUrl}/${encodeURIComponent(filename)}`;
|
|
44
|
+
|
|
45
|
+
// extract file extension from URL
|
|
46
|
+
const urlExtension = item.url.split('.').pop()?.toLowerCase();
|
|
47
|
+
let extension = '.webp';
|
|
48
|
+
if (urlExtension && ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(urlExtension)) {
|
|
49
|
+
extension = `.${urlExtension}`;
|
|
50
|
+
}
|
|
51
|
+
const downloadPrefix = t('downloadPrefix');
|
|
52
|
+
|
|
53
|
+
// fetch file from proxy
|
|
54
|
+
const response = await fetch(proxyUrl);
|
|
55
|
+
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// convert to blob
|
|
24
61
|
const blob = await response.blob();
|
|
25
|
-
const
|
|
62
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
63
|
+
|
|
64
|
+
// create download link and trigger download
|
|
26
65
|
const a = document.createElement('a');
|
|
27
|
-
a.href =
|
|
28
|
-
a.download =
|
|
66
|
+
a.href = blobUrl;
|
|
67
|
+
a.download = `${downloadPrefix}-${index + 1}${extension}`;
|
|
68
|
+
a.style.display = 'none';
|
|
29
69
|
document.body.appendChild(a);
|
|
30
70
|
a.click();
|
|
31
|
-
|
|
32
|
-
|
|
71
|
+
|
|
72
|
+
// clean up DOM elements and blob URL
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
document.body.removeChild(a);
|
|
75
|
+
URL.revokeObjectURL(blobUrl);
|
|
76
|
+
}, 100);
|
|
77
|
+
|
|
33
78
|
} catch (error) {
|
|
34
79
|
console.error('Download failed:', error);
|
|
80
|
+
} finally {
|
|
81
|
+
// clear download status
|
|
82
|
+
setDownloadingItems(prev => {
|
|
83
|
+
const newSet = new Set(prev);
|
|
84
|
+
newSet.delete(index);
|
|
85
|
+
return newSet;
|
|
86
|
+
});
|
|
35
87
|
}
|
|
36
88
|
};
|
|
37
89
|
|
|
@@ -65,22 +117,29 @@ export function Gallery({ sectionClassName }: { sectionClassName?: string }) {
|
|
|
65
117
|
<div className="absolute inset-0 flex items-end justify-end p-4 opacity-0 group-hover:opacity-100 transition duration-300">
|
|
66
118
|
<button
|
|
67
119
|
onClick={() => handleDownload(item, index)}
|
|
68
|
-
|
|
120
|
+
disabled={downloadingItems.has(index)}
|
|
121
|
+
className={cn(
|
|
122
|
+
"p-2 rounded-full transition-all duration-300",
|
|
123
|
+
downloadingItems.has(index)
|
|
124
|
+
? "bg-black/30 text-white/50"
|
|
125
|
+
: "bg-black/50 hover:bg-black/70 text-white/80 hover:text-white"
|
|
126
|
+
)}
|
|
69
127
|
>
|
|
70
|
-
|
|
128
|
+
{downloadingItems.has(index) ? (
|
|
129
|
+
<icons.Loader2 className="h-5 w-5 text-white animate-spin" />
|
|
130
|
+
) : (
|
|
131
|
+
<icons.Download className="h-5 w-5 text-white" />
|
|
132
|
+
)}
|
|
71
133
|
</button>
|
|
72
134
|
</div>
|
|
73
135
|
</div>
|
|
74
136
|
))}
|
|
75
137
|
</div>
|
|
76
|
-
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
/>
|
|
82
|
-
</div>
|
|
138
|
+
{button && (
|
|
139
|
+
<div className="text-center mt-12">
|
|
140
|
+
{button}
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
83
143
|
</section>
|
|
84
144
|
)
|
|
85
145
|
}
|
|
86
|
-
|