sanity-plugin-media 4.1.1 → 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 (59) 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 +259 -98
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +259 -98
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +9 -2
  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/UploadDropzone/UploadDropzone.test.tsx +39 -0
  25. package/src/contexts/ToolOptionsContext.tsx +6 -3
  26. package/src/formSchema/index.test.ts +55 -0
  27. package/src/formSchema/index.ts +28 -12
  28. package/src/hooks/useVersionedClient.ts +1 -1
  29. package/src/modules/assets/deleteAndUpdateEpics.test.ts +86 -0
  30. package/src/modules/assets/fetchEpic.test.ts +72 -0
  31. package/src/modules/assets/reducer.test.ts +90 -0
  32. package/src/modules/assets/tagsAndListenerEpics.test.ts +205 -0
  33. package/src/modules/dialog/epics.test.ts +167 -0
  34. package/src/modules/dialog/reducer.test.ts +184 -0
  35. package/src/modules/notifications/epics.test.ts +373 -0
  36. package/src/modules/notifications/index.ts +24 -4
  37. package/src/modules/notifications/reducer.test.ts +53 -0
  38. package/src/modules/search/index.test.ts +35 -0
  39. package/src/modules/selectors.test.ts +20 -0
  40. package/src/modules/tags/epics.test.ts +95 -0
  41. package/src/modules/tags/index.test.ts +41 -0
  42. package/src/modules/uploads/epics.test.ts +108 -0
  43. package/src/modules/uploads/index.test.ts +58 -0
  44. package/src/operators/checkTagName.test.ts +28 -0
  45. package/src/types/index.ts +20 -7
  46. package/src/utils/blocksToText.test.ts +42 -0
  47. package/src/utils/constructFilter.test.ts +119 -0
  48. package/src/utils/generatePreviewBlobUrl.test.ts +69 -0
  49. package/src/utils/getAssetResolution.test.ts +12 -0
  50. package/src/utils/getDocumentAssetIds.test.ts +49 -0
  51. package/src/utils/getSchemeColor.test.ts +11 -0
  52. package/src/utils/getTagSelectOptions.test.ts +43 -0
  53. package/src/utils/getUniqueDocuments.test.ts +25 -0
  54. package/src/utils/imageDprUrl.test.ts +45 -0
  55. package/src/utils/isSupportedAssetType.test.ts +15 -0
  56. package/src/utils/sanitizeFormData.test.ts +58 -0
  57. package/src/utils/typeGuards.test.ts +17 -0
  58. package/src/utils/uploadSanityAsset.test.ts +28 -0
  59. package/src/utils/withMaxConcurrency.test.ts +42 -0
package/dist/index.js CHANGED
@@ -542,7 +542,7 @@ const useKeyPress = (hotkey, onPress) => {
542
542
  if (context === void 0)
543
543
  throw new Error("useAssetSourceActions must be used within an AssetSourceDispatchProvider");
544
544
  return context;
545
- }, useVersionedClient = () => sanity.useClient({ apiVersion: "2022-10-01" }), ORDER_DICTIONARY = {
545
+ }, useVersionedClient = () => sanity.useClient({ apiVersion: "2025-10-02" }), ORDER_DICTIONARY = {
546
546
  _createdAt: {
547
547
  asc: "Last created: Oldest first",
548
548
  desc: "Last created: Newest first"
@@ -2365,14 +2365,16 @@ const Container$1 = styledComponents.styled(ui.Box)(({ $scheme, theme }) => styl
2365
2365
  enabled: options?.creditLine?.enabled || !1,
2366
2366
  excludeSources: creditLineExcludeSources
2367
2367
  },
2368
- directUploads: options?.directUploads ?? !0
2368
+ directUploads: options?.directUploads ?? !0,
2369
+ locales: options?.locales
2369
2370
  };
2370
2371
  }, [
2371
2372
  options?.creditLine?.enabled,
2372
2373
  options?.components,
2373
2374
  options?.creditLine?.excludeSources,
2374
2375
  options?.maximumUploadSize,
2375
- options?.directUploads
2376
+ options?.directUploads,
2377
+ options?.locales
2376
2378
  ]);
2377
2379
  return /* @__PURE__ */ jsxRuntime.jsx(ToolOptionsContext.Provider, { value, children });
