@strapi/upload 5.55.1 → 5.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/hooks/useAIMetadataAvailability.js +14 -0
- package/dist/admin/hooks/useAIMetadataAvailability.js.map +1 -0
- package/dist/admin/hooks/useAIMetadataAvailability.mjs +12 -0
- package/dist/admin/hooks/useAIMetadataAvailability.mjs.map +1 -0
- package/dist/admin/hooks/useAIMetadataEnabled.js +7 -5
- package/dist/admin/hooks/useAIMetadataEnabled.js.map +1 -1
- package/dist/admin/hooks/useAIMetadataEnabled.mjs +7 -5
- package/dist/admin/hooks/useAIMetadataEnabled.mjs.map +1 -1
- package/dist/admin/hooks/useTracking.js +4 -10
- package/dist/admin/hooks/useTracking.js.map +1 -1
- package/dist/admin/hooks/useTracking.mjs +5 -10
- package/dist/admin/hooks/useTracking.mjs.map +1 -1
- package/dist/admin/legacy/hooks/useAIMetadataAvailability.js +14 -0
- package/dist/admin/legacy/hooks/useAIMetadataAvailability.js.map +1 -0
- package/dist/admin/legacy/hooks/useAIMetadataAvailability.mjs +12 -0
- package/dist/admin/legacy/hooks/useAIMetadataAvailability.mjs.map +1 -0
- package/dist/admin/legacy/hooks/useAIMetadataEnabled.js +23 -0
- package/dist/admin/legacy/hooks/useAIMetadataEnabled.js.map +1 -0
- package/dist/admin/legacy/hooks/useAIMetadataEnabled.mjs +21 -0
- package/dist/admin/legacy/hooks/useAIMetadataEnabled.mjs.map +1 -0
- package/dist/admin/legacy/hooks/useTracking.js +5 -3
- package/dist/admin/legacy/hooks/useTracking.js.map +1 -1
- package/dist/admin/legacy/hooks/useTracking.mjs +5 -3
- package/dist/admin/legacy/hooks/useTracking.mjs.map +1 -1
- package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.js +2 -1
- package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.js.map +1 -1
- package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.mjs +2 -1
- package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.mjs.map +1 -1
- package/dist/admin/legacy/pages/App/MediaLibrary.js +6 -5
- package/dist/admin/legacy/pages/App/MediaLibrary.js.map +1 -1
- package/dist/admin/legacy/pages/App/MediaLibrary.mjs +6 -5
- package/dist/admin/legacy/pages/App/MediaLibrary.mjs.map +1 -1
- package/dist/admin/pages/Assets/AssetsPage.js +24 -12
- package/dist/admin/pages/Assets/AssetsPage.js.map +1 -1
- package/dist/admin/pages/Assets/AssetsPage.mjs +25 -13
- package/dist/admin/pages/Assets/AssetsPage.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetActions.js +326 -0
- package/dist/admin/pages/Assets/components/AssetActions.js.map +1 -0
- package/dist/admin/pages/Assets/components/AssetActions.mjs +324 -0
- package/dist/admin/pages/Assets/components/AssetActions.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/AssetActionsMenu.js +28 -309
- package/dist/admin/pages/Assets/components/AssetActionsMenu.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetActionsMenu.mjs +31 -312
- package/dist/admin/pages/Assets/components/AssetActionsMenu.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetContextMenu.js +79 -0
- package/dist/admin/pages/Assets/components/AssetContextMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/AssetContextMenu.mjs +77 -0
- package/dist/admin/pages/Assets/components/AssetContextMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.js +87 -5
- package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.mjs +89 -7
- package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +3 -3
- package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +3 -3
- package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.js +62 -5
- package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.mjs +60 -6
- package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetsGrid.js +27 -4
- package/dist/admin/pages/Assets/components/AssetsGrid.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetsGrid.mjs +27 -4
- package/dist/admin/pages/Assets/components/AssetsGrid.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetsTable.js +27 -4
- package/dist/admin/pages/Assets/components/AssetsTable.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetsTable.mjs +27 -4
- package/dist/admin/pages/Assets/components/AssetsTable.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/CursorAnchoredMenu.js +77 -0
- package/dist/admin/pages/Assets/components/CursorAnchoredMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/CursorAnchoredMenu.mjs +75 -0
- package/dist/admin/pages/Assets/components/CursorAnchoredMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/DeleteItemsDialog.js +4 -2
- package/dist/admin/pages/Assets/components/DeleteItemsDialog.js.map +1 -1
- package/dist/admin/pages/Assets/components/DeleteItemsDialog.mjs +4 -2
- package/dist/admin/pages/Assets/components/DeleteItemsDialog.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/FolderActions.js +139 -0
- package/dist/admin/pages/Assets/components/FolderActions.js.map +1 -0
- package/dist/admin/pages/Assets/components/FolderActions.mjs +137 -0
- package/dist/admin/pages/Assets/components/FolderActions.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/FolderActionsMenu.js +25 -119
- package/dist/admin/pages/Assets/components/FolderActionsMenu.js.map +1 -1
- package/dist/admin/pages/Assets/components/FolderActionsMenu.mjs +27 -121
- package/dist/admin/pages/Assets/components/FolderActionsMenu.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/FolderContextMenu.js +67 -0
- package/dist/admin/pages/Assets/components/FolderContextMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/FolderContextMenu.mjs +65 -0
- package/dist/admin/pages/Assets/components/FolderContextMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/FolderTree/FolderTree.js +139 -45
- package/dist/admin/pages/Assets/components/FolderTree/FolderTree.js.map +1 -1
- package/dist/admin/pages/Assets/components/FolderTree/FolderTree.mjs +140 -47
- package/dist/admin/pages/Assets/components/FolderTree/FolderTree.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.js +115 -0
- package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.mjs +113 -0
- package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/ItemContextMenu.js +125 -0
- package/dist/admin/pages/Assets/components/ItemContextMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/ItemContextMenu.mjs +122 -0
- package/dist/admin/pages/Assets/components/ItemContextMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/MainAreaContextMenu.js +88 -63
- package/dist/admin/pages/Assets/components/MainAreaContextMenu.js.map +1 -1
- package/dist/admin/pages/Assets/components/MainAreaContextMenu.mjs +88 -63
- package/dist/admin/pages/Assets/components/MainAreaContextMenu.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/SelectionActions.js +111 -0
- package/dist/admin/pages/Assets/components/SelectionActions.js.map +1 -0
- package/dist/admin/pages/Assets/components/SelectionActions.mjs +109 -0
- package/dist/admin/pages/Assets/components/SelectionActions.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/SelectionContextMenu.js +82 -0
- package/dist/admin/pages/Assets/components/SelectionContextMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/SelectionContextMenu.mjs +80 -0
- package/dist/admin/pages/Assets/components/SelectionContextMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/hooks/useAssetSelection.js +10 -0
- package/dist/admin/pages/Assets/hooks/useAssetSelection.js.map +1 -1
- package/dist/admin/pages/Assets/hooks/useAssetSelection.mjs +10 -0
- package/dist/admin/pages/Assets/hooks/useAssetSelection.mjs.map +1 -1
- package/dist/admin/pages/SettingsPage/SettingsPage.js +10 -3
- package/dist/admin/pages/SettingsPage/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage/SettingsPage.mjs +10 -3
- package/dist/admin/pages/SettingsPage/SettingsPage.mjs.map +1 -1
- package/dist/admin/services/api.js.map +1 -1
- package/dist/admin/services/api.mjs.map +1 -1
- package/dist/admin/src/hooks/useAIMetadataAvailability.d.ts +5 -0
- package/dist/admin/src/hooks/useAIMetadataEnabled.d.ts +5 -3
- package/dist/admin/src/hooks/useTracking.d.ts +0 -7
- package/dist/admin/src/legacy/components/EditAssetDialog/PreviewBox/PreviewComponents.d.ts +2 -2
- package/dist/admin/src/legacy/hooks/useAIMetadataAvailability.d.ts +5 -0
- package/dist/admin/src/legacy/hooks/useAIMetadataEnabled.d.ts +12 -0
- package/dist/admin/src/legacy/hooks/useSettings.d.ts +1 -0
- package/dist/admin/src/pages/Assets/components/ActionsMenuContent.d.ts +2 -2
- package/dist/admin/src/pages/Assets/components/AssetActions.d.ts +70 -0
- package/dist/admin/src/pages/Assets/components/AssetActionsMenu.d.ts +5 -14
- package/dist/admin/src/pages/Assets/components/AssetContextMenu.d.ts +27 -0
- package/dist/admin/src/pages/Assets/components/AssetDetails/useCropImg.d.ts +16 -0
- package/dist/admin/src/pages/Assets/components/CursorAnchoredMenu.d.ts +32 -0
- package/dist/admin/src/pages/Assets/components/FolderActions.d.ts +48 -0
- package/dist/admin/src/pages/Assets/components/FolderActionsMenu.d.ts +4 -8
- package/dist/admin/src/pages/Assets/components/FolderContextMenu.d.ts +21 -0
- package/dist/admin/src/pages/Assets/components/FolderTree/FolderTree.d.ts +5 -1
- package/dist/admin/src/pages/Assets/components/FolderTree/FolderTreeRowMenu.d.ts +25 -0
- package/dist/admin/src/pages/Assets/components/ItemContextMenu.d.ts +53 -0
- package/dist/admin/src/pages/Assets/components/MainAreaContextMenu.d.ts +15 -1
- package/dist/admin/src/pages/Assets/components/SelectionActions.d.ts +40 -0
- package/dist/admin/src/pages/Assets/components/SelectionContextMenu.d.ts +25 -0
- package/dist/admin/src/pages/Assets/hooks/useAssetSelection.d.ts +5 -0
- package/dist/admin/src/utils/mediaLibraryVersion.d.ts +18 -0
- package/dist/admin/translations/en.json.js +9 -3
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +9 -3
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/admin/utils/mediaLibraryVersion.js +24 -0
- package/dist/admin/utils/mediaLibraryVersion.js.map +1 -0
- package/dist/admin/utils/mediaLibraryVersion.mjs +20 -0
- package/dist/admin/utils/mediaLibraryVersion.mjs.map +1 -0
- package/dist/server/bootstrap.js +7 -0
- package/dist/server/bootstrap.js.map +1 -1
- package/dist/server/bootstrap.mjs +7 -0
- package/dist/server/bootstrap.mjs.map +1 -1
- package/dist/server/controllers/admin-file.js +2 -2
- package/dist/server/controllers/admin-file.js.map +1 -1
- package/dist/server/controllers/admin-file.mjs +2 -2
- package/dist/server/controllers/admin-file.mjs.map +1 -1
- package/dist/server/controllers/admin-folder-file.js +51 -4
- package/dist/server/controllers/admin-folder-file.js.map +1 -1
- package/dist/server/controllers/admin-folder-file.mjs +52 -5
- package/dist/server/controllers/admin-folder-file.mjs.map +1 -1
- package/dist/server/controllers/admin-folder.js +5 -5
- package/dist/server/controllers/admin-folder.js.map +1 -1
- package/dist/server/controllers/admin-folder.mjs +5 -5
- package/dist/server/controllers/admin-folder.mjs.map +1 -1
- package/dist/server/controllers/admin-settings.js +2 -1
- package/dist/server/controllers/admin-settings.js.map +1 -1
- package/dist/server/controllers/admin-settings.mjs +2 -1
- package/dist/server/controllers/admin-settings.mjs.map +1 -1
- package/dist/server/controllers/content-api.js +30 -7
- package/dist/server/controllers/content-api.js.map +1 -1
- package/dist/server/controllers/content-api.mjs +30 -7
- package/dist/server/controllers/content-api.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/folder-file.js +4 -4
- package/dist/server/controllers/validation/admin/folder-file.js.map +1 -1
- package/dist/server/controllers/validation/admin/folder-file.mjs +2 -2
- package/dist/server/controllers/validation/admin/folder-file.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/folder.js +3 -4
- package/dist/server/controllers/validation/admin/folder.js.map +1 -1
- package/dist/server/controllers/validation/admin/folder.mjs +3 -4
- package/dist/server/controllers/validation/admin/folder.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/upload.js +1 -2
- package/dist/server/controllers/validation/admin/upload.js.map +1 -1
- package/dist/server/controllers/validation/admin/upload.mjs +1 -2
- package/dist/server/controllers/validation/admin/upload.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/utils.js +1 -2
- package/dist/server/controllers/validation/admin/utils.js.map +1 -1
- package/dist/server/controllers/validation/admin/utils.mjs +1 -2
- package/dist/server/controllers/validation/admin/utils.mjs.map +1 -1
- package/dist/server/errors.js +12 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/errors.mjs +10 -0
- package/dist/server/errors.mjs.map +1 -0
- package/dist/server/migrations/unsign-richtext-and-blocks-urls.js +2 -2
- package/dist/server/migrations/unsign-richtext-and-blocks-urls.js.map +1 -1
- package/dist/server/migrations/unsign-richtext-and-blocks-urls.mjs +1 -1
- package/dist/server/migrations/unsign-richtext-and-blocks-urls.mjs.map +1 -1
- package/dist/server/routes/content-api.js +3 -3
- package/dist/server/routes/content-api.js.map +1 -1
- package/dist/server/routes/content-api.mjs +3 -3
- package/dist/server/routes/content-api.mjs.map +1 -1
- package/dist/server/routes/validation/upload.js +8 -0
- package/dist/server/routes/validation/upload.js.map +1 -1
- package/dist/server/routes/validation/upload.mjs +8 -0
- package/dist/server/routes/validation/upload.mjs.map +1 -1
- package/dist/server/services/ai-metadata-provider.js +83 -0
- package/dist/server/services/ai-metadata-provider.js.map +1 -0
- package/dist/server/services/ai-metadata-provider.mjs +61 -0
- package/dist/server/services/ai-metadata-provider.mjs.map +1 -0
- package/dist/server/services/ai-metadata-strapi-managed.js +48 -0
- package/dist/server/services/ai-metadata-strapi-managed.js.map +1 -0
- package/dist/server/services/ai-metadata-strapi-managed.mjs +46 -0
- package/dist/server/services/ai-metadata-strapi-managed.mjs.map +1 -0
- package/dist/server/services/ai-metadata.js +5 -58
- package/dist/server/services/ai-metadata.js.map +1 -1
- package/dist/server/services/ai-metadata.mjs +6 -39
- package/dist/server/services/ai-metadata.mjs.map +1 -1
- package/dist/server/services/api-upload-folder.js +3 -3
- package/dist/server/services/api-upload-folder.js.map +1 -1
- package/dist/server/services/api-upload-folder.mjs +3 -3
- package/dist/server/services/api-upload-folder.mjs.map +1 -1
- package/dist/server/services/extensions/utils.js +19 -15
- package/dist/server/services/extensions/utils.js.map +1 -1
- package/dist/server/services/extensions/utils.mjs +19 -15
- package/dist/server/services/extensions/utils.mjs.map +1 -1
- package/dist/server/services/file.js +2 -2
- package/dist/server/services/file.js.map +1 -1
- package/dist/server/services/file.mjs +1 -1
- package/dist/server/services/file.mjs.map +1 -1
- package/dist/server/services/folder.js +9 -6
- package/dist/server/services/folder.js.map +1 -1
- package/dist/server/services/folder.mjs +9 -6
- package/dist/server/services/folder.mjs.map +1 -1
- package/dist/server/services/index.js +3 -1
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/index.mjs +3 -1
- package/dist/server/services/index.mjs.map +1 -1
- package/dist/server/services/metrics.js +1 -1
- package/dist/server/services/metrics.js.map +1 -1
- package/dist/server/services/metrics.mjs +1 -1
- package/dist/server/services/metrics.mjs.map +1 -1
- package/dist/server/services/provider.js +3 -4
- package/dist/server/services/provider.js.map +1 -1
- package/dist/server/services/provider.mjs +3 -4
- package/dist/server/services/provider.mjs.map +1 -1
- package/dist/server/services/upload.js +5 -6
- package/dist/server/services/upload.js.map +1 -1
- package/dist/server/services/upload.mjs +4 -5
- package/dist/server/services/upload.mjs.map +1 -1
- package/dist/server/services/weekly-metrics.js +2 -2
- package/dist/server/services/weekly-metrics.js.map +1 -1
- package/dist/server/services/weekly-metrics.mjs +2 -2
- package/dist/server/services/weekly-metrics.mjs.map +1 -1
- package/dist/server/src/bootstrap.d.ts.map +1 -1
- package/dist/server/src/controllers/admin-folder-file.d.ts.map +1 -1
- package/dist/server/src/controllers/admin-folder.d.ts.map +1 -1
- package/dist/server/src/controllers/admin-settings.d.ts.map +1 -1
- package/dist/server/src/controllers/content-api.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/admin/folder.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/admin/upload.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/admin/utils.d.ts.map +1 -1
- package/dist/server/src/errors.d.ts +6 -0
- package/dist/server/src/errors.d.ts.map +1 -0
- package/dist/server/src/index.d.ts +6 -1
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/mcp/ambient-instance.d.ts +3 -1
- package/dist/server/src/mcp/ambient-instance.d.ts.map +1 -1
- package/dist/server/src/routes/validation/upload.d.ts +4 -0
- package/dist/server/src/routes/validation/upload.d.ts.map +1 -1
- package/dist/server/src/services/ai-metadata-provider.d.ts +26 -0
- package/dist/server/src/services/ai-metadata-provider.d.ts.map +1 -0
- package/dist/server/src/services/ai-metadata-strapi-managed.d.ts +7 -0
- package/dist/server/src/services/ai-metadata-strapi-managed.d.ts.map +1 -0
- package/dist/server/src/services/ai-metadata.d.ts.map +1 -1
- package/dist/server/src/services/extensions/utils.d.ts +33 -1
- package/dist/server/src/services/extensions/utils.d.ts.map +1 -1
- package/dist/server/src/services/folder.d.ts +5 -2
- package/dist/server/src/services/folder.d.ts.map +1 -1
- package/dist/server/src/services/index.d.ts +6 -1
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/services/provider.d.ts.map +1 -1
- package/dist/server/src/services/upload.d.ts.map +1 -1
- package/dist/server/src/utils/images.d.ts +2 -2
- package/dist/server/src/utils/images.d.ts.map +1 -1
- package/dist/server/src/utils/index.d.ts +2 -0
- package/dist/server/src/utils/index.d.ts.map +1 -1
- package/dist/server/utils/images.js +6 -7
- package/dist/server/utils/images.js.map +1 -1
- package/dist/server/utils/images.mjs +6 -7
- package/dist/server/utils/images.mjs.map +1 -1
- package/dist/server/utils/index.js.map +1 -1
- package/dist/server/utils/index.mjs.map +1 -1
- package/dist/shared/contracts/settings.d.ts +6 -0
- package/package.json +11 -11
- package/dist/admin/legacy/hooks/useAiAvailability.js +0 -22
- package/dist/admin/legacy/hooks/useAiAvailability.js.map +0 -1
- package/dist/admin/legacy/hooks/useAiAvailability.mjs +0 -20
- package/dist/admin/legacy/hooks/useAiAvailability.mjs.map +0 -1
- package/dist/admin/src/legacy/hooks/useAiAvailability.d.ts +0 -4
|
@@ -35,10 +35,12 @@ const FOCAL_DIAMETER_REM = 5.6;
|
|
|
35
35
|
const HANDLE_PX = 12;
|
|
36
36
|
/* -------------------------------------------------------------------------------------------------
|
|
37
37
|
* Styled
|
|
38
|
-
* -----------------------------------------------------------------------------------------------*/ // Full-viewport takeover with a small inset so the editor reads as a card.
|
|
38
|
+
* -----------------------------------------------------------------------------------------------*/ // Full-viewport takeover with a small inset so the editor reads as a card. It sits
|
|
39
|
+
// below the tooltip layer so the header buttons' tooltips, which portal to the body,
|
|
40
|
+
// are not covered by it — and above notifications, which it does take over.
|
|
39
41
|
const Overlay = styledComponents.styled(designSystem.Flex)`
|
|
40
42
|
position: fixed;
|
|
41
|
-
z-index:
|
|
43
|
+
z-index: ${({ theme })=>theme.zIndices.tooltip - 100};
|
|
42
44
|
flex-direction: column;
|
|
43
45
|
top: ${({ theme })=>theme.spaces[1]};
|
|
44
46
|
left: ${({ theme })=>theme.spaces[1]};
|
|
@@ -50,6 +52,14 @@ const Overlay = styledComponents.styled(designSystem.Flex)`
|
|
|
50
52
|
/* Focused programmatically on open (tabIndex -1) — no visible ring needed. */
|
|
51
53
|
outline: none;
|
|
52
54
|
`;
|
|
55
|
+
// Stacked over the editor content, top right. Above the crop area so the buttons stay
|
|
56
|
+
// clickable where the image runs under them.
|
|
57
|
+
const RotateControls = styledComponents.styled(designSystem.Flex)`
|
|
58
|
+
position: absolute;
|
|
59
|
+
top: ${({ theme })=>theme.spaces[3]};
|
|
60
|
+
right: ${({ theme })=>theme.spaces[3]};
|
|
61
|
+
z-index: 1;
|
|
62
|
+
`;
|
|
53
63
|
const HeaderBar = styledComponents.styled(designSystem.Flex)`
|
|
54
64
|
width: 100%;
|
|
55
65
|
gap: ${({ theme })=>theme.spaces[2]};
|
|
@@ -240,13 +250,20 @@ const AssetCropEditor = ({ asset, isBusy = false, onClose, onApply, onSaveAsCopy
|
|
|
240
250
|
React__namespace.useEffect(()=>{
|
|
241
251
|
overlayRef.current?.focus();
|
|
242
252
|
}, []);
|
|
243
|
-
const { init, crop, naturalSize, aspectRatio, setCropSize, setCropPosition, setAspectRatio, produceFile, width, height } = useCropImg.useCropImg();
|
|
253
|
+
const { init, crop, naturalSize, aspectRatio, setCropSize, setCropPosition, setAspectRatio, produceFile, rotation, rotate, width, height } = useCropImg.useCropImg();
|
|
244
254
|
const [aspectLocked, setAspectLocked] = React__namespace.useState(false);
|
|
245
255
|
// Focal point as a percentage of the crop area (matches the {x,y} contract).
|
|
246
256
|
const [focal, setFocal] = React__namespace.useState(asset.focalPoint ?? {
|
|
247
257
|
x: 50,
|
|
248
258
|
y: 50
|
|
249
259
|
});
|
|
260
|
+
const handleRotate = (direction)=>{
|
|
261
|
+
rotate(direction);
|
|
262
|
+
setFocal((prev)=>({
|
|
263
|
+
x: 100 - prev.y,
|
|
264
|
+
y: prev.x
|
|
265
|
+
}) );
|
|
266
|
+
};
|
|
250
267
|
// The crop editor reads pixels from a canvas (useCropImg -> toBlob), so its
|
|
251
268
|
// <img> must be CORS-clean and always sets crossOrigin="anonymous" (see the
|
|
252
269
|
// img below) — even for unsigned URLs, which AssetPreview leaves unset because
|
|
@@ -262,6 +279,39 @@ const AssetCropEditor = ({ asset, isBusy = false, onClose, onApply, onSaveAsCopy
|
|
|
262
279
|
const rawImageUrl = files.prefixFileUrlWithBackendUrl(asset.url);
|
|
263
280
|
const cacheKey = asset.updatedAt && !asset.isUrlSigned ? new Date(asset.updatedAt).getTime() : undefined;
|
|
264
281
|
const imageUrl = cacheKey !== undefined ? `${rawImageUrl}${rawImageUrl.includes('?') ? '&' : '?'}updatedAt=${cacheKey}` : rawImageUrl;
|
|
282
|
+
const isQuarterTurn = rotation === 90 || rotation === 270;
|
|
283
|
+
/**
|
|
284
|
+
* Applied inline rather than through the styled component: the admin's global
|
|
285
|
+
* `img { max-width: 100% }` reset would otherwise clamp the reciprocal sizing and
|
|
286
|
+
* square the image off. A half turn keeps the aspect, so it only spins; a quarter
|
|
287
|
+
* turn swaps it, and the image is laid out against the container's other axis
|
|
288
|
+
* before being turned into place.
|
|
289
|
+
*/ const rotatedImageStyle = React__namespace.useMemo(()=>{
|
|
290
|
+
if (!rotation) return undefined;
|
|
291
|
+
const aspect = naturalSize.width && naturalSize.height ? naturalSize.width / naturalSize.height : undefined;
|
|
292
|
+
// A half turn keeps the aspect, so the image stays in flow and only spins. Taking
|
|
293
|
+
// it out of flow there would collapse the crop area, which is sized by it.
|
|
294
|
+
if (!isQuarterTurn || !aspect) {
|
|
295
|
+
return {
|
|
296
|
+
transform: `rotate(${rotation}deg)`
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
position: 'absolute',
|
|
301
|
+
top: '50%',
|
|
302
|
+
left: '50%',
|
|
303
|
+
maxWidth: 'none',
|
|
304
|
+
maxHeight: 'none',
|
|
305
|
+
width: `${100 / aspect}%`,
|
|
306
|
+
height: `${100 * aspect}%`,
|
|
307
|
+
transform: `translate(-50%, -50%) rotate(${rotation}deg)`
|
|
308
|
+
};
|
|
309
|
+
}, [
|
|
310
|
+
rotation,
|
|
311
|
+
isQuarterTurn,
|
|
312
|
+
naturalSize.width,
|
|
313
|
+
naturalSize.height
|
|
314
|
+
]);
|
|
265
315
|
const handleImageLoad = ()=>{
|
|
266
316
|
if (imgRef.current) {
|
|
267
317
|
init(imgRef.current);
|
|
@@ -398,6 +448,12 @@ const AssetCropEditor = ({ asset, isBusy = false, onClose, onApply, onSaveAsCopy
|
|
|
398
448
|
}));
|
|
399
449
|
};
|
|
400
450
|
/**
|
|
451
|
+
* The design system's `NumberInput` stops adopting an external `value` once the
|
|
452
|
+
* user has typed into it, so a rotation that swaps width and height leaves the
|
|
453
|
+
* fields showing the old numbers. Remounting on rotation re-seeds them — the
|
|
454
|
+
* same reason the focal fields remount on blur.
|
|
455
|
+
*/ const sizeFieldsKey = rotation;
|
|
456
|
+
/**
|
|
401
457
|
* Forces a focal field to redisplay the state on blur.
|
|
402
458
|
*
|
|
403
459
|
* One key per axis: a shared key remounted both fields, so tabbing out of X
|
|
@@ -466,12 +522,38 @@ const AssetCropEditor = ({ asset, isBusy = false, onClose, onApply, onSaveAsCopy
|
|
|
466
522
|
}),
|
|
467
523
|
/*#__PURE__*/ jsxRuntime.jsxs(Body, {
|
|
468
524
|
children: [
|
|
525
|
+
/*#__PURE__*/ jsxRuntime.jsx(RotateControls, {
|
|
526
|
+
direction: "column",
|
|
527
|
+
gap: 1,
|
|
528
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
|
|
529
|
+
disabled: !isReady,
|
|
530
|
+
onClick: ()=>handleRotate('right'),
|
|
531
|
+
label: formatMessage({
|
|
532
|
+
id: translations.getTranslationKey('asset-details.crop.rotate-right'),
|
|
533
|
+
defaultMessage: 'Rotate 90° right'
|
|
534
|
+
}),
|
|
535
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(icons.ArrowClockwise, {})
|
|
536
|
+
})
|
|
537
|
+
}),
|
|
469
538
|
/*#__PURE__*/ jsxRuntime.jsxs(CropArea, {
|
|
470
539
|
ref: cropAreaRef,
|
|
471
540
|
$aspect: naturalSize.width && naturalSize.height ? naturalSize.width / naturalSize.height : undefined,
|
|
472
541
|
children: [
|
|
542
|
+
isQuarterTurn ? /**
|
|
543
|
+
* On a quarter turn the image is taken out of flow, leaving the box with no
|
|
544
|
+
* in-flow content to size it. A definite height instead of this would fight
|
|
545
|
+
* `max-width`: `aspect-ratio` only holds while one side stays `auto`.
|
|
546
|
+
*/ /*#__PURE__*/ jsxRuntime.jsx("div", {
|
|
547
|
+
"aria-hidden": true,
|
|
548
|
+
style: {
|
|
549
|
+
width: naturalSize.width,
|
|
550
|
+
maxWidth: '100%'
|
|
551
|
+
}
|
|
552
|
+
}) : null,
|
|
473
553
|
/*#__PURE__*/ jsxRuntime.jsx("img", {
|
|
474
554
|
ref: imgRef,
|
|
555
|
+
"data-testid": "crop-editor-image",
|
|
556
|
+
style: rotatedImageStyle,
|
|
475
557
|
src: imageUrl,
|
|
476
558
|
alt: asset.name,
|
|
477
559
|
// Always anonymous, unlike AssetPreview/AssetsGrid which gate it:
|
|
@@ -619,7 +701,7 @@ const AssetCropEditor = ({ asset, isBusy = false, onClose, onApply, onSaveAsCopy
|
|
|
619
701
|
width: next
|
|
620
702
|
});
|
|
621
703
|
}
|
|
622
|
-
})
|
|
704
|
+
}, `crop-width-${sizeFieldsKey}`)
|
|
623
705
|
]
|
|
624
706
|
}),
|
|
625
707
|
/*#__PURE__*/ jsxRuntime.jsxs(FieldRow, {
|
|
@@ -643,7 +725,7 @@ const AssetCropEditor = ({ asset, isBusy = false, onClose, onApply, onSaveAsCopy
|
|
|
643
725
|
height: next
|
|
644
726
|
});
|
|
645
727
|
}
|
|
646
|
-
})
|
|
728
|
+
}, `crop-height-${sizeFieldsKey}`)
|
|
647
729
|
]
|
|
648
730
|
})
|
|
649
731
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssetCropEditor.js","sources":["../../../../../../admin/src/pages/Assets/components/AssetDetails/AssetCropEditor.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { useNotification } from '@strapi/admin/strapi-admin';\nimport {\n Box,\n Button,\n Field,\n Flex,\n FocusTrap,\n IconButton,\n NumberInput,\n Portal,\n Typography,\n} from '@strapi/design-system';\nimport { ArrowsHorizontal, ArrowsVertical, Crop, Link } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\nimport { styled, useTheme } from 'styled-components';\n\nimport { prefixFileUrlWithBackendUrl } from '../../../../utils/files';\nimport { getTranslationKey } from '../../../../utils/translations';\n\nimport { resolveCornerResize, useCropImg } from './useCropImg';\n\nimport type {\n AssetWithPopulatedCreatedBy,\n FocalPoint,\n} from '../../../../../../shared/contracts/files';\n\nconst FOCAL_DIAMETER_REM = 5.6;\nconst HANDLE_PX = 12;\n\n/* -------------------------------------------------------------------------------------------------\n * Styled\n * -----------------------------------------------------------------------------------------------*/\n\n// Full-viewport takeover with a small inset so the editor reads as a card.\nconst Overlay = styled(Flex)`\n position: fixed;\n z-index: 1200;\n flex-direction: column;\n top: ${({ theme }) => theme.spaces[1]};\n left: ${({ theme }) => theme.spaces[1]};\n right: ${({ theme }) => theme.spaces[1]};\n bottom: ${({ theme }) => theme.spaces[1]};\n border-radius: ${({ theme }) => theme.borderRadius};\n border: 1px solid ${({ theme }) => theme.colors.neutral150};\n background: ${({ theme }) => theme.colors.neutral0};\n /* Focused programmatically on open (tabIndex -1) — no visible ring needed. */\n outline: none;\n`;\n\nconst HeaderBar = styled(Flex)`\n width: 100%;\n gap: ${({ theme }) => theme.spaces[2]};\n padding: ${({ theme }) => `${theme.spaces[3]} ${theme.spaces[5]}`};\n border-bottom: 1px solid ${({ theme }) => theme.colors.neutral150};\n background: ${({ theme }) => theme.colors.neutral0};\n`;\n\n// Fills the remaining space between header/footer; checkerboard pattern.\n// The horizontal padding keeps the image off the viewport edges — the corner\n// handles overhang the image by half their size, and on narrow (mobile)\n// screens an edge-to-edge image leaves nothing to grab.\nconst Body = styled(Box)`\n width: 100%;\n position: relative;\n flex: 1;\n min-height: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n padding: 0 ${({ theme }) => theme.spaces[4]};\n background: repeating-conic-gradient(\n ${({ theme }) => theme.colors.neutral100} 0% 25%,\n ${({ theme }) => theme.colors.neutral0} 0% 50%\n )\n 50% / 20px 20px;\n`;\n\n// Wrapper sized to the image's natural aspect ratio so the <img> inside fills\n// it without letterboxing. Percentage-based positioning of the crop overlay\n// then maps directly to natural-px coordinates.\nconst CropArea = styled.div<{ $aspect?: number }>`\n position: relative;\n max-width: 100%;\n max-height: 100%;\n ${({ $aspect }) => ($aspect ? `aspect-ratio: ${$aspect};` : '')}\n\n img {\n display: block;\n width: 100%;\n height: 100%;\n user-select: none;\n -webkit-user-drag: none;\n }\n`;\n\n// Crop selection rectangle, positioned by % of CropArea. Inverts the dim\n// outside via box-shadow so the focused area shows full color.\nconst CropOverlay = styled.div`\n position: absolute;\n border: 1px dashed ${({ theme }) => theme.colors.primary600};\n box-shadow: 0 0 0 9999px rgba(33, 33, 52, 0.5);\n cursor: move;\n /* Without this, touch browsers claim the gesture for scrolling and fire\n pointercancel mid-drag — the crop drag dies while the finger is down. */\n touch-action: none;\n`;\n\nconst ResizeHandle = styled.button<{ $cursor: string }>`\n position: absolute;\n width: ${HANDLE_PX}px;\n height: ${HANDLE_PX}px;\n margin: -${HANDLE_PX / 2}px;\n padding: 0;\n border: 1px solid ${({ theme }) => theme.colors.primary600};\n border-radius: 2px;\n background: ${({ theme }) => theme.colors.neutral0};\n cursor: ${({ $cursor }) => $cursor};\n touch-action: none;\n`;\n\nconst FocalPointHandle = styled.button`\n position: absolute;\n width: ${FOCAL_DIAMETER_REM}rem;\n height: ${FOCAL_DIAMETER_REM}rem;\n margin: ${-FOCAL_DIAMETER_REM / 2}rem 0 0 ${-FOCAL_DIAMETER_REM / 2}rem;\n border-radius: 50%;\n border: 1px solid ${({ theme }) => theme.colors.neutral800};\n background: transparent;\n cursor: grab;\n padding: 0;\n touch-action: none;\n\n &::before {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.16);\n transform: translate(-50%, -50%);\n }\n &::after {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.neutral800};\n transform: translate(-50%, -50%);\n }\n\n &:active {\n cursor: grabbing;\n }\n`;\n\n// Hidden on mobile — the panel (title/hint + numeric crop & focal inputs) is\n// too large for a phone; touch users set the crop by dragging the rectangle and\n// focal handle directly on the image. Shown from tablet up.\nconst InfoBox = styled(Box)`\n display: none;\n\n ${({ theme }) => theme.breakpoints.medium} {\n display: block;\n position: absolute;\n right: ${({ theme }) => theme.spaces[1]};\n bottom: ${({ theme }) => theme.spaces[1]};\n width: 100%;\n max-width: 32rem;\n padding: ${({ theme }) => theme.spaces[3]};\n border-radius: ${({ theme }) => theme.borderRadius};\n background: ${({ theme }) =>\n theme.colorScheme === 'dark' ? theme.colors.neutral150 : theme.colors.neutral900};\n z-index: 20;\n }\n`;\n\nconst FooterBar = styled(Flex)`\n width: 100%;\n justify-content: space-between;\n padding: ${({ theme }) => `${theme.spaces[3]} ${theme.spaces[5]}`};\n border-top: 1px solid ${({ theme }) => theme.colors.neutral150};\n background: ${({ theme }) => theme.colors.neutral0};\n`;\n\nconst FieldRow = styled(Field.Root)`\n flex-direction: row;\n align-items: center;\n`;\n\nconst FieldNumberInput = styled(NumberInput)`\n width: 8.4rem;\n`;\n\nconst LabelIcon = styled(Field.Label)`\n display: flex;\n align-items: center;\n justify-content: center;\n width: 1.6rem;\n height: 1.6rem;\n`;\n\nconst BackgroundPreserveRatioContainer = styled(Box)`\n position: absolute;\n top: 50%;\n left: 0;\n transform: translateY(-50%);\n\n svg {\n display: block;\n }\n`;\n\nconst BackgroundPreserveRatio = () => (\n <BackgroundPreserveRatioContainer>\n <svg width=\"17\" height=\"49\" viewBox=\"0 0 17 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M0.5 0.5H8.5C12.9183 0.5 16.5 4.08172 16.5 8.5M0.5 48.5H8.5C12.9183 48.5 16.5 44.9183 16.5 40.5\"\n stroke=\"#666687\"\n strokeLinecap=\"round\"\n />\n </svg>\n </BackgroundPreserveRatioContainer>\n);\n\n/* -------------------------------------------------------------------------------------------------\n * AssetCropEditor\n * -----------------------------------------------------------------------------------------------*/\n\ninterface AssetCropEditorProps {\n asset: AssetWithPopulatedCreatedBy;\n isBusy?: boolean;\n onClose: () => void;\n onApply: (file: globalThis.File, focalPoint: FocalPoint) => void;\n onSaveAsCopy: (file: globalThis.File, focalPoint: FocalPoint) => void;\n /**\n * \"Save as copy\" creates a new asset (`assets.create`), a different permission\n * from cropping in place (`assets.update`). Hidden when the user lacks it, so\n * an update-only role can't trigger a 403.\n */\n canSaveAsCopy: boolean;\n}\n\ntype Corner = 'tl' | 'tr' | 'bl' | 'br';\n\nexport const AssetCropEditor = ({\n asset,\n isBusy = false,\n onClose,\n onApply,\n onSaveAsCopy,\n canSaveAsCopy,\n}: AssetCropEditorProps) => {\n const { formatMessage } = useIntl();\n const { toggleNotification } = useNotification();\n // The InfoBox is a fixed dark surface in both themes, so its text can't use a\n // single neutral token (those invert with the scheme). Pick the scheme's white\n // and a muted grey explicitly.\n const theme = useTheme();\n const isDark = theme.colorScheme === 'dark';\n const infoTextColor = isDark ? 'neutral1000' : 'neutral0';\n const infoMutedColor = isDark ? 'neutral600' : 'neutral200';\n const imgRef = React.useRef<HTMLImageElement>(null);\n const cropAreaRef = React.useRef<HTMLDivElement>(null);\n const overlayRef = React.useRef<HTMLDivElement>(null);\n\n // FocusTrap's auto-focus lands on the first focusable element — the crop\n // width NumberInput — which pops the keyboard on mobile the moment the\n // editor opens. Focus the (tabIndex -1) overlay instead: keyboard users are\n // one Tab away from the first control, Escape still reaches the trap.\n React.useEffect(() => {\n overlayRef.current?.focus();\n }, []);\n\n const {\n init,\n crop,\n naturalSize,\n aspectRatio,\n setCropSize,\n setCropPosition,\n setAspectRatio,\n produceFile,\n width,\n height,\n } = useCropImg();\n\n const [aspectLocked, setAspectLocked] = React.useState(false);\n // Focal point as a percentage of the crop area (matches the {x,y} contract).\n const [focal, setFocal] = React.useState<FocalPoint>(asset.focalPoint ?? { x: 50, y: 50 });\n\n // The crop editor reads pixels from a canvas (useCropImg -> toBlob), so its\n // <img> must be CORS-clean and always sets crossOrigin=\"anonymous\" (see the\n // img below) — even for unsigned URLs, which AssetPreview leaves unset because\n // it only displays. That difference is why the crop URL is kept DISTINCT from\n // the thumbnail's: identical URL + different crossOrigin collide in the HTTP\n // cache (#26581), and the crop's anonymous request could otherwise reuse the\n // thumbnail's non-CORS cached response and taint the canvas. So the buster\n // uses `updatedAt` where AssetPreview uses `v`, giving each its own entry\n // (mirrors the legacy PreviewBox fix).\n // Signed URLs get no param at all — a presigned S3 URL breaks if you add one,\n // and on the signed side AssetPreview also loads anonymous, so the identical\n // URL there shares one CORS-consistent entry, which is fine.\n const rawImageUrl = prefixFileUrlWithBackendUrl(asset.url) as string;\n const cacheKey =\n asset.updatedAt && !asset.isUrlSigned ? new Date(asset.updatedAt).getTime() : undefined;\n const imageUrl =\n cacheKey !== undefined\n ? `${rawImageUrl}${rawImageUrl.includes('?') ? '&' : '?'}updatedAt=${cacheKey}`\n : rawImageUrl;\n\n const handleImageLoad = () => {\n if (imgRef.current) {\n init(imgRef.current);\n }\n };\n\n // Map a pointer event to natural-px coordinates inside the image.\n const pointerToNatural = (event: PointerEvent | React.PointerEvent) => {\n const rect = cropAreaRef.current?.getBoundingClientRect();\n if (!rect || !naturalSize.width || !naturalSize.height) return null;\n const ratioX = naturalSize.width / rect.width;\n const ratioY = naturalSize.height / rect.height;\n return {\n x: (event.clientX - rect.left) * ratioX,\n y: (event.clientY - rect.top) * ratioY,\n };\n };\n\n // Teardown for the drag currently in flight, so it can be run both on pointer\n // release AND on unmount. Without the unmount path, closing the editor mid-drag\n // (Escape, drawer close, a re-render dropping the overlay) would never fire a\n // pointer event, leaking the window listeners and letting a stale `onMove`\n // call `setCropPosition` on an unmounted component.\n const activeDragCleanupRef = React.useRef<(() => void) | null>(null);\n\n React.useEffect(() => {\n return () => {\n activeDragCleanupRef.current?.();\n };\n }, []);\n\n // Track a pointer drag until the pointer is released or the browser cancels\n // it. The pointer is captured so the drag follows the finger even once it\n // leaves the (12px) handle it started on, and pointercancel is treated as\n // release so an interrupted touch never leaves stale window listeners.\n const trackPointerDrag = (event: React.PointerEvent, onMove: (e: PointerEvent) => void) => {\n event.preventDefault();\n event.stopPropagation();\n const { pointerId } = event;\n try {\n event.currentTarget.setPointerCapture(pointerId);\n } catch {\n // Unsupported environment (jsdom) — window listeners still track the drag.\n }\n const move = (e: PointerEvent) => {\n if (e.pointerId !== pointerId) return;\n onMove(e);\n };\n const cleanup = () => {\n window.removeEventListener('pointermove', move);\n window.removeEventListener('pointerup', end);\n window.removeEventListener('pointercancel', end);\n activeDragCleanupRef.current = null;\n };\n const end = (e: PointerEvent) => {\n if (e.pointerId !== pointerId) return;\n cleanup();\n };\n // Only one drag is ever live at a time; tear down any previous one first.\n activeDragCleanupRef.current?.();\n activeDragCleanupRef.current = cleanup;\n window.addEventListener('pointermove', move);\n window.addEventListener('pointerup', end);\n window.addEventListener('pointercancel', end);\n };\n\n // Drag the entire crop rectangle.\n const handleMovePointerDown = (event: React.PointerEvent) => {\n const start = pointerToNatural(event);\n if (!start) return;\n const startCrop = { ...crop };\n trackPointerDrag(event, (e) => {\n const next = pointerToNatural(e);\n if (!next) return;\n setCropPosition({\n x: startCrop.x + (next.x - start.x),\n y: startCrop.y + (next.y - start.y),\n });\n });\n };\n\n // Resize from a corner; the opposite corner stays anchored.\n const handleResizePointerDown = (corner: Corner) => (event: React.PointerEvent) => {\n const startCrop = { ...crop };\n const anchorX =\n corner === 'tl' || corner === 'bl' ? startCrop.x + startCrop.width : startCrop.x;\n const anchorY =\n corner === 'tl' || corner === 'tr' ? startCrop.y + startCrop.height : startCrop.y;\n trackPointerDrag(event, (e) => {\n const point = pointerToNatural(e);\n if (!point) return;\n const {\n x,\n y,\n width: w,\n height: h,\n } = resolveCornerResize({\n anchorX,\n anchorY,\n point,\n aspectRatio: aspectLocked ? aspectRatio : null,\n });\n setCropPosition({ x, y });\n setCropSize({ width: w, height: h });\n });\n };\n\n const toggleAspectLock = () => {\n setAspectLocked((locked) => {\n const next = !locked;\n setAspectRatio(next && height ? width / height : null);\n return next;\n });\n };\n\n // Drag the focal handle within the crop rectangle, clamped 0–100.\n const handleFocalPointerDown = (event: React.PointerEvent) => {\n trackPointerDrag(event, (e) => {\n const point = pointerToNatural(e);\n if (!point) return;\n // Convert pointer to a percentage of the current crop area.\n const px = ((point.x - crop.x) / crop.width) * 100;\n const py = ((point.y - crop.y) / crop.height) * 100;\n setFocal({\n x: Math.round(Math.min(100, Math.max(0, px))),\n y: Math.round(Math.min(100, Math.max(0, py))),\n });\n });\n };\n\n const focalPxX = Math.round((focal.x / 100) * width);\n const focalPxY = Math.round((focal.y / 100) * height);\n\n const setFocalPx = (axis: 'x' | 'y', value: number) => {\n const dim = axis === 'x' ? width : height;\n if (!dim) return;\n const pct = Math.min(100, Math.max(0, (value / dim) * 100));\n setFocal((prev) => ({ ...prev, [axis]: Math.round(pct) }));\n };\n\n /**\n * Forces a focal field to redisplay the state on blur.\n *\n * One key per axis: a shared key remounted both fields, so tabbing out of X\n * destroyed Y before it could take focus and it was unreachable by keyboard.\n */\n const [focalXKey, setFocalXKey] = React.useState(0);\n const [focalYKey, setFocalYKey] = React.useState(0);\n const syncFocalX = () => setFocalXKey((key) => key + 1);\n const syncFocalY = () => setFocalYKey((key) => key + 1);\n\n const cropPercents =\n naturalSize.width && naturalSize.height\n ? {\n left: (crop.x / naturalSize.width) * 100,\n top: (crop.y / naturalSize.height) * 100,\n width: (crop.width / naturalSize.width) * 100,\n height: (crop.height / naturalSize.height) * 100,\n }\n : null;\n\n // The crop is only usable once the image has loaded and `init()` has seeded\n // the natural size. Until then the produce/export path would reject.\n const isReady = cropPercents !== null;\n\n const handleAction = async (action: 'apply' | 'copy') => {\n if (!isReady) return;\n\n let file: globalThis.File;\n try {\n file = await produceFile(asset.name, asset.mime ?? 'image/png', asset.updatedAt);\n } catch {\n toggleNotification({\n type: 'danger',\n message: formatMessage({\n id: getTranslationKey('asset-details.crop.export-error'),\n defaultMessage: 'Could not process the cropped image.',\n }),\n });\n return;\n }\n\n const roundedFocal = { x: Math.round(focal.x), y: Math.round(focal.y) };\n if (action === 'apply') {\n onApply(file, roundedFocal);\n } else {\n onSaveAsCopy(file, roundedFocal);\n }\n };\n\n return (\n <Portal>\n <FocusTrap onEscape={onClose} skipAutoFocus>\n <Overlay ref={overlayRef} tabIndex={-1}>\n <HeaderBar alignItems=\"center\">\n <Crop aria-hidden />\n <Typography variant=\"omega\" fontWeight=\"bold\">\n {formatMessage({\n id: getTranslationKey('asset-details.crop.title'),\n defaultMessage: 'Crop & Focus area',\n })}\n </Typography>\n </HeaderBar>\n\n <Body>\n <CropArea\n ref={cropAreaRef}\n $aspect={\n naturalSize.width && naturalSize.height\n ? naturalSize.width / naturalSize.height\n : undefined\n }\n >\n <img\n ref={imgRef}\n src={imageUrl}\n alt={asset.name}\n // Always anonymous, unlike AssetPreview/AssetsGrid which gate it:\n // the editor reads pixels via canvas (useCropImg -> toBlob), which\n // taints on a cross-origin image loaded without CORS, so every\n // remote image must be fetched CORS-clean.\n crossOrigin=\"anonymous\"\n onLoad={handleImageLoad}\n draggable={false}\n />\n\n {cropPercents ? (\n <CropOverlay\n style={{\n left: `${cropPercents.left}%`,\n top: `${cropPercents.top}%`,\n width: `${cropPercents.width}%`,\n height: `${cropPercents.height}%`,\n }}\n onPointerDown={handleMovePointerDown}\n >\n <ResizeHandle\n type=\"button\"\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.resize.top-left'),\n defaultMessage: 'Resize top-left',\n })}\n $cursor=\"nwse-resize\"\n style={{ left: 0, top: 0 }}\n onPointerDown={handleResizePointerDown('tl')}\n />\n <ResizeHandle\n type=\"button\"\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.resize.top-right'),\n defaultMessage: 'Resize top-right',\n })}\n $cursor=\"nesw-resize\"\n style={{ right: 0, top: 0 }}\n onPointerDown={handleResizePointerDown('tr')}\n />\n <ResizeHandle\n type=\"button\"\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.resize.bottom-left'),\n defaultMessage: 'Resize bottom-left',\n })}\n $cursor=\"nesw-resize\"\n style={{ left: 0, bottom: 0 }}\n onPointerDown={handleResizePointerDown('bl')}\n />\n <ResizeHandle\n type=\"button\"\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.resize.bottom-right'),\n defaultMessage: 'Resize bottom-right',\n })}\n $cursor=\"nwse-resize\"\n style={{ right: 0, bottom: 0 }}\n onPointerDown={handleResizePointerDown('br')}\n />\n\n <FocalPointHandle\n type=\"button\"\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.focal-point'),\n defaultMessage: 'Focal point',\n })}\n style={{ left: `${focal.x}%`, top: `${focal.y}%` }}\n onPointerDown={handleFocalPointerDown}\n />\n </CropOverlay>\n ) : null}\n </CropArea>\n\n <InfoBox>\n <Flex direction=\"column\" alignItems=\"stretch\" gap={1} paddingBottom={3}>\n <Typography variant=\"omega\" fontWeight=\"bold\" textColor={infoTextColor}>\n {formatMessage({\n id: getTranslationKey('asset-details.crop.title'),\n defaultMessage: 'Crop & Focus area',\n })}\n </Typography>\n <Typography variant=\"pi\" textColor={infoMutedColor}>\n {formatMessage({\n id: getTranslationKey('asset-details.crop.hint'),\n defaultMessage:\n 'Set the crop area with the rectangle. Pin the always-visible area with the circle.',\n })}\n </Typography>\n </Flex>\n\n <Flex gap={6} alignItems=\"center\">\n <Flex alignItems=\"center\" gap={2}>\n <Flex direction=\"column\" gap={2}>\n <FieldRow name=\"crop-width\" gap={2}>\n <LabelIcon textColor={infoTextColor}>\n <ArrowsHorizontal />\n </LabelIcon>\n <FieldNumberInput\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.width'),\n defaultMessage: 'Width (px)',\n })}\n value={width}\n min={1}\n max={naturalSize.width || undefined}\n onValueChange={(next) => {\n if (next !== undefined) setCropSize({ width: next });\n }}\n />\n </FieldRow>\n <FieldRow name=\"crop-height\" gap={2}>\n <LabelIcon textColor={infoTextColor}>\n <ArrowsVertical />\n </LabelIcon>\n <FieldNumberInput\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.height'),\n defaultMessage: 'Height (px)',\n })}\n value={height}\n min={1}\n max={naturalSize.height || undefined}\n onValueChange={(next) => {\n if (next !== undefined) setCropSize({ height: next });\n }}\n />\n </FieldRow>\n </Flex>\n\n <Flex position=\"relative\">\n <IconButton\n label={formatMessage({\n id: getTranslationKey('asset-details.crop.aspect-lock'),\n defaultMessage: 'Lock aspect ratio',\n })}\n variant={aspectLocked ? 'secondary' : 'ghost'}\n onClick={toggleAspectLock}\n >\n <Link />\n </IconButton>\n <BackgroundPreserveRatio />\n </Flex>\n </Flex>\n\n <Flex direction=\"column\" gap={2} marginLeft={'auto'}>\n <FieldRow name=\"focal-x\" gap={2}>\n <Field.Label textColor={infoTextColor}>\n {formatMessage({\n id: getTranslationKey('asset-details.crop.focal-x-axis'),\n defaultMessage: 'X',\n })}\n </Field.Label>\n <FieldNumberInput\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.focal-x'),\n defaultMessage: 'Focal point X (px)',\n })}\n key={`focal-x-${focalXKey}`}\n value={focalPxX}\n min={0}\n max={width || undefined}\n onValueChange={(next) => {\n if (next !== undefined) setFocalPx('x', next);\n }}\n onBlur={syncFocalX}\n />\n </FieldRow>\n <FieldRow name=\"focal-y\" gap={2}>\n <Field.Label textColor={infoTextColor}>\n {formatMessage({\n id: getTranslationKey('asset-details.crop.focal-y-axis'),\n defaultMessage: 'Y',\n })}\n </Field.Label>\n <FieldNumberInput\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.focal-y'),\n defaultMessage: 'Focal point Y (px)',\n })}\n key={`focal-y-${focalYKey}`}\n value={focalPxY}\n min={0}\n max={height || undefined}\n onValueChange={(next) => {\n if (next !== undefined) setFocalPx('y', next);\n }}\n onBlur={syncFocalY}\n />\n </FieldRow>\n </Flex>\n </Flex>\n </InfoBox>\n </Body>\n\n <FooterBar alignItems=\"center\">\n <Button variant=\"tertiary\" onClick={onClose} disabled={isBusy}>\n {formatMessage({ id: 'app.components.Button.cancel', defaultMessage: 'Cancel' })}\n </Button>\n <Flex gap={2}>\n {canSaveAsCopy && (\n <Button\n variant=\"secondary\"\n onClick={() => handleAction('copy')}\n loading={isBusy}\n disabled={!isReady}\n >\n {formatMessage({\n id: getTranslationKey('asset-details.crop.save-as-copy'),\n defaultMessage: 'Save as copy',\n })}\n </Button>\n )}\n <Button\n variant=\"default\"\n onClick={() => handleAction('apply')}\n loading={isBusy}\n disabled={!isReady}\n >\n {formatMessage({\n id: getTranslationKey('asset-details.crop.apply'),\n defaultMessage: 'Apply',\n })}\n </Button>\n </Flex>\n </FooterBar>\n </Overlay>\n </FocusTrap>\n </Portal>\n );\n};\n"],"names":["FOCAL_DIAMETER_REM","HANDLE_PX","Overlay","styled","Flex","theme","spaces","borderRadius","colors","neutral150","neutral0","HeaderBar","Body","Box","neutral100","CropArea","div","$aspect","CropOverlay","primary600","ResizeHandle","button","$cursor","FocalPointHandle","neutral800","InfoBox","breakpoints","medium","colorScheme","neutral900","FooterBar","FieldRow","Field","Root","FieldNumberInput","NumberInput","LabelIcon","Label","BackgroundPreserveRatioContainer","BackgroundPreserveRatio","_jsx","svg","width","height","viewBox","fill","xmlns","path","d","stroke","strokeLinecap","AssetCropEditor","asset","isBusy","onClose","onApply","onSaveAsCopy","canSaveAsCopy","formatMessage","useIntl","toggleNotification","useNotification","useTheme","isDark","infoTextColor","infoMutedColor","imgRef","React","useRef","cropAreaRef","overlayRef","useEffect","current","focus","init","crop","naturalSize","aspectRatio","setCropSize","setCropPosition","setAspectRatio","produceFile","useCropImg","aspectLocked","setAspectLocked","useState","focal","setFocal","focalPoint","x","y","rawImageUrl","prefixFileUrlWithBackendUrl","url","cacheKey","updatedAt","isUrlSigned","Date","getTime","undefined","imageUrl","includes","handleImageLoad","pointerToNatural","event","rect","getBoundingClientRect","ratioX","ratioY","clientX","left","clientY","top","activeDragCleanupRef","trackPointerDrag","onMove","preventDefault","stopPropagation","pointerId","currentTarget","setPointerCapture","move","e","cleanup","window","removeEventListener","end","addEventListener","handleMovePointerDown","start","startCrop","next","handleResizePointerDown","corner","anchorX","anchorY","point","w","h","resolveCornerResize","toggleAspectLock","locked","handleFocalPointerDown","px","py","Math","round","min","max","focalPxX","focalPxY","setFocalPx","axis","value","dim","pct","prev","focalXKey","setFocalXKey","focalYKey","setFocalYKey","syncFocalX","key","syncFocalY","cropPercents","isReady","handleAction","action","file","name","mime","type","message","id","getTranslationKey","defaultMessage","roundedFocal","Portal","FocusTrap","onEscape","skipAutoFocus","_jsxs","ref","tabIndex","alignItems","Crop","aria-hidden","Typography","variant","fontWeight","img","src","alt","crossOrigin","onLoad","draggable","style","onPointerDown","aria-label","right","bottom","direction","gap","paddingBottom","textColor","ArrowsHorizontal","onValueChange","ArrowsVertical","position","IconButton","label","onClick","Link","marginLeft","onBlur","Button","disabled","loading"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,MAAMA,kBAAAA,GAAqB,GAAA;AAC3B,MAAMC,SAAAA,GAAY,EAAA;AAElB;;AAEkG;AAGlG,MAAMC,OAAAA,GAAUC,uBAAAA,CAAOC,iBAAAA,CAAK;;;;OAIrB,EAAE,CAAC,EAAEC,KAAK,EAAE,GAAKA,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAE,CAAC;QAChC,EAAE,CAAC,EAAED,KAAK,EAAE,GAAKA,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAE,CAAC;SAChC,EAAE,CAAC,EAAED,KAAK,EAAE,GAAKA,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAE,CAAC;UAChC,EAAE,CAAC,EAAED,KAAK,EAAE,GAAKA,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAE,CAAC;AAC1B,iBAAA,EAAE,CAAC,EAAED,KAAK,EAAE,GAAKA,KAAAA,CAAME,YAAY,CAAC;oBACjC,EAAE,CAAC,EAAEF,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACC,UAAU,CAAC;cAC/C,EAAE,CAAC,EAAEJ,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACE,QAAQ,CAAC;;;AAGrD,CAAC;AAED,MAAMC,SAAAA,GAAYR,uBAAAA,CAAOC,iBAAAA,CAAK;;OAEvB,EAAE,CAAC,EAAEC,KAAK,EAAE,GAAKA,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAE,CAAC;AAC7B,WAAA,EAAE,CAAC,EAAED,KAAK,EAAE,GAAK,CAAA,EAAGA,MAAMC,MAAM,CAAC,CAAA,CAAE,CAAC,CAAC,EAAED,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAE,EAAE,CAAC;2BACzC,EAAE,CAAC,EAAED,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACC,UAAU,CAAC;cACtD,EAAE,CAAC,EAAEJ,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACE,QAAQ,CAAC;AACrD,CAAC;AAED;AACA;AACA;AACA;AACA,MAAME,IAAAA,GAAOT,uBAAAA,CAAOU,gBAAAA,CAAI;;;;;;;;;aASX,EAAE,CAAC,EAAER,KAAK,EAAE,GAAKA,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAE,CAAC;;MAExC,EAAE,CAAC,EAAED,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACM,UAAU,CAAC;MACzC,EAAE,CAAC,EAAET,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACE,QAAQ,CAAC;;;AAG7C,CAAC;AAED;AACA;AACA;AACA,MAAMK,QAAAA,GAAWZ,uBAAAA,CAAOa,GAAyB;;;;AAI/C,EAAA,EAAE,CAAC,EAAEC,OAAO,EAAE,GAAMA,OAAAA,GAAU,CAAC,cAAc,EAAEA,OAAAA,CAAQ,CAAC,CAAC,GAAG,EAAA;;;;;;;;;AAS9D,CAAC;AAED;AACA;AACA,MAAMC,WAAAA,GAAcf,uBAAAA,CAAOa,GAAG;;qBAET,EAAE,CAAC,EAAEX,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACW,UAAU,CAAC;;;;;;AAM9D,CAAC;AAED,MAAMC,YAAAA,GAAejB,uBAAAA,CAAOkB,MAA2B;;AAE9C,SAAA,EAAEpB,SAAAA,CAAU;AACX,UAAA,EAAEA,SAAAA,CAAU;AACX,WAAA,EAAEA,YAAY,CAAA,CAAE;;oBAEP,EAAE,CAAC,EAAEI,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACW,UAAU,CAAC;;cAE/C,EAAE,CAAC,EAAEd,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACE,QAAQ,CAAC;AAC3C,UAAA,EAAE,CAAC,EAAEY,OAAO,EAAE,GAAKA,OAAAA,CAAQ;;AAErC,CAAC;AAED,MAAMC,gBAAAA,GAAmBpB,uBAAAA,CAAOkB,MAAM;;AAE7B,SAAA,EAAErB,kBAAAA,CAAmB;AACpB,UAAA,EAAEA,kBAAAA,CAAmB;AACrB,UAAA,EAAE,CAACA,kBAAAA,GAAqB,CAAA,CAAE,QAAQ,EAAE,CAACA,qBAAqB,CAAA,CAAE;;oBAElD,EAAE,CAAC,EAAEK,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACgB,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;gBAyB7C,EAAE,CAAC,EAAEnB,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACgB,UAAU,CAAC;;;;;;;AAOzD,CAAC;AAED;AACA;AACA;AACA,MAAMC,OAAAA,GAAUtB,uBAAAA,CAAOU,gBAAAA,CAAI;;;EAGzB,EAAE,CAAC,EAAER,KAAK,EAAE,GAAKA,KAAAA,CAAMqB,WAAW,CAACC,MAAM,CAAC;;;WAGjC,EAAE,CAAC,EAAEtB,KAAK,EAAE,GAAKA,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAE,CAAC;YAChC,EAAE,CAAC,EAAED,KAAK,EAAE,GAAKA,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAE,CAAC;;;aAGhC,EAAE,CAAC,EAAED,KAAK,EAAE,GAAKA,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAE,CAAC;AAC3B,mBAAA,EAAE,CAAC,EAAED,KAAK,EAAE,GAAKA,KAAAA,CAAME,YAAY,CAAC;AACvC,gBAAA,EAAE,CAAC,EAAEF,KAAK,EAAE,GACtBA,KAAAA,CAAMuB,WAAW,KAAK,MAAA,GAASvB,KAAAA,CAAMG,MAAM,CAACC,UAAU,GAAGJ,MAAMG,MAAM,CAACqB,UAAU,CAAC;;;AAGvF,CAAC;AAED,MAAMC,SAAAA,GAAY3B,uBAAAA,CAAOC,iBAAAA,CAAK;;;AAGnB,WAAA,EAAE,CAAC,EAAEC,KAAK,EAAE,GAAK,CAAA,EAAGA,MAAMC,MAAM,CAAC,CAAA,CAAE,CAAC,CAAC,EAAED,KAAAA,CAAMC,MAAM,CAAC,CAAA,CAAE,EAAE,CAAC;wBAC5C,EAAE,CAAC,EAAED,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACC,UAAU,CAAC;cACnD,EAAE,CAAC,EAAEJ,KAAK,EAAE,GAAKA,KAAAA,CAAMG,MAAM,CAACE,QAAQ,CAAC;AACrD,CAAC;AAED,MAAMqB,QAAAA,GAAW5B,uBAAAA,CAAO6B,kBAAAA,CAAMC,IAAI,CAAC;;;AAGnC,CAAC;AAED,MAAMC,gBAAAA,GAAmB/B,uBAAAA,CAAOgC,wBAAAA,CAAY;;AAE5C,CAAC;AAED,MAAMC,SAAAA,GAAYjC,uBAAAA,CAAO6B,kBAAAA,CAAMK,KAAK,CAAC;;;;;;AAMrC,CAAC;AAED,MAAMC,gCAAAA,GAAmCnC,uBAAAA,CAAOU,gBAAAA,CAAI;;;;;;;;;AASpD,CAAC;AAED,MAAM0B,uBAAAA,GAA0B,kBAC9BC,cAAA,CAACF,gCAAAA,EAAAA;AACC,QAAA,QAAA,gBAAAE,cAAA,CAACC,KAAAA,EAAAA;YAAIC,KAAAA,EAAM,IAAA;YAAKC,MAAAA,EAAO,IAAA;YAAKC,OAAAA,EAAQ,WAAA;YAAYC,IAAAA,EAAK,MAAA;YAAOC,KAAAA,EAAM,4BAAA;AAChE,YAAA,QAAA,gBAAAN,cAAA,CAACO,MAAAA,EAAAA;gBACCC,CAAAA,EAAE,iGAAA;gBACFC,MAAAA,EAAO,SAAA;gBACPC,aAAAA,EAAc;;;;MA0BTC,eAAAA,GAAkB,CAAC,EAC9BC,KAAK,EACLC,MAAAA,GAAS,KAAK,EACdC,OAAO,EACPC,OAAO,EACPC,YAAY,EACZC,aAAa,EACQ,GAAA;IACrB,MAAM,EAAEC,aAAa,EAAE,GAAGC,iBAAAA,EAAAA;IAC1B,MAAM,EAAEC,kBAAkB,EAAE,GAAGC,2BAAAA,EAAAA;;;;AAI/B,IAAA,MAAMxD,KAAAA,GAAQyD,yBAAAA,EAAAA;IACd,MAAMC,MAAAA,GAAS1D,KAAAA,CAAMuB,WAAW,KAAK,MAAA;IACrC,MAAMoC,aAAAA,GAAgBD,SAAS,aAAA,GAAgB,UAAA;IAC/C,MAAME,cAAAA,GAAiBF,SAAS,YAAA,GAAe,YAAA;IAC/C,MAAMG,MAAAA,GAASC,gBAAAA,CAAMC,MAAM,CAAmB,IAAA,CAAA;IAC9C,MAAMC,WAAAA,GAAcF,gBAAAA,CAAMC,MAAM,CAAiB,IAAA,CAAA;IACjD,MAAME,UAAAA,GAAaH,gBAAAA,CAAMC,MAAM,CAAiB,IAAA,CAAA;;;;;AAMhDD,IAAAA,gBAAAA,CAAMI,SAAS,CAAC,IAAA;AACdD,QAAAA,UAAAA,CAAWE,OAAO,EAAEC,KAAAA,EAAAA;AACtB,IAAA,CAAA,EAAG,EAAE,CAAA;IAEL,MAAM,EACJC,IAAI,EACJC,IAAI,EACJC,WAAW,EACXC,WAAW,EACXC,WAAW,EACXC,eAAe,EACfC,cAAc,EACdC,WAAW,EACXvC,KAAK,EACLC,MAAM,EACP,GAAGuC,qBAAAA,EAAAA;AAEJ,IAAA,MAAM,CAACC,YAAAA,EAAcC,eAAAA,CAAgB,GAAGjB,gBAAAA,CAAMkB,QAAQ,CAAC,KAAA,CAAA;;IAEvD,MAAM,CAACC,OAAOC,QAAAA,CAAS,GAAGpB,iBAAMkB,QAAQ,CAAajC,KAAAA,CAAMoC,UAAU,IAAI;QAAEC,CAAAA,EAAG,EAAA;QAAIC,CAAAA,EAAG;AAAG,KAAA,CAAA;;;;;;;;;;;;;IAcxF,MAAMC,WAAAA,GAAcC,iCAAAA,CAA4BxC,KAAAA,CAAMyC,GAAG,CAAA;AACzD,IAAA,MAAMC,QAAAA,GACJ1C,KAAAA,CAAM2C,SAAS,IAAI,CAAC3C,KAAAA,CAAM4C,WAAW,GAAG,IAAIC,IAAAA,CAAK7C,KAAAA,CAAM2C,SAAS,CAAA,CAAEG,OAAO,EAAA,GAAKC,SAAAA;AAChF,IAAA,MAAMC,QAAAA,GACJN,QAAAA,KAAaK,SAAAA,GACT,CAAA,EAAGR,cAAcA,WAAAA,CAAYU,QAAQ,CAAC,GAAA,CAAA,GAAO,GAAA,GAAM,GAAA,CAAI,UAAU,EAAEP,UAAU,GAC7EH,WAAAA;AAEN,IAAA,MAAMW,eAAAA,GAAkB,IAAA;QACtB,IAAIpC,MAAAA,CAAOM,OAAO,EAAE;AAClBE,YAAAA,IAAAA,CAAKR,OAAOM,OAAO,CAAA;AACrB,QAAA;AACF,IAAA,CAAA;;AAGA,IAAA,MAAM+B,mBAAmB,CAACC,KAAAA,GAAAA;QACxB,MAAMC,IAAAA,GAAOpC,WAAAA,CAAYG,OAAO,EAAEkC,qBAAAA,EAAAA;QAClC,IAAI,CAACD,IAAAA,IAAQ,CAAC7B,WAAAA,CAAYlC,KAAK,IAAI,CAACkC,WAAAA,CAAYjC,MAAM,EAAE,OAAO,IAAA;AAC/D,QAAA,MAAMgE,MAAAA,GAAS/B,WAAAA,CAAYlC,KAAK,GAAG+D,KAAK/D,KAAK;AAC7C,QAAA,MAAMkE,MAAAA,GAAShC,WAAAA,CAAYjC,MAAM,GAAG8D,KAAK9D,MAAM;QAC/C,OAAO;YACL8C,CAAAA,EAAIe,CAAAA,KAAAA,CAAMK,OAAO,GAAGJ,IAAAA,CAAKK,IAAI,IAAIH,MAAAA;YACjCjB,CAAAA,EAAIc,CAAAA,KAAAA,CAAMO,OAAO,GAAGN,IAAAA,CAAKO,GAAG,IAAIJ;AAClC,SAAA;AACF,IAAA,CAAA;;;;;;IAOA,MAAMK,oBAAAA,GAAuB9C,gBAAAA,CAAMC,MAAM,CAAsB,IAAA,CAAA;AAE/DD,IAAAA,gBAAAA,CAAMI,SAAS,CAAC,IAAA;QACd,OAAO,IAAA;AACL0C,YAAAA,oBAAAA,CAAqBzC,OAAO,IAAA;AAC9B,QAAA,CAAA;AACF,IAAA,CAAA,EAAG,EAAE,CAAA;;;;;IAML,MAAM0C,gBAAAA,GAAmB,CAACV,KAAAA,EAA2BW,MAAAA,GAAAA;AACnDX,QAAAA,KAAAA,CAAMY,cAAc,EAAA;AACpBZ,QAAAA,KAAAA,CAAMa,eAAe,EAAA;QACrB,MAAM,EAAEC,SAAS,EAAE,GAAGd,KAAAA;QACtB,IAAI;YACFA,KAAAA,CAAMe,aAAa,CAACC,iBAAiB,CAACF,SAAAA,CAAAA;AACxC,QAAA,CAAA,CAAE,OAAM;;AAER,QAAA;AACA,QAAA,MAAMG,OAAO,CAACC,CAAAA,GAAAA;YACZ,IAAIA,CAAAA,CAAEJ,SAAS,KAAKA,SAAAA,EAAW;YAC/BH,MAAAA,CAAOO,CAAAA,CAAAA;AACT,QAAA,CAAA;AACA,QAAA,MAAMC,OAAAA,GAAU,IAAA;YACdC,MAAAA,CAAOC,mBAAmB,CAAC,aAAA,EAAeJ,IAAAA,CAAAA;YAC1CG,MAAAA,CAAOC,mBAAmB,CAAC,WAAA,EAAaC,GAAAA,CAAAA;YACxCF,MAAAA,CAAOC,mBAAmB,CAAC,eAAA,EAAiBC,GAAAA,CAAAA;AAC5Cb,YAAAA,oBAAAA,CAAqBzC,OAAO,GAAG,IAAA;AACjC,QAAA,CAAA;AACA,QAAA,MAAMsD,MAAM,CAACJ,CAAAA,GAAAA;YACX,IAAIA,CAAAA,CAAEJ,SAAS,KAAKA,SAAAA,EAAW;AAC/BK,YAAAA,OAAAA,EAAAA;AACF,QAAA,CAAA;;AAEAV,QAAAA,oBAAAA,CAAqBzC,OAAO,IAAA;AAC5ByC,QAAAA,oBAAAA,CAAqBzC,OAAO,GAAGmD,OAAAA;QAC/BC,MAAAA,CAAOG,gBAAgB,CAAC,aAAA,EAAeN,IAAAA,CAAAA;QACvCG,MAAAA,CAAOG,gBAAgB,CAAC,WAAA,EAAaD,GAAAA,CAAAA;QACrCF,MAAAA,CAAOG,gBAAgB,CAAC,eAAA,EAAiBD,GAAAA,CAAAA;AAC3C,IAAA,CAAA;;AAGA,IAAA,MAAME,wBAAwB,CAACxB,KAAAA,GAAAA;AAC7B,QAAA,MAAMyB,QAAQ1B,gBAAAA,CAAiBC,KAAAA,CAAAA;AAC/B,QAAA,IAAI,CAACyB,KAAAA,EAAO;AACZ,QAAA,MAAMC,SAAAA,GAAY;AAAE,YAAA,GAAGvD;AAAK,SAAA;AAC5BuC,QAAAA,gBAAAA,CAAiBV,OAAO,CAACkB,CAAAA,GAAAA;AACvB,YAAA,MAAMS,OAAO5B,gBAAAA,CAAiBmB,CAAAA,CAAAA;AAC9B,YAAA,IAAI,CAACS,IAAAA,EAAM;YACXpD,eAAAA,CAAgB;gBACdU,CAAAA,EAAGyC,SAAAA,CAAUzC,CAAC,IAAI0C,KAAK1C,CAAC,GAAGwC,KAAAA,CAAMxC,CAAC,CAADA;gBACjCC,CAAAA,EAAGwC,SAAAA,CAAUxC,CAAC,IAAIyC,KAAKzC,CAAC,GAAGuC,KAAAA,CAAMvC,CAAC;AACpC,aAAA,CAAA;AACF,QAAA,CAAA,CAAA;AACF,IAAA,CAAA;;IAGA,MAAM0C,uBAAAA,GAA0B,CAACC,MAAAA,GAAmB,CAAC7B,KAAAA,GAAAA;AACnD,YAAA,MAAM0B,SAAAA,GAAY;AAAE,gBAAA,GAAGvD;AAAK,aAAA;AAC5B,YAAA,MAAM2D,OAAAA,GACJD,MAAAA,KAAW,IAAA,IAAQA,MAAAA,KAAW,IAAA,GAAOH,SAAAA,CAAUzC,CAAC,GAAGyC,SAAAA,CAAUxF,KAAK,GAAGwF,SAAAA,CAAUzC,CAAC;AAClF,YAAA,MAAM8C,OAAAA,GACJF,MAAAA,KAAW,IAAA,IAAQA,MAAAA,KAAW,IAAA,GAAOH,SAAAA,CAAUxC,CAAC,GAAGwC,SAAAA,CAAUvF,MAAM,GAAGuF,SAAAA,CAAUxC,CAAC;AACnFwB,YAAAA,gBAAAA,CAAiBV,OAAO,CAACkB,CAAAA,GAAAA;AACvB,gBAAA,MAAMc,QAAQjC,gBAAAA,CAAiBmB,CAAAA,CAAAA;AAC/B,gBAAA,IAAI,CAACc,KAAAA,EAAO;AACZ,gBAAA,MAAM,EACJ/C,CAAC,EACDC,CAAC,EACDhD,KAAAA,EAAO+F,CAAC,EACR9F,MAAAA,EAAQ+F,CAAC,EACV,GAAGC,8BAAAA,CAAoB;AACtBL,oBAAAA,OAAAA;AACAC,oBAAAA,OAAAA;AACAC,oBAAAA,KAAAA;AACA3D,oBAAAA,WAAAA,EAAaM,eAAeN,WAAAA,GAAc;AAC5C,iBAAA,CAAA;gBACAE,eAAAA,CAAgB;AAAEU,oBAAAA,CAAAA;AAAGC,oBAAAA;AAAE,iBAAA,CAAA;gBACvBZ,WAAAA,CAAY;oBAAEpC,KAAAA,EAAO+F,CAAAA;oBAAG9F,MAAAA,EAAQ+F;AAAE,iBAAA,CAAA;AACpC,YAAA,CAAA,CAAA;AACF,QAAA,CAAA;AAEA,IAAA,MAAME,gBAAAA,GAAmB,IAAA;AACvBxD,QAAAA,eAAAA,CAAgB,CAACyD,MAAAA,GAAAA;AACf,YAAA,MAAMV,OAAO,CAACU,MAAAA;YACd7D,cAAAA,CAAemD,IAAAA,IAAQxF,MAAAA,GAASD,KAAAA,GAAQC,MAAAA,GAAS,IAAA,CAAA;YACjD,OAAOwF,IAAAA;AACT,QAAA,CAAA,CAAA;AACF,IAAA,CAAA;;AAGA,IAAA,MAAMW,yBAAyB,CAACtC,KAAAA,GAAAA;AAC9BU,QAAAA,gBAAAA,CAAiBV,OAAO,CAACkB,CAAAA,GAAAA;AACvB,YAAA,MAAMc,QAAQjC,gBAAAA,CAAiBmB,CAAAA,CAAAA;AAC/B,YAAA,IAAI,CAACc,KAAAA,EAAO;;AAEZ,YAAA,MAAMO,EAAAA,GAAOP,CAAAA,KAAAA,CAAM/C,CAAC,GAAGd,IAAAA,CAAKc,CAAC,IAAId,IAAAA,CAAKjC,KAAK,GAAI,GAAA;AAC/C,YAAA,MAAMsG,EAAAA,GAAOR,CAAAA,KAAAA,CAAM9C,CAAC,GAAGf,IAAAA,CAAKe,CAAC,IAAIf,IAAAA,CAAKhC,MAAM,GAAI,GAAA;YAChD4C,QAAAA,CAAS;gBACPE,CAAAA,EAAGwD,IAAAA,CAAKC,KAAK,CAACD,IAAAA,CAAKE,GAAG,CAAC,GAAA,EAAKF,IAAAA,CAAKG,GAAG,CAAC,CAAA,EAAGL,EAAAA,CAAAA,CAAAA,CAAAA;gBACxCrD,CAAAA,EAAGuD,IAAAA,CAAKC,KAAK,CAACD,IAAAA,CAAKE,GAAG,CAAC,GAAA,EAAKF,IAAAA,CAAKG,GAAG,CAAC,CAAA,EAAGJ,EAAAA,CAAAA,CAAAA;AAC1C,aAAA,CAAA;AACF,QAAA,CAAA,CAAA;AACF,IAAA,CAAA;IAEA,MAAMK,QAAAA,GAAWJ,KAAKC,KAAK,CAAC,KAAC5D,CAAMG,CAAC,GAAG,GAAA,GAAO/C,KAAAA,CAAAA;IAC9C,MAAM4G,QAAAA,GAAWL,KAAKC,KAAK,CAAC,KAAC5D,CAAMI,CAAC,GAAG,GAAA,GAAO/C,MAAAA,CAAAA;IAE9C,MAAM4G,UAAAA,GAAa,CAACC,IAAAA,EAAiBC,KAAAA,GAAAA;QACnC,MAAMC,GAAAA,GAAMF,IAAAA,KAAS,GAAA,GAAM9G,KAAAA,GAAQC,MAAAA;AACnC,QAAA,IAAI,CAAC+G,GAAAA,EAAK;QACV,MAAMC,GAAAA,GAAMV,IAAAA,CAAKE,GAAG,CAAC,GAAA,EAAKF,IAAAA,CAAKG,GAAG,CAAC,CAAA,EAAG,KAACK,GAAQC,GAAAA,GAAO,GAAA,CAAA,CAAA;QACtDnE,QAAAA,CAAS,CAACqE,QAAU;AAAE,gBAAA,GAAGA,IAAI;AAAE,gBAAA,CAACJ,IAAAA,GAAOP,IAAAA,CAAKC,KAAK,CAACS,GAAAA;aAAK,CAAA,CAAA;AACzD,IAAA,CAAA;AAEA;;;;;AAKC,MACD,MAAM,CAACE,SAAAA,EAAWC,aAAa,GAAG3F,gBAAAA,CAAMkB,QAAQ,CAAC,CAAA,CAAA;AACjD,IAAA,MAAM,CAAC0E,SAAAA,EAAWC,YAAAA,CAAa,GAAG7F,gBAAAA,CAAMkB,QAAQ,CAAC,CAAA,CAAA;AACjD,IAAA,MAAM4E,UAAAA,GAAa,IAAMH,YAAAA,CAAa,CAACI,MAAQA,GAAAA,GAAM,CAAA,CAAA;AACrD,IAAA,MAAMC,UAAAA,GAAa,IAAMH,YAAAA,CAAa,CAACE,MAAQA,GAAAA,GAAM,CAAA,CAAA;AAErD,IAAA,MAAME,eACJxF,WAAAA,CAAYlC,KAAK,IAAIkC,WAAAA,CAAYjC,MAAM,GACnC;AACEmE,QAAAA,IAAAA,EAAM,IAACnC,CAAKc,CAAC,GAAGb,WAAAA,CAAYlC,KAAK,GAAI,GAAA;AACrCsE,QAAAA,GAAAA,EAAK,IAACrC,CAAKe,CAAC,GAAGd,WAAAA,CAAYjC,MAAM,GAAI,GAAA;AACrCD,QAAAA,KAAAA,EAAO,IAACiC,CAAKjC,KAAK,GAAGkC,WAAAA,CAAYlC,KAAK,GAAI,GAAA;AAC1CC,QAAAA,MAAAA,EAAQ,IAACgC,CAAKhC,MAAM,GAAGiC,WAAAA,CAAYjC,MAAM,GAAI;KAC/C,GACA,IAAA;;;AAIN,IAAA,MAAM0H,UAAUD,YAAAA,KAAiB,IAAA;AAEjC,IAAA,MAAME,eAAe,OAAOC,MAAAA,GAAAA;AAC1B,QAAA,IAAI,CAACF,OAAAA,EAAS;QAEd,IAAIG,IAAAA;QACJ,IAAI;YACFA,IAAAA,GAAO,MAAMvF,WAAAA,CAAY7B,KAAAA,CAAMqH,IAAI,EAAErH,MAAMsH,IAAI,IAAI,WAAA,EAAatH,KAAAA,CAAM2C,SAAS,CAAA;AACjF,QAAA,CAAA,CAAE,OAAM;YACNnC,kBAAAA,CAAmB;gBACjB+G,IAAAA,EAAM,QAAA;AACNC,gBAAAA,OAAAA,EAASlH,aAAAA,CAAc;AACrBmH,oBAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,iCAAA,CAAA;oBACtBC,cAAAA,EAAgB;AAClB,iBAAA;AACF,aAAA,CAAA;AACA,YAAA;AACF,QAAA;AAEA,QAAA,MAAMC,YAAAA,GAAe;AAAEvF,YAAAA,CAAAA,EAAGwD,IAAAA,CAAKC,KAAK,CAAC5D,KAAAA,CAAMG,CAAC,CAAA;AAAGC,YAAAA,CAAAA,EAAGuD,IAAAA,CAAKC,KAAK,CAAC5D,KAAAA,CAAMI,CAAC;AAAE,SAAA;AACtE,QAAA,IAAI6E,WAAW,OAAA,EAAS;AACtBhH,YAAAA,OAAAA,CAAQiH,IAAAA,EAAMQ,YAAAA,CAAAA;QAChB,CAAA,MAAO;AACLxH,YAAAA,YAAAA,CAAagH,IAAAA,EAAMQ,YAAAA,CAAAA;AACrB,QAAA;AACF,IAAA,CAAA;AAEA,IAAA,qBACExI,cAAA,CAACyI,mBAAAA,EAAAA;AACC,QAAA,QAAA,gBAAAzI,cAAA,CAAC0I,sBAAAA,EAAAA;YAAUC,QAAAA,EAAU7H,OAAAA;YAAS8H,aAAa,EAAA,IAAA;AACzC,YAAA,QAAA,gBAAAC,eAAA,CAACnL,OAAAA,EAAAA;gBAAQoL,GAAAA,EAAKhH,UAAAA;AAAYiH,gBAAAA,QAAAA,EAAU,EAAC;;kCACnCF,eAAA,CAAC1K,SAAAA,EAAAA;wBAAU6K,UAAAA,EAAW,QAAA;;0CACpBhJ,cAAA,CAACiJ,UAAAA,EAAAA;gCAAKC,aAAW,EAAA;;0CACjBlJ,cAAA,CAACmJ,uBAAAA,EAAAA;gCAAWC,OAAAA,EAAQ,OAAA;gCAAQC,UAAAA,EAAW,MAAA;0CACpCnI,aAAAA,CAAc;AACbmH,oCAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,0BAAA,CAAA;oCACtBC,cAAAA,EAAgB;AAClB,iCAAA;;;;kCAIJM,eAAA,CAACzK,IAAAA,EAAAA;;0CACCyK,eAAA,CAACtK,QAAAA,EAAAA;gCACCuK,GAAAA,EAAKjH,WAAAA;gCACLpD,OAAAA,EACE2D,WAAAA,CAAYlC,KAAK,IAAIkC,WAAAA,CAAYjC,MAAM,GACnCiC,WAAAA,CAAYlC,KAAK,GAAGkC,WAAAA,CAAYjC,MAAM,GACtCwD,SAAAA;;kDAGN3D,cAAA,CAACsJ,KAAAA,EAAAA;wCACCR,GAAAA,EAAKpH,MAAAA;wCACL6H,GAAAA,EAAK3F,QAAAA;AACL4F,wCAAAA,GAAAA,EAAK5I,MAAMqH,IAAI;;;;;wCAKfwB,WAAAA,EAAY,WAAA;wCACZC,MAAAA,EAAQ5F,eAAAA;wCACR6F,SAAAA,EAAW;;AAGZ/B,oCAAAA,YAAAA,iBACCiB,eAAA,CAACnK,WAAAA,EAAAA;wCACCkL,KAAAA,EAAO;AACLtF,4CAAAA,IAAAA,EAAM,CAAA,EAAGsD,YAAAA,CAAatD,IAAI,CAAC,CAAC,CAAC;AAC7BE,4CAAAA,GAAAA,EAAK,CAAA,EAAGoD,YAAAA,CAAapD,GAAG,CAAC,CAAC,CAAC;AAC3BtE,4CAAAA,KAAAA,EAAO,CAAA,EAAG0H,YAAAA,CAAa1H,KAAK,CAAC,CAAC,CAAC;AAC/BC,4CAAAA,MAAAA,EAAQ,CAAA,EAAGyH,YAAAA,CAAazH,MAAM,CAAC,CAAC;AAClC,yCAAA;wCACA0J,aAAAA,EAAerE,qBAAAA;;0DAEfxF,cAAA,CAACpB,YAAAA,EAAAA;gDACCuJ,IAAAA,EAAK,QAAA;AACL2B,gDAAAA,YAAAA,EAAY5I,aAAAA,CAAc;AACxBmH,oDAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,oCAAA,CAAA;oDACtBC,cAAAA,EAAgB;AAClB,iDAAA,CAAA;gDACAzJ,OAAAA,EAAQ,aAAA;gDACR8K,KAAAA,EAAO;oDAAEtF,IAAAA,EAAM,CAAA;oDAAGE,GAAAA,EAAK;AAAE,iDAAA;AACzBqF,gDAAAA,aAAAA,EAAejE,uBAAAA,CAAwB,IAAA;;0DAEzC5F,cAAA,CAACpB,YAAAA,EAAAA;gDACCuJ,IAAAA,EAAK,QAAA;AACL2B,gDAAAA,YAAAA,EAAY5I,aAAAA,CAAc;AACxBmH,oDAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,qCAAA,CAAA;oDACtBC,cAAAA,EAAgB;AAClB,iDAAA,CAAA;gDACAzJ,OAAAA,EAAQ,aAAA;gDACR8K,KAAAA,EAAO;oDAAEG,KAAAA,EAAO,CAAA;oDAAGvF,GAAAA,EAAK;AAAE,iDAAA;AAC1BqF,gDAAAA,aAAAA,EAAejE,uBAAAA,CAAwB,IAAA;;0DAEzC5F,cAAA,CAACpB,YAAAA,EAAAA;gDACCuJ,IAAAA,EAAK,QAAA;AACL2B,gDAAAA,YAAAA,EAAY5I,aAAAA,CAAc;AACxBmH,oDAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,uCAAA,CAAA;oDACtBC,cAAAA,EAAgB;AAClB,iDAAA,CAAA;gDACAzJ,OAAAA,EAAQ,aAAA;gDACR8K,KAAAA,EAAO;oDAAEtF,IAAAA,EAAM,CAAA;oDAAG0F,MAAAA,EAAQ;AAAE,iDAAA;AAC5BH,gDAAAA,aAAAA,EAAejE,uBAAAA,CAAwB,IAAA;;0DAEzC5F,cAAA,CAACpB,YAAAA,EAAAA;gDACCuJ,IAAAA,EAAK,QAAA;AACL2B,gDAAAA,YAAAA,EAAY5I,aAAAA,CAAc;AACxBmH,oDAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,wCAAA,CAAA;oDACtBC,cAAAA,EAAgB;AAClB,iDAAA,CAAA;gDACAzJ,OAAAA,EAAQ,aAAA;gDACR8K,KAAAA,EAAO;oDAAEG,KAAAA,EAAO,CAAA;oDAAGC,MAAAA,EAAQ;AAAE,iDAAA;AAC7BH,gDAAAA,aAAAA,EAAejE,uBAAAA,CAAwB,IAAA;;0DAGzC5F,cAAA,CAACjB,gBAAAA,EAAAA;gDACCoJ,IAAAA,EAAK,QAAA;AACL2B,gDAAAA,YAAAA,EAAY5I,aAAAA,CAAc;AACxBmH,oDAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,gCAAA,CAAA;oDACtBC,cAAAA,EAAgB;AAClB,iDAAA,CAAA;gDACAqB,KAAAA,EAAO;AAAEtF,oDAAAA,IAAAA,EAAM,CAAA,EAAGxB,KAAAA,CAAMG,CAAC,CAAC,CAAC,CAAC;AAAEuB,oDAAAA,GAAAA,EAAK,CAAA,EAAG1B,KAAAA,CAAMI,CAAC,CAAC,CAAC;AAAE,iDAAA;gDACjD2G,aAAAA,EAAevD;;;AAGjB,qCAAA,CAAA,GAAA;;;0CAGNuC,eAAA,CAAC5J,OAAAA,EAAAA;;kDACC4J,eAAA,CAACjL,iBAAAA,EAAAA;wCAAKqM,SAAAA,EAAU,QAAA;wCAASjB,UAAAA,EAAW,SAAA;wCAAUkB,GAAAA,EAAK,CAAA;wCAAGC,aAAAA,EAAe,CAAA;;0DACnEnK,cAAA,CAACmJ,uBAAAA,EAAAA;gDAAWC,OAAAA,EAAQ,OAAA;gDAAQC,UAAAA,EAAW,MAAA;gDAAOe,SAAAA,EAAW5I,aAAAA;0DACtDN,aAAAA,CAAc;AACbmH,oDAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,0BAAA,CAAA;oDACtBC,cAAAA,EAAgB;AAClB,iDAAA;;0DAEFvI,cAAA,CAACmJ,uBAAAA,EAAAA;gDAAWC,OAAAA,EAAQ,IAAA;gDAAKgB,SAAAA,EAAW3I,cAAAA;0DACjCP,aAAAA,CAAc;AACbmH,oDAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,yBAAA,CAAA;oDACtBC,cAAAA,EACE;AACJ,iDAAA;;;;kDAIJM,eAAA,CAACjL,iBAAAA,EAAAA;wCAAKsM,GAAAA,EAAK,CAAA;wCAAGlB,UAAAA,EAAW,QAAA;;0DACvBH,eAAA,CAACjL,iBAAAA,EAAAA;gDAAKoL,UAAAA,EAAW,QAAA;gDAASkB,GAAAA,EAAK,CAAA;;kEAC7BrB,eAAA,CAACjL,iBAAAA,EAAAA;wDAAKqM,SAAAA,EAAU,QAAA;wDAASC,GAAAA,EAAK,CAAA;;0EAC5BrB,eAAA,CAACtJ,QAAAA,EAAAA;gEAAS0I,IAAAA,EAAK,YAAA;gEAAaiC,GAAAA,EAAK,CAAA;;kFAC/BlK,cAAA,CAACJ,SAAAA,EAAAA;wEAAUwK,SAAAA,EAAW5I,aAAAA;AACpB,wEAAA,QAAA,gBAAAxB,cAAA,CAACqK,sBAAAA,EAAAA,EAAAA;;kFAEHrK,cAAA,CAACN,gBAAAA,EAAAA;AACCoK,wEAAAA,YAAAA,EAAY5I,aAAAA,CAAc;AACxBmH,4EAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,0BAAA,CAAA;4EACtBC,cAAAA,EAAgB;AAClB,yEAAA,CAAA;wEACAtB,KAAAA,EAAO/G,KAAAA;wEACPyG,GAAAA,EAAK,CAAA;wEACLC,GAAAA,EAAKxE,WAAAA,CAAYlC,KAAK,IAAIyD,SAAAA;AAC1B2G,wEAAAA,aAAAA,EAAe,CAAC3E,IAAAA,GAAAA;4EACd,IAAIA,IAAAA,KAAShC,WAAWrB,WAAAA,CAAY;gFAAEpC,KAAAA,EAAOyF;AAAK,6EAAA,CAAA;AACpD,wEAAA;;;;0EAGJkD,eAAA,CAACtJ,QAAAA,EAAAA;gEAAS0I,IAAAA,EAAK,aAAA;gEAAciC,GAAAA,EAAK,CAAA;;kFAChClK,cAAA,CAACJ,SAAAA,EAAAA;wEAAUwK,SAAAA,EAAW5I,aAAAA;AACpB,wEAAA,QAAA,gBAAAxB,cAAA,CAACuK,oBAAAA,EAAAA,EAAAA;;kFAEHvK,cAAA,CAACN,gBAAAA,EAAAA;AACCoK,wEAAAA,YAAAA,EAAY5I,aAAAA,CAAc;AACxBmH,4EAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,2BAAA,CAAA;4EACtBC,cAAAA,EAAgB;AAClB,yEAAA,CAAA;wEACAtB,KAAAA,EAAO9G,MAAAA;wEACPwG,GAAAA,EAAK,CAAA;wEACLC,GAAAA,EAAKxE,WAAAA,CAAYjC,MAAM,IAAIwD,SAAAA;AAC3B2G,wEAAAA,aAAAA,EAAe,CAAC3E,IAAAA,GAAAA;4EACd,IAAIA,IAAAA,KAAShC,WAAWrB,WAAAA,CAAY;gFAAEnC,MAAAA,EAAQwF;AAAK,6EAAA,CAAA;AACrD,wEAAA;;;;;;kEAKNkD,eAAA,CAACjL,iBAAAA,EAAAA;wDAAK4M,QAAAA,EAAS,UAAA;;0EACbxK,cAAA,CAACyK,uBAAAA,EAAAA;AACCC,gEAAAA,KAAAA,EAAOxJ,aAAAA,CAAc;AACnBmH,oEAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,gCAAA,CAAA;oEACtBC,cAAAA,EAAgB;AAClB,iEAAA,CAAA;AACAa,gEAAAA,OAAAA,EAASzG,eAAe,WAAA,GAAc,OAAA;gEACtCgI,OAAAA,EAASvE,gBAAAA;AAET,gEAAA,QAAA,gBAAApG,cAAA,CAAC4K,UAAAA,EAAAA,EAAAA;;0EAEH5K,cAAA,CAACD,uBAAAA,EAAAA,EAAAA;;;;;0DAIL8I,eAAA,CAACjL,iBAAAA,EAAAA;gDAAKqM,SAAAA,EAAU,QAAA;gDAASC,GAAAA,EAAK,CAAA;gDAAGW,UAAAA,EAAY,MAAA;;kEAC3ChC,eAAA,CAACtJ,QAAAA,EAAAA;wDAAS0I,IAAAA,EAAK,SAAA;wDAAUiC,GAAAA,EAAK,CAAA;;AAC5B,0EAAAlK,cAAA,CAACR,mBAAMK,KAAK,EAAA;gEAACuK,SAAAA,EAAW5I,aAAAA;0EACrBN,aAAAA,CAAc;AACbmH,oEAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,iCAAA,CAAA;oEACtBC,cAAAA,EAAgB;AAClB,iEAAA;;0EAEFvI,cAAA,CAACN,gBAAAA,EAAAA;AACCoK,gEAAAA,YAAAA,EAAY5I,aAAAA,CAAc;AACxBmH,oEAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,4BAAA,CAAA;oEACtBC,cAAAA,EAAgB;AAClB,iEAAA,CAAA;gEAEAtB,KAAAA,EAAOJ,QAAAA;gEACPF,GAAAA,EAAK,CAAA;AACLC,gEAAAA,GAAAA,EAAK1G,KAAAA,IAASyD,SAAAA;AACd2G,gEAAAA,aAAAA,EAAe,CAAC3E,IAAAA,GAAAA;oEACd,IAAIA,IAAAA,KAAShC,SAAAA,EAAWoD,UAAAA,CAAW,GAAA,EAAKpB,IAAAA,CAAAA;AAC1C,gEAAA,CAAA;gEACAmF,MAAAA,EAAQrD;+DAPH,CAAC,QAAQ,EAAEJ,SAAAA,CAAAA,CAAW;;;kEAU/BwB,eAAA,CAACtJ,QAAAA,EAAAA;wDAAS0I,IAAAA,EAAK,SAAA;wDAAUiC,GAAAA,EAAK,CAAA;;AAC5B,0EAAAlK,cAAA,CAACR,mBAAMK,KAAK,EAAA;gEAACuK,SAAAA,EAAW5I,aAAAA;0EACrBN,aAAAA,CAAc;AACbmH,oEAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,iCAAA,CAAA;oEACtBC,cAAAA,EAAgB;AAClB,iEAAA;;0EAEFvI,cAAA,CAACN,gBAAAA,EAAAA;AACCoK,gEAAAA,YAAAA,EAAY5I,aAAAA,CAAc;AACxBmH,oEAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,4BAAA,CAAA;oEACtBC,cAAAA,EAAgB;AAClB,iEAAA,CAAA;gEAEAtB,KAAAA,EAAOH,QAAAA;gEACPH,GAAAA,EAAK,CAAA;AACLC,gEAAAA,GAAAA,EAAKzG,MAAAA,IAAUwD,SAAAA;AACf2G,gEAAAA,aAAAA,EAAe,CAAC3E,IAAAA,GAAAA;oEACd,IAAIA,IAAAA,KAAShC,SAAAA,EAAWoD,UAAAA,CAAW,GAAA,EAAKpB,IAAAA,CAAAA;AAC1C,gEAAA,CAAA;gEACAmF,MAAAA,EAAQnD;+DAPH,CAAC,QAAQ,EAAEJ,SAAAA,CAAAA,CAAW;;;;;;;;;;;kCAevCsB,eAAA,CAACvJ,SAAAA,EAAAA;wBAAU0J,UAAAA,EAAW,QAAA;;0CACpBhJ,cAAA,CAAC+K,mBAAAA,EAAAA;gCAAO3B,OAAAA,EAAQ,UAAA;gCAAWuB,OAAAA,EAAS7J,OAAAA;gCAASkK,QAAAA,EAAUnK,MAAAA;0CACpDK,aAAAA,CAAc;oCAAEmH,EAAAA,EAAI,8BAAA;oCAAgCE,cAAAA,EAAgB;AAAS,iCAAA;;0CAEhFM,eAAA,CAACjL,iBAAAA,EAAAA;gCAAKsM,GAAAA,EAAK,CAAA;;AACRjJ,oCAAAA,aAAAA,kBACCjB,cAAA,CAAC+K,mBAAAA,EAAAA;wCACC3B,OAAAA,EAAQ,WAAA;AACRuB,wCAAAA,OAAAA,EAAS,IAAM7C,YAAAA,CAAa,MAAA,CAAA;wCAC5BmD,OAAAA,EAASpK,MAAAA;AACTmK,wCAAAA,QAAAA,EAAU,CAACnD,OAAAA;kDAEV3G,aAAAA,CAAc;AACbmH,4CAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,iCAAA,CAAA;4CACtBC,cAAAA,EAAgB;AAClB,yCAAA;;kDAGJvI,cAAA,CAAC+K,mBAAAA,EAAAA;wCACC3B,OAAAA,EAAQ,SAAA;AACRuB,wCAAAA,OAAAA,EAAS,IAAM7C,YAAAA,CAAa,OAAA,CAAA;wCAC5BmD,OAAAA,EAASpK,MAAAA;AACTmK,wCAAAA,QAAAA,EAAU,CAACnD,OAAAA;kDAEV3G,aAAAA,CAAc;AACbmH,4CAAAA,EAAAA,EAAIC,8BAAAA,CAAkB,0BAAA,CAAA;4CACtBC,cAAAA,EAAgB;AAClB,yCAAA;;;;;;;;;;AAQhB;;;;"}
|
|
1
|
+
{"version":3,"file":"AssetCropEditor.js","sources":["../../../../../../admin/src/pages/Assets/components/AssetDetails/AssetCropEditor.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { useNotification } from '@strapi/admin/strapi-admin';\nimport {\n Box,\n Button,\n Field,\n Flex,\n FocusTrap,\n IconButton,\n NumberInput,\n Portal,\n Typography,\n} from '@strapi/design-system';\nimport { ArrowClockwise, ArrowsHorizontal, ArrowsVertical, Crop, Link } from '@strapi/icons';\nimport { useIntl } from 'react-intl';\nimport { styled, useTheme } from 'styled-components';\n\nimport { prefixFileUrlWithBackendUrl } from '../../../../utils/files';\nimport { getTranslationKey } from '../../../../utils/translations';\n\nimport { resolveCornerResize, useCropImg } from './useCropImg';\n\nimport type {\n AssetWithPopulatedCreatedBy,\n FocalPoint,\n} from '../../../../../../shared/contracts/files';\n\nconst FOCAL_DIAMETER_REM = 5.6;\nconst HANDLE_PX = 12;\n\n/* -------------------------------------------------------------------------------------------------\n * Styled\n * -----------------------------------------------------------------------------------------------*/\n\n// Full-viewport takeover with a small inset so the editor reads as a card. It sits\n// below the tooltip layer so the header buttons' tooltips, which portal to the body,\n// are not covered by it — and above notifications, which it does take over.\nconst Overlay = styled(Flex)`\n position: fixed;\n z-index: ${({ theme }) => theme.zIndices.tooltip - 100};\n flex-direction: column;\n top: ${({ theme }) => theme.spaces[1]};\n left: ${({ theme }) => theme.spaces[1]};\n right: ${({ theme }) => theme.spaces[1]};\n bottom: ${({ theme }) => theme.spaces[1]};\n border-radius: ${({ theme }) => theme.borderRadius};\n border: 1px solid ${({ theme }) => theme.colors.neutral150};\n background: ${({ theme }) => theme.colors.neutral0};\n /* Focused programmatically on open (tabIndex -1) — no visible ring needed. */\n outline: none;\n`;\n\n// Stacked over the editor content, top right. Above the crop area so the buttons stay\n// clickable where the image runs under them.\nconst RotateControls = styled(Flex)`\n position: absolute;\n top: ${({ theme }) => theme.spaces[3]};\n right: ${({ theme }) => theme.spaces[3]};\n z-index: 1;\n`;\n\nconst HeaderBar = styled(Flex)`\n width: 100%;\n gap: ${({ theme }) => theme.spaces[2]};\n padding: ${({ theme }) => `${theme.spaces[3]} ${theme.spaces[5]}`};\n border-bottom: 1px solid ${({ theme }) => theme.colors.neutral150};\n background: ${({ theme }) => theme.colors.neutral0};\n`;\n\n// Fills the remaining space between header/footer; checkerboard pattern.\n// The horizontal padding keeps the image off the viewport edges — the corner\n// handles overhang the image by half their size, and on narrow (mobile)\n// screens an edge-to-edge image leaves nothing to grab.\nconst Body = styled(Box)`\n width: 100%;\n position: relative;\n flex: 1;\n min-height: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n padding: 0 ${({ theme }) => theme.spaces[4]};\n background: repeating-conic-gradient(\n ${({ theme }) => theme.colors.neutral100} 0% 25%,\n ${({ theme }) => theme.colors.neutral0} 0% 50%\n )\n 50% / 20px 20px;\n`;\n\n// Wrapper sized to the image's natural aspect ratio so the <img> inside fills\n// it without letterboxing. Percentage-based positioning of the crop overlay\n// then maps directly to natural-px coordinates.\nconst CropArea = styled.div<{ $aspect?: number }>`\n position: relative;\n max-width: 100%;\n max-height: 100%;\n ${({ $aspect }) => ($aspect ? `aspect-ratio: ${$aspect};` : '')}\n\n img {\n display: block;\n width: 100%;\n height: 100%;\n user-select: none;\n -webkit-user-drag: none;\n }\n`;\n\n// Crop selection rectangle, positioned by % of CropArea. Inverts the dim\n// outside via box-shadow so the focused area shows full color.\nconst CropOverlay = styled.div`\n position: absolute;\n border: 1px dashed ${({ theme }) => theme.colors.primary600};\n box-shadow: 0 0 0 9999px rgba(33, 33, 52, 0.5);\n cursor: move;\n /* Without this, touch browsers claim the gesture for scrolling and fire\n pointercancel mid-drag — the crop drag dies while the finger is down. */\n touch-action: none;\n`;\n\nconst ResizeHandle = styled.button<{ $cursor: string }>`\n position: absolute;\n width: ${HANDLE_PX}px;\n height: ${HANDLE_PX}px;\n margin: -${HANDLE_PX / 2}px;\n padding: 0;\n border: 1px solid ${({ theme }) => theme.colors.primary600};\n border-radius: 2px;\n background: ${({ theme }) => theme.colors.neutral0};\n cursor: ${({ $cursor }) => $cursor};\n touch-action: none;\n`;\n\nconst FocalPointHandle = styled.button`\n position: absolute;\n width: ${FOCAL_DIAMETER_REM}rem;\n height: ${FOCAL_DIAMETER_REM}rem;\n margin: ${-FOCAL_DIAMETER_REM / 2}rem 0 0 ${-FOCAL_DIAMETER_REM / 2}rem;\n border-radius: 50%;\n border: 1px solid ${({ theme }) => theme.colors.neutral800};\n background: transparent;\n cursor: grab;\n padding: 0;\n touch-action: none;\n\n &::before {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.16);\n transform: translate(-50%, -50%);\n }\n &::after {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.neutral800};\n transform: translate(-50%, -50%);\n }\n\n &:active {\n cursor: grabbing;\n }\n`;\n\n// Hidden on mobile — the panel (title/hint + numeric crop & focal inputs) is\n// too large for a phone; touch users set the crop by dragging the rectangle and\n// focal handle directly on the image. Shown from tablet up.\nconst InfoBox = styled(Box)`\n display: none;\n\n ${({ theme }) => theme.breakpoints.medium} {\n display: block;\n position: absolute;\n right: ${({ theme }) => theme.spaces[1]};\n bottom: ${({ theme }) => theme.spaces[1]};\n width: 100%;\n max-width: 32rem;\n padding: ${({ theme }) => theme.spaces[3]};\n border-radius: ${({ theme }) => theme.borderRadius};\n background: ${({ theme }) =>\n theme.colorScheme === 'dark' ? theme.colors.neutral150 : theme.colors.neutral900};\n z-index: 20;\n }\n`;\n\nconst FooterBar = styled(Flex)`\n width: 100%;\n justify-content: space-between;\n padding: ${({ theme }) => `${theme.spaces[3]} ${theme.spaces[5]}`};\n border-top: 1px solid ${({ theme }) => theme.colors.neutral150};\n background: ${({ theme }) => theme.colors.neutral0};\n`;\n\nconst FieldRow = styled(Field.Root)`\n flex-direction: row;\n align-items: center;\n`;\n\nconst FieldNumberInput = styled(NumberInput)`\n width: 8.4rem;\n`;\n\nconst LabelIcon = styled(Field.Label)`\n display: flex;\n align-items: center;\n justify-content: center;\n width: 1.6rem;\n height: 1.6rem;\n`;\n\nconst BackgroundPreserveRatioContainer = styled(Box)`\n position: absolute;\n top: 50%;\n left: 0;\n transform: translateY(-50%);\n\n svg {\n display: block;\n }\n`;\n\nconst BackgroundPreserveRatio = () => (\n <BackgroundPreserveRatioContainer>\n <svg width=\"17\" height=\"49\" viewBox=\"0 0 17 49\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path\n d=\"M0.5 0.5H8.5C12.9183 0.5 16.5 4.08172 16.5 8.5M0.5 48.5H8.5C12.9183 48.5 16.5 44.9183 16.5 40.5\"\n stroke=\"#666687\"\n strokeLinecap=\"round\"\n />\n </svg>\n </BackgroundPreserveRatioContainer>\n);\n\n/* -------------------------------------------------------------------------------------------------\n * AssetCropEditor\n * -----------------------------------------------------------------------------------------------*/\n\ninterface AssetCropEditorProps {\n asset: AssetWithPopulatedCreatedBy;\n isBusy?: boolean;\n onClose: () => void;\n onApply: (file: globalThis.File, focalPoint: FocalPoint) => void;\n onSaveAsCopy: (file: globalThis.File, focalPoint: FocalPoint) => void;\n /**\n * \"Save as copy\" creates a new asset (`assets.create`), a different permission\n * from cropping in place (`assets.update`). Hidden when the user lacks it, so\n * an update-only role can't trigger a 403.\n */\n canSaveAsCopy: boolean;\n}\n\ntype Corner = 'tl' | 'tr' | 'bl' | 'br';\n\nexport const AssetCropEditor = ({\n asset,\n isBusy = false,\n onClose,\n onApply,\n onSaveAsCopy,\n canSaveAsCopy,\n}: AssetCropEditorProps) => {\n const { formatMessage } = useIntl();\n const { toggleNotification } = useNotification();\n // The InfoBox is a fixed dark surface in both themes, so its text can't use a\n // single neutral token (those invert with the scheme). Pick the scheme's white\n // and a muted grey explicitly.\n const theme = useTheme();\n const isDark = theme.colorScheme === 'dark';\n const infoTextColor = isDark ? 'neutral1000' : 'neutral0';\n const infoMutedColor = isDark ? 'neutral600' : 'neutral200';\n const imgRef = React.useRef<HTMLImageElement>(null);\n const cropAreaRef = React.useRef<HTMLDivElement>(null);\n const overlayRef = React.useRef<HTMLDivElement>(null);\n\n // FocusTrap's auto-focus lands on the first focusable element — the crop\n // width NumberInput — which pops the keyboard on mobile the moment the\n // editor opens. Focus the (tabIndex -1) overlay instead: keyboard users are\n // one Tab away from the first control, Escape still reaches the trap.\n React.useEffect(() => {\n overlayRef.current?.focus();\n }, []);\n\n const {\n init,\n crop,\n naturalSize,\n aspectRatio,\n setCropSize,\n setCropPosition,\n setAspectRatio,\n produceFile,\n rotation,\n rotate,\n width,\n height,\n } = useCropImg();\n\n const [aspectLocked, setAspectLocked] = React.useState(false);\n // Focal point as a percentage of the crop area (matches the {x,y} contract).\n const [focal, setFocal] = React.useState<FocalPoint>(asset.focalPoint ?? { x: 50, y: 50 });\n\n const handleRotate = (direction: 'left' | 'right') => {\n rotate(direction);\n setFocal((prev) =>\n direction === 'right' ? { x: 100 - prev.y, y: prev.x } : { x: prev.y, y: 100 - prev.x }\n );\n };\n\n // The crop editor reads pixels from a canvas (useCropImg -> toBlob), so its\n // <img> must be CORS-clean and always sets crossOrigin=\"anonymous\" (see the\n // img below) — even for unsigned URLs, which AssetPreview leaves unset because\n // it only displays. That difference is why the crop URL is kept DISTINCT from\n // the thumbnail's: identical URL + different crossOrigin collide in the HTTP\n // cache (#26581), and the crop's anonymous request could otherwise reuse the\n // thumbnail's non-CORS cached response and taint the canvas. So the buster\n // uses `updatedAt` where AssetPreview uses `v`, giving each its own entry\n // (mirrors the legacy PreviewBox fix).\n // Signed URLs get no param at all — a presigned S3 URL breaks if you add one,\n // and on the signed side AssetPreview also loads anonymous, so the identical\n // URL there shares one CORS-consistent entry, which is fine.\n const rawImageUrl = prefixFileUrlWithBackendUrl(asset.url) as string;\n const cacheKey =\n asset.updatedAt && !asset.isUrlSigned ? new Date(asset.updatedAt).getTime() : undefined;\n const imageUrl =\n cacheKey !== undefined\n ? `${rawImageUrl}${rawImageUrl.includes('?') ? '&' : '?'}updatedAt=${cacheKey}`\n : rawImageUrl;\n\n const isQuarterTurn = rotation === 90 || rotation === 270;\n\n /**\n * Applied inline rather than through the styled component: the admin's global\n * `img { max-width: 100% }` reset would otherwise clamp the reciprocal sizing and\n * square the image off. A half turn keeps the aspect, so it only spins; a quarter\n * turn swaps it, and the image is laid out against the container's other axis\n * before being turned into place.\n */\n const rotatedImageStyle = React.useMemo((): React.CSSProperties | undefined => {\n if (!rotation) return undefined;\n\n const aspect =\n naturalSize.width && naturalSize.height ? naturalSize.width / naturalSize.height : undefined;\n\n // A half turn keeps the aspect, so the image stays in flow and only spins. Taking\n // it out of flow there would collapse the crop area, which is sized by it.\n if (!isQuarterTurn || !aspect) {\n return { transform: `rotate(${rotation}deg)` };\n }\n\n return {\n position: 'absolute',\n top: '50%',\n left: '50%',\n maxWidth: 'none',\n maxHeight: 'none',\n width: `${100 / aspect}%`,\n height: `${100 * aspect}%`,\n transform: `translate(-50%, -50%) rotate(${rotation}deg)`,\n };\n }, [rotation, isQuarterTurn, naturalSize.width, naturalSize.height]);\n\n const handleImageLoad = () => {\n if (imgRef.current) {\n init(imgRef.current);\n }\n };\n\n // Map a pointer event to natural-px coordinates inside the image.\n const pointerToNatural = (event: PointerEvent | React.PointerEvent) => {\n const rect = cropAreaRef.current?.getBoundingClientRect();\n if (!rect || !naturalSize.width || !naturalSize.height) return null;\n const ratioX = naturalSize.width / rect.width;\n const ratioY = naturalSize.height / rect.height;\n return {\n x: (event.clientX - rect.left) * ratioX,\n y: (event.clientY - rect.top) * ratioY,\n };\n };\n\n // Teardown for the drag currently in flight, so it can be run both on pointer\n // release AND on unmount. Without the unmount path, closing the editor mid-drag\n // (Escape, drawer close, a re-render dropping the overlay) would never fire a\n // pointer event, leaking the window listeners and letting a stale `onMove`\n // call `setCropPosition` on an unmounted component.\n const activeDragCleanupRef = React.useRef<(() => void) | null>(null);\n\n React.useEffect(() => {\n return () => {\n activeDragCleanupRef.current?.();\n };\n }, []);\n\n // Track a pointer drag until the pointer is released or the browser cancels\n // it. The pointer is captured so the drag follows the finger even once it\n // leaves the (12px) handle it started on, and pointercancel is treated as\n // release so an interrupted touch never leaves stale window listeners.\n const trackPointerDrag = (event: React.PointerEvent, onMove: (e: PointerEvent) => void) => {\n event.preventDefault();\n event.stopPropagation();\n const { pointerId } = event;\n try {\n event.currentTarget.setPointerCapture(pointerId);\n } catch {\n // Unsupported environment (jsdom) — window listeners still track the drag.\n }\n const move = (e: PointerEvent) => {\n if (e.pointerId !== pointerId) return;\n onMove(e);\n };\n const cleanup = () => {\n window.removeEventListener('pointermove', move);\n window.removeEventListener('pointerup', end);\n window.removeEventListener('pointercancel', end);\n activeDragCleanupRef.current = null;\n };\n const end = (e: PointerEvent) => {\n if (e.pointerId !== pointerId) return;\n cleanup();\n };\n // Only one drag is ever live at a time; tear down any previous one first.\n activeDragCleanupRef.current?.();\n activeDragCleanupRef.current = cleanup;\n window.addEventListener('pointermove', move);\n window.addEventListener('pointerup', end);\n window.addEventListener('pointercancel', end);\n };\n\n // Drag the entire crop rectangle.\n const handleMovePointerDown = (event: React.PointerEvent) => {\n const start = pointerToNatural(event);\n if (!start) return;\n const startCrop = { ...crop };\n trackPointerDrag(event, (e) => {\n const next = pointerToNatural(e);\n if (!next) return;\n setCropPosition({\n x: startCrop.x + (next.x - start.x),\n y: startCrop.y + (next.y - start.y),\n });\n });\n };\n\n // Resize from a corner; the opposite corner stays anchored.\n const handleResizePointerDown = (corner: Corner) => (event: React.PointerEvent) => {\n const startCrop = { ...crop };\n const anchorX =\n corner === 'tl' || corner === 'bl' ? startCrop.x + startCrop.width : startCrop.x;\n const anchorY =\n corner === 'tl' || corner === 'tr' ? startCrop.y + startCrop.height : startCrop.y;\n trackPointerDrag(event, (e) => {\n const point = pointerToNatural(e);\n if (!point) return;\n const {\n x,\n y,\n width: w,\n height: h,\n } = resolveCornerResize({\n anchorX,\n anchorY,\n point,\n aspectRatio: aspectLocked ? aspectRatio : null,\n });\n setCropPosition({ x, y });\n setCropSize({ width: w, height: h });\n });\n };\n\n const toggleAspectLock = () => {\n setAspectLocked((locked) => {\n const next = !locked;\n setAspectRatio(next && height ? width / height : null);\n return next;\n });\n };\n\n // Drag the focal handle within the crop rectangle, clamped 0–100.\n const handleFocalPointerDown = (event: React.PointerEvent) => {\n trackPointerDrag(event, (e) => {\n const point = pointerToNatural(e);\n if (!point) return;\n // Convert pointer to a percentage of the current crop area.\n const px = ((point.x - crop.x) / crop.width) * 100;\n const py = ((point.y - crop.y) / crop.height) * 100;\n setFocal({\n x: Math.round(Math.min(100, Math.max(0, px))),\n y: Math.round(Math.min(100, Math.max(0, py))),\n });\n });\n };\n\n const focalPxX = Math.round((focal.x / 100) * width);\n const focalPxY = Math.round((focal.y / 100) * height);\n\n const setFocalPx = (axis: 'x' | 'y', value: number) => {\n const dim = axis === 'x' ? width : height;\n if (!dim) return;\n const pct = Math.min(100, Math.max(0, (value / dim) * 100));\n setFocal((prev) => ({ ...prev, [axis]: Math.round(pct) }));\n };\n\n /**\n * The design system's `NumberInput` stops adopting an external `value` once the\n * user has typed into it, so a rotation that swaps width and height leaves the\n * fields showing the old numbers. Remounting on rotation re-seeds them — the\n * same reason the focal fields remount on blur.\n */\n const sizeFieldsKey = rotation;\n\n /**\n * Forces a focal field to redisplay the state on blur.\n *\n * One key per axis: a shared key remounted both fields, so tabbing out of X\n * destroyed Y before it could take focus and it was unreachable by keyboard.\n */\n const [focalXKey, setFocalXKey] = React.useState(0);\n const [focalYKey, setFocalYKey] = React.useState(0);\n const syncFocalX = () => setFocalXKey((key) => key + 1);\n const syncFocalY = () => setFocalYKey((key) => key + 1);\n\n const cropPercents =\n naturalSize.width && naturalSize.height\n ? {\n left: (crop.x / naturalSize.width) * 100,\n top: (crop.y / naturalSize.height) * 100,\n width: (crop.width / naturalSize.width) * 100,\n height: (crop.height / naturalSize.height) * 100,\n }\n : null;\n\n // The crop is only usable once the image has loaded and `init()` has seeded\n // the natural size. Until then the produce/export path would reject.\n const isReady = cropPercents !== null;\n\n const handleAction = async (action: 'apply' | 'copy') => {\n if (!isReady) return;\n\n let file: globalThis.File;\n try {\n file = await produceFile(asset.name, asset.mime ?? 'image/png', asset.updatedAt);\n } catch {\n toggleNotification({\n type: 'danger',\n message: formatMessage({\n id: getTranslationKey('asset-details.crop.export-error'),\n defaultMessage: 'Could not process the cropped image.',\n }),\n });\n return;\n }\n\n const roundedFocal = { x: Math.round(focal.x), y: Math.round(focal.y) };\n if (action === 'apply') {\n onApply(file, roundedFocal);\n } else {\n onSaveAsCopy(file, roundedFocal);\n }\n };\n\n return (\n <Portal>\n <FocusTrap onEscape={onClose} skipAutoFocus>\n <Overlay ref={overlayRef} tabIndex={-1}>\n <HeaderBar alignItems=\"center\">\n <Crop aria-hidden />\n <Typography variant=\"omega\" fontWeight=\"bold\">\n {formatMessage({\n id: getTranslationKey('asset-details.crop.title'),\n defaultMessage: 'Crop & Focus area',\n })}\n </Typography>\n </HeaderBar>\n\n <Body>\n <RotateControls direction=\"column\" gap={1}>\n <IconButton\n disabled={!isReady}\n onClick={() => handleRotate('right')}\n label={formatMessage({\n id: getTranslationKey('asset-details.crop.rotate-right'),\n defaultMessage: 'Rotate 90° right',\n })}\n >\n <ArrowClockwise />\n </IconButton>\n </RotateControls>\n\n <CropArea\n ref={cropAreaRef}\n $aspect={\n naturalSize.width && naturalSize.height\n ? naturalSize.width / naturalSize.height\n : undefined\n }\n >\n {isQuarterTurn ? (\n /**\n * On a quarter turn the image is taken out of flow, leaving the box with no\n * in-flow content to size it. A definite height instead of this would fight\n * `max-width`: `aspect-ratio` only holds while one side stays `auto`.\n */\n <div aria-hidden style={{ width: naturalSize.width, maxWidth: '100%' }} />\n ) : null}\n\n <img\n ref={imgRef}\n data-testid=\"crop-editor-image\"\n style={rotatedImageStyle}\n src={imageUrl}\n alt={asset.name}\n // Always anonymous, unlike AssetPreview/AssetsGrid which gate it:\n // the editor reads pixels via canvas (useCropImg -> toBlob), which\n // taints on a cross-origin image loaded without CORS, so every\n // remote image must be fetched CORS-clean.\n crossOrigin=\"anonymous\"\n onLoad={handleImageLoad}\n draggable={false}\n />\n\n {cropPercents ? (\n <CropOverlay\n style={{\n left: `${cropPercents.left}%`,\n top: `${cropPercents.top}%`,\n width: `${cropPercents.width}%`,\n height: `${cropPercents.height}%`,\n }}\n onPointerDown={handleMovePointerDown}\n >\n <ResizeHandle\n type=\"button\"\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.resize.top-left'),\n defaultMessage: 'Resize top-left',\n })}\n $cursor=\"nwse-resize\"\n style={{ left: 0, top: 0 }}\n onPointerDown={handleResizePointerDown('tl')}\n />\n <ResizeHandle\n type=\"button\"\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.resize.top-right'),\n defaultMessage: 'Resize top-right',\n })}\n $cursor=\"nesw-resize\"\n style={{ right: 0, top: 0 }}\n onPointerDown={handleResizePointerDown('tr')}\n />\n <ResizeHandle\n type=\"button\"\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.resize.bottom-left'),\n defaultMessage: 'Resize bottom-left',\n })}\n $cursor=\"nesw-resize\"\n style={{ left: 0, bottom: 0 }}\n onPointerDown={handleResizePointerDown('bl')}\n />\n <ResizeHandle\n type=\"button\"\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.resize.bottom-right'),\n defaultMessage: 'Resize bottom-right',\n })}\n $cursor=\"nwse-resize\"\n style={{ right: 0, bottom: 0 }}\n onPointerDown={handleResizePointerDown('br')}\n />\n\n <FocalPointHandle\n type=\"button\"\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.focal-point'),\n defaultMessage: 'Focal point',\n })}\n style={{ left: `${focal.x}%`, top: `${focal.y}%` }}\n onPointerDown={handleFocalPointerDown}\n />\n </CropOverlay>\n ) : null}\n </CropArea>\n\n <InfoBox>\n <Flex direction=\"column\" alignItems=\"stretch\" gap={1} paddingBottom={3}>\n <Typography variant=\"omega\" fontWeight=\"bold\" textColor={infoTextColor}>\n {formatMessage({\n id: getTranslationKey('asset-details.crop.title'),\n defaultMessage: 'Crop & Focus area',\n })}\n </Typography>\n <Typography variant=\"pi\" textColor={infoMutedColor}>\n {formatMessage({\n id: getTranslationKey('asset-details.crop.hint'),\n defaultMessage:\n 'Set the crop area with the rectangle. Pin the always-visible area with the circle.',\n })}\n </Typography>\n </Flex>\n\n <Flex gap={6} alignItems=\"center\">\n <Flex alignItems=\"center\" gap={2}>\n <Flex direction=\"column\" gap={2}>\n <FieldRow name=\"crop-width\" gap={2}>\n <LabelIcon textColor={infoTextColor}>\n <ArrowsHorizontal />\n </LabelIcon>\n <FieldNumberInput\n key={`crop-width-${sizeFieldsKey}`}\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.width'),\n defaultMessage: 'Width (px)',\n })}\n value={width}\n min={1}\n max={naturalSize.width || undefined}\n onValueChange={(next) => {\n if (next !== undefined) setCropSize({ width: next });\n }}\n />\n </FieldRow>\n <FieldRow name=\"crop-height\" gap={2}>\n <LabelIcon textColor={infoTextColor}>\n <ArrowsVertical />\n </LabelIcon>\n <FieldNumberInput\n key={`crop-height-${sizeFieldsKey}`}\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.height'),\n defaultMessage: 'Height (px)',\n })}\n value={height}\n min={1}\n max={naturalSize.height || undefined}\n onValueChange={(next) => {\n if (next !== undefined) setCropSize({ height: next });\n }}\n />\n </FieldRow>\n </Flex>\n\n <Flex position=\"relative\">\n <IconButton\n label={formatMessage({\n id: getTranslationKey('asset-details.crop.aspect-lock'),\n defaultMessage: 'Lock aspect ratio',\n })}\n variant={aspectLocked ? 'secondary' : 'ghost'}\n onClick={toggleAspectLock}\n >\n <Link />\n </IconButton>\n <BackgroundPreserveRatio />\n </Flex>\n </Flex>\n\n <Flex direction=\"column\" gap={2} marginLeft={'auto'}>\n <FieldRow name=\"focal-x\" gap={2}>\n <Field.Label textColor={infoTextColor}>\n {formatMessage({\n id: getTranslationKey('asset-details.crop.focal-x-axis'),\n defaultMessage: 'X',\n })}\n </Field.Label>\n <FieldNumberInput\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.focal-x'),\n defaultMessage: 'Focal point X (px)',\n })}\n key={`focal-x-${focalXKey}`}\n value={focalPxX}\n min={0}\n max={width || undefined}\n onValueChange={(next) => {\n if (next !== undefined) setFocalPx('x', next);\n }}\n onBlur={syncFocalX}\n />\n </FieldRow>\n <FieldRow name=\"focal-y\" gap={2}>\n <Field.Label textColor={infoTextColor}>\n {formatMessage({\n id: getTranslationKey('asset-details.crop.focal-y-axis'),\n defaultMessage: 'Y',\n })}\n </Field.Label>\n <FieldNumberInput\n aria-label={formatMessage({\n id: getTranslationKey('asset-details.crop.focal-y'),\n defaultMessage: 'Focal point Y (px)',\n })}\n key={`focal-y-${focalYKey}`}\n value={focalPxY}\n min={0}\n max={height || undefined}\n onValueChange={(next) => {\n if (next !== undefined) setFocalPx('y', next);\n }}\n onBlur={syncFocalY}\n />\n </FieldRow>\n </Flex>\n </Flex>\n </InfoBox>\n </Body>\n\n <FooterBar alignItems=\"center\">\n <Button variant=\"tertiary\" onClick={onClose} disabled={isBusy}>\n {formatMessage({ id: 'app.components.Button.cancel', defaultMessage: 'Cancel' })}\n </Button>\n <Flex gap={2}>\n {canSaveAsCopy && (\n <Button\n variant=\"secondary\"\n onClick={() => handleAction('copy')}\n loading={isBusy}\n disabled={!isReady}\n >\n {formatMessage({\n id: getTranslationKey('asset-details.crop.save-as-copy'),\n defaultMessage: 'Save as copy',\n })}\n </Button>\n )}\n <Button\n variant=\"default\"\n onClick={() => handleAction('apply')}\n loading={isBusy}\n disabled={!isReady}\n >\n {formatMessage({\n id: getTranslationKey('asset-details.crop.apply'),\n defaultMessage: 'Apply',\n })}\n </Button>\n </Flex>\n </FooterBar>\n </Overlay>\n </FocusTrap>\n </Portal>\n );\n};\n"],"names":["FOCAL_DIAMETER_REM","HANDLE_PX","Overlay","styled","Flex","theme","zIndices","tooltip","spaces","borderRadius","colors","neutral150","neutral0","RotateControls","HeaderBar","Body","Box","neutral100","CropArea","div","$aspect","CropOverlay","primary600","ResizeHandle","button","$cursor","FocalPointHandle","neutral800","InfoBox","breakpoints","medium","colorScheme","neutral900","FooterBar","FieldRow","Field","Root","FieldNumberInput","NumberInput","LabelIcon","Label","BackgroundPreserveRatioContainer","BackgroundPreserveRatio","_jsx","svg","width","height","viewBox","fill","xmlns","path","d","stroke","strokeLinecap","AssetCropEditor","asset","isBusy","onClose","onApply","onSaveAsCopy","canSaveAsCopy","formatMessage","useIntl","toggleNotification","useNotification","useTheme","isDark","infoTextColor","infoMutedColor","imgRef","React","useRef","cropAreaRef","overlayRef","useEffect","current","focus","init","crop","naturalSize","aspectRatio","setCropSize","setCropPosition","setAspectRatio","produceFile","rotation","rotate","useCropImg","aspectLocked","setAspectLocked","useState","focal","setFocal","focalPoint","x","y","handleRotate","direction","prev","rawImageUrl","prefixFileUrlWithBackendUrl","url","cacheKey","updatedAt","isUrlSigned","Date","getTime","undefined","imageUrl","includes","isQuarterTurn","rotatedImageStyle","useMemo","aspect","transform","position","top","left","maxWidth","maxHeight","handleImageLoad","pointerToNatural","event","rect","getBoundingClientRect","ratioX","ratioY","clientX","clientY","activeDragCleanupRef","trackPointerDrag","onMove","preventDefault","stopPropagation","pointerId","currentTarget","setPointerCapture","move","e","cleanup","window","removeEventListener","end","addEventListener","handleMovePointerDown","start","startCrop","next","handleResizePointerDown","corner","anchorX","anchorY","point","w","h","resolveCornerResize","toggleAspectLock","locked","handleFocalPointerDown","px","py","Math","round","min","max","focalPxX","focalPxY","setFocalPx","axis","value","dim","pct","sizeFieldsKey","focalXKey","setFocalXKey","focalYKey","setFocalYKey","syncFocalX","key","syncFocalY","cropPercents","isReady","handleAction","action","file","name","mime","type","message","id","getTranslationKey","defaultMessage","roundedFocal","Portal","FocusTrap","onEscape","skipAutoFocus","_jsxs","ref","tabIndex","alignItems","Crop","aria-hidden","Typography","variant","fontWeight","gap","IconButton","disabled","onClick","label","ArrowClockwise","style","img","data-testid","src","alt","crossOrigin","onLoad","draggable","onPointerDown","aria-label","right","bottom","paddingBottom","textColor","ArrowsHorizontal","onValueChange","ArrowsVertical","Link","marginLeft","onBlur","Button","loading"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { useNotification } from '@strapi/admin/strapi-admin';
|
|
4
|
-
import { Portal, FocusTrap, Typography,
|
|
5
|
-
import { Crop, ArrowsHorizontal, ArrowsVertical, Link } from '@strapi/icons';
|
|
4
|
+
import { Portal, FocusTrap, Typography, IconButton, Flex, Field, Button, Box, NumberInput } from '@strapi/design-system';
|
|
5
|
+
import { Crop, ArrowClockwise, ArrowsHorizontal, ArrowsVertical, Link } from '@strapi/icons';
|
|
6
6
|
import { useIntl } from 'react-intl';
|
|
7
7
|
import { useTheme, styled } from 'styled-components';
|
|
8
8
|
import { prefixFileUrlWithBackendUrl } from '../../../../utils/files.mjs';
|
|
@@ -13,10 +13,12 @@ const FOCAL_DIAMETER_REM = 5.6;
|
|
|
13
13
|
const HANDLE_PX = 12;
|
|
14
14
|
/* -------------------------------------------------------------------------------------------------
|
|
15
15
|
* Styled
|
|
16
|
-
* -----------------------------------------------------------------------------------------------*/ // Full-viewport takeover with a small inset so the editor reads as a card.
|
|
16
|
+
* -----------------------------------------------------------------------------------------------*/ // Full-viewport takeover with a small inset so the editor reads as a card. It sits
|
|
17
|
+
// below the tooltip layer so the header buttons' tooltips, which portal to the body,
|
|
18
|
+
// are not covered by it — and above notifications, which it does take over.
|
|
17
19
|
const Overlay = styled(Flex)`
|
|
18
20
|
position: fixed;
|
|
19
|
-
z-index:
|
|
21
|
+
z-index: ${({ theme })=>theme.zIndices.tooltip - 100};
|
|
20
22
|
flex-direction: column;
|
|
21
23
|
top: ${({ theme })=>theme.spaces[1]};
|
|
22
24
|
left: ${({ theme })=>theme.spaces[1]};
|
|
@@ -28,6 +30,14 @@ const Overlay = styled(Flex)`
|
|
|
28
30
|
/* Focused programmatically on open (tabIndex -1) — no visible ring needed. */
|
|
29
31
|
outline: none;
|
|
30
32
|
`;
|
|
33
|
+
// Stacked over the editor content, top right. Above the crop area so the buttons stay
|
|
34
|
+
// clickable where the image runs under them.
|
|
35
|
+
const RotateControls = styled(Flex)`
|
|
36
|
+
position: absolute;
|
|
37
|
+
top: ${({ theme })=>theme.spaces[3]};
|
|
38
|
+
right: ${({ theme })=>theme.spaces[3]};
|
|
39
|
+
z-index: 1;
|
|
40
|
+
`;
|
|
31
41
|
const HeaderBar = styled(Flex)`
|
|
32
42
|
width: 100%;
|
|
33
43
|
gap: ${({ theme })=>theme.spaces[2]};
|
|
@@ -218,13 +228,20 @@ const AssetCropEditor = ({ asset, isBusy = false, onClose, onApply, onSaveAsCopy
|
|
|
218
228
|
React.useEffect(()=>{
|
|
219
229
|
overlayRef.current?.focus();
|
|
220
230
|
}, []);
|
|
221
|
-
const { init, crop, naturalSize, aspectRatio, setCropSize, setCropPosition, setAspectRatio, produceFile, width, height } = useCropImg();
|
|
231
|
+
const { init, crop, naturalSize, aspectRatio, setCropSize, setCropPosition, setAspectRatio, produceFile, rotation, rotate, width, height } = useCropImg();
|
|
222
232
|
const [aspectLocked, setAspectLocked] = React.useState(false);
|
|
223
233
|
// Focal point as a percentage of the crop area (matches the {x,y} contract).
|
|
224
234
|
const [focal, setFocal] = React.useState(asset.focalPoint ?? {
|
|
225
235
|
x: 50,
|
|
226
236
|
y: 50
|
|
227
237
|
});
|
|
238
|
+
const handleRotate = (direction)=>{
|
|
239
|
+
rotate(direction);
|
|
240
|
+
setFocal((prev)=>({
|
|
241
|
+
x: 100 - prev.y,
|
|
242
|
+
y: prev.x
|
|
243
|
+
}) );
|
|
244
|
+
};
|
|
228
245
|
// The crop editor reads pixels from a canvas (useCropImg -> toBlob), so its
|
|
229
246
|
// <img> must be CORS-clean and always sets crossOrigin="anonymous" (see the
|
|
230
247
|
// img below) — even for unsigned URLs, which AssetPreview leaves unset because
|
|
@@ -240,6 +257,39 @@ const AssetCropEditor = ({ asset, isBusy = false, onClose, onApply, onSaveAsCopy
|
|
|
240
257
|
const rawImageUrl = prefixFileUrlWithBackendUrl(asset.url);
|
|
241
258
|
const cacheKey = asset.updatedAt && !asset.isUrlSigned ? new Date(asset.updatedAt).getTime() : undefined;
|
|
242
259
|
const imageUrl = cacheKey !== undefined ? `${rawImageUrl}${rawImageUrl.includes('?') ? '&' : '?'}updatedAt=${cacheKey}` : rawImageUrl;
|
|
260
|
+
const isQuarterTurn = rotation === 90 || rotation === 270;
|
|
261
|
+
/**
|
|
262
|
+
* Applied inline rather than through the styled component: the admin's global
|
|
263
|
+
* `img { max-width: 100% }` reset would otherwise clamp the reciprocal sizing and
|
|
264
|
+
* square the image off. A half turn keeps the aspect, so it only spins; a quarter
|
|
265
|
+
* turn swaps it, and the image is laid out against the container's other axis
|
|
266
|
+
* before being turned into place.
|
|
267
|
+
*/ const rotatedImageStyle = React.useMemo(()=>{
|
|
268
|
+
if (!rotation) return undefined;
|
|
269
|
+
const aspect = naturalSize.width && naturalSize.height ? naturalSize.width / naturalSize.height : undefined;
|
|
270
|
+
// A half turn keeps the aspect, so the image stays in flow and only spins. Taking
|
|
271
|
+
// it out of flow there would collapse the crop area, which is sized by it.
|
|
272
|
+
if (!isQuarterTurn || !aspect) {
|
|
273
|
+
return {
|
|
274
|
+
transform: `rotate(${rotation}deg)`
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
position: 'absolute',
|
|
279
|
+
top: '50%',
|
|
280
|
+
left: '50%',
|
|
281
|
+
maxWidth: 'none',
|
|
282
|
+
maxHeight: 'none',
|
|
283
|
+
width: `${100 / aspect}%`,
|
|
284
|
+
height: `${100 * aspect}%`,
|
|
285
|
+
transform: `translate(-50%, -50%) rotate(${rotation}deg)`
|
|
286
|
+
};
|
|
287
|
+
}, [
|
|
288
|
+
rotation,
|
|
289
|
+
isQuarterTurn,
|
|
290
|
+
naturalSize.width,
|
|
291
|
+
naturalSize.height
|
|
292
|
+
]);
|
|
243
293
|
const handleImageLoad = ()=>{
|
|
244
294
|
if (imgRef.current) {
|
|
245
295
|
init(imgRef.current);
|
|
@@ -376,6 +426,12 @@ const AssetCropEditor = ({ asset, isBusy = false, onClose, onApply, onSaveAsCopy
|
|
|
376
426
|
}));
|
|
377
427
|
};
|
|
378
428
|
/**
|
|
429
|
+
* The design system's `NumberInput` stops adopting an external `value` once the
|
|
430
|
+
* user has typed into it, so a rotation that swaps width and height leaves the
|
|
431
|
+
* fields showing the old numbers. Remounting on rotation re-seeds them — the
|
|
432
|
+
* same reason the focal fields remount on blur.
|
|
433
|
+
*/ const sizeFieldsKey = rotation;
|
|
434
|
+
/**
|
|
379
435
|
* Forces a focal field to redisplay the state on blur.
|
|
380
436
|
*
|
|
381
437
|
* One key per axis: a shared key remounted both fields, so tabbing out of X
|
|
@@ -444,12 +500,38 @@ const AssetCropEditor = ({ asset, isBusy = false, onClose, onApply, onSaveAsCopy
|
|
|
444
500
|
}),
|
|
445
501
|
/*#__PURE__*/ jsxs(Body, {
|
|
446
502
|
children: [
|
|
503
|
+
/*#__PURE__*/ jsx(RotateControls, {
|
|
504
|
+
direction: "column",
|
|
505
|
+
gap: 1,
|
|
506
|
+
children: /*#__PURE__*/ jsx(IconButton, {
|
|
507
|
+
disabled: !isReady,
|
|
508
|
+
onClick: ()=>handleRotate('right'),
|
|
509
|
+
label: formatMessage({
|
|
510
|
+
id: getTranslationKey('asset-details.crop.rotate-right'),
|
|
511
|
+
defaultMessage: 'Rotate 90° right'
|
|
512
|
+
}),
|
|
513
|
+
children: /*#__PURE__*/ jsx(ArrowClockwise, {})
|
|
514
|
+
})
|
|
515
|
+
}),
|
|
447
516
|
/*#__PURE__*/ jsxs(CropArea, {
|
|
448
517
|
ref: cropAreaRef,
|
|
449
518
|
$aspect: naturalSize.width && naturalSize.height ? naturalSize.width / naturalSize.height : undefined,
|
|
450
519
|
children: [
|
|
520
|
+
isQuarterTurn ? /**
|
|
521
|
+
* On a quarter turn the image is taken out of flow, leaving the box with no
|
|
522
|
+
* in-flow content to size it. A definite height instead of this would fight
|
|
523
|
+
* `max-width`: `aspect-ratio` only holds while one side stays `auto`.
|
|
524
|
+
*/ /*#__PURE__*/ jsx("div", {
|
|
525
|
+
"aria-hidden": true,
|
|
526
|
+
style: {
|
|
527
|
+
width: naturalSize.width,
|
|
528
|
+
maxWidth: '100%'
|
|
529
|
+
}
|
|
530
|
+
}) : null,
|
|
451
531
|
/*#__PURE__*/ jsx("img", {
|
|
452
532
|
ref: imgRef,
|
|
533
|
+
"data-testid": "crop-editor-image",
|
|
534
|
+
style: rotatedImageStyle,
|
|
453
535
|
src: imageUrl,
|
|
454
536
|
alt: asset.name,
|
|
455
537
|
// Always anonymous, unlike AssetPreview/AssetsGrid which gate it:
|
|
@@ -597,7 +679,7 @@ const AssetCropEditor = ({ asset, isBusy = false, onClose, onApply, onSaveAsCopy
|
|
|
597
679
|
width: next
|
|
598
680
|
});
|
|
599
681
|
}
|
|
600
|
-
})
|
|
682
|
+
}, `crop-width-${sizeFieldsKey}`)
|
|
601
683
|
]
|
|
602
684
|
}),
|
|
603
685
|
/*#__PURE__*/ jsxs(FieldRow, {
|
|
@@ -621,7 +703,7 @@ const AssetCropEditor = ({ asset, isBusy = false, onClose, onApply, onSaveAsCopy
|
|
|
621
703
|
height: next
|
|
622
704
|
});
|
|
623
705
|
}
|
|
624
|
-
})
|
|
706
|
+
}, `crop-height-${sizeFieldsKey}`)
|
|
625
707
|
]
|
|
626
708
|
})
|
|
627
709
|
]
|