@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 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.109",
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.61",
55
- "@stoker-platform/utils": "0.5.52",
56
- "@stoker-platform/web-client": "0.5.62",
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 ? (