sanity-plugin-media 4.1.0 → 4.2.0

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.
Files changed (62) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +56 -4
  3. package/dist/index.d.mts +131 -57
  4. package/dist/index.d.ts +131 -57
  5. package/dist/index.js +273 -106
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +273 -106
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +11 -4
  10. package/src/__tests__/fixtures/createEpicTestStore.ts +27 -0
  11. package/src/__tests__/fixtures/listenMock.ts +9 -0
  12. package/src/__tests__/fixtures/mockSanityClient.ts +84 -0
  13. package/src/__tests__/fixtures/renderWithProviders.tsx +54 -0
  14. package/src/__tests__/fixtures/rootState.ts +27 -0
  15. package/src/__tests__/fixtures/withinDialog.ts +28 -0
  16. package/src/components/Browser/Browser.test.tsx +44 -0
  17. package/src/components/CardAsset/CardAsset.test.tsx +322 -0
  18. package/src/components/DialogAssetEdit/Details.tsx +123 -44
  19. package/src/components/DialogAssetEdit/DialogAssetEdit.test.tsx +215 -0
  20. package/src/components/DialogAssetEdit/index.tsx +138 -30
  21. package/src/components/DialogTagCreate/DialogTagCreate.test.tsx +120 -0
  22. package/src/components/DialogTagEdit/DialogTagEdit.test.tsx +164 -0
  23. package/src/components/FormBuilderTool/FormBuilderTool.test.tsx +62 -0
  24. package/src/components/ReduxProvider/index.tsx +2 -1
  25. package/src/components/UploadDropzone/UploadDropzone.test.tsx +39 -0
  26. package/src/constants.ts +6 -0
  27. package/src/contexts/ToolOptionsContext.tsx +6 -3
  28. package/src/formSchema/index.test.ts +55 -0
  29. package/src/formSchema/index.ts +28 -12
  30. package/src/hooks/useVersionedClient.ts +1 -1
  31. package/src/modules/assets/deleteAndUpdateEpics.test.ts +86 -0
  32. package/src/modules/assets/fetchEpic.test.ts +72 -0
  33. package/src/modules/assets/reducer.test.ts +90 -0
  34. package/src/modules/assets/tagsAndListenerEpics.test.ts +205 -0
  35. package/src/modules/dialog/epics.test.ts +167 -0
  36. package/src/modules/dialog/reducer.test.ts +184 -0
  37. package/src/modules/notifications/epics.test.ts +373 -0
  38. package/src/modules/notifications/index.ts +24 -4
  39. package/src/modules/notifications/reducer.test.ts +53 -0
  40. package/src/modules/search/index.test.ts +35 -0
  41. package/src/modules/selectors.test.ts +20 -0
  42. package/src/modules/tags/epics.test.ts +95 -0
  43. package/src/modules/tags/index.test.ts +41 -0
  44. package/src/modules/uploads/epics.test.ts +108 -0
  45. package/src/modules/uploads/index.test.ts +58 -0
  46. package/src/operators/checkTagName.test.ts +28 -0
  47. package/src/types/index.ts +23 -7
  48. package/src/utils/blocksToText.test.ts +42 -0
  49. package/src/utils/constructFilter.test.ts +119 -0
  50. package/src/utils/generatePreviewBlobUrl.test.ts +69 -0
  51. package/src/utils/getAssetResolution.test.ts +12 -0
  52. package/src/utils/getDocumentAssetIds.test.ts +49 -0
  53. package/src/utils/getSchemeColor.test.ts +11 -0
  54. package/src/utils/getTagSelectOptions.test.ts +43 -0
  55. package/src/utils/getUniqueDocuments.test.ts +25 -0
  56. package/src/utils/imageDprUrl.test.ts +45 -0
  57. package/src/utils/isSupportedAssetType.test.ts +15 -0
  58. package/src/utils/isSupportedAssetType.ts +15 -0
  59. package/src/utils/sanitizeFormData.test.ts +58 -0
  60. package/src/utils/typeGuards.test.ts +17 -0
  61. package/src/utils/uploadSanityAsset.test.ts +28 -0
  62. package/src/utils/withMaxConcurrency.test.ts +42 -0
package/dist/index.mjs CHANGED
@@ -474,7 +474,10 @@ const useKeyPress = (hotkey, onPress) => {
474
474
  fn: (value, _field) => value ? `references('${value}')` : void 0,
475
475
  label: "includes"
476
476
  }