2378
2380
  }, useToolOptions = () => {
@@ -2651,21 +2653,35 @@ const DebugControls = () => {
2651
2653
  ] })
2652
2654
  }
2653
2655
  ) : null;
2654
- }, tagOptionSchema = z__namespace.object({
2656
+ };
2657
+ function localizedStringSchema(locales) {
2658
+ if (!locales || locales.length === 0)
2659
+ return z__namespace.string().trim().optional();
2660
+ const shape = {};
2661
+ for (const locale of locales)
2662
+ shape[locale.id] = z__namespace.string().trim().optional();
2663
+ return z__namespace.object(shape).passthrough();
2664
+ }
2665
+ const tagOptionSchema = z__namespace.object({
2655
2666
  label: z__namespace.string().trim().min(1, { message: "Label cannot be empty" }),
2656
2667
  value: z__namespace.string().trim().min(1, { message: "Value cannot be empty" })
2657
- }), assetFormSchema = z__namespace.object({
2658
- altText: z__namespace.string().trim().optional(),
2659
- creditLine: z__namespace.string().trim().optional(),
2660
- description: z__namespace.string().trim().optional(),
2661
- opt: z__namespace.object({
2662
- media: z__namespace.object({
2663
- tags: z__namespace.array(tagOptionSchema).nullable()
2664
- })
2665
- }),
2666
- originalFilename: z__namespace.string().trim().min(1, { message: "Filename cannot be empty" }),
2667
- title: z__namespace.string().trim().optional()
2668
- }), tagFormSchema = z__namespace.object({
2668
+ });
2669
+ function getAssetFormSchema(locales) {
2670
+ return z__namespace.object({
2671
+ altText: localizedStringSchema(locales),
2672
+ creditLine: localizedStringSchema(locales),
2673
+ description: localizedStringSchema(locales),
2674
+ opt: z__namespace.object({
2675
+ media: z__namespace.object({
2676
+ tags: z__namespace.array(tagOptionSchema).nullable()
2677
+ })
2678
+ }),
2679
+ originalFilename: z__namespace.string().trim().min(1, { message: "Filename cannot be empty" }),
2680
+ title: localizedStringSchema(locales)
2681
+ });
2682
+ }
2683
+ getAssetFormSchema();
2684
+ const tagFormSchema = z__namespace.object({
2669
2685
  name: z__namespace.string().min(1, { message: "Name cannot be empty" })
2670
2686
  });
2671
2687
  function getUniqueDocuments(documents) {
@@ -3100,6 +3116,11 @@ const imageDprUrl = (asset, options) => {
3100
3116
  )
3101
3117
  ] });
3102
3118
  });
