@webiny/app-file-manager 6.6.0-alpha.0 → 6.6.0-alpha.1

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 (132) hide show
  1. package/app.js +8 -0
  2. package/app.js.map +1 -1
  3. package/features/shared/FILE_FIELDS.js +4 -0
  4. package/features/shared/FILE_FIELDS.js.map +1 -1
  5. package/features/shared/FileFieldsProviderWithWcp.d.ts +5 -5
  6. package/features/shared/FileFieldsProviderWithWcp.js +7 -7
  7. package/features/shared/FileFieldsProviderWithWcp.js.map +1 -1
  8. package/modules/AiEnrichment/ReenrichFileGateway.d.ts +12 -0
  9. package/modules/AiEnrichment/ReenrichFileGateway.js +23 -0
  10. package/modules/AiEnrichment/ReenrichFileGateway.js.map +1 -0
  11. package/modules/AiEnrichment/ReenrichWithAi.d.ts +11 -0
  12. package/modules/AiEnrichment/ReenrichWithAi.js +57 -0
  13. package/modules/AiEnrichment/ReenrichWithAi.js.map +1 -0
  14. package/modules/AiEnrichment/ReenrichWithAiPresenter.d.ts +50 -0
  15. package/modules/AiEnrichment/ReenrichWithAiPresenter.js +114 -0
  16. package/modules/AiEnrichment/ReenrichWithAiPresenter.js.map +1 -0
  17. package/modules/AiEnrichment/StreamingPlaceholder.d.ts +15 -0
  18. package/modules/AiEnrichment/StreamingPlaceholder.js +32 -0
  19. package/modules/AiEnrichment/StreamingPlaceholder.js.map +1 -0
  20. package/modules/AiEnrichment/abstractions.d.ts +70 -0
  21. package/modules/AiEnrichment/abstractions.js +6 -0
  22. package/modules/AiEnrichment/abstractions.js.map +1 -0
  23. package/modules/AiEnrichment/feature.d.ts +3 -1
  24. package/modules/AiEnrichment/feature.js +10 -0
  25. package/modules/AiEnrichment/feature.js.map +1 -1
  26. package/modules/Enterprise/index.js +3 -3
  27. package/modules/Enterprise/index.js.map +1 -1
  28. package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.d.ts +26 -0
  29. package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.js +39 -0
  30. package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.js.map +1 -0
  31. package/modules/HeadlessCms/fieldType/AssetFieldType.d.ts +34 -0
  32. package/modules/HeadlessCms/fieldType/AssetFieldType.js +42 -0
  33. package/modules/HeadlessCms/fieldType/AssetFieldType.js.map +1 -0
  34. package/modules/HeadlessCms/fieldType/AssetFormFieldType.d.ts +21 -0
  35. package/modules/HeadlessCms/fieldType/AssetFormFieldType.js +42 -0
  36. package/modules/HeadlessCms/fieldType/AssetFormFieldType.js.map +1 -0
  37. package/modules/HeadlessCms/fieldType/AssetInputRenderer.d.ts +15 -0
  38. package/modules/HeadlessCms/fieldType/AssetInputRenderer.js +20 -0
  39. package/modules/HeadlessCms/fieldType/AssetInputRenderer.js.map +1 -0
  40. package/modules/HeadlessCms/fieldType/AssetInputsRenderer.d.ts +15 -0
  41. package/modules/HeadlessCms/fieldType/AssetInputsRenderer.js +20 -0
  42. package/modules/HeadlessCms/fieldType/AssetInputsRenderer.js.map +1 -0
  43. package/modules/HeadlessCms/fieldType/FileFieldType.d.ts +1 -0
  44. package/modules/HeadlessCms/fieldType/FileFieldType.js +1 -0
  45. package/modules/HeadlessCms/fieldType/FileFieldType.js.map +1 -1
  46. package/modules/HeadlessCms/fieldType/feature.js +10 -0
  47. package/modules/HeadlessCms/fieldType/feature.js.map +1 -1
  48. package/modules/HeadlessCms/fieldType/isAssetField.d.ts +2 -0
  49. package/modules/HeadlessCms/fieldType/isAssetField.js +4 -0
  50. package/modules/HeadlessCms/fieldType/isAssetField.js.map +1 -0
  51. package/package.json +22 -22
  52. package/presentation/FileActions/FileDetails/EditImage.d.ts +2 -0
  53. package/presentation/FileActions/FileDetails/EditImage.js +39 -0
  54. package/presentation/FileActions/FileDetails/EditImage.js.map +1 -0
  55. package/presentation/FileActions/index.js +8 -0
  56. package/presentation/FileActions/index.js.map +1 -1
  57. package/presentation/FileDetails/FileDetailsPresenter.d.ts +2 -0
  58. package/presentation/FileDetails/FileDetailsPresenter.js +5 -0
  59. package/presentation/FileDetails/FileDetailsPresenter.js.map +1 -1
  60. package/presentation/FileDetails/FileDetailsPresenter.test.js +37 -4
  61. package/presentation/FileDetails/FileDetailsPresenter.test.js.map +1 -1
  62. package/presentation/FileDetails/abstractions.d.ts +2 -0
  63. package/presentation/FileDetails/abstractions.js.map +1 -1
  64. package/presentation/FileDetails/components/Content.d.ts +2 -1
  65. package/presentation/FileDetails/components/Content.js +2 -2
  66. package/presentation/FileDetails/components/Content.js.map +1 -1
  67. package/presentation/FileDetails/components/FileDetailsDrawer.d.ts +2 -2
  68. package/presentation/FileDetails/components/FileDetailsDrawer.js +11 -5
  69. package/presentation/FileDetails/components/FileDetailsDrawer.js.map +1 -1
  70. package/presentation/FileList/FileListDataSource.js +34 -1
  71. package/presentation/FileList/FileListDataSource.js.map +1 -1
  72. package/presentation/FileList/FileListPresenter.test.js +2 -1
  73. package/presentation/FileList/FileListPresenter.test.js.map +1 -1
  74. package/presentation/FileList/FileManagerPresenter.js +6 -4
  75. package/presentation/FileList/FileManagerPresenter.js.map +1 -1
  76. package/presentation/FileList/abstractions.d.ts +1 -0
  77. package/presentation/FileList/abstractions.js.map +1 -1
  78. package/presentation/FileList/components/BulkActions/BulkActionBar.d.ts +2 -2
  79. package/presentation/FileList/components/BulkActions/BulkActionDelete.d.ts +2 -2
  80. package/presentation/FileList/components/BulkActions/BulkActionMove.d.ts +2 -2
  81. package/presentation/FileList/components/Filters/SubFoldersToggle.d.ts +2 -2
  82. package/presentation/FileList/components/Filters/TagsFilter.d.ts +2 -2
  83. package/presentation/FileList/components/Filters/TypeFilter.d.ts +2 -2
  84. package/presentation/FileList/components/Grid/FileGrid.d.ts +2 -2
  85. package/presentation/FileList/components/Header/FileManagerHeader.d.ts +2 -2
  86. package/presentation/FileList/components/Search/SearchBar.d.ts +2 -2
  87. package/presentation/FileList/components/Table/FileTable.d.ts +2 -2
  88. package/presentation/FileList/components/Upload/UploadProgress.d.ts +2 -2
  89. package/presentation/FileManager/FileManagerBreadcrumbs.d.ts +12 -0
  90. package/presentation/FileManager/FileManagerBreadcrumbs.js +37 -0
  91. package/presentation/FileManager/FileManagerBreadcrumbs.js.map +1 -0
  92. package/presentation/FileManager/FileManagerView.js +40 -8
  93. package/presentation/FileManager/FileManagerView.js.map +1 -1
  94. package/presentation/Settings/components/SettingsView.d.ts +2 -2
  95. package/presentation/config/DefaultFileManagerConfig.js +12 -7
  96. package/presentation/config/DefaultFileManagerConfig.js.map +1 -1
  97. package/presentation/config/thumbnailRenderers/CroppedFileImage.d.ts +18 -0
  98. package/presentation/config/thumbnailRenderers/CroppedFileImage.js +66 -0
  99. package/presentation/config/thumbnailRenderers/CroppedFileImage.js.map +1 -0
  100. package/presentation/config/thumbnailRenderers/FilePreviewImageRenderer.js +6 -9
  101. package/presentation/config/thumbnailRenderers/FilePreviewImageRenderer.js.map +1 -1
  102. package/presentation/config/thumbnailRenderers/GridItemImageRenderer.js +6 -9
  103. package/presentation/config/thumbnailRenderers/GridItemImageRenderer.js.map +1 -1
  104. package/presentation/config/thumbnailRenderers/TableItemImageRenderer.js +6 -9
  105. package/presentation/config/thumbnailRenderers/TableItemImageRenderer.js.map +1 -1
  106. package/presentation/fieldRenderers/CmsAssetPickerRenderer.d.ts +22 -0
  107. package/presentation/fieldRenderers/CmsAssetPickerRenderer.js +56 -0
  108. package/presentation/fieldRenderers/CmsAssetPickerRenderer.js.map +1 -0
  109. package/presentation/fieldRenderers/CmsFilePickerRenderer.d.ts +1 -1
  110. package/presentation/fieldRenderers/CmsFilePickerRenderer.js +11 -4
  111. package/presentation/fieldRenderers/CmsFilePickerRenderer.js.map +1 -1
  112. package/presentation/fieldRenderers/CmsImageEditorDialog.d.ts +7 -0
  113. package/presentation/fieldRenderers/CmsImageEditorDialog.js +32 -0
  114. package/presentation/fieldRenderers/CmsImageEditorDialog.js.map +1 -0
  115. package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.d.ts +18 -0
  116. package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.js +88 -0
  117. package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.js.map +1 -0
  118. package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.d.ts +1 -1
  119. package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.js +11 -2
  120. package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.js.map +1 -1
  121. package/presentation/fieldRenderers/CroppedAssetThumb.d.ts +15 -0
  122. package/presentation/fieldRenderers/CroppedAssetThumb.js +25 -0
  123. package/presentation/fieldRenderers/CroppedAssetThumb.js.map +1 -0
  124. package/presentation/fieldRenderers/assetValue.d.ts +10 -0
  125. package/presentation/fieldRenderers/assetValue.js +88 -0
  126. package/presentation/fieldRenderers/assetValue.js.map +1 -0
  127. package/presentation/hooks/useCmsImageEditor.d.ts +10 -0
  128. package/presentation/hooks/useCmsImageEditor.js +76 -0
  129. package/presentation/hooks/useCmsImageEditor.js.map +1 -0
  130. package/presentation/hooks/useEditImage.d.ts +14 -0
  131. package/presentation/hooks/useEditImage.js +67 -0
  132. package/presentation/hooks/useEditImage.js.map +1 -0
