@stoker-platform/web-app 0.5.28 → 0.5.30
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 +12 -0
- package/package.json +1 -1
- package/src/Form.tsx +134 -65
- package/src/utils/getFormattedFieldValue.tsx +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @stoker-platform/web-app
|
|
2
2
|
|
|
3
|
+
## 0.5.30
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fix: wait for conditional fields to render
|
|
8
|
+
|
|
9
|
+
## 0.5.29
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- feat: skip file permissions selection when no optional permissions present
|
|
14
|
+
|
|
3
15
|
## 0.5.28
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
package/src/Form.tsx
CHANGED
|
@@ -301,7 +301,6 @@ const RecordFormField = (props: FieldProps) => {
|
|
|
301
301
|
setCondition(!!condition)
|
|
302
302
|
|
|
303
303
|
const [
|
|
304
|
-
readOnly,
|
|
305
304
|
label,
|
|
306
305
|
description,
|
|
307
306
|
descriptionCondition,
|
|
@@ -316,7 +315,6 @@ const RecordFormField = (props: FieldProps) => {
|
|
|
316
315
|
isRadio,
|
|
317
316
|
icon,
|
|
318
317
|
] = await Promise.all([
|
|
319
|
-
tryPromise(admin?.readOnly, [operation, record]),
|
|
320
318
|
tryFunction(admin?.label),
|
|
321
319
|
tryFunction(admin?.description?.message, [record]),
|
|
322
320
|
tryPromise(admin?.description?.condition, [record]),
|
|
@@ -362,6 +360,10 @@ const RecordFormField = (props: FieldProps) => {
|
|
|
362
360
|
return
|
|
363
361
|
}
|
|
364
362
|
setCondition(true)
|
|
363
|
+
setTimeout(() => {
|
|
364
|
+
const readOnly = tryFunction(admin?.readOnly, [operation, form.getValues()])
|
|
365
|
+
setReadOnly(!!readOnly)
|
|
366
|
+
}, 0)
|
|
365
367
|
}, [form.watch()])
|
|
366
368
|
|
|
367
369
|
const hasUpdateAccess = useMemo(() => {
|
|
@@ -2322,6 +2324,36 @@ function RecordForm({
|
|
|
2322
2324
|
[collectionPath, record, path],
|
|
2323
2325
|
)
|
|
2324
2326
|
|
|
2327
|
+
const getUserRoleAssignment = useCallback(() => {
|
|
2328
|
+
const userRole = permissions?.Role
|
|
2329
|
+
if (!userRole) return null
|
|
2330
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
2331
|
+
const assignment = collection.access?.files?.assignment?.[userRole]
|
|
2332
|
+
return assignment || null
|
|
2333
|
+
}, [collection, permissions])
|
|
2334
|
+
|
|
2335
|
+
const shouldSkipPermissionsDialog = useCallback(() => {
|
|
2336
|
+
const assignment = getUserRoleAssignment()
|
|
2337
|
+
if (!assignment) return false
|
|
2338
|
+
const optional = assignment.optional || {}
|
|
2339
|
+
const hasOptional = Boolean(
|
|
2340
|
+
(optional.read && optional.read.length) ||
|
|
2341
|
+
(optional.update && optional.update.length) ||
|
|
2342
|
+
(optional.delete && optional.delete.length),
|
|
2343
|
+
)
|
|
2344
|
+
return !hasOptional
|
|
2345
|
+
}, [getUserRoleAssignment])
|
|
2346
|
+
|
|
2347
|
+
const getDefaultPermissions = useCallback((): FilePermissions => {
|
|
2348
|
+
const assignment = getUserRoleAssignment()
|
|
2349
|
+
const required = assignment?.required || {}
|
|
2350
|
+
return {
|
|
2351
|
+
read: (required.read || []).join(","),
|
|
2352
|
+
update: (required.update || []).join(","),
|
|
2353
|
+
delete: (required.delete || []).join(","),
|
|
2354
|
+
}
|
|
2355
|
+
}, [getUserRoleAssignment])
|
|
2356
|
+
|
|
2325
2357
|
const uploadFilesToRecord = useCallback(
|
|
2326
2358
|
async (targetId: string, files: File[] | FileList, permissions: FilePermissions, customFilename?: string) => {
|
|
2327
2359
|
if (!files || !currentUser) return
|
|
@@ -2407,56 +2439,6 @@ function RecordForm({
|
|
|
2407
2439
|
[computeBasePath, currentUser, path, record],
|
|
2408
2440
|
)
|
|
2409
2441
|
|
|
2410
|
-
const enqueueImageForCreate = useCallback((fieldName: string, file: File) => {
|
|
2411
|
-
setPermissionsContext("image-create")
|
|
2412
|
-
setPendingImageFieldName(fieldName)
|
|
2413
|
-
setPendingUploadFile(file)
|
|
2414
|
-
setPendingUploadField(fieldName)
|
|
2415
|
-
setEditingFilename(file.name)
|
|
2416
|
-
setIsMultipleFileUpload(false)
|
|
2417
|
-
setShowPermissionsDialog(true)
|
|
2418
|
-
}, [])
|
|
2419
|
-
|
|
2420
|
-
const uploadImageForUpdate = useCallback(async (fieldName: string, file: File) => {
|
|
2421
|
-
return await new Promise<void>((resolve) => {
|
|
2422
|
-
setPermissionsContext("image-update")
|
|
2423
|
-
setPendingImageForUpdate({ fieldName, file })
|
|
2424
|
-
setEditingFilename(file.name)
|
|
2425
|
-
setIsMultipleFileUpload(false)
|
|
2426
|
-
setShowPermissionsDialog(true)
|
|
2427
|
-
setImageUpdateResolver(() => resolve)
|
|
2428
|
-
})
|
|
2429
|
-
}, [])
|
|
2430
|
-
|
|
2431
|
-
const handleFormFileUpload = useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
2432
|
-
const files = event.target.files
|
|
2433
|
-
if (!files) return
|
|
2434
|
-
if (files.length === 1) {
|
|
2435
|
-
const file = files[0]
|
|
2436
|
-
setPendingUploadFile(file)
|
|
2437
|
-
setPendingUploadField(null)
|
|
2438
|
-
setEditingFilename(file.name)
|
|
2439
|
-
setIsMultipleFileUpload(false)
|
|
2440
|
-
setShowFilenameDialog(true)
|
|
2441
|
-
} else {
|
|
2442
|
-
setPendingUploadFiles(Array.from(files))
|
|
2443
|
-
setIsMultipleFileUpload(true)
|
|
2444
|
-
setShowPermissionsDialog(true)
|
|
2445
|
-
}
|
|
2446
|
-
event.target.value = ""
|
|
2447
|
-
}, [])
|
|
2448
|
-
|
|
2449
|
-
const handleConfirmFilename = useCallback(() => {
|
|
2450
|
-
if (!pendingUploadFile) return
|
|
2451
|
-
const trimmed = editingFilename.trim()
|
|
2452
|
-
const validationError = validateStorageName(trimmed)
|
|
2453
|
-
if (validationError) {
|
|
2454
|
-
toast({ title: "Invalid file name", description: validationError, variant: "destructive" })
|
|
2455
|
-
return
|
|
2456
|
-
}
|
|
2457
|
-
setShowPermissionsDialog(true)
|
|
2458
|
-
}, [pendingUploadFile, editingFilename])
|
|
2459
|
-
|
|
2460
2442
|
const handlePermissionsConfirm = useCallback(
|
|
2461
2443
|
async (selectedPermissions: FilePermissions) => {
|
|
2462
2444
|
if (permissionsContext === "files") {
|
|
@@ -2620,6 +2602,91 @@ function RecordForm({
|
|
|
2620
2602
|
],
|
|
2621
2603
|
)
|
|
2622
2604
|
|
|
2605
|
+
const enqueueImageForCreate = useCallback(
|
|
2606
|
+
(fieldName: string, file: File) => {
|
|
2607
|
+
if (shouldSkipPermissionsDialog()) {
|
|
2608
|
+
setQueuedImageUploads((prev) => ({
|
|
2609
|
+
...prev,
|
|
2610
|
+
[fieldName]: { file, permissions: getDefaultPermissions() },
|
|
2611
|
+
}))
|
|
2612
|
+
} else {
|
|
2613
|
+
setPermissionsContext("image-create")
|
|
2614
|
+
setPendingImageFieldName(fieldName)
|
|
2615
|
+
setPendingUploadFile(file)
|
|
2616
|
+
setEditingFilename(file.name)
|
|
2617
|
+
setIsMultipleFileUpload(false)
|
|
2618
|
+
setShowPermissionsDialog(true)
|
|
2619
|
+
}
|
|
2620
|
+
},
|
|
2621
|
+
[shouldSkipPermissionsDialog, getDefaultPermissions],
|
|
2622
|
+
)
|
|
2623
|
+
|
|
2624
|
+
const uploadImageForUpdate = useCallback(
|
|
2625
|
+
async (fieldName: string, file: File) => {
|
|
2626
|
+
return await new Promise<void>((resolve) => {
|
|
2627
|
+
setPermissionsContext("image-update")
|
|
2628
|
+
setPendingImageForUpdate({ fieldName, file })
|
|
2629
|
+
setEditingFilename(file.name)
|
|
2630
|
+
setIsMultipleFileUpload(false)
|
|
2631
|
+
setImageUpdateResolver(() => resolve)
|
|
2632
|
+
if (shouldSkipPermissionsDialog()) {
|
|
2633
|
+
setTimeout(() => handlePermissionsConfirm(getDefaultPermissions()), 0)
|
|
2634
|
+
} else {
|
|
2635
|
+
setShowPermissionsDialog(true)
|
|
2636
|
+
}
|
|
2637
|
+
})
|
|
2638
|
+
},
|
|
2639
|
+
[shouldSkipPermissionsDialog, getDefaultPermissions, handlePermissionsConfirm],
|
|
2640
|
+
)
|
|
2641
|
+
|
|
2642
|
+
const handleFormFileUpload = useCallback(
|
|
2643
|
+
async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
2644
|
+
const files = event.target.files
|
|
2645
|
+
if (!files) return
|
|
2646
|
+
if (files.length === 1) {
|
|
2647
|
+
const file = files[0]
|
|
2648
|
+
setPendingUploadFile(file)
|
|
2649
|
+
setPendingUploadField(null)
|
|
2650
|
+
setEditingFilename(file.name)
|
|
2651
|
+
setIsMultipleFileUpload(false)
|
|
2652
|
+
setShowFilenameDialog(true)
|
|
2653
|
+
} else {
|
|
2654
|
+
const fileList = Array.from(files)
|
|
2655
|
+
if (shouldSkipPermissionsDialog()) {
|
|
2656
|
+
setQueuedUploads((prev) => [...prev, { files: fileList, permissions: getDefaultPermissions() }])
|
|
2657
|
+
} else {
|
|
2658
|
+
setPendingUploadFiles(fileList)
|
|
2659
|
+
setIsMultipleFileUpload(true)
|
|
2660
|
+
setShowPermissionsDialog(true)
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
event.target.value = ""
|
|
2664
|
+
},
|
|
2665
|
+
[shouldSkipPermissionsDialog, getDefaultPermissions],
|
|
2666
|
+
)
|
|
2667
|
+
|
|
2668
|
+
const handleConfirmFilename = useCallback(() => {
|
|
2669
|
+
if (!pendingUploadFile) return
|
|
2670
|
+
const trimmed = editingFilename.trim()
|
|
2671
|
+
const validationError = validateStorageName(trimmed)
|
|
2672
|
+
if (validationError) {
|
|
2673
|
+
toast({ title: "Invalid file name", description: validationError, variant: "destructive" })
|
|
2674
|
+
return
|
|
2675
|
+
}
|
|
2676
|
+
if (shouldSkipPermissionsDialog()) {
|
|
2677
|
+
setQueuedUploads((prev) => [
|
|
2678
|
+
...prev,
|
|
2679
|
+
{ files: [pendingUploadFile], permissions: getDefaultPermissions(), customFilename: trimmed },
|
|
2680
|
+
])
|
|
2681
|
+
setShowFilenameDialog(false)
|
|
2682
|
+
setPendingUploadFile(null)
|
|
2683
|
+
setPendingUploadField(null)
|
|
2684
|
+
setEditingFilename("")
|
|
2685
|
+
} else {
|
|
2686
|
+
setShowPermissionsDialog(true)
|
|
2687
|
+
}
|
|
2688
|
+
}, [pendingUploadFile, editingFilename, shouldSkipPermissionsDialog, getDefaultPermissions])
|
|
2689
|
+
|
|
2623
2690
|
const handlePermissionsCancel = useCallback(() => {
|
|
2624
2691
|
if (permissionsContext === "image-create" && pendingImageFieldName) {
|
|
2625
2692
|
setTimeout(() => {
|
|
@@ -3388,19 +3455,21 @@ function RecordForm({
|
|
|
3388
3455
|
}
|
|
3389
3456
|
}
|
|
3390
3457
|
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3458
|
+
setTimeout(() => {
|
|
3459
|
+
if (isInitialized && (operation === "create" || operation === "update") && customization.admin?.onChange) {
|
|
3460
|
+
tryPromise(customization.admin.onChange, [
|
|
3461
|
+
operation,
|
|
3462
|
+
cloneDeep(form.getValues()) as StokerRecord,
|
|
3463
|
+
prevState as StokerRecord,
|
|
3464
|
+
]).then((updatedRecord: StokerRecord) => {
|
|
3465
|
+
if (updatedRecord && !isEqual(updatedRecord, formValues)) {
|
|
3466
|
+
Object.entries(updatedRecord).forEach(([key, value]) => {
|
|
3467
|
+
form.setValue(key, value)
|
|
3468
|
+
})
|
|
3469
|
+
}
|
|
3470
|
+
})
|
|
3471
|
+
}
|
|
3472
|
+
}, 0)
|
|
3404
3473
|
}, [form.watch()])
|
|
3405
3474
|
|
|
3406
3475
|
const recordLoaded = useRef(false)
|