477
- }, ORDER_OPTIONS = [
477
+ }, SUPPORTED_ASSET_TYPES = [
478
+ "file",
479
+ "image"
480
+ ], ORDER_OPTIONS = [
478
481
  {
479
482
  direction: "desc",
480
483
  field: "_createdAt"
@@ -546,7 +549,7 @@ const useKeyPress = (hotkey, onPress) => {
546
549
  if (context === void 0)
547
550
  throw new Error("useAssetSourceActions must be used within an AssetSourceDispatchProvider");
548
551
  return context;
549
- }, useVersionedClient = () => useClient({ apiVersion: "2022-10-01" }), ORDER_DICTIONARY = {
552
+ }, useVersionedClient = () => useClient({ apiVersion: "2025-10-02" }), ORDER_DICTIONARY = {
550
553
  _createdAt: {
551
554
  asc: "Last created: Oldest first",
552
555
  desc: "Last created: Newest first"
@@ -2369,14 +2372,16 @@ const Container$1 = styled(Box)(({ $scheme, theme }) => css`
2369
2372
  enabled: options?.creditLine?.enabled || !1,
2370
2373
  excludeSources: creditLineExcludeSources
2371
2374
  },
2372
- directUploads: options?.directUploads ?? !0
2375
+ directUploads: options?.directUploads ?? !0,
2376
+ locales: options?.locales
2373
2377
  };
2374
2378
  }, [
2375
2379
  options?.creditLine?.enabled,
2376
2380
  options?.components,
2377
2381
  options?.creditLine?.excludeSources,
2378
2382
  options?.maximumUploadSize,
2379
- options?.directUploads
2383
+ options?.directUploads,
2384
+ options?.locales
2380
2385
  ]);
2381
2386
  return /* @__PURE__ */ jsx(ToolOptionsContext.Provider, { value, children });
2382
2387
  }, useToolOptions = () => {
@@ -2655,21 +2660,35 @@ const DebugControls = () => {
2655
2660
  ] })
2656
2661
  }
2657
2662
  ) : null;
2658
- }, tagOptionSchema = z.object({
2663
+ };
2664
+ function localizedStringSchema(locales) {
2665
+ if (!locales || locales.length === 0)
2666
+ return z.string().trim().optional();
2667
+ const shape = {};
2668
+ for (const locale of locales)
2669
+ shape[locale.id] = z.string().trim().optional();
2670
+ return z.object(shape).passthrough();
2671
+ }
2672
+ const tagOptionSchema = z.object({
2659
2673
  label: z.string().trim().min(1, { message: "Label cannot be empty" }),
2660
2674
  value: z.string().trim().min(1, { message: "Value cannot be empty" })
2661
- }), assetFormSchema = z.object({
2662
- altText: z.string().trim().optional(),
2663
- creditLine: z.string().trim().optional(),
2664
- description: z.string().trim().optional(),
2665
- opt: z.object({
2666
- media: z.object({
2667
- tags: z.array(tagOptionSchema).nullable()
2668
- })
2669
- }),
2670
- originalFilename: z.string().trim().min(1, { message: "Filename cannot be empty" }),
2671
- title: z.string().trim().optional()
2672
- }), tagFormSchema = z.object({
2675
+ });
2676
+ function getAssetFormSchema(locales) {
2677
+ return z.object({
2678
+ altText: localizedStringSchema(locales),
2679
+ creditLine: localizedStringSchema(locales),
2680
+ description: localizedStringSchema(locales),
2681
+ opt: z.object({
2682
+ media: z.object({
2683
+ tags: z.array(tagOptionSchema).nullable()
2684
+ })
2685
+ }),
2686
+ originalFilename: z.string().trim().min(1, { message: "Filename cannot be empty" }),
2687
+ title: localizedStringSchema(locales)
2688
+ });
2689
+ }
2690
+ getAssetFormSchema();
2691
+ const tagFormSchema = z.object({
2673
2692
  name: z.string().min(1, { message: "Name cannot be empty" })
2674
2693
  });
2675
2694
  function getUniqueDocuments(documents) {
@@ -3104,6 +3123,11 @@ const imageDprUrl = (asset, options) => {
3104
3123
  )
3105
3124
  ] });
3106
3125
  });
3126
+ function toStringField(value) {
3127
+ if (typeof value == "string") return value;
3128
+ if (typeof value == "object" && value !== null)
3129
+ return Object.values(value).find((v) => v) || void 0;
3130
+ }
3107
3131
  function Details({
3108
3132
  formUpdating,
3109
3133
  handleCreateTag,
@@ -3113,8 +3137,10 @@ function Details({
3113
3137
  allTagOptions,
3114
3138
  assetTagOptions,
3115
3139
  currentAsset,
3116
- creditLine
3140
+ creditLine,
3141
+ locales
3117
3142
  }) {
3143
+ const hasLocales = locales && locales.length > 0, [activeLocaleTab, setActiveLocaleTab] = useState(0);
3118
3144
  return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3119
3145
  /* @__PURE__ */ jsx(
3120
3146
  FormFieldInputTags,
@@ -3141,51 +3167,117 @@ function Details({
3141
3167
  value: currentAsset?.originalFilename
3142
3168
  }
3143
3169
  ),
3144
- /* @__PURE__ */ jsx(
3145
- FormFieldInputText,
3146
- {
3147
- ...register("title"),
3148
- disabled: formUpdating,
3149
- error: errors?.title?.message,
3150
- label: "Title",
3151
- name: "title",
3152
- value: currentAsset?.title
3153
- }
3154
- ),
3155
- /* @__PURE__ */ jsx(
3156
- FormFieldInputText,
3157
- {
3158
- ...register("altText"),
3159
- disabled: formUpdating,
3160
- error: errors?.altText?.message,
3161
- label: "Alt Text",
3162
- name: "altText",
3163
- value: currentAsset?.altText
3164
- }
3165
- ),
3166
- /* @__PURE__ */ jsx(
3167
- FormFieldInputTextarea,
3168
- {
3169
- ...register("description"),
3170
- disabled: formUpdating,
3171
- error: errors?.description?.message,
3172
- label: "Description",
3173
- name: "description",
3174
- rows: 5,
3175
- value: currentAsset?.description
3176
- }
3177
- ),
3178
- creditLine?.enabled && /* @__PURE__ */ jsx(
3179
- FormFieldInputText,
3180
- {
3181
- ...register("creditLine"),
3182
- error: errors?.creditLine?.message,
3183
- label: "Credit",
3184
- name: "creditLine",
3185
- value: currentAsset?.creditLine,
3186
- disabled: formUpdating || creditLine?.excludeSources?.includes(currentAsset?.source?.name)
3187
- }
3188
- )
3170
+ hasLocales ? /* @__PURE__ */ jsx(Card, { marginTop: 2, shadow: 1, padding: 3, radius: 1, children: /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
3171
+ /* @__PURE__ */ jsx(TabList, { space: 2, children: locales.map((locale, idx) => /* @__PURE__ */ jsx(
3172
+ Tab,
3173
+ {
3174
+ id: `locale-tab-${locale.id}`,
3175
+ "aria-controls": `locale-panel-${locale.id}`,
3176
+ selected: activeLocaleTab === idx,
3177
+ onClick: () => setActiveLocaleTab(idx),
3178
+ label: locale.title
3179
+ },
3180
+ locale.id
3181
+ )) }),
3182
+ locales.map((locale, idx) => /* @__PURE__ */ jsx(
3183
+ TabPanel,
3184
+ {
3185
+ id: `locale-panel-${locale.id}`,
3186
+ "aria-labelledby": `locale-tab-${locale.id}`,
3187
+ hidden: activeLocaleTab !== idx,
3188
+ children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3189
+ /* @__PURE__ */ jsx(
3190
+ FormFieldInputText,
3191
+ {
3192
+ ...register(`title.${locale.id}`),
3193
+ disabled: formUpdating,
3194
+ error: errors?.title?.[locale.id]?.message,
3195
+ label: "Title",
3196
+ name: `title.${locale.id}`
3197
+ }
3198
+ ),
3199
+ /* @__PURE__ */ jsx(
3200
+ FormFieldInputText,
3201
+ {
3202
+ ...register(`altText.${locale.id}`),
3203
+ disabled: formUpdating,
3204
+ error: errors?.altText?.[locale.id]?.message,
3205
+ label: "Alt Text",
3206
+ name: `altText.${locale.id}`
3207
+ }
3208
+ ),
3209
+ /* @__PURE__ */ jsx(
3210
+ FormFieldInputTextarea,
3211
+ {
3212
+ ...register(`description.${locale.id}`),
3213
+ disabled: formUpdating,
3214
+ error: errors?.description?.[locale.id]?.message,
3215
+ label: "Description",
3216
+ name: `description.${locale.id}`,
3217
+ rows: 5
3218
+ }
3219
+ ),
3220
+ creditLine?.enabled && /* @__PURE__ */ jsx(
3221
+ FormFieldInputText,
3222
+ {
3223
+ ...register(`creditLine.${locale.id}`),
3224
+ error: errors?.creditLine?.[locale.id]?.message,
3225
+ label: "Credit",
3226
+ name: `creditLine.${locale.id}`,
3227
+ disabled: formUpdating || creditLine?.excludeSources?.includes(currentAsset?.source?.name)
3228
+ }
3229
+ )
3230
+ ] })
3231
+ },
3232
+ locale.id
3233
+ ))
3234
+ ] }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3235
+ /* @__PURE__ */ jsx(
3236
+ FormFieldInputText,
3237
+ {
3238
+ ...register("title"),
3239
+ disabled: formUpdating,
3240
+ error: errors?.title?.message,
3241
+ label: "Title",
3242
+ name: "title",
3243
+ value: toStringField(currentAsset?.title)
3244
+ }
3245
+ ),
3246
+ /* @__PURE__ */ jsx(
3247
+ FormFieldInputText,
3248
+ {
3249
+ ...register("altText"),
3250
+ disabled: formUpdating,
3251
+ error: errors?.altText?.message,
3252
+ label: "Alt Text",
3253
+ name: "altText",
3254
+ value: toStringField(currentAsset?.altText)
3255
+ }
3256
+ ),
3257
+ /* @__PURE__ */ jsx(
3258
+ FormFieldInputTextarea,
3259
+ {
3260
+ ...register("description"),
3261
+ disabled: formUpdating,
3262
+ error: errors?.description?.message,
3263
+ label: "Description",
3264
+ name: "description",
3265
+ rows: 5,
3266
+ value: toStringField(currentAsset?.description)
3267
+ }
3268
+ ),
3269
+ creditLine?.enabled && /* @__PURE__ */ jsx(
3270
+ FormFieldInputText,
3271
+ {
3272
+ ...register("creditLine"),
3273
+ error: errors?.creditLine?.message,
3274
+ label: "Credit",
3275
+ name: "creditLine",
3276
+ value: toStringField(currentAsset?.creditLine),
3277
+ disabled: formUpdating || creditLine?.excludeSources?.includes(currentAsset?.source?.name)
3278
+ }
3279
+ )
3280
+ ] })
3189
3281
  ] });
3190
3282
  }
3191
3283
  function renderDefaultDetails(props) {
@@ -3195,16 +3287,37 @@ const DialogAssetEdit = (props) => {
3195
3287
  const {
3196
3288
  children,
3197
3289
  dialog: { assetId, id, lastCreatedTag, lastRemovedTagIds }
3198
- } = props, client = useVersionedClient(), scheme = useColorSchemeValue(), documentStore = useDocumentStore(), dispatch = useDispatch(), assetItem = useTypedSelector((state) => selectAssetById(state, String(assetId))), tags = useTypedSelector(selectTags), assetUpdatedPrev = useRef(void 0), [assetSnapshot, setAssetSnapshot] = useState(assetItem?.asset), [tabSection, setTabSection] = useState("details"), currentAsset = assetItem ? assetItem?.asset : assetSnapshot, allTagOptions = getTagSelectOptions(tags), assetTagOptions = useTypedSelector(selectTagSelectOptions(currentAsset)), { creditLine, components: { details: CustomDetails } = {} } = useToolOptions(), generateDefaultValues = useCallback(
3199
- (asset) => ({
3200
- altText: asset?.altText || "",
3201
- creditLine: asset?.creditLine || "",
3202
- description: asset?.description || "",
3203
- originalFilename: asset?.originalFilename || "",
3204
- opt: { media: { tags: assetTagOptions } },
3205
- title: asset?.title || ""
3206
- }),
3207
- [assetTagOptions]
3290
+ } = props, client = useVersionedClient(), scheme = useColorSchemeValue(), documentStore = useDocumentStore(), dispatch = useDispatch(), assetItem = useTypedSelector((state) => selectAssetById(state, String(assetId))), tags = useTypedSelector(selectTags), assetUpdatedPrev = useRef(void 0), [assetSnapshot, setAssetSnapshot] = useState(assetItem?.asset), [tabSection, setTabSection] = useState("details"), currentAsset = assetItem ? assetItem?.asset : assetSnapshot, allTagOptions = getTagSelectOptions(tags), assetTagOptions = useTypedSelector(selectTagSelectOptions(currentAsset)), { creditLine, components: { details: CustomDetails } = {}, locales } = useToolOptions(), generateDefaultValues = useCallback(
3291
+ (asset) => {
3292
+ if (locales && locales.length > 0) {
3293
+ const makeLocaleObj = (field) => {
3294
+ const obj = {};
3295
+ for (let i = 0; i < locales.length; i++) {
3296
+ const locale = locales[i];
3297
+ typeof field == "object" && field && field[locale.id] ? obj[locale.id] = field[locale.id] : typeof field == "string" ? obj[locale.id] = i === 0 ? field : "" : obj[locale.id] = "";
3298
+ }
3299
+ return obj;
3300
+ };
3301
+ return {
3302
+ altText: makeLocaleObj(asset?.altText),
3303
+ creditLine: makeLocaleObj(asset?.creditLine),
3304
+ description: makeLocaleObj(asset?.description),
3305
+ originalFilename: asset?.originalFilename || "",
3306
+ opt: { media: { tags: assetTagOptions } },
3307
+ title: makeLocaleObj(asset?.title)
3308
+ };
3309
+ }
3310
+ const flattenField = (field) => typeof field == "string" ? field : typeof field == "object" && field !== null && Object.values(field).find((v) => v) || "";
3311
+ return {
3312
+ altText: flattenField(asset?.altText),
3313
+ creditLine: flattenField(asset?.creditLine),
3314
+ description: flattenField(asset?.description),
3315
+ originalFilename: asset?.originalFilename || "",
3316
+ opt: { media: { tags: assetTagOptions } },
3317
+ title: flattenField(asset?.title)
3318
+ };
3319
+ },
3320
+ [assetTagOptions, locales]
3208
3321
  ), {
3209
3322
  control,
3210
3323
  // Read the formState before render to subscribe the form state through Proxy
@@ -3217,7 +3330,7 @@ const DialogAssetEdit = (props) => {
3217
3330
  } = useForm({
3218
3331
  defaultValues: generateDefaultValues(assetItem?.asset),
3219
3332
  mode: "onChange",
3220
- resolver: zodResolver(assetFormSchema)
3333
+ resolver: zodResolver(getAssetFormSchema(locales))
3221
3334
  }), formUpdating = !assetItem || assetItem?.updating, handleClose = useCallback(() => {
3222
3335
  dispatch(dialogActions.remove({ id }));
3223
3336
  }, [dispatch, id]), handleDelete = useCallback(() => {
@@ -3240,7 +3353,39 @@ const DialogAssetEdit = (props) => {
3240
3353
  );
3241
3354
  },
3242
3355
  [currentAsset?._id, dispatch]
3243
- ), onSubmit = useCallback(
3356
+ ), hasOrphanedLocales = useMemo(() => {
3357
+ if (!currentAsset) return !1;
3358
+ const isLocaleObj = (v) => typeof v == "object" && v !== null && !Array.isArray(v), fields = [
3359
+ currentAsset.title,
3360
+ currentAsset.altText,
3361
+ currentAsset.description,
3362
+ ...currentAsset._type === "sanity.imageAsset" ? [currentAsset.creditLine] : []
3363
+ ];
3364
+ if (!fields.some((f) => isLocaleObj(f))) return !1;
3365
+ if (!locales || locales.length === 0) return !0;
3366
+ const configuredIds = new Set(locales.map((l) => l.id));
3367
+ return fields.some((f) => isLocaleObj(f) ? Object.keys(f).some((k) => !configuredIds.has(k)) : !1);
3368
+ }, [currentAsset, locales]), handleCleanupLocales = useCallback(async () => {
3369
+ if (!currentAsset) return;
3370
+ const cleanField = (field) => {
3371
+ if (typeof field != "object" || field === null || Array.isArray(field)) return field;
3372
+ const obj = field;
3373
+ if (!locales || locales.length === 0)
3374
+ return Object.keys(obj).sort().map((k) => obj[k]).find((v) => v) || "";
3375
+ const configuredIds = new Set(locales.map((l) => l.id)), cleaned = {};
3376
+ for (const [key, val] of Object.entries(obj))
3377
+ configuredIds.has(key) && (cleaned[key] = val);
3378
+ return cleaned;
3379
+ };
3380
+ await client.patch(currentAsset._id).set({
3381
+ title: cleanField(currentAsset.title),
3382
+ altText: cleanField(currentAsset.altText),
3383
+ description: cleanField(currentAsset.description),
3384
+ ...currentAsset._type === "sanity.imageAsset" && {
3385
+ creditLine: cleanField(currentAsset.creditLine)
3386
+ }
3387
+ }).commit();
3388
+ }, [client, currentAsset, locales]), onSubmit = useCallback(
3244
3389
  (formData) => {
3245
3390
  if (!assetItem?.asset)
3246
3391
  return;
@@ -3288,27 +3433,42 @@ const DialogAssetEdit = (props) => {
3288
3433
  }, [getValues, lastRemovedTagIds, setValue]), useEffect(() => {
3289
3434
  assetUpdatedPrev.current !== assetItem?.asset._updatedAt && reset(generateDefaultValues(assetItem?.asset)), assetUpdatedPrev.current = assetItem?.asset._updatedAt;
3290
3435
  }, [assetItem?.asset, generateDefaultValues, reset]);
3291
- const Footer = () => /* @__PURE__ */ jsx(Box, { padding: 3, children: /* @__PURE__ */ jsxs(Flex, { justify: "space-between", children: [
3292
- /* @__PURE__ */ jsx(
3293
- Button,
3294
- {
3295
- disabled: formUpdating,
3296
- fontSize: 1,
3297
- mode: "bleed",
3298
- onClick: handleDelete,
3299
- text: "Delete",
3300
- tone: "critical"
3301
- }
3302
- ),
3303
- /* @__PURE__ */ jsx(
3304
- FormSubmitButton,
3305
- {
3306
- disabled: formUpdating || !isDirty || !isValid,
3307
- isValid,
3308
- lastUpdated: currentAsset?._updatedAt,
3309
- onClick: handleSubmit(onSubmit)
3310
- }
3311
- )
3436
+ const Footer = () => /* @__PURE__ */ jsx(Box, { padding: 3, children: /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
3437
+ hasOrphanedLocales && /* @__PURE__ */ jsx(Card, { padding: 3, radius: 2, shadow: 1, tone: "caution", children: /* @__PURE__ */ jsxs(Flex, { align: "center", justify: "space-between", gap: 3, children: [
3438
+ /* @__PURE__ */ jsx(Text, { size: 1, children: "This asset has localized fields that are no longer configured. Clean them up to avoid validation errors." }),
3439
+ /* @__PURE__ */ jsx(
3440
+ Button,
3441
+ {
3442
+ fontSize: 1,
3443
+ mode: "ghost",
3444
+ onClick: handleCleanupLocales,
3445
+ text: "Cleanup localized fields",
3446
+ tone: "caution"
3447
+ }
3448
+ )
3449
+ ] }) }),
3450
+ /* @__PURE__ */ jsxs(Flex, { justify: "space-between", children: [
3451
+ /* @__PURE__ */ jsx(
3452
+ Button,
3453
+ {
3454
+ disabled: formUpdating,
3455
+ fontSize: 1,
3456
+ mode: "bleed",
3457
+ onClick: handleDelete,
3458
+ text: "Delete",
3459
+ tone: "critical"
3460
+ }
3461
+ ),
3462
+ /* @__PURE__ */ jsx(
3463
+ FormSubmitButton,
3464
+ {
3465
+ disabled: formUpdating || !isDirty || !isValid || hasOrphanedLocales,
3466
+ isValid,
3467
+ lastUpdated: currentAsset?._updatedAt,
3468
+ onClick: handleSubmit(onSubmit)
3469
+ }
3470
+ )
3471
+ ] })
3312
3472
  ] }) });
3313
3473
  if (!currentAsset)
3314
3474
  return null;
@@ -3322,7 +3482,8 @@ const DialogAssetEdit = (props) => {
3322
3482
  allTagOptions,
3323
3483
  handleCreateTag,
3324
3484
  currentAsset,
3325
- creditLine
3485
+ creditLine,
3486
+ locales
3326
3487
  };
3327
3488
  return /* @__PURE__ */ jsxs(
3328
3489
  Dialog,
@@ -4014,7 +4175,7 @@ const DialogAssetEdit = (props) => {
4014
4175
  `
4015
4176
  ), StyledWarningOutlineIcon = styled(WarningFilledIcon)(({ theme }) => ({
4016
4177
  color: theme.sanity.color.spot.red
4017
- })), CardAsset = (props) => {
4178
+ })), CardAsset$1 = (props) => {
4018
4179
  const { id, selected } = props, scheme = useColorSchemeValue(), shiftPressed = useKeyPress("shift"), dispatch = useDispatch(), lastPicked = useTypedSelector((state) => state.assets.lastPicked), item = useTypedSelector((state) => selectAssetById(state, id)), asset = item?.asset, error = item?.error, isOpaque = item?.asset?.metadata?.isOpaque, picked = item?.picked, updating = item?.updating, { onSelect } = useAssetSourceActions();
4019
4180
  if (!asset)
4020
4181
  return null;
@@ -4144,7 +4305,7 @@ const DialogAssetEdit = (props) => {
4144
4305
  )
4145
4306
  ] }) });