3119
+ function toStringField(value) {
3120
+ if (typeof value == "string") return value;
3121
+ if (typeof value == "object" && value !== null)
3122
+ return Object.values(value).find((v) => v) || void 0;
3123
+ }
3103
3124
  function Details({
3104
3125
  formUpdating,
3105
3126
  handleCreateTag,
@@ -3109,8 +3130,10 @@ function Details({
3109
3130
  allTagOptions,
3110
3131
  assetTagOptions,
3111
3132
  currentAsset,
3112
- creditLine
3133
+ creditLine,
3134
+ locales
3113
3135
  }) {
3136
+ const hasLocales = locales && locales.length > 0, [activeLocaleTab, setActiveLocaleTab] = react.useState(0);
3114
3137
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
3115
3138
  /* @__PURE__ */ jsxRuntime.jsx(
3116
3139
  FormFieldInputTags,
@@ -3137,51 +3160,117 @@ function Details({
3137
3160
  value: currentAsset?.originalFilename
3138
3161
  }
3139
3162
  ),
3140
- /* @__PURE__ */ jsxRuntime.jsx(
3141
- FormFieldInputText,
3142
- {
3143
- ...register("title"),
3144
- disabled: formUpdating,
3145
- error: errors?.title?.message,
3146
- label: "Title",
3147
- name: "title",
3148
- value: currentAsset?.title
3149
- }
3150
- ),
3151
- /* @__PURE__ */ jsxRuntime.jsx(
3152
- FormFieldInputText,
3153
- {
3154
- ...register("altText"),
3155
- disabled: formUpdating,
3156
- error: errors?.altText?.message,
3157
- label: "Alt Text",
3158
- name: "altText",
3159
- value: currentAsset?.altText
3160
- }
3161
- ),
3162
- /* @__PURE__ */ jsxRuntime.jsx(
3163
- FormFieldInputTextarea,
3164
- {
3165
- ...register("description"),
3166
- disabled: formUpdating,
3167
- error: errors?.description?.message,
3168
- label: "Description",
3169
- name: "description",
3170
- rows: 5,
3171
- value: currentAsset?.description
3172
- }
3173
- ),
3174
- creditLine?.enabled && /* @__PURE__ */ jsxRuntime.jsx(
3175
- FormFieldInputText,
3176
- {
3177
- ...register("creditLine"),
3178
- error: errors?.creditLine?.message,
3179
- label: "Credit",
3180
- name: "creditLine",
3181
- value: currentAsset?.creditLine,
3182
- disabled: formUpdating || creditLine?.excludeSources?.includes(currentAsset?.source?.name)
3183
- }
3184
- )
3163
+ hasLocales ? /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { marginTop: 2, shadow: 1, padding: 3, radius: 1, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, children: [
3164
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TabList, { space: 2, children: locales.map((locale, idx) => /* @__PURE__ */ jsxRuntime.jsx(
3165
+ ui.Tab,
3166
+ {
3167
+ id: `locale-tab-${locale.id}`,
3168
+ "aria-controls": `locale-panel-${locale.id}`,
3169
+ selected: activeLocaleTab === idx,
3170
+ onClick: () => setActiveLocaleTab(idx),
3171
+ label: locale.title
3172
+ },
3173
+ locale.id
3174
+ )) }),
3175
+ locales.map((locale, idx) => /* @__PURE__ */ jsxRuntime.jsx(
3176
+ ui.TabPanel,
3177
+ {
3178
+ id: `locale-panel-${locale.id}`,
3179
+ "aria-labelledby": `locale-tab-${locale.id}`,
3180
+ hidden: activeLocaleTab !== idx,
3181
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
3182
+ /* @__PURE__ */ jsxRuntime.jsx(
3183
+ FormFieldInputText,
3184
+ {
3185
+ ...register(`title.${locale.id}`),
3186
+ disabled: formUpdating,
3187
+ error: errors?.title?.[locale.id]?.message,
3188
+ label: "Title",
3189
+ name: `title.${locale.id}`
3190
+ }
3191
+ ),
3192
+ /* @__PURE__ */ jsxRuntime.jsx(
3193
+ FormFieldInputText,
3194
+ {
3195
+ ...register(`altText.${locale.id}`),
3196
+ disabled: formUpdating,
3197
+ error: errors?.altText?.[locale.id]?.message,
3198
+ label: "Alt Text",
3199
+ name: `altText.${locale.id}`
3200
+ }
3201
+ ),
3202
+ /* @__PURE__ */ jsxRuntime.jsx(
3203
+ FormFieldInputTextarea,
3204
+ {
3205
+ ...register(`description.${locale.id}`),
3206
+ disabled: formUpdating,
3207
+ error: errors?.description?.[locale.id]?.message,
3208
+ label: "Description",
3209
+ name: `description.${locale.id}`,
3210
+ rows: 5
3211
+ }
3212
+ ),
3213
+ creditLine?.enabled && /* @__PURE__ */ jsxRuntime.jsx(
3214
+ FormFieldInputText,
3215
+ {
3216
+ ...register(`creditLine.${locale.id}`),
3217
+ error: errors?.creditLine?.[locale.id]?.message,
3218
+ label: "Credit",
3219
+ name: `creditLine.${locale.id}`,
3220
+ disabled: formUpdating || creditLine?.excludeSources?.includes(currentAsset?.source?.name)
3221
+ }
3222
+ )
3223
+ ] })
3224
+ },
3225
+ locale.id
3226
+ ))
3227
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3228
+ /* @__PURE__ */ jsxRuntime.jsx(
3229
+ FormFieldInputText,
3230
+ {
3231
+ ...register("title"),
3232
+ disabled: formUpdating,
3233
+ error: errors?.title?.message,
3234
+ label: "Title",
3235
+ name: "title",
3236
+ value: toStringField(currentAsset?.title)
3237
+ }
3238
+ ),
3239
+ /* @__PURE__ */ jsxRuntime.jsx(
3240
+ FormFieldInputText,
3241
+ {
3242
+ ...register("altText"),
3243
+ disabled: formUpdating,
3244
+ error: errors?.altText?.message,
3245
+ label: "Alt Text",
3246
+ name: "altText",
3247
+ value: toStringField(currentAsset?.altText)
3248
+ }
3249
+ ),
3250
+ /* @__PURE__ */ jsxRuntime.jsx(
3251
+ FormFieldInputTextarea,
3252
+ {
3253
+ ...register("description"),
3254
+ disabled: formUpdating,
3255
+ error: errors?.description?.message,
3256
+ label: "Description",
3257
+ name: "description",
3258
+ rows: 5,
3259
+ value: toStringField(currentAsset?.description)
3260
+ }
3261
+ ),
3262
+ creditLine?.enabled && /* @__PURE__ */ jsxRuntime.jsx(
3263
+ FormFieldInputText,
3264
+ {
3265
+ ...register("creditLine"),
3266
+ error: errors?.creditLine?.message,
3267
+ label: "Credit",
3268
+ name: "creditLine",
3269
+ value: toStringField(currentAsset?.creditLine),
3270
+ disabled: formUpdating || creditLine?.excludeSources?.includes(currentAsset?.source?.name)
3271
+ }
3272
+ )
3273
+ ] })
3185
3274
  ] });