@@ -0,0 +1,32 @@
1
+ import react from "react";
2
+ import { ImageEditor } from "@webiny/admin-ui";
3
+ const CmsImageEditorDialog = ({ editor })=>{
4
+ const { open, close, file, save } = editor;
5
+ if (!file) return null;
6
+ const img = file.metadata?.image;
7
+ const value = img?.crop || img?.focalPoint ? {
8
+ crop: img.crop ?? void 0,
9
+ hotspot: img.focalPoint ? {
10
+ x: img.focalPoint.x,
11
+ y: img.focalPoint.y,
12
+ width: 1,
13
+ height: 1
14
+ } : void 0,
15
+ alt: img.alt ?? void 0,
16
+ caption: img.caption ?? void 0
17
+ } : void 0;
18
+ return /*#__PURE__*/ react.createElement(ImageEditor, {
19
+ open: open,
20
+ onClose: close,
21
+ image: {
22
+ src: file.src,
23
+ width: img?.width ?? 0,
24
+ height: img?.height ?? 0
25
+ },
26
+ value: value,
27
+ onSave: save
28
+ });
29
+ };
30
+ export { CmsImageEditorDialog };
31
+
32
+ //# sourceMappingURL=CmsImageEditorDialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation/fieldRenderers/CmsImageEditorDialog.js","sources":["../../../src/presentation/fieldRenderers/CmsImageEditorDialog.tsx"],"sourcesContent":["import React from \"react\";\nimport { ImageEditor, type ImageEditorValue } from \"@webiny/admin-ui\";\nimport type { useCmsImageEditor } from \"~/presentation/hooks/useCmsImageEditor.js\";\n\ninterface CmsImageEditorDialogProps {\n editor: ReturnType<typeof useCmsImageEditor>;\n}\n\nexport const CmsImageEditorDialog = ({ editor }: CmsImageEditorDialogProps) => {\n const { open, close, file, save } = editor;\n\n if (!file) {\n return null;\n }\n\n const img = file.metadata?.image as Record<string, any> | undefined;\n const value: ImageEditorValue | undefined =\n img?.crop || img?.focalPoint\n ? {\n crop: img.crop ?? undefined,\n hotspot: img.focalPoint\n ? { x: img.focalPoint.x, y: img.focalPoint.y, width: 1, height: 1 }\n : undefined,\n alt: img.alt ?? undefined,\n caption: img.caption ?? undefined\n }\n : undefined;\n\n return (\n <ImageEditor\n open={open}\n onClose={close}\n image={{\n src: file.src,\n width: img?.width ?? 0,\n height: img?.height ?? 0\n }}\n value={value}\n onSave={save}\n />\n );\n};\n"],"names":["CmsImageEditorDialog","editor","open","close","file","save","img","value","undefined","ImageEditor"],"mappings":";;AAQO,MAAMA,uBAAuB,CAAC,EAAEC,MAAM,EAA6B;IACtE,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAEC,IAAI,EAAEC,IAAI,EAAE,GAAGJ;IAEpC,IAAI,CAACG,MACD,OAAO;IAGX,MAAME,MAAMF,KAAK,QAAQ,EAAE;IAC3B,MAAMG,QACFD,KAAK,QAAQA,KAAK,aACZ;QACI,MAAMA,IAAI,IAAI,IAAIE;QAClB,SAASF,IAAI,UAAU,GACjB;YAAE,GAAGA,IAAI,UAAU,CAAC,CAAC;YAAE,GAAGA,IAAI,UAAU,CAAC,CAAC;YAAE,OAAO;YAAG,QAAQ;QAAE,IAChEE;QACN,KAAKF,IAAI,GAAG,IAAIE;QAChB,SAASF,IAAI,OAAO,IAAIE;IAC5B,IACAA;IAEV,OAAO,WAAP,GACI,oBAACC,aAAWA;QACR,MAAMP;QACN,SAASC;QACT,OAAO;YACH,KAAKC,KAAK,GAAG;YACb,OAAOE,KAAK,SAAS;YACrB,QAAQA,KAAK,UAAU;QAC3B;QACA,OAAOC;QACP,QAAQF;;AAGpB"}
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import type { AssetFieldRendererSettings } from "./CmsAssetPickerRenderer.js";
3
+ declare module "@webiny/app-admin/features/formModel/abstractions.js" {
4
+ interface IFieldRendererRegistry {
5
+ cmsMultiAssetPicker: {
6
+ fieldType: "asset";
7
+ settings: AssetFieldRendererSettings;
8
+ };
9
+ }
10
+ }
11
+ /**
12
+ * Multi-asset picker for a list-mode CMS Asset field. The value is an array of
13
+ * typed Asset objects; the picker shows each file's URL and offers a per-item
14
+ * crop / focal point / alt editor that writes back onto that item's `image`.
15
+ */
16
+ export declare const CmsMultiAssetPickerRenderer: React.ComponentType<{
17
+ field: import("@webiny/app-admin/index.js").IFieldVM;
18
+ }>;
@@ -0,0 +1,88 @@
1
+ import react, { useState } from "react";
2
+ import { createFieldRenderer } from "@webiny/app-admin/features/formModel/createFieldRenderer.js";
3
+ import { FileManager } from "@webiny/app-admin/base/ui/FileManager.js";
4
+ import { ImageEditor, MultiFilePicker, RichItemPreview } from "@webiny/admin-ui";
5
+ import { applyImageEditToAsset, assetImageToEditorValue, fileItemToAsset, hasAsset, hasImageEdit, isImageAsset } from "./assetValue.js";
6
+ import { CroppedAssetThumb } from "./CroppedAssetThumb.js";
7
+ const CmsMultiAssetPickerRenderer = createFieldRenderer(({ field })=>{
8
+ const settings = field.rendererSettings;
9
+ const value = field.value;
10
+ const assets = Array.isArray(value) ? value : [];
11
+ const [editingIndex, setEditingIndex] = useState(null);
12
+ const editingAsset = null !== editingIndex ? assets[editingIndex] : void 0;
13
+ const editable = isImageAsset(editingAsset) && hasAsset(editingAsset);
14
+ const replaceAt = (index, next)=>{
15
+ field.onChange([
16
+ ...assets.slice(0, index),
17
+ ...next,
18
+ ...assets.slice(index + 1)
19
+ ]);
20
+ };
21
+ return /*#__PURE__*/ react.createElement(FileManager, {
22
+ multiple: true,
23
+ images: settings?.imagesOnly,
24
+ accept: settings?.accept,
25
+ render: ({ showFileManager })=>{
26
+ const selectFiles = (replaceIndex = -1)=>{
27
+ showFileManager((files)=>{
28
+ const picked = files.map(fileItemToAsset);
29
+ if (-1 === replaceIndex) field.onChange([
30
+ ...assets,
31
+ ...picked
32
+ ]);
33
+ else replaceAt(replaceIndex, picked);
34
+ });
35
+ };
36
+ return /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(MultiFilePicker, {
37
+ label: field.label,
38
+ description: field.description,
39
+ note: field.note,
40
+ hint: field.help,
41
+ type: "compact",
42
+ values: assets.map((a)=>a.src ?? ""),
43
+ renderFilePreview: (previewProps)=>{
44
+ const asset = assets.find((a)=>a.src === previewProps.value.url);
45
+ if (asset?.src && hasImageEdit(asset.image)) return /*#__PURE__*/ react.createElement(RichItemPreview, {
46
+ ...previewProps,
47
+ renderImage: ({ name })=>/*#__PURE__*/ react.createElement(CroppedAssetThumb, {
48
+ src: asset.src,
49
+ name: name,
50
+ image: asset.image
51
+ })
52
+ });
53
+ return /*#__PURE__*/ react.createElement(RichItemPreview, previewProps);
54
+ },
55
+ onSelectItem: ()=>selectFiles(),
56
+ onReplaceItem: (_, index)=>selectFiles(index),
57
+ onEditItem: (_, index)=>{
58
+ const asset = assets[index];
59
+ if (isImageAsset(asset) && hasAsset(asset)) setEditingIndex(index);
60
+ },
61
+ onRemoveItem: (_, index)=>{
62
+ field.onChange([
63
+ ...assets.slice(0, index),
64
+ ...assets.slice(index + 1)
65
+ ]);
66
+ }
67
+ }), editable && editingAsset ? /*#__PURE__*/ react.createElement(ImageEditor, {
68
+ open: null !== editingIndex,
69
+ onClose: ()=>setEditingIndex(null),
70
+ image: {
71
+ src: editingAsset.src ?? "",
72
+ width: editingAsset.image?.width ?? 0,
73
+ height: editingAsset.image?.height ?? 0
74
+ },
75
+ value: assetImageToEditorValue(editingAsset.image),
76
+ onSave: (edit)=>{
77
+ if (null !== editingIndex) replaceAt(editingIndex, [
78
+ applyImageEditToAsset(editingAsset, edit)
79
+ ]);
80
+ setEditingIndex(null);
81
+ }
82
+ }) : null);
83
+ }
84
+ });
85
+ });
86
+ export { CmsMultiAssetPickerRenderer };
87
+
88
+ //# sourceMappingURL=CmsMultiAssetPickerRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation/fieldRenderers/CmsMultiAssetPickerRenderer.js","sources":["../../../src/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.tsx"],"sourcesContent":["import React, { useState } from \"react\";\nimport { createFieldRenderer } from \"@webiny/app-admin/features/formModel/createFieldRenderer.js\";\nimport { FileManager } from \"@webiny/app-admin/base/ui/FileManager.js\";\nimport type { FileManagerFileItem } from \"@webiny/app-admin/base/ui/FileManager.js\";\nimport {\n MultiFilePicker,\n RichItemPreview,\n ImageEditor,\n type ImageEditorValue\n} from \"@webiny/admin-ui\";\nimport type { AssetFieldRendererSettings } from \"./CmsAssetPickerRenderer.js\";\nimport {\n applyImageEditToAsset,\n assetImageToEditorValue,\n fileItemToAsset,\n hasAsset,\n hasImageEdit,\n isImageAsset,\n type Asset\n} from \"./assetValue.js\";\nimport { CroppedAssetThumb } from \"./CroppedAssetThumb.js\";\n\ndeclare module \"@webiny/app-admin/features/formModel/abstractions.js\" {\n interface IFieldRendererRegistry {\n cmsMultiAssetPicker: { fieldType: \"asset\"; settings: AssetFieldRendererSettings };\n }\n}\n\n/**\n * Multi-asset picker for a list-mode CMS Asset field. The value is an array of\n * typed Asset objects; the picker shows each file's URL and offers a per-item\n * crop / focal point / alt editor that writes back onto that item's `image`.\n */\nexport const CmsMultiAssetPickerRenderer = createFieldRenderer<\"cmsMultiAssetPicker\">(\n ({ field }) => {\n const settings = field.rendererSettings as AssetFieldRendererSettings | undefined;\n const value = field.value as Asset[] | null | undefined;\n const assets = Array.isArray(value) ? value : [];\n const [editingIndex, setEditingIndex] = useState<number | null>(null);\n\n const editingAsset = editingIndex !== null ? assets[editingIndex] : undefined;\n const editable = isImageAsset(editingAsset) && hasAsset(editingAsset);\n\n const replaceAt = (index: number, next: Asset[]) => {\n field.onChange([...assets.slice(0, index), ...next, ...assets.slice(index + 1)]);\n };\n\n return (\n <FileManager\n multiple\n images={settings?.imagesOnly}\n accept={settings?.accept}\n render={({ showFileManager }) => {\n const selectFiles = (replaceIndex = -1) => {\n showFileManager((files: FileManagerFileItem[]) => {\n const picked = files.map(fileItemToAsset);\n if (replaceIndex === -1) {\n field.onChange([...assets, ...picked]);\n } else {\n replaceAt(replaceIndex, picked);\n }\n });\n };\n\n return (\n <>\n <MultiFilePicker\n label={field.label}\n description={field.description}\n note={field.note}\n hint={field.help}\n type=\"compact\"\n values={assets.map(a => a.src ?? \"\")}\n // Reflect each item's per-usage crop + focal point.\n renderFilePreview={previewProps => {\n const asset = assets.find(\n a => a.src === previewProps.value.url\n );\n if (asset?.src && hasImageEdit(asset.image)) {\n return (\n <RichItemPreview\n {...previewProps}\n renderImage={({ name }) => (\n <CroppedAssetThumb\n src={asset.src as string}\n name={name}\n image={asset.image!}\n />\n )}\n />\n );\n }\n return <RichItemPreview {...previewProps} />;\n }}\n onSelectItem={() => selectFiles()}\n onReplaceItem={(_, index) => selectFiles(index)}\n onEditItem={(_, index) => {\n const asset = assets[index];\n if (isImageAsset(asset) && hasAsset(asset)) {\n setEditingIndex(index);\n }\n }}\n onRemoveItem={(_, index) => {\n field.onChange([\n ...assets.slice(0, index),\n ...assets.slice(index + 1)\n ]);\n }}\n />\n {editable && editingAsset ? (\n <ImageEditor\n open={editingIndex !== null}\n onClose={() => setEditingIndex(null)}\n image={{\n src: editingAsset.src ?? \"\",\n width: editingAsset.image?.width ?? 0,\n height: editingAsset.image?.height ?? 0\n }}\n value={assetImageToEditorValue(editingAsset.image)}\n onSave={(edit: ImageEditorValue) => {\n if (editingIndex !== null) {\n replaceAt(editingIndex, [\n applyImageEditToAsset(editingAsset, edit)\n ]);\n }\n setEditingIndex(null);\n }}\n />\n ) : null}\n </>\n );\n }}\n />\n );\n }\n);\n"],"names":["CmsMultiAssetPickerRenderer","createFieldRenderer","field","settings","value","assets","Array","editingIndex","setEditingIndex","useState","editingAsset","undefined","editable","isImageAsset","hasAsset","replaceAt","index","next","FileManager","showFileManager","selectFiles","replaceIndex","files","picked","fileItemToAsset","MultiFilePicker","a","previewProps","asset","hasImageEdit","RichItemPreview","name","CroppedAssetThumb","_","ImageEditor","assetImageToEditorValue","edit","applyImageEditToAsset"],"mappings":";;;;;;AAiCO,MAAMA,8BAA8BC,oBACvC,CAAC,EAAEC,KAAK,EAAE;IACN,MAAMC,WAAWD,MAAM,gBAAgB;IACvC,MAAME,QAAQF,MAAM,KAAK;IACzB,MAAMG,SAASC,MAAM,OAAO,CAACF,SAASA,QAAQ,EAAE;IAChD,MAAM,CAACG,cAAcC,gBAAgB,GAAGC,SAAwB;IAEhE,MAAMC,eAAeH,AAAiB,SAAjBA,eAAwBF,MAAM,CAACE,aAAa,GAAGI;IACpE,MAAMC,WAAWC,aAAaH,iBAAiBI,SAASJ;IAExD,MAAMK,YAAY,CAACC,OAAeC;QAC9Bf,MAAM,QAAQ,CAAC;eAAIG,OAAO,KAAK,CAAC,GAAGW;eAAWC;eAASZ,OAAO,KAAK,CAACW,QAAQ;SAAG;IACnF;IAEA,OAAO,WAAP,GACI,oBAACE,aAAWA;QACR;QACA,QAAQf,UAAU;QAClB,QAAQA,UAAU;QAClB,QAAQ,CAAC,EAAEgB,eAAe,EAAE;YACxB,MAAMC,cAAc,CAACC,eAAe,EAAE;gBAClCF,gBAAgB,CAACG;oBACb,MAAMC,SAASD,MAAM,GAAG,CAACE;oBACzB,IAAIH,AAAiB,OAAjBA,cACAnB,MAAM,QAAQ,CAAC;2BAAIG;2BAAWkB;qBAAO;yBAErCR,UAAUM,cAAcE;gBAEhC;YACJ;YAEA,OAAO,WAAP,GACI,wDACI,oBAACE,iBAAeA;gBACZ,OAAOvB,MAAM,KAAK;gBAClB,aAAaA,MAAM,WAAW;gBAC9B,MAAMA,MAAM,IAAI;gBAChB,MAAMA,MAAM,IAAI;gBAChB,MAAK;gBACL,QAAQG,OAAO,GAAG,CAACqB,CAAAA,IAAKA,EAAE,GAAG,IAAI;gBAEjC,mBAAmBC,CAAAA;oBACf,MAAMC,QAAQvB,OAAO,IAAI,CACrBqB,CAAAA,IAAKA,EAAE,GAAG,KAAKC,aAAa,KAAK,CAAC,GAAG;oBAEzC,IAAIC,OAAO,OAAOC,aAAaD,MAAM,KAAK,GACtC,OAAO,WAAP,GACI,oBAACE,iBAAeA;wBACX,GAAGH,YAAY;wBAChB,aAAa,CAAC,EAAEI,IAAI,EAAE,iBAClB,oBAACC,mBAAiBA;gCACd,KAAKJ,MAAM,GAAG;gCACd,MAAMG;gCACN,OAAOH,MAAM,KAAK;;;oBAMtC,OAAO,WAAP,GAAO,oBAACE,iBAAoBH;gBAChC;gBACA,cAAc,IAAMP;gBACpB,eAAe,CAACa,GAAGjB,QAAUI,YAAYJ;gBACzC,YAAY,CAACiB,GAAGjB;oBACZ,MAAMY,QAAQvB,MAAM,CAACW,MAAM;oBAC3B,IAAIH,aAAae,UAAUd,SAASc,QAChCpB,gBAAgBQ;gBAExB;gBACA,cAAc,CAACiB,GAAGjB;oBACdd,MAAM,QAAQ,CAAC;2BACRG,OAAO,KAAK,CAAC,GAAGW;2BAChBX,OAAO,KAAK,CAACW,QAAQ;qBAC3B;gBACL;gBAEHJ,YAAYF,eAAe,WAAfA,GACT,oBAACwB,aAAWA;gBACR,MAAM3B,AAAiB,SAAjBA;gBACN,SAAS,IAAMC,gBAAgB;gBAC/B,OAAO;oBACH,KAAKE,aAAa,GAAG,IAAI;oBACzB,OAAOA,aAAa,KAAK,EAAE,SAAS;oBACpC,QAAQA,aAAa,KAAK,EAAE,UAAU;gBAC1C;gBACA,OAAOyB,wBAAwBzB,aAAa,KAAK;gBACjD,QAAQ,CAAC0B;oBACL,IAAI7B,AAAiB,SAAjBA,cACAQ,UAAUR,cAAc;wBACpB8B,sBAAsB3B,cAAc0B;qBACvC;oBAEL5B,gBAAgB;gBACpB;iBAEJ;QAGhB;;AAGZ"}
@@ -9,5 +9,5 @@ declare module "@webiny/app-admin/features/formModel/abstractions.js" {
9
9
  }
