@stoker-platform/web-app 0.5.109 → 0.5.111
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 +15 -0
- package/package.json +4 -4
- package/src/Breadcrumbs.tsx +1 -1
- package/src/Files.tsx +91 -2
- package/src/Form.tsx +37 -39
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @stoker-platform/web-app
|
|
2
2
|
|
|
3
|
+
## 0.5.111
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fix: hide convert button when target collection create access not granted
|
|
8
|
+
|
|
9
|
+
## 0.5.110
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- feat: add image thumbnails option
|
|
14
|
+
- @stoker-platform/node-client@0.5.62
|
|
15
|
+
- @stoker-platform/utils@0.5.53
|
|
16
|
+
- @stoker-platform/web-client@0.5.63
|
|
17
|
+
|
|
3
18
|
## 0.5.109
|
|
4
19
|
|
|
5
20
|
### 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.111",
|
|
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/Breadcrumbs.tsx
CHANGED
|
@@ -59,7 +59,7 @@ export const Breadcrumbs = ({
|
|
|
59
59
|
const titleField = field.titleField
|
|
60
60
|
if (titleField && field.includeFields?.includes(titleField)) {
|
|
61
61
|
const relationCollection = schema.collections[field.collection]
|
|
62
|
-
if (!record[breadcrumb]) continue
|
|
62
|
+
if (!(record[breadcrumb] && Object.values(record[breadcrumb])[0])) continue
|
|
63
63
|
recordMap[breadcrumb] = {
|
|
64
64
|
title: (Object.values(record[breadcrumb])[0] as StokerRelation)[titleField],
|
|
65
65
|
collection: relationCollection,
|
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 ? (
|
package/src/Form.tsx
CHANGED
|
@@ -2477,7 +2477,6 @@ function RecordForm({
|
|
|
2477
2477
|
const [showDuplicateModal, setShowDuplicateModal] = useState(false)
|
|
2478
2478
|
const [duplicateRecordData, setDuplicateRecordData] = useState<Partial<StokerRecord> | undefined>(undefined)
|
|
2479
2479
|
const [convert, setConvert] = useState<Convert[] | undefined>(undefined)
|
|
2480
|
-
const [convertAllowed, setConvertAllowed] = useState<Record<string, boolean>>({})
|
|
2481
2480
|
const [showConvertModal, setShowConvertModal] = useState(false)
|
|
2482
2481
|
const [convertRecordData, setConvertRecordData] = useState<Partial<StokerRecord> | undefined>(undefined)
|
|
2483
2482
|
const [convertTargetCollection, setConvertTargetCollection] = useState<CollectionSchema | undefined>(undefined)
|
|
@@ -2504,6 +2503,21 @@ function RecordForm({
|
|
|
2504
2503
|
return collectionAccess("Delete", collectionPermissions)
|
|
2505
2504
|
}, [collection, permissions])
|
|
2506
2505
|
|
|
2506
|
+
const convertMenuItems = useMemo(() => {
|
|
2507
|
+
if (!convert?.length || !permissions?.Role) return []
|
|
2508
|
+
const role = permissions.Role
|
|
2509
|
+
return convert.filter((convertConfig) => {
|
|
2510
|
+
const targetPermissions = permissions.collections?.[convertConfig.collection] as
|
|
2511
|
+
| CollectionPermissions
|
|
2512
|
+
| undefined
|
|
2513
|
+
if (!collectionAccess("Create", targetPermissions as CollectionPermissions)) return false
|
|
2514
|
+
if (convertConfig.roles && convertConfig.roles.length > 0) {
|
|
2515
|
+
return convertConfig.roles.includes(role)
|
|
2516
|
+
}
|
|
2517
|
+
return true
|
|
2518
|
+
})
|
|
2519
|
+
}, [convert, permissions])
|
|
2520
|
+
|
|
2507
2521
|
const isPending = !!(id && isGlobalLoading.has(id))
|
|
2508
2522
|
const isPendingServer = !!(id && isGlobalLoading.get(id)?.server)
|
|
2509
2523
|
const [isAddingServer, setIsAddingServer] = useState(false)
|
|
@@ -3624,12 +3638,6 @@ function RecordForm({
|
|
|
3624
3638
|
...prev,
|
|
3625
3639
|
[collection.labels.collection]: collectionTitles?.record || collection.labels.record,
|
|
3626
3640
|
}))
|
|
3627
|
-
if (convert && convert.length > 0) {
|
|
3628
|
-
setConvertAllowed((prev) => ({
|
|
3629
|
-
...prev,
|
|
3630
|
-
[collection.labels.collection]: !disableCreate,
|
|
3631
|
-
}))
|
|
3632
|
-
}
|
|
3633
3641
|
})
|
|
3634
3642
|
permissionTitleResults.forEach(({ collection, titles: collectionTitles }) => {
|
|
3635
3643
|
setPermissionsTitles((prev) => ({
|
|
@@ -4502,10 +4510,17 @@ function RecordForm({
|
|
|
4502
4510
|
const convertRecord = useCallback(
|
|
4503
4511
|
async (targetCollection: CollectionSchema) => {
|
|
4504
4512
|
if (!formValues || !originalRecord) return
|
|
4513
|
+
const targetPermissions = permissions?.collections?.[targetCollection.labels.collection] as
|
|
4514
|
+
| CollectionPermissions
|
|
4515
|
+
| undefined
|
|
4516
|
+
if (!collectionAccess("Create", targetPermissions as CollectionPermissions)) return
|
|
4505
4517
|
const record = cloneDeep(originalRecord) as Partial<StokerRecord>
|
|
4506
4518
|
|
|
4507
4519
|
const convertConfig = convert?.find((convert) => convert.collection === targetCollection.labels.collection)
|
|
4508
4520
|
if (!convertConfig) return
|
|
4521
|
+
if (convertConfig.roles && convertConfig.roles.length > 0 && permissions?.Role) {
|
|
4522
|
+
if (!convertConfig.roles.includes(permissions.Role)) return
|
|
4523
|
+
}
|
|
4509
4524
|
|
|
4510
4525
|
const convertedRecord = await convertConfig.convert(record as StokerRecord)
|
|
4511
4526
|
|
|
@@ -4513,7 +4528,7 @@ function RecordForm({
|
|
|
4513
4528
|
setConvertTargetCollection(targetCollection)
|
|
4514
4529
|
setShowConvertModal(true)
|
|
4515
4530
|
},
|
|
4516
|
-
[formValues, originalRecord, permissions],
|
|
4531
|
+
[formValues, originalRecord, permissions, convert],
|
|
4517
4532
|
)
|
|
4518
4533
|
|
|
4519
4534
|
const hasBreadcrumbs = useMemo(() => {
|
|
@@ -5651,7 +5666,7 @@ function RecordForm({
|
|
|
5651
5666
|
Duplicate
|
|
5652
5667
|
</Button>
|
|
5653
5668
|
)}
|
|
5654
|
-
{
|
|
5669
|
+
{convertMenuItems.length > 0 && (
|
|
5655
5670
|
<DropdownMenu>
|
|
5656
5671
|
<DropdownMenuTrigger asChild>
|
|
5657
5672
|
<Button variant="outline" disabled={isCreateDisabled}>
|
|
@@ -5660,36 +5675,19 @@ function RecordForm({
|
|
|
5660
5675
|
</Button>
|
|
5661
5676
|
</DropdownMenuTrigger>
|
|
5662
5677
|
<DropdownMenuContent align="end">
|
|
5663
|
-
{
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
convertConfig.
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
return convertConfig.roles.includes(permissions?.Role)
|
|
5677
|
-
}
|
|
5678
|
-
return true
|
|
5679
|
-
})
|
|
5680
|
-
.map((convertConfig) => {
|
|
5681
|
-
const targetCollection =
|
|
5682
|
-
schema.collections[convertConfig.collection]
|
|
5683
|
-
if (!targetCollection) return null
|
|
5684
|
-
return (
|
|
5685
|
-
<DropdownMenuItem
|
|
5686
|
-
key={convertConfig.collection}
|
|
5687
|
-
onClick={() => convertRecord(targetCollection)}
|
|
5688
|
-
>
|
|
5689
|
-
{allRecordTitles[convertConfig.collection]}
|
|
5690
|
-
</DropdownMenuItem>
|
|
5691
|
-
)
|
|
5692
|
-
})}
|
|
5678
|
+
{convertMenuItems.map((convertConfig) => {
|
|
5679
|
+
const targetCollection =
|
|
5680
|
+
schema.collections[convertConfig.collection]
|
|
5681
|
+
if (!targetCollection) return null
|
|
5682
|
+
return (
|
|
5683
|
+
<DropdownMenuItem
|
|
5684
|
+
key={convertConfig.collection}
|
|
5685
|
+
onClick={() => convertRecord(targetCollection)}
|
|
5686
|
+
>
|
|
5687
|
+
{allRecordTitles[convertConfig.collection]}
|
|
5688
|
+
</DropdownMenuItem>
|
|
5689
|
+
)
|
|
5690
|
+
})}
|
|
5693
5691
|
</DropdownMenuContent>
|
|
5694
5692
|
</DropdownMenu>
|
|
5695
5693
|
)}
|