3186
3275
  }
3187
3276
  function renderDefaultDetails(props) {
@@ -3191,16 +3280,37 @@ const DialogAssetEdit = (props) => {
3191
3280
  const {
3192
3281
  children,
3193
3282
  dialog: { assetId, id, lastCreatedTag, lastRemovedTagIds }
3194
- } = props, client = useVersionedClient(), scheme = sanity.useColorSchemeValue(), documentStore = sanity.useDocumentStore(), dispatch = reactRedux.useDispatch(), assetItem = useTypedSelector((state) => selectAssetById(state, String(assetId))), tags = useTypedSelector(selectTags), assetUpdatedPrev = react.useRef(void 0), [assetSnapshot, setAssetSnapshot] = react.useState(assetItem?.asset), [tabSection, setTabSection] = react.useState("details"), currentAsset = assetItem ? assetItem?.asset : assetSnapshot, allTagOptions = getTagSelectOptions(tags), assetTagOptions = useTypedSelector(selectTagSelectOptions(currentAsset)), { creditLine, components: { details: CustomDetails } = {} } = useToolOptions(), generateDefaultValues = react.useCallback(
3195
- (asset) => ({
3196
- altText: asset?.altText || "",
3197
- creditLine: asset?.creditLine || "",
3198
- description: asset?.description || "",
3199
- originalFilename: asset?.originalFilename || "",
3200
- opt: { media: { tags: assetTagOptions } },
3201
- title: asset?.title || ""
3202
- }),
3203
- [assetTagOptions]
3283
+ } = props, client = useVersionedClient(), scheme = sanity.useColorSchemeValue(), documentStore = sanity.useDocumentStore(), dispatch = reactRedux.useDispatch(), assetItem = useTypedSelector((state) => selectAssetById(state, String(assetId))), tags = useTypedSelector(selectTags), assetUpdatedPrev = react.useRef(void 0), [assetSnapshot, setAssetSnapshot] = react.useState(assetItem?.asset), [tabSection, setTabSection] = react.useState("details"), currentAsset = assetItem ? assetItem?.asset : assetSnapshot, allTagOptions = getTagSelectOptions(tags), assetTagOptions = useTypedSelector(selectTagSelectOptions(currentAsset)), { creditLine, components: { details: CustomDetails } = {}, locales } = useToolOptions(), generateDefaultValues = react.useCallback(
3284
+ (asset) => {
3285
+ if (locales && locales.length > 0) {
3286
+ const makeLocaleObj = (field) => {
3287
+ const obj = {};
3288
+ for (let i = 0; i < locales.length; i++) {
3289
+ const locale = locales[i];
3290
+ 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] = "";
3291
+ }
3292
+ return obj;
3293
+ };
3294
+ return {
3295
+ altText: makeLocaleObj(asset?.altText),
3296
+ creditLine: makeLocaleObj(asset?.creditLine),
3297
+ description: makeLocaleObj(asset?.description),
3298
+ originalFilename: asset?.originalFilename || "",
3299
+ opt: { media: { tags: assetTagOptions } },
3300
+ title: makeLocaleObj(asset?.title)
3301
+ };
3302
+ }
3303
+ const flattenField = (field) => typeof field == "string" ? field : typeof field == "object" && field !== null && Object.values(field).find((v) => v) || "";
3304
+ return {
3305
+ altText: flattenField(asset?.altText),
3306
+ creditLine: flattenField(asset?.creditLine),
3307
+ description: flattenField(asset?.description),
3308
+ originalFilename: asset?.originalFilename || "",
3309
+ opt: { media: { tags: assetTagOptions } },
3310
+ title: flattenField(asset?.title)
3311
+ };
3312
+ },
3313
+ [assetTagOptions, locales]
3204
3314
  ), {
3205
3315
  control,
3206
3316
  // Read the formState before render to subscribe the form state through Proxy
@@ -3213,7 +3323,7 @@ const DialogAssetEdit = (props) => {
3213
3323
  } = reactHookForm.useForm({
3214
3324
  defaultValues: generateDefaultValues(assetItem?.asset),
3215
3325
  mode: "onChange",
3216
- resolver: zod.zodResolver(assetFormSchema)
3326
+ resolver: zod.zodResolver(getAssetFormSchema(locales))
3217
3327
  }), formUpdating = !assetItem || assetItem?.updating, handleClose = react.useCallback(() => {
3218
3328
  dispatch(dialogActions.remove({ id }));
3219
3329
  }, [dispatch, id]), handleDelete = react.useCallback(() => {
@@ -3236,7 +3346,39 @@ const DialogAssetEdit = (props) => {
3236
3346
  );
3237
3347
  },
3238
3348
  [currentAsset?._id, dispatch]
3239
- ), onSubmit = react.useCallback(
3349
+ ), hasOrphanedLocales = react.useMemo(() => {
3350
+ if (!currentAsset) return !1;
3351
+ const isLocaleObj = (v) => typeof v == "object" && v !== null && !Array.isArray(v), fields = [
3352
+ currentAsset.title,
3353
+ currentAsset.altText,
3354
+ currentAsset.description,
3355
+ ...currentAsset._type === "sanity.imageAsset" ? [currentAsset.creditLine] : []
3356
+ ];
3357
+ if (!fields.some((f) => isLocaleObj(f))) return !1;
3358
+ if (!locales || locales.length === 0) return !0;
3359
+ const configuredIds = new Set(locales.map((l) => l.id));
3360
+ return fields.some((f) => isLocaleObj(f) ? Object.keys(f).some((k) => !configuredIds.has(k)) : !1);
3361
+ }, [currentAsset, locales]), handleCleanupLocales = react.useCallback(async () => {
3362
+ if (!currentAsset) return;
3363
+ const cleanField = (field) => {
3364
+ if (typeof field != "object" || field === null || Array.isArray(field)) return field;
3365
+ const obj = field;
3366
+ if (!locales || locales.length === 0)
3367
+ return Object.keys(obj).sort().map((k) => obj[k]).find((v) => v) || "";
3368
+ const configuredIds = new Set(locales.map((l) => l.id)), cleaned = {};
3369
+ for (const [key, val] of Object.entries(obj))
3370
+ configuredIds.has(key) && (cleaned[key] = val);
3371
+ return cleaned;
3372
+ };
3373
+ await client.patch(currentAsset._id).set({
3374
+ title: cleanField(currentAsset.title),
3375
+ altText: cleanField(currentAsset.altText),
3376
+ description: cleanField(currentAsset.description),
3377
+ ...currentAsset._type === "sanity.imageAsset" && {
3378
+ creditLine: cleanField(currentAsset.creditLine)
3379
+ }
3380
+ }).commit();
3381
+ }, [client, currentAsset, locales]), onSubmit = react.useCallback(
3240
3382
  (formData) => {
3241
3383
  if (!assetItem?.asset)
3242
3384
  return;
@@ -3284,27 +3426,42 @@ const DialogAssetEdit = (props) => {
3284
3426
  }, [getValues, lastRemovedTagIds, setValue]), react.useEffect(() => {
3285
3427
  assetUpdatedPrev.current !== assetItem?.asset._updatedAt && reset(generateDefaultValues(assetItem?.asset)), assetUpdatedPrev.current = assetItem?.asset._updatedAt;
3286
3428
  }, [assetItem?.asset, generateDefaultValues, reset]);
3287
- const Footer = () => /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { padding: 3, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { justify: "space-between", children: [
3288
- /* @__PURE__ */ jsxRuntime.jsx(
3289
- ui.Button,
3290
- {
3291
- disabled: formUpdating,
3292
- fontSize: 1,
3293
- mode: "bleed",
3294
- onClick: handleDelete,
3295
- text: "Delete",
3296
- tone: "critical"
3297
- }
3298
- ),
3299
- /* @__PURE__ */ jsxRuntime.jsx(
3300
- FormSubmitButton,
3301
- {
3302
- disabled: formUpdating || !isDirty || !isValid,
3303
- isValid,
3304
- lastUpdated: currentAsset?._updatedAt,
3305
- onClick: handleSubmit(onSubmit)
3306
- }
3307
- )
3429
+ const Footer = () => /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { padding: 3, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 3, children: [
3430
+ hasOrphanedLocales && /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: 3, radius: 2, shadow: 1, tone: "caution", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", justify: "space-between", gap: 3, children: [
3431
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, children: "This asset has localized fields that are no longer configured. Clean them up to avoid validation errors." }),
3432
+ /* @__PURE__ */ jsxRuntime.jsx(
3433
+ ui.Button,
3434
+ {
3435
+ fontSize: 1,
3436
+ mode: "ghost",
3437
+ onClick: handleCleanupLocales,
3438
+ text: "Cleanup localized fields",
3439
+ tone: "caution"
3440
+ }
3441
+ )
3442
+ ] }) }),
3443
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { justify: "space-between", children: [
3444
+ /* @__PURE__ */ jsxRuntime.jsx(
3445
+ ui.Button,
3446
+ {
3447
+ disabled: formUpdating,
3448
+ fontSize: 1,
3449
+ mode: "bleed",
3450
+ onClick: handleDelete,
3451
+ text: "Delete",
3452
+ tone: "critical"
3453
+ }
3454
+ ),
3455
+ /* @__PURE__ */ jsxRuntime.jsx(
3456
+ FormSubmitButton,
3457
+ {
3458
+ disabled: formUpdating || !isDirty || !isValid || hasOrphanedLocales,
3459
+ isValid,
3460
+ lastUpdated: currentAsset?._updatedAt,
3461
+ onClick: handleSubmit(onSubmit)
3462
+ }
3463
+ )
3464
+ ] })
3308
3465
  ] }) });