4146
4307
  };
4147
- var CardAsset$1 = memo(CardAsset);
4308
+ var CardAsset = memo(CardAsset$1);
4148
4309
  const PREVIEW_WIDTH = 180, createBlob = (img) => new Promise((resolve) => {
4149
4310
  const imageAspect = img.width / img.height, canvas = document.createElement("canvas");
4150
4311
  canvas.width = PREVIEW_WIDTH, canvas.height = Math.max(PREVIEW_WIDTH / imageAspect, 1);
@@ -4501,7 +4662,7 @@ const CardWrapper = styled(Flex)`
4501
4662
  }
4502
4663
  ) });
4503
4664
  }, CARD_HEIGHT = 220, CARD_WIDTH = 240, VirtualCell = memo(
4504
- ({ item, selected }) => item?.type === "asset" ? /* @__PURE__ */ jsx(CardAsset$1, { id: item.id, selected }) : item?.type === "upload" ? /* @__PURE__ */ jsx(CardUpload, { id: item.id }) : null
4665
+ ({ item, selected }) => item?.type === "asset" ? /* @__PURE__ */ jsx(CardAsset, { id: item.id, selected }) : item?.type === "upload" ? /* @__PURE__ */ jsx(CardUpload, { id: item.id }) : null
4505
4666
  ), StyledItemContainer = styled.div`
4506
4667
  height: ${CARD_HEIGHT}px;
4507
4668
  width: ${CARD_WIDTH}px;
@@ -4666,7 +4827,7 @@ const CardWrapper = styled(Flex)`
4666
4827
  `
4667
4828
  ), StyledWarningIcon = styled(WarningFilledIcon)(({ theme }) => ({
4668
4829
  color: theme.sanity.color.spot.red
4669
- })), TableRowAsset = (props) => {
4830
+ })), TableRowAsset$1 = (props) => {
4670
4831
  const { id, selected } = props, scheme = useColorSchemeValue(), shiftPressed = useKeyPress("shift"), [referenceCountVisible, setReferenceCountVisible] = useState(!1), refCountVisibleTimeout = useRef(null), dispatch = useDispatch(), lastPicked = useTypedSelector((state) => state.assets.lastPicked), item = useTypedSelector((state) => selectAssetById(state, id)), mediaIndex = useMediaIndex(), asset = item?.asset, error = item?.error, isOpaque = item?.asset?.metadata?.isOpaque, picked = item?.picked, updating = item?.updating, { onSelect } = useAssetSourceActions(), handleContextActionClick = useCallback(
4671
4832
  (e) => {
4672
4833
  e.stopPropagation(), asset && (onSelect ? dispatch(dialogActions.showAssetEdit({ assetId: asset._id })) : shiftPressed.current && !picked ? dispatch(assetsActions.pickRange({ startId: lastPicked || asset._id, endId: asset._id })) : dispatch(assetsActions.pick({ assetId: asset._id, picked: !picked })));
@@ -4892,7 +5053,7 @@ const CardWrapper = styled(Flex)`
4892
5053
  }
4893
5054
  ) : null;
4894
5055
  };
4895
- var TableRowAsset$1 = memo(TableRowAsset);
5056
+ var TableRowAsset = memo(TableRowAsset$1);
4896
5057
  const TableRowUpload = (props) => {
4897
5058
  const { id } = props, scheme = useColorSchemeValue(), dispatch = useDispatch(), item = useTypedSelector((state) => selectUploadById(state, id)), mediaIndex = useMediaIndex();
4898
5059
  if (!item)
@@ -5006,7 +5167,7 @@ const TableRowUpload = (props) => {
5006
5167
  }
5007
5168
  );
5008
5169
  }, VirtualRow = memo(
5009
- ({ item, selected }) => item?.type === "asset" ? /* @__PURE__ */ jsx(Box, { style: { height: "100px" }, children: /* @__PURE__ */ jsx(TableRowAsset$1, { id: item.id, selected }) }) : item?.type === "upload" ? /* @__PURE__ */ jsx(Box, { style: { height: "100px" }, children: /* @__PURE__ */ jsx(TableRowUpload, { id: item.id }) }) : null
5170
+ ({ item, selected }) => item?.type === "asset" ? /* @__PURE__ */ jsx(Box, { style: { height: "100px" }, children: /* @__PURE__ */ jsx(TableRowAsset, { id: item.id, selected }) }) : item?.type === "upload" ? /* @__PURE__ */ jsx(Box, { style: { height: "100px" }, children: /* @__PURE__ */ jsx(TableRowUpload, { id: item.id }) }) : null
5010
5171
  ), AssetTableVirtualized = (props) => {
5011
5172
  const { items, onLoadMore } = props, selectedAssets = useTypedSelector((state) => state.selected.assets), selectedIds = selectedAssets && selectedAssets.map((asset) => asset._id) || [], totalCount = items?.length;
5012
5173
  return totalCount === 0 ? null : /* @__PURE__ */ jsx(
@@ -5106,6 +5267,9 @@ const TableRowUpload = (props) => {
5106
5267
  reducers: {}
5107
5268
  });
5108
5269
  var selectedReducer = selectedSlice.reducer;
5270
+ function messageFromGenericErrorPayload(payload) {
5271
+ return !payload || typeof payload != "object" ? "Unknown error" : "error" in payload && payload.error && typeof payload.error == "object" && payload.error !== null && "message" in payload.error ? String(payload.error.message) : "message" in payload && typeof payload.message == "string" ? String(payload.message) : "Unknown error";
5272
+ }
5109
5273
  const initialState = {
5110
5274
  items: []
5111
5275
  }, notificationsSlice = createSlice({
@@ -5192,11 +5356,11 @@ const initialState = {
5192
5356
  uploadsActions.uploadError.type
5193
5357
  ),
5194
5358
  mergeMap((action) => {
5195
- const error = action.payload?.error;
5359
+ const title = `An error occurred: ${messageFromGenericErrorPayload(action.payload)}`;
5196
5360
  return of(
5197
5361
  notificationsSlice.actions.add({
5198
5362
  status: "error",
5199
- title: `An error occured: ${error.message}`
5363
+ title
5200
5364
  })
5201
5365
  );
5202
5366
  })
@@ -5269,6 +5433,9 @@ const rootEpic = combineEpics(
5269
5433
  const assetIds = getAssetIds(document2);
5270
5434
  return [...new Set(assetIds.sort())];
5271
5435
  };
5436
+ function isSupportedAssetType(assetType) {
5437
+ return assetType ? SUPPORTED_ASSET_TYPES.includes(assetType) : !1;
5438
+ }
5272
5439
  class ReduxProvider extends Component {
5273
5440
  store;
5274
5441
  constructor(props) {
@@ -5299,7 +5466,7 @@ class ReduxProvider extends Component {
5299
5466
  preloadedState: {
5300
5467
  assets: {
5301
5468
  ...initialState$5,
5302
- assetTypes: props?.assetType ? [props.assetType] : ["file", "image"]
5469
+ assetTypes: isSupportedAssetType(props?.assetType) ? [props.assetType] : ["file", "image"]
5303
5470
  },
5304
5471
  debug: {
5305
5472
  badConnection: !1,