10
10
  }
11
11
  export declare const CmsMultiFilePickerRenderer: React.ComponentType<{
12
- field: import("@webiny/app-admin/index").IFieldVM;
12
+ field: import("@webiny/app-admin/index.js").IFieldVM;
13
13
  }>;
@@ -2,10 +2,13 @@ import react from "react";
2
2
  import { createFieldRenderer } from "@webiny/app-admin/features/formModel/createFieldRenderer.js";
3
3
  import { FileManager } from "@webiny/app-admin/base/ui/FileManager.js";
4
4
  import { MultiFilePicker } from "@webiny/admin-ui";
5
+ import { isEditableImageUrl, useCmsImageEditor } from "../hooks/useCmsImageEditor.js";
6
+ import { CmsImageEditorDialog } from "./CmsImageEditorDialog.js";
5
7
  const CmsMultiFilePickerRenderer = createFieldRenderer(({ field })=>{
6
8
  const value = field.value;
7
9
  const settings = field.rendererSettings;
8
10
  const currentValues = Array.isArray(value) ? value : [];
11
+ const editor = useCmsImageEditor();
9
12
  return /*#__PURE__*/ react.createElement(FileManager, {
10
13
  multiple: true,
11
14
  images: settings?.images,
@@ -27,7 +30,7 @@ const CmsMultiFilePickerRenderer = createFieldRenderer(({ field })=>{
27
30
  ]);
28
31
  });
29
32
  };
30
- return /*#__PURE__*/ react.createElement(MultiFilePicker, {
33
+ return /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(MultiFilePicker, {
31
34
  label: field.label,
32
35
  description: field.description,
33
36
  note: field.note,
@@ -36,13 +39,19 @@ const CmsMultiFilePickerRenderer = createFieldRenderer(({ field })=>{
36
39
  type: "compact",
37
40
  onSelectItem: ()=>selectFiles(),
38
41
  onReplaceItem: (_, index)=>selectFiles(index),
42
+ onEditItem: (_, index)=>{
43
+ const url = currentValues[index];
44
+ if (isEditableImageUrl(url)) editor.openFor(url);
45
+ },
39
46
  onRemoveItem: (_, index)=>{
40
47
  field.onChange([
41
48
  ...currentValues.slice(0, index),
42
49
  ...currentValues.slice(index + 1)
43
50
  ]);
44
51
  }
45
- });
52
+ }), /*#__PURE__*/ react.createElement(CmsImageEditorDialog, {
53
+ editor: editor
54
+ }));
46
55
  }
47
56
  });
