@webiny/app-file-manager 6.4.11 → 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.
- package/app.js +21 -2
- package/app.js.map +1 -1
- package/domain/tagsHelpers.d.ts +2 -2
- package/domain/types.d.ts +0 -8
- package/features/permissions/abstractions.d.ts +2 -2
- package/features/permissions/feature.d.ts +3 -3
- package/features/shared/FILE_FIELDS.js +4 -0
- package/features/shared/FILE_FIELDS.js.map +1 -1
- package/features/shared/FileFieldsProviderWithWcp.d.ts +5 -5
- package/features/shared/FileFieldsProviderWithWcp.js +7 -7
- package/features/shared/FileFieldsProviderWithWcp.js.map +1 -1
- package/modules/AiEnrichment/AiImageEnrichmentEventHandler.d.ts +18 -0
- package/modules/AiEnrichment/AiImageEnrichmentEventHandler.js +33 -0
- package/modules/AiEnrichment/AiImageEnrichmentEventHandler.js.map +1 -0
- package/modules/AiEnrichment/ReenrichFileGateway.d.ts +12 -0
- package/modules/AiEnrichment/ReenrichFileGateway.js +23 -0
- package/modules/AiEnrichment/ReenrichFileGateway.js.map +1 -0
- package/modules/AiEnrichment/ReenrichWithAi.d.ts +11 -0
- package/modules/AiEnrichment/ReenrichWithAi.js +57 -0
- package/modules/AiEnrichment/ReenrichWithAi.js.map +1 -0
- package/modules/AiEnrichment/ReenrichWithAiPresenter.d.ts +50 -0
- package/modules/AiEnrichment/ReenrichWithAiPresenter.js +114 -0
- package/modules/AiEnrichment/ReenrichWithAiPresenter.js.map +1 -0
- package/modules/AiEnrichment/StreamingPlaceholder.d.ts +15 -0
- package/modules/AiEnrichment/StreamingPlaceholder.js +32 -0
- package/modules/AiEnrichment/StreamingPlaceholder.js.map +1 -0
- package/modules/AiEnrichment/abstractions.d.ts +70 -0
- package/modules/AiEnrichment/abstractions.js +6 -0
- package/modules/AiEnrichment/abstractions.js.map +1 -0
- package/modules/AiEnrichment/feature.d.ts +3 -0
- package/modules/AiEnrichment/feature.js +21 -0
- package/modules/AiEnrichment/feature.js.map +1 -0
- package/modules/AiEnrichment/index.d.ts +2 -0
- package/modules/AiEnrichment/index.js +9 -0
- package/modules/AiEnrichment/index.js.map +1 -0
- package/modules/Enterprise/index.js +3 -3
- package/modules/Enterprise/index.js.map +1 -1
- package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.d.ts +26 -0
- package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.js +39 -0
- package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.js.map +1 -0
- package/modules/HeadlessCms/fieldType/AssetFieldType.d.ts +34 -0
- package/modules/HeadlessCms/fieldType/AssetFieldType.js +42 -0
- package/modules/HeadlessCms/fieldType/AssetFieldType.js.map +1 -0
- package/modules/HeadlessCms/fieldType/AssetFormFieldType.d.ts +21 -0
- package/modules/HeadlessCms/fieldType/AssetFormFieldType.js +42 -0
- package/modules/HeadlessCms/fieldType/AssetFormFieldType.js.map +1 -0
- package/modules/HeadlessCms/fieldType/AssetInputRenderer.d.ts +15 -0
- package/modules/HeadlessCms/fieldType/AssetInputRenderer.js +20 -0
- package/modules/HeadlessCms/fieldType/AssetInputRenderer.js.map +1 -0
- package/modules/HeadlessCms/fieldType/AssetInputsRenderer.d.ts +15 -0
- package/modules/HeadlessCms/fieldType/AssetInputsRenderer.js +20 -0
- package/modules/HeadlessCms/fieldType/AssetInputsRenderer.js.map +1 -0
- package/modules/HeadlessCms/fieldType/FileFieldSettingsModifier.d.ts +16 -0
- package/modules/HeadlessCms/fieldType/FileFieldSettingsModifier.js +33 -0
- package/modules/HeadlessCms/fieldType/FileFieldSettingsModifier.js.map +1 -0
- package/modules/HeadlessCms/fieldType/FileFieldType.d.ts +25 -0
- package/modules/HeadlessCms/fieldType/FileFieldType.js +38 -0
- package/modules/HeadlessCms/fieldType/FileFieldType.js.map +1 -0
- package/modules/HeadlessCms/fieldType/FileInputRenderer.d.ts +15 -0
- package/modules/HeadlessCms/fieldType/FileInputRenderer.js +19 -0
- package/modules/HeadlessCms/fieldType/FileInputRenderer.js.map +1 -0
- package/modules/HeadlessCms/fieldType/FileInputsRenderer.d.ts +15 -0
- package/modules/HeadlessCms/fieldType/FileInputsRenderer.js +19 -0
- package/modules/HeadlessCms/fieldType/FileInputsRenderer.js.map +1 -0
- package/modules/HeadlessCms/fieldType/feature.d.ts +1 -0
- package/modules/HeadlessCms/fieldType/feature.js +27 -0
- package/modules/HeadlessCms/fieldType/feature.js.map +1 -0
- package/modules/HeadlessCms/fieldType/isAssetField.d.ts +2 -0
- package/modules/HeadlessCms/fieldType/isAssetField.js +4 -0
- package/modules/HeadlessCms/fieldType/isAssetField.js.map +1 -0
- package/package.json +24 -34
- package/presentation/FileActions/FileDetails/EditImage.d.ts +2 -0
- package/presentation/FileActions/FileDetails/EditImage.js +39 -0
- package/presentation/FileActions/FileDetails/EditImage.js.map +1 -0
- package/presentation/FileActions/index.js +8 -0
- package/presentation/FileActions/index.js.map +1 -1
- package/presentation/FileDetails/FileDetailsPresenter.d.ts +5 -4
- package/presentation/FileDetails/FileDetailsPresenter.js +40 -46
- package/presentation/FileDetails/FileDetailsPresenter.js.map +1 -1
- package/presentation/FileDetails/FileDetailsPresenter.test.js +39 -56
- package/presentation/FileDetails/FileDetailsPresenter.test.js.map +1 -1
- package/presentation/FileDetails/abstractions.d.ts +2 -0
- package/presentation/FileDetails/abstractions.js.map +1 -1
- package/presentation/FileDetails/components/Content.d.ts +6 -4
- package/presentation/FileDetails/components/Content.js +2 -2
- package/presentation/FileDetails/components/Content.js.map +1 -1
- package/presentation/FileDetails/components/FileDetailsDrawer.d.ts +2 -2
- package/presentation/FileDetails/components/FileDetailsDrawer.js +11 -5
- package/presentation/FileDetails/components/FileDetailsDrawer.js.map +1 -1
- package/presentation/FileList/FileListDataSource.d.ts +8 -12
- package/presentation/FileList/FileListDataSource.js +47 -87
- package/presentation/FileList/FileListDataSource.js.map +1 -1
- package/presentation/FileList/FileListPresenter.test.js +9 -3
- package/presentation/FileList/FileListPresenter.test.js.map +1 -1
- package/presentation/FileList/FileManagerPresenter.d.ts +1 -0
- package/presentation/FileList/FileManagerPresenter.js +15 -5
- package/presentation/FileList/FileManagerPresenter.js.map +1 -1
- package/presentation/FileList/abstractions.d.ts +3 -0
- package/presentation/FileList/abstractions.js.map +1 -1
- package/presentation/FileList/components/BulkActions/BulkActionBar.d.ts +2 -2
- package/presentation/FileList/components/BulkActions/BulkActionDelete.d.ts +2 -2
- package/presentation/FileList/components/BulkActions/BulkActionMove.d.ts +2 -2
- package/presentation/FileList/components/BulkActions/index.d.ts +0 -1
- package/presentation/FileList/components/BulkActions/index.js +0 -1
- package/presentation/FileList/components/BulkActions/useBulkActionWorker.d.ts +9 -8
- package/presentation/FileList/components/BulkActions/useBulkActionWorker.js +3 -9
- package/presentation/FileList/components/BulkActions/useBulkActionWorker.js.map +1 -1
- package/presentation/FileList/components/Filters/SubFoldersToggle.d.ts +2 -2
- package/presentation/FileList/components/Filters/TagsFilter.d.ts +2 -2
- package/presentation/FileList/components/Filters/TypeFilter.d.ts +2 -2
- package/presentation/FileList/components/Grid/FileGrid.d.ts +2 -2
- package/presentation/FileList/components/Grid/FileGrid.js +1 -1
- package/presentation/FileList/components/Grid/FileGrid.js.map +1 -1
- package/presentation/FileList/components/Grid/FileGrid.test.js +18 -7
- package/presentation/FileList/components/Grid/FileGrid.test.js.map +1 -1
- package/presentation/FileList/components/Header/FileManagerHeader.d.ts +2 -2
- package/presentation/FileList/components/Search/SearchBar.d.ts +2 -2
- package/presentation/FileList/components/Table/FileTable.d.ts +2 -2
- package/presentation/FileList/components/Table/FileTable.js +1 -1
- package/presentation/FileList/components/Table/FileTable.js.map +1 -1
- package/presentation/FileList/components/Table/FileTable.test.js +9 -4
- package/presentation/FileList/components/Table/FileTable.test.js.map +1 -1
- package/presentation/FileList/components/Upload/UploadProgress.d.ts +2 -2
- package/presentation/FileManager/FileManagerBreadcrumbs.d.ts +12 -0
- package/presentation/FileManager/FileManagerBreadcrumbs.js +37 -0
- package/presentation/FileManager/FileManagerBreadcrumbs.js.map +1 -0
- package/presentation/FileManager/FileManagerView.js +36 -2
- package/presentation/FileManager/FileManagerView.js.map +1 -1
- package/presentation/FileManager/RouteParamsSync.d.ts +2 -0
- package/presentation/FileManager/RouteParamsSync.js +30 -0
- package/presentation/FileManager/RouteParamsSync.js.map +1 -0
- package/presentation/Settings/components/SettingsView.d.ts +2 -2
- package/presentation/config/DefaultFileManagerConfig.js +13 -11
- package/presentation/config/DefaultFileManagerConfig.js.map +1 -1
- package/presentation/config/FileManagerViewConfig.d.ts +19 -25
- package/presentation/config/configComponents/Browser/BulkAction.d.ts +10 -8
- package/presentation/config/configComponents/Browser/BulkAction.js +3 -9
- package/presentation/config/configComponents/Browser/BulkAction.js.map +1 -1
- package/presentation/config/configComponents/Browser/FileAction.d.ts +2 -1
- package/presentation/config/configComponents/Browser/FolderAction.d.ts +2 -1
- package/presentation/config/configComponents/Browser/Table/Column.d.ts +9 -8
- package/presentation/config/configComponents/Browser/Table/index.d.ts +9 -9
- package/presentation/config/configComponents/Browser/index.d.ts +20 -20
- package/presentation/config/configComponents/Browser/index.js +0 -8
- package/presentation/config/configComponents/Browser/index.js.map +1 -1
- package/presentation/config/configComponents/FileDetails/index.d.ts +4 -2
- package/presentation/config/thumbnailRenderers/CroppedFileImage.d.ts +18 -0
- package/presentation/config/thumbnailRenderers/CroppedFileImage.js +66 -0
- package/presentation/config/thumbnailRenderers/CroppedFileImage.js.map +1 -0
- package/presentation/config/thumbnailRenderers/FilePreviewImageRenderer.js +6 -9
- package/presentation/config/thumbnailRenderers/FilePreviewImageRenderer.js.map +1 -1
- package/presentation/config/thumbnailRenderers/GridItemImageRenderer.js +6 -9
- package/presentation/config/thumbnailRenderers/GridItemImageRenderer.js.map +1 -1
- package/presentation/config/thumbnailRenderers/TableItemImageRenderer.js +6 -9
- package/presentation/config/thumbnailRenderers/TableItemImageRenderer.js.map +1 -1
- package/presentation/fieldRenderers/CmsAssetPickerRenderer.d.ts +22 -0
- package/presentation/fieldRenderers/CmsAssetPickerRenderer.js +56 -0
- package/presentation/fieldRenderers/CmsAssetPickerRenderer.js.map +1 -0
- package/presentation/fieldRenderers/CmsFilePickerRenderer.d.ts +13 -0
- package/presentation/fieldRenderers/CmsFilePickerRenderer.js +36 -0
- package/presentation/fieldRenderers/CmsFilePickerRenderer.js.map +1 -0
- package/presentation/fieldRenderers/CmsImageEditorDialog.d.ts +7 -0
- package/presentation/fieldRenderers/CmsImageEditorDialog.js +32 -0
- package/presentation/fieldRenderers/CmsImageEditorDialog.js.map +1 -0
- package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.d.ts +18 -0
- package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.js +88 -0
- package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.js.map +1 -0
- package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.d.ts +13 -0
- package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.js +60 -0
- package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.js.map +1 -0
- package/presentation/fieldRenderers/CroppedAssetThumb.d.ts +15 -0
- package/presentation/fieldRenderers/CroppedAssetThumb.js +25 -0
- package/presentation/fieldRenderers/CroppedAssetThumb.js.map +1 -0
- package/presentation/fieldRenderers/assetValue.d.ts +10 -0
- package/presentation/fieldRenderers/assetValue.js +88 -0
- package/presentation/fieldRenderers/assetValue.js.map +1 -0
- package/presentation/hooks/useCmsImageEditor.d.ts +10 -0
- package/presentation/hooks/useCmsImageEditor.js +76 -0
- package/presentation/hooks/useCmsImageEditor.js.map +1 -0
- package/presentation/hooks/useEditImage.d.ts +14 -0
- package/presentation/hooks/useEditImage.js +67 -0
- package/presentation/hooks/useEditImage.js.map +1 -0
- package/routes.d.ts +1 -0
- package/routes.js +2 -1
- package/routes.js.map +1 -1
- package/modules/HeadlessCms/fileField.d.ts +0 -2
- package/modules/HeadlessCms/fileField.js +0 -44
- package/modules/HeadlessCms/fileField.js.map +0 -1
- package/modules/HeadlessCms/fileRenderer/fileField.d.ts +0 -2
- package/modules/HeadlessCms/fileRenderer/fileField.js +0 -53
- package/modules/HeadlessCms/fileRenderer/fileField.js.map +0 -1
- package/modules/HeadlessCms/fileRenderer/fileFields.d.ts +0 -2
- package/modules/HeadlessCms/fileRenderer/fileFields.js +0 -73
- package/modules/HeadlessCms/fileRenderer/fileFields.js.map +0 -1
- package/modules/HeadlessCms/fileRenderer/utils.d.ts +0 -2
- package/modules/HeadlessCms/fileRenderer/utils.js +0 -15
- package/modules/HeadlessCms/fileRenderer/utils.js.map +0 -1
- package/modules/HeadlessCms/index.d.ts +0 -1
- package/modules/HeadlessCms/index.js +0 -14
- package/modules/HeadlessCms/index.js.map +0 -1
- package/modules/Settings/graphql.d.ts +0 -12
- package/modules/Settings/graphql.js +0 -36
- package/modules/Settings/graphql.js.map +0 -1
- package/modules/Settings/views/FileManagerSettings.d.ts +0 -2
- package/modules/Settings/views/FileManagerSettings.js +0 -134
- package/modules/Settings/views/FileManagerSettings.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/AddOperation.d.ts +0 -7
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/AddOperation.js +0 -15
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/AddOperation.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditor.d.ts +0 -15
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditor.js +0 -44
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditor.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialog.d.ts +0 -15
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialog.js +0 -52
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialog.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.d.ts +0 -61
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.js +0 -111
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.test.d.ts +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.test.js +0 -372
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.test.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/FieldRenderer.d.ts +0 -8
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/FieldRenderer.js +0 -31
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/FieldRenderer.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/Operation.d.ts +0 -11
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/Operation.js +0 -33
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/Operation.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/index.d.ts +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/index.js +0 -1
- package/presentation/FileList/components/BulkActions/BulkActionEdit.d.ts +0 -4
- package/presentation/FileList/components/BulkActions/BulkActionEdit.js +0 -105
- package/presentation/FileList/components/BulkActions/BulkActionEdit.js.map +0 -1
- package/presentation/config/configComponents/Browser/FolderFieldDecorator.d.ts +0 -7
- package/presentation/config/configComponents/Browser/FolderFieldDecorator.js +0 -11
- package/presentation/config/configComponents/Browser/FolderFieldDecorator.js.map +0 -1
|
@@ -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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { FileFieldSettings } from "@webiny/app-admin/features/formModel/fieldTypes/FileFieldType.js";
|
|
3
|
+
declare module "@webiny/app-admin/features/formModel/abstractions.js" {
|
|
4
|
+
interface IFieldRendererRegistry {
|
|
5
|
+
cmsMultiFilePicker: {
|
|
6
|
+
fieldType: "file";
|
|
7
|
+
settings: FileFieldSettings;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export declare const CmsMultiFilePickerRenderer: React.ComponentType<{
|
|
12
|
+
field: import("@webiny/app-admin/index.js").IFieldVM;
|
|
13
|
+
}>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import react 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 { MultiFilePicker } from "@webiny/admin-ui";
|
|
5
|
+
import { isEditableImageUrl, useCmsImageEditor } from "../hooks/useCmsImageEditor.js";
|
|
6
|
+
import { CmsImageEditorDialog } from "./CmsImageEditorDialog.js";
|
|
7
|
+
const CmsMultiFilePickerRenderer = createFieldRenderer(({ field })=>{
|
|
8
|
+
const value = field.value;
|
|
9
|
+
const settings = field.rendererSettings;
|
|
10
|
+
const currentValues = Array.isArray(value) ? value : [];
|
|
11
|
+
const editor = useCmsImageEditor();
|
|
12
|
+
return /*#__PURE__*/ react.createElement(FileManager, {
|
|
13
|
+
multiple: true,
|
|
14
|
+
images: settings?.images,
|
|
15
|
+
accept: settings?.accept,
|
|
16
|
+
own: settings?.own,
|
|
17
|
+
scope: settings?.scope,
|
|
18
|
+
render: ({ showFileManager })=>{
|
|
19
|
+
const selectFiles = (replaceIndex = -1)=>{
|
|
20
|
+
showFileManager((files)=>{
|
|
21
|
+
const newSrcs = files.map((f)=>f.src || "");
|
|
22
|
+
if (-1 === replaceIndex) field.onChange([
|
|
23
|
+
...currentValues,
|
|
24
|
+
...newSrcs
|
|
25
|
+
]);
|
|
26
|
+
else field.onChange([
|
|
27
|
+
...currentValues.slice(0, replaceIndex),
|
|
28
|
+
...newSrcs,
|
|
29
|
+
...currentValues.slice(replaceIndex + 1)
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
return /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(MultiFilePicker, {
|
|
34
|
+
label: field.label,
|
|
35
|
+
description: field.description,
|
|
36
|
+
note: field.note,
|
|
37
|
+
hint: field.help,
|
|
38
|
+
values: currentValues,
|
|
39
|
+
type: "compact",
|
|
40
|
+
onSelectItem: ()=>selectFiles(),
|
|
41
|
+
onReplaceItem: (_, index)=>selectFiles(index),
|
|
42
|
+
onEditItem: (_, index)=>{
|
|
43
|
+
const url = currentValues[index];
|
|
44
|
+
if (isEditableImageUrl(url)) editor.openFor(url);
|
|
45
|
+
},
|
|
46
|
+
onRemoveItem: (_, index)=>{
|
|
47
|
+
field.onChange([
|
|
48
|
+
...currentValues.slice(0, index),
|
|
49
|
+
...currentValues.slice(index + 1)
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
}), /*#__PURE__*/ react.createElement(CmsImageEditorDialog, {
|
|
53
|
+
editor: editor
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
export { CmsMultiFilePickerRenderer };
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=CmsMultiFilePickerRenderer.js.map
|
|
@@ -0,0 +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\";\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"}
|
package/routes.d.ts
CHANGED
package/routes.js
CHANGED
package/routes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sources":["../src/routes.ts"],"sourcesContent":["import { Route } from \"@webiny/app-admin\";\n\nexport const Routes = {\n List: new Route({\n name: \"FileManager/List\",\n path: \"/file-manager\",\n params: zod => {\n return {\n folderId: zod.string().optional()\n };\n }\n }),\n Settings: new Route({\n name: \"FileManager/Settings\",\n path: \"/settings/file-manager/general\"\n })\n};\n"],"names":["Routes","Route","zod"],"mappings":";AAEO,MAAMA,SAAS;IAClB,MAAM,IAAIC,MAAM;QACZ,MAAM;QACN,MAAM;QACN,QAAQC,CAAAA,MACG;gBACH,UAAUA,IAAI,MAAM,GAAG,QAAQ;
|
|
1
|
+
{"version":3,"file":"routes.js","sources":["../src/routes.ts"],"sourcesContent":["import { Route } from \"@webiny/app-admin\";\n\nexport const Routes = {\n List: new Route({\n name: \"FileManager/List\",\n path: \"/file-manager\",\n params: zod => {\n return {\n folderId: zod.string().optional(),\n search: zod.string().optional()\n };\n }\n }),\n Settings: new Route({\n name: \"FileManager/Settings\",\n path: \"/settings/file-manager/general\"\n })\n};\n"],"names":["Routes","Route","zod"],"mappings":";AAEO,MAAMA,SAAS;IAClB,MAAM,IAAIC,MAAM;QACZ,MAAM;QACN,MAAM;QACN,QAAQC,CAAAA,MACG;gBACH,UAAUA,IAAI,MAAM,GAAG,QAAQ;gBAC/B,QAAQA,IAAI,MAAM,GAAG,QAAQ;YACjC;IAER;IACA,UAAU,IAAID,MAAM;QAChB,MAAM;QACN,MAAM;IACV;AACJ"}
|