@stoker-platform/web-app 0.5.109 → 0.5.110
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 +9 -0
- package/package.json +4 -4
- package/src/Files.tsx +91 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @stoker-platform/web-app
|
|
2
2
|
|
|
3
|
+
## 0.5.110
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- feat: add image thumbnails option
|
|
8
|
+
- @stoker-platform/node-client@0.5.62
|
|
9
|
+
- @stoker-platform/utils@0.5.53
|
|
10
|
+
- @stoker-platform/web-client@0.5.63
|
|
11
|
+
|
|
3
12
|
## 0.5.109
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stoker-platform/web-app",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.110",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"scripts": {
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
52
52
|
"@react-google-maps/api": "^2.20.8",
|
|
53
53
|
"@sentry/react": "^10.50.0",
|
|
54
|
-
"@stoker-platform/node-client": "0.5.
|
|
55
|
-
"@stoker-platform/utils": "0.5.
|
|
56
|
-
"@stoker-platform/web-client": "0.5.
|
|
54
|
+
"@stoker-platform/node-client": "0.5.62",
|
|
55
|
+
"@stoker-platform/utils": "0.5.53",
|
|
56
|
+
"@stoker-platform/web-client": "0.5.63",
|
|
57
57
|
"@tanstack/react-table": "^8.21.3",
|
|
58
58
|
"@types/react": "18.3.13",
|
|
59
59
|
"@types/react-dom": "18.3.1",
|
package/src/Files.tsx
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
getMetadata,
|
|
28
28
|
updateMetadata,
|
|
29
29
|
} from "firebase/storage"
|
|
30
|
+
import type { FirebaseStorage } from "firebase/storage"
|
|
30
31
|
import { getAuth } from "firebase/auth"
|
|
31
32
|
import { Progress } from "./components/ui/progress"
|
|
32
33
|
import { Button } from "./components/ui/button"
|
|
@@ -71,6 +72,83 @@ import {
|
|
|
71
72
|
import { FilePermissionsDialog, FilePermissions } from "./FilePermissions"
|
|
72
73
|
import { prepareFile } from "./utils/prepareFile"
|
|
73
74
|
|
|
75
|
+
const IMAGE_FILE_EXTENSIONS = new Set([
|
|
76
|
+
"avif",
|
|
77
|
+
"bmp",
|
|
78
|
+
"gif",
|
|
79
|
+
"heic",
|
|
80
|
+
"heif",
|
|
81
|
+
"ico",
|
|
82
|
+
"jpeg",
|
|
83
|
+
"jpg",
|
|
84
|
+
"png",
|
|
85
|
+
"svg",
|
|
86
|
+
"webp",
|
|
87
|
+
])
|
|
88
|
+
|
|
89
|
+
const isImageFile = (name: string): boolean => {
|
|
90
|
+
const dot = name.lastIndexOf(".")
|
|
91
|
+
if (dot <= 0) return false
|
|
92
|
+
return IMAGE_FILE_EXTENSIONS.has(name.slice(dot + 1).toLowerCase())
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface FileImageThumbnailProps {
|
|
96
|
+
storage: FirebaseStorage
|
|
97
|
+
fullPath: string
|
|
98
|
+
fileName: string
|
|
99
|
+
pathPrefix: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const FileImageThumbnail = ({ storage, fullPath, fileName, pathPrefix }: FileImageThumbnailProps) => {
|
|
103
|
+
const [phase, setPhase] = useState<"loading" | "display" | "fallback">("loading")
|
|
104
|
+
const [url, setUrl] = useState<string | null>(null)
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (!pathPrefix || !fullPath.startsWith(pathPrefix)) {
|
|
108
|
+
setPhase("fallback")
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
let cancelled = false
|
|
112
|
+
setPhase("loading")
|
|
113
|
+
setUrl(null)
|
|
114
|
+
const load = async () => {
|
|
115
|
+
try {
|
|
116
|
+
const fileRef = ref(storage, fullPath)
|
|
117
|
+
const downloadUrl = await getDownloadURL(fileRef)
|
|
118
|
+
if (!cancelled) {
|
|
119
|
+
setUrl(downloadUrl)
|
|
120
|
+
setPhase("display")
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
if (!cancelled) {
|
|
124
|
+
setPhase("fallback")
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
void load()
|
|
129
|
+
return () => {
|
|
130
|
+
cancelled = true
|
|
131
|
+
}
|
|
132
|
+
}, [storage, fullPath, pathPrefix])
|
|
133
|
+
|
|
134
|
+
if (phase === "loading") {
|
|
135
|
+
return <div className="h-12 w-12 shrink-0 rounded-md border bg-muted animate-pulse" aria-hidden />
|
|
136
|
+
}
|
|
137
|
+
if (phase === "fallback" || !url) {
|
|
138
|
+
return <File className="h-5 w-5 shrink-0 text-gray-500" aria-hidden />
|
|
139
|
+
}
|
|
140
|
+
return (
|
|
141
|
+
<img
|
|
142
|
+
src={url}
|
|
143
|
+
alt={`Thumbnail for ${fileName}`}
|
|
144
|
+
className="max-h-12 w-12 shrink-0 rounded-md object-contain"
|
|
145
|
+
loading="lazy"
|
|
146
|
+
decoding="async"
|
|
147
|
+
onError={() => setPhase("fallback")}
|
|
148
|
+
/>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
74
152
|
interface FilesProps {
|
|
75
153
|
collection: CollectionSchema
|
|
76
154
|
record: StokerRecord | undefined
|
|
@@ -1038,9 +1116,20 @@ export const RecordFiles = ({ collection, record }: FilesProps) => {
|
|
|
1038
1116
|
>
|
|
1039
1117
|
<div className="flex items-center space-x-3 flex-1 min-w-0">
|
|
1040
1118
|
{item.isFolder ? (
|
|
1041
|
-
<Folder className="h-5 w-5 text-blue-500" />
|
|
1119
|
+
<Folder className="h-5 w-5 shrink-0 text-blue-500" />
|
|
1120
|
+
) : fileOptions?.thumbnails === true && isImageFile(item.name) ? (
|
|
1121
|
+
<FileImageThumbnail
|
|
1122
|
+
storage={storage}
|
|
1123
|
+
fullPath={item.fullPath}
|
|
1124
|
+
fileName={item.name}
|
|
1125
|
+
pathPrefix={
|
|
1126
|
+
record
|
|
1127
|
+
? `${tenantId}/${labels.collection}/${record.id}`
|
|
1128
|
+
: ""
|
|
1129
|
+
}
|
|
1130
|
+
/>
|
|
1042
1131
|
) : (
|
|
1043
|
-
<File className="h-5 w-5 text-gray-500" />
|
|
1132
|
+
<File className="h-5 w-5 shrink-0 text-gray-500" />
|
|
1044
1133
|
)}
|
|
1045
1134
|
|
|
1046
1135
|
{editingFile === item.name && !isDisabled ? (
|