48
57
  });
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/fieldRenderers/CmsMultiFilePickerRenderer.js","sources":["../../../src/presentation/fieldRenderers/CmsMultiFilePickerRenderer.tsx"],"sourcesContent":["import React from \"react\";\nimport { createFieldRenderer } from \"@webiny/app-admin/features/formModel/createFieldRenderer.js\";\nimport { FileManager } from \"@webiny/app-admin/base/ui/FileManager.js\";\nimport type { FileManagerFileItem } from \"@webiny/app-admin/base/ui/FileManager.js\";\nimport { MultiFilePicker } from \"@webiny/admin-ui\";\nimport type { FileFieldSettings } from \"@webiny/app-admin/features/formModel/fieldTypes/FileFieldType.js\";\n\ndeclare module \"@webiny/app-admin/features/formModel/abstractions.js\" {\n interface IFieldRendererRegistry {\n cmsMultiFilePicker: { fieldType: \"file\"; settings: FileFieldSettings };\n }\n}\n\nexport const CmsMultiFilePickerRenderer = createFieldRenderer<\"cmsMultiFilePicker\">(({ field }) => {\n const value = field.value as string[] | undefined;\n const settings = field.rendererSettings as FileFieldSettings | undefined;\n const currentValues = Array.isArray(value) ? value : [];\n\n return (\n <FileManager\n multiple\n images={settings?.images}\n accept={settings?.accept}\n own={settings?.own}\n scope={settings?.scope}\n render={({ showFileManager }) => {\n const selectFiles = (replaceIndex = -1) => {\n showFileManager((files: FileManagerFileItem[]) => {\n const newSrcs = files.map(f => f.src || \"\");\n if (replaceIndex === -1) {\n field.onChange([...currentValues, ...newSrcs]);\n } else {\n field.onChange([\n ...currentValues.slice(0, replaceIndex),\n ...newSrcs,\n ...currentValues.slice(replaceIndex + 1)\n ]);\n }\n });\n };\n\n return (\n <MultiFilePicker\n label={field.label}\n description={field.description}\n note={field.note}\n hint={field.help}\n values={currentValues}\n type=\"compact\"\n onSelectItem={() => selectFiles()}\n onReplaceItem={(_, index) => selectFiles(index)}\n onRemoveItem={(_, index) => {\n field.onChange([\n ...currentValues.slice(0, index),\n ...currentValues.slice(index + 1)\n ]);\n }}\n />\n );\n }}\n />\n );\n});\n"],"names":["CmsMultiFilePickerRenderer","createFieldRenderer","field","value","settings","currentValues","Array","FileManager","showFileManager","selectFiles","replaceIndex","files","newSrcs","f","MultiFilePicker","_","index"],"mappings":";;;;AAaO,MAAMA,6BAA6BC,oBAA0C,CAAC,EAAEC,KAAK,EAAE;IAC1F,MAAMC,QAAQD,MAAM,KAAK;IACzB,MAAME,WAAWF,MAAM,gBAAgB;IACvC,MAAMG,gBAAgBC,MAAM,OAAO,CAACH,SAASA,QAAQ,EAAE;IAEvD,OAAO,WAAP,GACI,oBAACI,aAAWA;QACR;QACA,QAAQH,UAAU;QAClB,QAAQA,UAAU;QAClB,KAAKA,UAAU;QACf,OAAOA,UAAU;QACjB,QAAQ,CAAC,EAAEI,eAAe,EAAE;YACxB,MAAMC,cAAc,CAACC,eAAe,EAAE;gBAClCF,gBAAgB,CAACG;oBACb,MAAMC,UAAUD,MAAM,GAAG,CAACE,CAAAA,IAAKA,EAAE,GAAG,IAAI;oBACxC,IAAIH,AAAiB,OAAjBA,cACAR,MAAM,QAAQ,CAAC;2BAAIG;2BAAkBO;qBAAQ;yBAE7CV,MAAM,QAAQ,CAAC;2BACRG,cAAc,KAAK,CAAC,GAAGK;2BACvBE;2BACAP,cAAc,KAAK,CAACK,eAAe;qBACzC;gBAET;YACJ;YAEA,OAAO,WAAP,GACI,oBAACI,iBAAeA;gBACZ,OAAOZ,MAAM,KAAK;gBAClB,aAAaA,MAAM,WAAW;gBAC9B,MAAMA,MAAM,IAAI;gBAChB,MAAMA,MAAM,IAAI;gBAChB,QAAQG;gBACR,MAAK;gBACL,cAAc,IAAMI;gBACpB,eAAe,CAACM,GAAGC,QAAUP,YAAYO;gBACzC,cAAc,CAACD,GAAGC;oBACdd,MAAM,QAAQ,CAAC;2BACRG,cAAc,KAAK,CAAC,GAAGW;2BACvBX,cAAc,KAAK,CAACW,QAAQ;qBAClC;gBACL;;QAGZ;;AAGZ"}
1
+ {"version":3,"file":"presentation/fieldRenderers/CmsMultiFilePickerRenderer.js","sources":["../../../src/presentation/fieldRenderers/CmsMultiFilePickerRenderer.tsx"],"sourcesContent":["import React from \"react\";\nimport { createFieldRenderer } from \"@webiny/app-admin/features/formModel/createFieldRenderer.js\";\nimport { FileManager } from \"@webiny/app-admin/base/ui/FileManager.js\";\nimport type { FileManagerFileItem } from \"@webiny/app-admin/base/ui/FileManager.js\";\nimport { MultiFilePicker } from \"@webiny/admin-ui\";\nimport type { FileFieldSettings } from \"@webiny/app-admin/features/formModel/fieldTypes/FileFieldType.js\";\nimport { isEditableImageUrl, useCmsImageEditor } from \"~/presentation/hooks/useCmsImageEditor.js\";\nimport { CmsImageEditorDialog } from \"./CmsImageEditorDialog.js\";\n\ndeclare module \"@webiny/app-admin/features/formModel/abstractions.js\" {\n interface IFieldRendererRegistry {\n cmsMultiFilePicker: { fieldType: \"file\"; settings: FileFieldSettings };\n }\n}\n\nexport const CmsMultiFilePickerRenderer = createFieldRenderer<\"cmsMultiFilePicker\">(({ field }) => {\n const value = field.value as string[] | undefined;\n const settings = field.rendererSettings as FileFieldSettings | undefined;\n const currentValues = Array.isArray(value) ? value : [];\n const editor = useCmsImageEditor();\n\n return (\n <FileManager\n multiple\n images={settings?.images}\n accept={settings?.accept}\n own={settings?.own}\n scope={settings?.scope}\n render={({ showFileManager }) => {\n const selectFiles = (replaceIndex = -1) => {\n showFileManager((files: FileManagerFileItem[]) => {\n const newSrcs = files.map(f => f.src || \"\");\n if (replaceIndex === -1) {\n field.onChange([...currentValues, ...newSrcs]);\n } else {\n field.onChange([\n ...currentValues.slice(0, replaceIndex),\n ...newSrcs,\n ...currentValues.slice(replaceIndex + 1)\n ]);\n }\n });\n };\n\n return (\n <>\n <MultiFilePicker\n label={field.label}\n description={field.description}\n note={field.note}\n hint={field.help}\n values={currentValues}\n type=\"compact\"\n onSelectItem={() => selectFiles()}\n onReplaceItem={(_, index) => selectFiles(index)}\n onEditItem={(_, index) => {\n const url = currentValues[index];\n if (isEditableImageUrl(url)) {\n editor.openFor(url);\n }\n }}\n onRemoveItem={(_, index) => {\n field.onChange([\n ...currentValues.slice(0, index),\n ...currentValues.slice(index + 1)\n ]);\n }}\n />\n <CmsImageEditorDialog editor={editor} />\n </>\n );\n }}\n />\n );\n});\n"],"names":["CmsMultiFilePickerRenderer","createFieldRenderer","field","value","settings","currentValues","Array","editor","useCmsImageEditor","FileManager","showFileManager","selectFiles","replaceIndex","files","newSrcs","f","MultiFilePicker","_","index","url","isEditableImageUrl","CmsImageEditorDialog"],"mappings":";;;;;;AAeO,MAAMA,6BAA6BC,oBAA0C,CAAC,EAAEC,KAAK,EAAE;IAC1F,MAAMC,QAAQD,MAAM,KAAK;IACzB,MAAME,WAAWF,MAAM,gBAAgB;IACvC,MAAMG,gBAAgBC,MAAM,OAAO,CAACH,SAASA,QAAQ,EAAE;IACvD,MAAMI,SAASC;IAEf,OAAO,WAAP,GACI,oBAACC,aAAWA;QACR;QACA,QAAQL,UAAU;QAClB,QAAQA,UAAU;QAClB,KAAKA,UAAU;QACf,OAAOA,UAAU;QACjB,QAAQ,CAAC,EAAEM,eAAe,EAAE;YACxB,MAAMC,cAAc,CAACC,eAAe,EAAE;gBAClCF,gBAAgB,CAACG;oBACb,MAAMC,UAAUD,MAAM,GAAG,CAACE,CAAAA,IAAKA,EAAE,GAAG,IAAI;oBACxC,IAAIH,AAAiB,OAAjBA,cACAV,MAAM,QAAQ,CAAC;2BAAIG;2BAAkBS;qBAAQ;yBAE7CZ,MAAM,QAAQ,CAAC;2BACRG,cAAc,KAAK,CAAC,GAAGO;2BACvBE;2BACAT,cAAc,KAAK,CAACO,eAAe;qBACzC;gBAET;YACJ;YAEA,OAAO,WAAP,GACI,wDACI,oBAACI,iBAAeA;gBACZ,OAAOd,MAAM,KAAK;gBAClB,aAAaA,MAAM,WAAW;gBAC9B,MAAMA,MAAM,IAAI;gBAChB,MAAMA,MAAM,IAAI;gBAChB,QAAQG;gBACR,MAAK;gBACL,cAAc,IAAMM;gBACpB,eAAe,CAACM,GAAGC,QAAUP,YAAYO;gBACzC,YAAY,CAACD,GAAGC;oBACZ,MAAMC,MAAMd,aAAa,CAACa,MAAM;oBAChC,IAAIE,mBAAmBD,MACnBZ,OAAO,OAAO,CAACY;gBAEvB;gBACA,cAAc,CAACF,GAAGC;oBACdhB,MAAM,QAAQ,CAAC;2BACRG,cAAc,KAAK,CAAC,GAAGa;2BACvBb,cAAc,KAAK,CAACa,QAAQ;qBAClC;gBACL;8BAEJ,oBAACG,sBAAoBA;gBAAC,QAAQd;;QAG1C;;AAGZ"}
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import type { AssetImage } from "./assetValue.js";
3
+ interface CroppedAssetThumbProps {
4
+ src: string;
5
+ name: string;
6
+ image: AssetImage;
7
+ }
8
+ /**
9
+ * Fills its parent thumbnail box (the FilePicker's 56px square) with the asset's
10
+ * per-usage crop, cropping the crop region toward the focal point (`cover`) — the
11
+ * same square footprint and `object-cover` feel as the default thumbnail, just
12
+ * showing the cropped/focused region instead of the whole image.
13
+ */
14
+ export declare const CroppedAssetThumb: ({ src, name, image }: CroppedAssetThumbProps) => React.JSX.Element;
15
+ export {};
@@ -0,0 +1,25 @@
1
+ import react from "react";
2
+ import { getCroppedImageRenderStyles } from "@webiny/admin-ui";
3
+ const CroppedAssetThumb = ({ src, name, image })=>{
4
+ const { wrapper, image: imageStyle } = getCroppedImageRenderStyles(image.width ?? 0, image.height ?? 0, image.crop ?? void 0, image.focalPoint ? {
5
+ x: image.focalPoint.x,
6
+ y: image.focalPoint.y,
7
+ width: 1,
8
+ height: 1
9
+ } : void 0, {
10
+ boxWidth: 1,
11
+ boxHeight: 1,
12
+ fit: "cover"
13
+ });
14
+ return /*#__PURE__*/ react.createElement("div", {
15
+ style: wrapper
16
+ }, /*#__PURE__*/ react.createElement("img", {
17
+ src: src,
18
+ alt: name,
19
+ draggable: false,
20
+ style: imageStyle
21
+ }));
22
+ };
23
+ export { CroppedAssetThumb };
24
+
25
+ //# sourceMappingURL=CroppedAssetThumb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation/fieldRenderers/CroppedAssetThumb.js","sources":["../../../src/presentation/fieldRenderers/CroppedAssetThumb.tsx"],"sourcesContent":["import React from \"react\";\nimport { getCroppedImageRenderStyles } from \"@webiny/admin-ui\";\nimport type { AssetImage } from \"./assetValue.js\";\n\ninterface CroppedAssetThumbProps {\n src: string;\n name: string;\n image: AssetImage;\n}\n\n/**\n * Fills its parent thumbnail box (the FilePicker's 56px square) with the asset's\n * per-usage crop, cropping the crop region toward the focal point (`cover`) — the\n * same square footprint and `object-cover` feel as the default thumbnail, just\n * showing the cropped/focused region instead of the whole image.\n */\nexport const CroppedAssetThumb = ({ src, name, image }: CroppedAssetThumbProps) => {\n const { wrapper, image: imageStyle } = getCroppedImageRenderStyles(\n image.width ?? 0,\n image.height ?? 0,\n image.crop ?? undefined,\n image.focalPoint\n ? { x: image.focalPoint.x, y: image.focalPoint.y, width: 1, height: 1 }\n : undefined,\n // Square target (the thumbnail box is square); cover fills the parent box.\n { boxWidth: 1, boxHeight: 1, fit: \"cover\" }\n );\n\n return (\n <div style={wrapper}>\n <img src={src} alt={name} draggable={false} style={imageStyle} />\n </div>\n );\n};\n"],"names":["CroppedAssetThumb","src","name","image","wrapper","imageStyle","getCroppedImageRenderStyles","undefined"],"mappings":";;AAgBO,MAAMA,oBAAoB,CAAC,EAAEC,GAAG,EAAEC,IAAI,EAAEC,KAAK,EAA0B;IAC1E,MAAM,EAAEC,OAAO,EAAE,OAAOC,UAAU,EAAE,GAAGC,4BACnCH,MAAM,KAAK,IAAI,GACfA,MAAM,MAAM,IAAI,GAChBA,MAAM,IAAI,IAAII,QACdJ,MAAM,UAAU,GACV;QAAE,GAAGA,MAAM,UAAU,CAAC,CAAC;QAAE,GAAGA,MAAM,UAAU,CAAC,CAAC;QAAE,OAAO;QAAG,QAAQ;IAAE,IACpEI,QAEN;QAAE,UAAU;QAAG,WAAW;QAAG,KAAK;IAAQ;IAG9C,OAAO,WAAP,GACI,oBAAC;QAAI,OAAOH;qBACR,oBAAC;QAAI,KAAKH;QAAK,KAAKC;QAAM,WAAW;QAAO,OAAOG;;AAG/D"}
@@ -0,0 +1,10 @@
1
+ import type { ImageEditorValue } from "@webiny/admin-ui";
2
+ import type { FileManagerFileItem } from "@webiny/app-admin/base/ui/FileManager.js";
3
+ import type { Asset, AssetImage } from "@webiny/sdk";
4
+ export type { Asset, AssetImage };
5
+ export declare const isImageAsset: (asset: Asset | null | undefined) => boolean;
6
+ export declare const hasAsset: (asset: Asset | null | undefined) => boolean;
7
+ export declare const hasImageEdit: (image: AssetImage | null | undefined) => boolean;
8
+ export declare const fileItemToAsset: (file: FileManagerFileItem) => Asset;
9
+ export declare const assetImageToEditorValue: (image: AssetImage | null | undefined) => ImageEditorValue | undefined;
10
+ export declare const applyImageEditToAsset: (asset: Asset, edit: ImageEditorValue) => Asset;
@@ -0,0 +1,88 @@
1
+ const isImageAsset = (asset)=>"string" == typeof asset?.type && asset.type.startsWith("image/");
2
+ const hasAsset = (asset)=>"string" == typeof asset?.src && asset.src.length > 0;
3
+ const isFullCrop = (crop)=>!crop || 0 === crop.top && 0 === crop.left && 0 === crop.bottom && 0 === crop.right;
4
+ const isCenteredFocal = (focalPoint)=>!focalPoint || 0.5 === focalPoint.x && 0.5 === focalPoint.y;
5
+ const hasImageEdit = (image)=>{
6
+ if (!image) return false;
7
+ return !isFullCrop(image.crop) || !isCenteredFocal(image.focalPoint);
8
+ };
9
+ const fileItemToAsset = (file)=>{
10
+ const src = file.src;
11
+ const asset = {
12
+ id: file.id,
13
+ src,
14
+ url: src,
15
+ name: file.name,
16
+ type: file.type,
17
+ size: file.size
18
+ };
19
+ if ("string" == typeof file.type && file.type.startsWith("image/")) {
20
+ const meta = file.metadata?.image;
21
+ const image = {
22
+ width: meta?.width ?? file.width,
23
+ height: meta?.height ?? file.height
24
+ };
25
+ if (meta?.crop) image.crop = meta.crop;
26
+ if (meta?.focalPoint) image.focalPoint = {
27
+ x: meta.focalPoint.x,
28
+ y: meta.focalPoint.y
29
+ };
30
+ if (meta?.alt) image.alt = meta.alt;
31
+ if (meta?.caption) image.caption = meta.caption;
32
+ asset.image = image;
33
+ asset.url = buildAssetUrl(src, image.crop);
34
+ }
35
+ return asset;
36
+ };
37
+ const assetImageToEditorValue = (image)=>{
38
+ if (!image) return;
39
+ return {
40
+ crop: image.crop ?? void 0,
41
+ hotspot: image.focalPoint ? {
42
+ x: image.focalPoint.x,
43
+ y: image.focalPoint.y,
44
+ width: 1,
45
+ height: 1
46
+ } : void 0,
47
+ alt: image.alt ?? void 0,
48
+ caption: image.caption ?? void 0
49
+ };
50
+ };
51
+ const applyImageEditToAsset = (asset, edit)=>{
52
+ const crop = edit.crop ?? void 0;
53
+ return {
54
+ ...asset,
55
+ url: buildAssetUrl(asset.src, crop),
56
+ image: {
57
+ width: asset.image?.width,
58
+ height: asset.image?.height,
59
+ crop,
60
+ focalPoint: edit.hotspot ? {
61
+ x: edit.hotspot.x,
62
+ y: edit.hotspot.y
63
+ } : void 0,
64
+ alt: edit.alt ?? void 0,
65
+ caption: edit.caption ?? void 0
66
+ }
67
+ };
68
+ };
69
+ const round = (n)=>Math.round(10000 * n) / 10000;
70
+ function buildAssetUrl(src, crop) {
71
+ if (!crop) return src;
72
+ const top = crop.top ?? 0;
73
+ const left = crop.left ?? 0;
74
+ const bottom = crop.bottom ?? 0;
75
+ const right = crop.right ?? 0;
76
+ if (0 === top && 0 === left && 0 === bottom && 0 === right) return src;
77
+ const param = [
78
+ top,
79
+ left,
80
+ bottom,
81
+ right
82
+ ].map(round).join(",");
83
+ const separator = src.includes("?") ? "&" : "?";
84
+ return `${src}${separator}crop=${param}`;
85
+ }
86
+ export { applyImageEditToAsset, assetImageToEditorValue, fileItemToAsset, hasAsset, hasImageEdit, isImageAsset };
87
+
88
+ //# sourceMappingURL=assetValue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation/fieldRenderers/assetValue.js","sources":["../../../src/presentation/fieldRenderers/assetValue.ts"],"sourcesContent":["import type { ImageEditorValue } from \"@webiny/admin-ui\";\nimport type { FileManagerFileItem } from \"@webiny/app-admin/base/ui/FileManager.js\";\nimport type { Asset, AssetImage } from \"@webiny/sdk\";\n\nexport type { Asset, AssetImage };\n\nexport const isImageAsset = (asset: Asset | null | undefined): boolean => {\n return typeof asset?.type === \"string\" && asset.type.startsWith(\"image/\");\n};\n\nexport const hasAsset = (asset: Asset | null | undefined): boolean => {\n return typeof asset?.src === \"string\" && asset.src.length > 0;\n};\n\nconst isFullCrop = (crop: AssetImage[\"crop\"]): boolean => {\n return !crop || (crop.top === 0 && crop.left === 0 && crop.bottom === 0 && crop.right === 0);\n};\n\nconst isCenteredFocal = (focalPoint: AssetImage[\"focalPoint\"]): boolean => {\n return !focalPoint || (focalPoint.x === 0.5 && focalPoint.y === 0.5);\n};\n\nexport const hasImageEdit = (image: AssetImage | null | undefined): boolean => {\n if (!image) {\n return false;\n }\n return !isFullCrop(image.crop) || !isCenteredFocal(image.focalPoint);\n};\n\ninterface FileImageMetadata {\n width?: number;\n height?: number;\n crop?: { top: number; left: number; bottom: number; right: number };\n focalPoint?: { x: number; y: number };\n alt?: string;\n caption?: string;\n}\n\nexport const fileItemToAsset = (file: FileManagerFileItem): Asset => {\n const src = file.src;\n const asset: Asset = {\n id: file.id,\n src,\n url: src,\n name: file.name,\n type: file.type,\n size: file.size\n };\n if (typeof file.type === \"string\" && file.type.startsWith(\"image/\")) {\n const meta = file.metadata?.image as FileImageMetadata | undefined;\n const image: AssetImage = {\n width: meta?.width ?? file.width,\n height: meta?.height ?? file.height\n };\n if (meta?.crop) {\n image.crop = meta.crop;\n }\n if (meta?.focalPoint) {\n image.focalPoint = { x: meta.focalPoint.x, y: meta.focalPoint.y };\n }\n if (meta?.alt) {\n image.alt = meta.alt;\n }\n if (meta?.caption) {\n image.caption = meta.caption;\n }\n asset.image = image;\n asset.url = buildAssetUrl(src, image.crop);\n }\n return asset;\n};\n\nexport const assetImageToEditorValue = (\n image: AssetImage | null | undefined\n): ImageEditorValue | undefined => {\n if (!image) {\n return undefined;\n }\n return {\n crop: image.crop ?? undefined,\n hotspot: image.focalPoint\n ? { x: image.focalPoint.x, y: image.focalPoint.y, width: 1, height: 1 }\n : undefined,\n alt: image.alt ?? undefined,\n caption: image.caption ?? undefined\n };\n};\n\nexport const applyImageEditToAsset = (asset: Asset, edit: ImageEditorValue): Asset => {\n const crop = edit.crop ?? undefined;\n return {\n ...asset,\n url: buildAssetUrl(asset.src, crop),\n image: {\n width: asset.image?.width,\n height: asset.image?.height,\n crop,\n focalPoint: edit.hotspot ? { x: edit.hotspot.x, y: edit.hotspot.y } : undefined,\n alt: edit.alt ?? undefined,\n caption: edit.caption ?? undefined\n }\n };\n};\n\nconst round = (n: number): number => Math.round(n * 10000) / 10000;\n\nfunction buildAssetUrl(src: string, crop: AssetImage[\"crop\"] | null | undefined): string {\n if (!crop) {\n return src;\n }\n const top = crop.top ?? 0;\n const left = crop.left ?? 0;\n const bottom = crop.bottom ?? 0;\n const right = crop.right ?? 0;\n if (top === 0 && left === 0 && bottom === 0 && right === 0) {\n return src;\n }\n const param = [top, left, bottom, right].map(round).join(\",\");\n const separator = src.includes(\"?\") ? \"&\" : \"?\";\n return `${src}${separator}crop=${param}`;\n}\n"],"names":["isImageAsset","asset","hasAsset","isFullCrop","crop","isCenteredFocal","focalPoint","hasImageEdit","image","fileItemToAsset","file","src","meta","buildAssetUrl","assetImageToEditorValue","undefined","applyImageEditToAsset","edit","round","n","Math","top","left","bottom","right","param","separator"],"mappings":"AAMO,MAAMA,eAAe,CAACC,QAClB,AAAuB,YAAvB,OAAOA,OAAO,QAAqBA,MAAM,IAAI,CAAC,UAAU,CAAC;AAG7D,MAAMC,WAAW,CAACD,QACd,AAAsB,YAAtB,OAAOA,OAAO,OAAoBA,MAAM,GAAG,CAAC,MAAM,GAAG;AAGhE,MAAME,aAAa,CAACC,OACT,CAACA,QAASA,AAAa,MAAbA,KAAK,GAAG,IAAUA,AAAc,MAAdA,KAAK,IAAI,IAAUA,AAAgB,MAAhBA,KAAK,MAAM,IAAUA,AAAe,MAAfA,KAAK,KAAK;AAGzF,MAAMC,kBAAkB,CAACC,aACd,CAACA,cAAeA,AAAiB,QAAjBA,WAAW,CAAC,IAAYA,AAAiB,QAAjBA,WAAW,CAAC;AAGxD,MAAMC,eAAe,CAACC;IACzB,IAAI,CAACA,OACD,OAAO;IAEX,OAAO,CAACL,WAAWK,MAAM,IAAI,KAAK,CAACH,gBAAgBG,MAAM,UAAU;AACvE;AAWO,MAAMC,kBAAkB,CAACC;IAC5B,MAAMC,MAAMD,KAAK,GAAG;IACpB,MAAMT,QAAe;QACjB,IAAIS,KAAK,EAAE;QACXC;QACA,KAAKA;QACL,MAAMD,KAAK,IAAI;QACf,MAAMA,KAAK,IAAI;QACf,MAAMA,KAAK,IAAI;IACnB;IACA,IAAI,AAAqB,YAArB,OAAOA,KAAK,IAAI,IAAiBA,KAAK,IAAI,CAAC,UAAU,CAAC,WAAW;QACjE,MAAME,OAAOF,KAAK,QAAQ,EAAE;QAC5B,MAAMF,QAAoB;YACtB,OAAOI,MAAM,SAASF,KAAK,KAAK;YAChC,QAAQE,MAAM,UAAUF,KAAK,MAAM;QACvC;QACA,IAAIE,MAAM,MACNJ,MAAM,IAAI,GAAGI,KAAK,IAAI;QAE1B,IAAIA,MAAM,YACNJ,MAAM,UAAU,GAAG;YAAE,GAAGI,KAAK,UAAU,CAAC,CAAC;YAAE,GAAGA,KAAK,UAAU,CAAC,CAAC;QAAC;QAEpE,IAAIA,MAAM,KACNJ,MAAM,GAAG,GAAGI,KAAK,GAAG;QAExB,IAAIA,MAAM,SACNJ,MAAM,OAAO,GAAGI,KAAK,OAAO;QAEhCX,MAAM,KAAK,GAAGO;QACdP,MAAM,GAAG,GAAGY,cAAcF,KAAKH,MAAM,IAAI;IAC7C;IACA,OAAOP;AACX;AAEO,MAAMa,0BAA0B,CACnCN;IAEA,IAAI,CAACA,OACD;IAEJ,OAAO;QACH,MAAMA,MAAM,IAAI,IAAIO;QACpB,SAASP,MAAM,UAAU,GACnB;YAAE,GAAGA,MAAM,UAAU,CAAC,CAAC;YAAE,GAAGA,MAAM,UAAU,CAAC,CAAC;YAAE,OAAO;YAAG,QAAQ;QAAE,IACpEO;QACN,KAAKP,MAAM,GAAG,IAAIO;QAClB,SAASP,MAAM,OAAO,IAAIO;IAC9B;AACJ;AAEO,MAAMC,wBAAwB,CAACf,OAAcgB;IAChD,MAAMb,OAAOa,KAAK,IAAI,IAAIF;IAC1B,OAAO;QACH,GAAGd,KAAK;QACR,KAAKY,cAAcZ,MAAM,GAAG,EAAEG;QAC9B,OAAO;YACH,OAAOH,MAAM,KAAK,EAAE;YACpB,QAAQA,MAAM,KAAK,EAAE;YACrBG;YACA,YAAYa,KAAK,OAAO,GAAG;gBAAE,GAAGA,KAAK,OAAO,CAAC,CAAC;gBAAE,GAAGA,KAAK,OAAO,CAAC,CAAC;YAAC,IAAIF;YACtE,KAAKE,KAAK,GAAG,IAAIF;YACjB,SAASE,KAAK,OAAO,IAAIF;QAC7B;IACJ;AACJ;AAEA,MAAMG,QAAQ,CAACC,IAAsBC,KAAK,KAAK,CAACD,AAAI,QAAJA,KAAa;AAE7D,SAASN,cAAcF,GAAW,EAAEP,IAA2C;IAC3E,IAAI,CAACA,MACD,OAAOO;IAEX,MAAMU,MAAMjB,KAAK,GAAG,IAAI;IACxB,MAAMkB,OAAOlB,KAAK,IAAI,IAAI;IAC1B,MAAMmB,SAASnB,KAAK,MAAM,IAAI;IAC9B,MAAMoB,QAAQpB,KAAK,KAAK,IAAI;IAC5B,IAAIiB,AAAQ,MAARA,OAAaC,AAAS,MAATA,QAAcC,AAAW,MAAXA,UAAgBC,AAAU,MAAVA,OAC3C,OAAOb;IAEX,MAAMc,QAAQ;QAACJ;QAAKC;QAAMC;QAAQC;KAAM,CAAC,GAAG,CAACN,OAAO,IAAI,CAAC;IACzD,MAAMQ,YAAYf,IAAI,QAAQ,CAAC,OAAO,MAAM;IAC5C,OAAO,GAAGA,MAAMe,UAAU,KAAK,EAAED,OAAO;AAC5C"}
@@ -0,0 +1,10 @@
1
+ import type { ImageEditorValue } from "@webiny/admin-ui";
2
+ /** Whether a stored file URL points at a raster image we can crop (SVGs excluded). */
3
+ export declare const isEditableImageUrl: (url: string | undefined) => url is string;
4
+ export declare function useCmsImageEditor(): {
5
+ open: boolean;
6
+ openFor: (url: string) => Promise<void>;
7
+ close: () => void;
8
+ file: import("@webiny/sdk/index").FmFile | null;
9
+ save: (edit: ImageEditorValue) => Promise<void>;
10
+ };
@@ -0,0 +1,76 @@
1
+ import { useCallback, useState } from "react";
2
+ import { useSnackbar } from "@webiny/app-admin";
3
+ import { useContainer } from "@webiny/app";
4
+ import { GetFileUseCase } from "../../features/getFile/abstractions.js";
5
+ import { UpdateFileUseCase } from "../../features/updateFile/abstractions.js";
6
+ const IMAGE_URL = /\.(jpe?g|png|webp|avif|gif)$/i;
7
+ const isEditableImageUrl = (url)=>!!url && IMAGE_URL.test(url.split("?")[0]);
8
+ const parseFileId = (url)=>{
9
+ const clean = url.split("?")[0].replace(/\/+$/, "");
10
+ const parts = clean.split("/").filter(Boolean);
11
+ return parts.length >= 2 ? parts[parts.length - 2] : void 0;
12
+ };
13
+ function useCmsImageEditor() {
14
+ const { showSnackbar } = useSnackbar();
15
+ const container = useContainer();
16
+ const getFileUseCase = container.resolve(GetFileUseCase);
17
+ const updateFileUseCase = container.resolve(UpdateFileUseCase);
18
+ const [open, setOpen] = useState(false);
19
+ const [file, setFile] = useState(null);
20
+ const openFor = useCallback(async (url)=>{
21
+ const id = parseFileId(url);
22
+ if (!id) return void showSnackbar("Could not resolve the image to edit.");
23
+ const result = await getFileUseCase.execute({
24
+ id
25
+ });
26
+ if (result.success) {
27
+ setFile(result.file);
28
+ setOpen(true);
29
+ } else showSnackbar(result.error.message);
30
+ }, [
31
+ getFileUseCase,
32
+ showSnackbar
33
+ ]);
34
+ const close = useCallback(()=>setOpen(false), []);
35
+ const save = useCallback(async (edit)=>{
36
+ if (!file) return;
37
+ const metadata = {
38
+ ...file.metadata
39
+ };
40
+ const image = {
41
+ ...metadata.image
42
+ };
43
+ image.crop = edit.crop ?? void 0;
44
+ image.focalPoint = edit.hotspot ? {
45
+ x: edit.hotspot.x,
46
+ y: edit.hotspot.y
47
+ } : void 0;
48
+ image.alt = edit.alt ?? void 0;
49
+ image.caption = edit.caption ?? void 0;
50
+ metadata.image = image;
51
+ const result = await updateFileUseCase.execute({
52
+ id: file.id,
53
+ data: {
54
+ metadata
55
+ }
56
+ });
57
+ if (result.success) {
58
+ showSnackbar("Image settings saved.");
59
+ setFile(result.file);
60
+ } else showSnackbar(result.error.message);
61
+ }, [
62
+ file,
63
+ updateFileUseCase,
64
+ showSnackbar
65
+ ]);
66
+ return {
67
+ open,
68
+ openFor,
69
+ close,
70
+ file,
71
+ save
72
+ };
73
+ }
74
+ export { isEditableImageUrl, useCmsImageEditor };
75
+
76
+ //# sourceMappingURL=useCmsImageEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation/hooks/useCmsImageEditor.js","sources":["../../../src/presentation/hooks/useCmsImageEditor.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport { useSnackbar } from \"@webiny/app-admin\";\nimport { useContainer } from \"@webiny/app\";\nimport type { ImageEditorValue } from \"@webiny/admin-ui\";\nimport { GetFileUseCase } from \"~/features/getFile/abstractions.js\";\nimport { UpdateFileUseCase } from \"~/features/updateFile/abstractions.js\";\nimport type { FileItem } from \"~/domain/types.js\";\n\nconst IMAGE_URL = /\\.(jpe?g|png|webp|avif|gif)$/i;\n\n/** Whether a stored file URL points at a raster image we can crop (SVGs excluded). */\nexport const isEditableImageUrl = (url: string | undefined): url is string => {\n return !!url && IMAGE_URL.test(url.split(\"?\")[0]);\n};\n\n/**\n * Resolve a File's id from its public URL. File keys are `{id}/{filename}` and the\n * URL is `{srcPrefix}{key}`, so the id is the second-to-last path segment (this is\n * the same convention the asset-delivery ObjectKey relies on).\n */\nconst parseFileId = (url: string): string | undefined => {\n const clean = url.split(\"?\")[0].replace(/\\/+$/, \"\");\n const parts = clean.split(\"/\").filter(Boolean);\n return parts.length >= 2 ? parts[parts.length - 2] : undefined;\n};\n\nexport function useCmsImageEditor() {\n const { showSnackbar } = useSnackbar();\n const container = useContainer();\n const getFileUseCase = container.resolve(GetFileUseCase);\n const updateFileUseCase = container.resolve(UpdateFileUseCase);\n\n const [open, setOpen] = useState(false);\n const [file, setFile] = useState<FileItem | null>(null);\n\n const openFor = useCallback(\n async (url: string) => {\n const id = parseFileId(url);\n if (!id) {\n showSnackbar(\"Could not resolve the image to edit.\");\n return;\n }\n const result = await getFileUseCase.execute({ id });\n if (result.success) {\n setFile(result.file);\n setOpen(true);\n } else {\n showSnackbar(result.error.message);\n }\n },\n [getFileUseCase, showSnackbar]\n );\n\n const close = useCallback(() => setOpen(false), []);\n\n const save = useCallback(\n async (edit: ImageEditorValue) => {\n if (!file) {\n return;\n }\n const metadata: Record<string, any> = { ...file.metadata };\n const image: Record<string, any> = { ...metadata.image };\n\n image.crop = edit.crop ?? undefined;\n image.focalPoint = edit.hotspot ? { x: edit.hotspot.x, y: edit.hotspot.y } : undefined;\n image.alt = edit.alt ?? undefined;\n image.caption = edit.caption ?? undefined;\n\n metadata.image = image;\n\n const result = await updateFileUseCase.execute({ id: file.id, data: { metadata } });\n if (result.success) {\n showSnackbar(\"Image settings saved.\");\n setFile(result.file);\n } else {\n showSnackbar(result.error.message);\n }\n },\n [file, updateFileUseCase, showSnackbar]\n );\n\n return { open, openFor, close, file, save };\n}\n"],"names":["IMAGE_URL","isEditableImageUrl","url","parseFileId","clean","parts","Boolean","undefined","useCmsImageEditor","showSnackbar","useSnackbar","container","useContainer","getFileUseCase","GetFileUseCase","updateFileUseCase","UpdateFileUseCase","open","setOpen","useState","file","setFile","openFor","useCallback","id","result","close","save","edit","metadata","image"],"mappings":";;;;;AAQA,MAAMA,YAAY;AAGX,MAAMC,qBAAqB,CAACC,MACxB,CAAC,CAACA,OAAOF,UAAU,IAAI,CAACE,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE;AAQpD,MAAMC,cAAc,CAACD;IACjB,MAAME,QAAQF,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,QAAQ;IAChD,MAAMG,QAAQD,MAAM,KAAK,CAAC,KAAK,MAAM,CAACE;IACtC,OAAOD,MAAM,MAAM,IAAI,IAAIA,KAAK,CAACA,MAAM,MAAM,GAAG,EAAE,GAAGE;AACzD;AAEO,SAASC;IACZ,MAAM,EAAEC,YAAY,EAAE,GAAGC;IACzB,MAAMC,YAAYC;IAClB,MAAMC,iBAAiBF,UAAU,OAAO,CAACG;IACzC,MAAMC,oBAAoBJ,UAAU,OAAO,CAACK;IAE5C,MAAM,CAACC,MAAMC,QAAQ,GAAGC,SAAS;IACjC,MAAM,CAACC,MAAMC,QAAQ,GAAGF,SAA0B;IAElD,MAAMG,UAAUC,YACZ,OAAOrB;QACH,MAAMsB,KAAKrB,YAAYD;QACvB,IAAI,CAACsB,IAAI,YACLf,aAAa;QAGjB,MAAMgB,SAAS,MAAMZ,eAAe,OAAO,CAAC;YAAEW;QAAG;QACjD,IAAIC,OAAO,OAAO,EAAE;YAChBJ,QAAQI,OAAO,IAAI;YACnBP,QAAQ;QACZ,OACIT,aAAagB,OAAO,KAAK,CAAC,OAAO;IAEzC,GACA;QAACZ;QAAgBJ;KAAa;IAGlC,MAAMiB,QAAQH,YAAY,IAAML,QAAQ,QAAQ,EAAE;IAElD,MAAMS,OAAOJ,YACT,OAAOK;QACH,IAAI,CAACR,MACD;QAEJ,MAAMS,WAAgC;YAAE,GAAGT,KAAK,QAAQ;QAAC;QACzD,MAAMU,QAA6B;YAAE,GAAGD,SAAS,KAAK;QAAC;QAEvDC,MAAM,IAAI,GAAGF,KAAK,IAAI,IAAIrB;QAC1BuB,MAAM,UAAU,GAAGF,KAAK,OAAO,GAAG;YAAE,GAAGA,KAAK,OAAO,CAAC,CAAC;YAAE,GAAGA,KAAK,OAAO,CAAC,CAAC;QAAC,IAAIrB;QAC7EuB,MAAM,GAAG,GAAGF,KAAK,GAAG,IAAIrB;QACxBuB,MAAM,OAAO,GAAGF,KAAK,OAAO,IAAIrB;QAEhCsB,SAAS,KAAK,GAAGC;QAEjB,MAAML,SAAS,MAAMV,kBAAkB,OAAO,CAAC;YAAE,IAAIK,KAAK,EAAE;YAAE,MAAM;gBAAES;YAAS;QAAE;QACjF,IAAIJ,OAAO,OAAO,EAAE;YAChBhB,aAAa;YACbY,QAAQI,OAAO,IAAI;QACvB,OACIhB,aAAagB,OAAO,KAAK,CAAC,OAAO;IAEzC,GACA;QAACL;QAAML;QAAmBN;KAAa;IAG3C,OAAO;QAAEQ;QAAMK;QAASI;QAAON;QAAMO;IAAK;AAC9C"}
@@ -0,0 +1,14 @@
1
+ import type { ImageEditorValue } from "@webiny/admin-ui";
2
+ import type { FileItem } from "../../domain/types.js";
3
+ interface UseEditImageOptions {
4
+ /** Called with the updated file after a successful save (e.g. to refresh the UI). */
5
+ onSaved?: (file: FileItem) => void;
6
+ }
7
+ export declare function useEditImage(file: FileItem, options?: UseEditImageOptions): {
8
+ open: boolean;
9
+ openEditor: () => void;
10
+ closeEditor: () => void;
11
+ value: ImageEditorValue | undefined;
12
+ save: (edit: ImageEditorValue) => Promise<void>;
13
+ };
14
+ export {};
@@ -0,0 +1,67 @@
1
+ import { useCallback, useState } from "react";
2
+ import { useSnackbar } from "@webiny/app-admin";
3
+ import { useContainer } from "@webiny/app";
4
+ import { UpdateFileUseCase } from "../../features/updateFile/abstractions.js";
5
+ function useEditImage(file, options = {}) {
6
+ const { showSnackbar } = useSnackbar();
7
+ const container = useContainer();
8
+ const updateFileUseCase = container.resolve(UpdateFileUseCase);
9
+ const { onSaved } = options;
10
+ const [open, setOpen] = useState(false);
11
+ const img = file.metadata?.image;
12
+ const value = img ? {
13
+ crop: img.crop ?? void 0,
14
+ hotspot: img.focalPoint ? {
15
+ x: img.focalPoint.x,
16
+ y: img.focalPoint.y,
17
+ width: 1,
18
+ height: 1
19
+ } : void 0,
20
+ alt: img.alt ?? void 0,
21
+ caption: img.caption ?? void 0
22
+ } : void 0;
23
+ const openEditor = useCallback(()=>setOpen(true), []);
24
+ const closeEditor = useCallback(()=>setOpen(false), []);
25
+ const save = useCallback(async (edit)=>{
26
+ const metadata = {
27
+ ...file.metadata
28
+ };
29
+ const image = {
30
+ ...metadata.image
31
+ };
32
+ image.crop = edit.crop ?? void 0;
33
+ image.focalPoint = edit.hotspot ? {
34
+ x: edit.hotspot.x,
35
+ y: edit.hotspot.y
36
+ } : void 0;
37
+ image.alt = edit.alt ?? void 0;
38
+ image.caption = edit.caption ?? void 0;
39
+ metadata.image = image;
40
+ const result = await updateFileUseCase.execute({
41
+ id: file.id,
42
+ data: {
43
+ metadata
44
+ }
45
+ });
46
+ if (result.success) {
47
+ showSnackbar("Image settings saved.");
48
+ onSaved?.(result.file);
49
+ } else showSnackbar(result.error.message);
50
+ }, [
51
+ file.id,
52
+ file.metadata,
53
+ updateFileUseCase,
54
+ showSnackbar,
55
+ onSaved
56
+ ]);
57
+ return {
58
+ open,
59
+ openEditor,
60
+ closeEditor,
61
+ value,
62
+ save
63
+ };
64
+ }
65
+ export { useEditImage };
66
+
67
+ //# sourceMappingURL=useEditImage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation/hooks/useEditImage.js","sources":["../../../src/presentation/hooks/useEditImage.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport { useSnackbar } from \"@webiny/app-admin\";\nimport { useContainer } from \"@webiny/app\";\nimport type { ImageEditorValue } from \"@webiny/admin-ui\";\nimport { UpdateFileUseCase } from \"~/features/updateFile/abstractions.js\";\nimport type { FileItem } from \"~/domain/types.js\";\n\ninterface UseEditImageOptions {\n /** Called with the updated file after a successful save (e.g. to refresh the UI). */\n onSaved?: (file: FileItem) => void;\n}\n\nexport function useEditImage(file: FileItem, options: UseEditImageOptions = {}) {\n const { showSnackbar } = useSnackbar();\n const container = useContainer();\n const updateFileUseCase = container.resolve(UpdateFileUseCase);\n const { onSaved } = options;\n\n const [open, setOpen] = useState(false);\n\n const img = file.metadata?.image as Record<string, any> | undefined;\n const value: ImageEditorValue | undefined = img\n ? {\n crop: img.crop ?? undefined,\n hotspot: img.focalPoint\n ? { x: img.focalPoint.x, y: img.focalPoint.y, width: 1, height: 1 }\n : undefined,\n alt: img.alt ?? undefined,\n caption: img.caption ?? undefined\n }\n : undefined;\n\n const openEditor = useCallback(() => setOpen(true), []);\n const closeEditor = useCallback(() => setOpen(false), []);\n\n const save = useCallback(\n async (edit: ImageEditorValue) => {\n const metadata: Record<string, any> = { ...file.metadata };\n const image: Record<string, any> = { ...metadata.image };\n\n image.crop = edit.crop ?? undefined;\n image.focalPoint = edit.hotspot ? { x: edit.hotspot.x, y: edit.hotspot.y } : undefined;\n image.alt = edit.alt ?? undefined;\n image.caption = edit.caption ?? undefined;\n\n metadata.image = image;\n\n const result = await updateFileUseCase.execute({ id: file.id, data: { metadata } });\n\n if (result.success) {\n showSnackbar(`Image settings saved.`);\n onSaved?.(result.file);\n } else {\n showSnackbar(result.error.message);\n }\n },\n [file.id, file.metadata, updateFileUseCase, showSnackbar, onSaved]\n );\n\n return { open, openEditor, closeEditor, value, save };\n}\n"],"names":["useEditImage","file","options","showSnackbar","useSnackbar","container","useContainer","updateFileUseCase","UpdateFileUseCase","onSaved","open","setOpen","useState","img","value","undefined","openEditor","useCallback","closeEditor","save","edit","metadata","image","result"],"mappings":";;;;AAYO,SAASA,aAAaC,IAAc,EAAEC,UAA+B,CAAC,CAAC;IAC1E,MAAM,EAAEC,YAAY,EAAE,GAAGC;IACzB,MAAMC,YAAYC;IAClB,MAAMC,oBAAoBF,UAAU,OAAO,CAACG;IAC5C,MAAM,EAAEC,OAAO,EAAE,GAAGP;IAEpB,MAAM,CAACQ,MAAMC,QAAQ,GAAGC,SAAS;IAEjC,MAAMC,MAAMZ,KAAK,QAAQ,EAAE;IAC3B,MAAMa,QAAsCD,MACtC;QACI,MAAMA,IAAI,IAAI,IAAIE;QAClB,SAASF,IAAI,UAAU,GACjB;YAAE,GAAGA,IAAI,UAAU,CAAC,CAAC;YAAE,GAAGA,IAAI,UAAU,CAAC,CAAC;YAAE,OAAO;YAAG,QAAQ;QAAE,IAChEE;QACN,KAAKF,IAAI,GAAG,IAAIE;QAChB,SAASF,IAAI,OAAO,IAAIE;IAC5B,IACAA;IAEN,MAAMC,aAAaC,YAAY,IAAMN,QAAQ,OAAO,EAAE;IACtD,MAAMO,cAAcD,YAAY,IAAMN,QAAQ,QAAQ,EAAE;IAExD,MAAMQ,OAAOF,YACT,OAAOG;QACH,MAAMC,WAAgC;YAAE,GAAGpB,KAAK,QAAQ;QAAC;QACzD,MAAMqB,QAA6B;YAAE,GAAGD,SAAS,KAAK;QAAC;QAEvDC,MAAM,IAAI,GAAGF,KAAK,IAAI,IAAIL;QAC1BO,MAAM,UAAU,GAAGF,KAAK,OAAO,GAAG;YAAE,GAAGA,KAAK,OAAO,CAAC,CAAC;YAAE,GAAGA,KAAK,OAAO,CAAC,CAAC;QAAC,IAAIL;QAC7EO,MAAM,GAAG,GAAGF,KAAK,GAAG,IAAIL;QACxBO,MAAM,OAAO,GAAGF,KAAK,OAAO,IAAIL;QAEhCM,SAAS,KAAK,GAAGC;QAEjB,MAAMC,SAAS,MAAMhB,kBAAkB,OAAO,CAAC;YAAE,IAAIN,KAAK,EAAE;YAAE,MAAM;gBAAEoB;YAAS;QAAE;QAEjF,IAAIE,OAAO,OAAO,EAAE;YAChBpB,aAAa;YACbM,UAAUc,OAAO,IAAI;QACzB,OACIpB,aAAaoB,OAAO,KAAK,CAAC,OAAO;IAEzC,GACA;QAACtB,KAAK,EAAE;QAAEA,KAAK,QAAQ;QAAEM;QAAmBJ;QAAcM;KAAQ;IAGtE,OAAO;QAAEC;QAAMM;QAAYE;QAAaJ;QAAOK;IAAK;AACxD"}