3309
3466
  if (!currentAsset)
3310
3467
  return null;
@@ -3318,7 +3475,8 @@ const DialogAssetEdit = (props) => {
3318
3475
  allTagOptions,
3319
3476
  handleCreateTag,
3320
3477
  currentAsset,
3321
- creditLine
3478
+ creditLine,
3479
+ locales
3322
3480
  };
3323
3481
  return /* @__PURE__ */ jsxRuntime.jsxs(
3324
3482
  Dialog,
@@ -5104,6 +5262,9 @@ const TableRowUpload = (props) => {
5104
5262
  reducers: {}
5105
5263
  });
5106
5264
  var selectedReducer = selectedSlice.reducer;
5265
+ function messageFromGenericErrorPayload(payload) {
5266
+ 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";
5267
+ }
5107
5268
  const initialState = {
5108
5269
  items: []
5109
5270
  }, notificationsSlice = toolkit.createSlice({
@@ -5190,11 +5351,11 @@ const initialState = {
5190
5351
  uploadsActions.uploadError.type
5191
5352
  ),
5192
5353
  operators$1.mergeMap((action) => {
5193
- const error = action.payload?.error;
5354
+ const title = `An error occurred: ${messageFromGenericErrorPayload(action.payload)}`;
5194
5355
  return rxjs.of(
5195
5356
  notificationsSlice.actions.add({
5196
5357
  status: "error",
5197
- title: `An error occured: ${error.message}`
5358
+ title
5198
5359
  })
5199
5360
  );
5200
5361
  })