@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.
Files changed (304) hide show
  1. package/dist/admin/hooks/useAIMetadataAvailability.js +14 -0
  2. package/dist/admin/hooks/useAIMetadataAvailability.js.map +1 -0
  3. package/dist/admin/hooks/useAIMetadataAvailability.mjs +12 -0
  4. package/dist/admin/hooks/useAIMetadataAvailability.mjs.map +1 -0
  5. package/dist/admin/hooks/useAIMetadataEnabled.js +7 -5
  6. package/dist/admin/hooks/useAIMetadataEnabled.js.map +1 -1
  7. package/dist/admin/hooks/useAIMetadataEnabled.mjs +7 -5
  8. package/dist/admin/hooks/useAIMetadataEnabled.mjs.map +1 -1
  9. package/dist/admin/hooks/useTracking.js +4 -10
  10. package/dist/admin/hooks/useTracking.js.map +1 -1
  11. package/dist/admin/hooks/useTracking.mjs +5 -10
  12. package/dist/admin/hooks/useTracking.mjs.map +1 -1
  13. package/dist/admin/legacy/hooks/useAIMetadataAvailability.js +14 -0
  14. package/dist/admin/legacy/hooks/useAIMetadataAvailability.js.map +1 -0
  15. package/dist/admin/legacy/hooks/useAIMetadataAvailability.mjs +12 -0
  16. package/dist/admin/legacy/hooks/useAIMetadataAvailability.mjs.map +1 -0
  17. package/dist/admin/legacy/hooks/useAIMetadataEnabled.js +23 -0
  18. package/dist/admin/legacy/hooks/useAIMetadataEnabled.js.map +1 -0
  19. package/dist/admin/legacy/hooks/useAIMetadataEnabled.mjs +21 -0
  20. package/dist/admin/legacy/hooks/useAIMetadataEnabled.mjs.map +1 -0
  21. package/dist/admin/legacy/hooks/useTracking.js +5 -3
  22. package/dist/admin/legacy/hooks/useTracking.js.map +1 -1
  23. package/dist/admin/legacy/hooks/useTracking.mjs +5 -3
  24. package/dist/admin/legacy/hooks/useTracking.mjs.map +1 -1
  25. package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.js +2 -1
  26. package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.js.map +1 -1
  27. package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.mjs +2 -1
  28. package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.mjs.map +1 -1
  29. package/dist/admin/legacy/pages/App/MediaLibrary.js +6 -5
  30. package/dist/admin/legacy/pages/App/MediaLibrary.js.map +1 -1
  31. package/dist/admin/legacy/pages/App/MediaLibrary.mjs +6 -5
  32. package/dist/admin/legacy/pages/App/MediaLibrary.mjs.map +1 -1
  33. package/dist/admin/pages/Assets/AssetsPage.js +24 -12
  34. package/dist/admin/pages/Assets/AssetsPage.js.map +1 -1
  35. package/dist/admin/pages/Assets/AssetsPage.mjs +25 -13
  36. package/dist/admin/pages/Assets/AssetsPage.mjs.map +1 -1
  37. package/dist/admin/pages/Assets/components/AssetActions.js +326 -0
  38. package/dist/admin/pages/Assets/components/AssetActions.js.map +1 -0
  39. package/dist/admin/pages/Assets/components/AssetActions.mjs +324 -0
  40. package/dist/admin/pages/Assets/components/AssetActions.mjs.map +1 -0
  41. package/dist/admin/pages/Assets/components/AssetActionsMenu.js +28 -309
  42. package/dist/admin/pages/Assets/components/AssetActionsMenu.js.map +1 -1
  43. package/dist/admin/pages/Assets/components/AssetActionsMenu.mjs +31 -312
  44. package/dist/admin/pages/Assets/components/AssetActionsMenu.mjs.map +1 -1
  45. package/dist/admin/pages/Assets/components/AssetContextMenu.js +79 -0
  46. package/dist/admin/pages/Assets/components/AssetContextMenu.js.map +1 -0
  47. package/dist/admin/pages/Assets/components/AssetContextMenu.mjs +77 -0
  48. package/dist/admin/pages/Assets/components/AssetContextMenu.mjs.map +1 -0
  49. package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.js +87 -5
  50. package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.js.map +1 -1
  51. package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.mjs +89 -7
  52. package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.mjs.map +1 -1
  53. package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +3 -3
  54. package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
  55. package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +3 -3
  56. package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
  57. package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.js +62 -5
  58. package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.js.map +1 -1
  59. package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.mjs +60 -6
  60. package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.mjs.map +1 -1
  61. package/dist/admin/pages/Assets/components/AssetsGrid.js +27 -4
  62. package/dist/admin/pages/Assets/components/AssetsGrid.js.map +1 -1
  63. package/dist/admin/pages/Assets/components/AssetsGrid.mjs +27 -4
  64. package/dist/admin/pages/Assets/components/AssetsGrid.mjs.map +1 -1
  65. package/dist/admin/pages/Assets/components/AssetsTable.js +27 -4
  66. package/dist/admin/pages/Assets/components/AssetsTable.js.map +1 -1
  67. package/dist/admin/pages/Assets/components/AssetsTable.mjs +27 -4
  68. package/dist/admin/pages/Assets/components/AssetsTable.mjs.map +1 -1
  69. package/dist/admin/pages/Assets/components/CursorAnchoredMenu.js +77 -0
  70. package/dist/admin/pages/Assets/components/CursorAnchoredMenu.js.map +1 -0
  71. package/dist/admin/pages/Assets/components/CursorAnchoredMenu.mjs +75 -0
  72. package/dist/admin/pages/Assets/components/CursorAnchoredMenu.mjs.map +1 -0
  73. package/dist/admin/pages/Assets/components/DeleteItemsDialog.js +4 -2
  74. package/dist/admin/pages/Assets/components/DeleteItemsDialog.js.map +1 -1
  75. package/dist/admin/pages/Assets/components/DeleteItemsDialog.mjs +4 -2
  76. package/dist/admin/pages/Assets/components/DeleteItemsDialog.mjs.map +1 -1
  77. package/dist/admin/pages/Assets/components/FolderActions.js +139 -0
  78. package/dist/admin/pages/Assets/components/FolderActions.js.map +1 -0
  79. package/dist/admin/pages/Assets/components/FolderActions.mjs +137 -0
  80. package/dist/admin/pages/Assets/components/FolderActions.mjs.map +1 -0
  81. package/dist/admin/pages/Assets/components/FolderActionsMenu.js +25 -119
  82. package/dist/admin/pages/Assets/components/FolderActionsMenu.js.map +1 -1
  83. package/dist/admin/pages/Assets/components/FolderActionsMenu.mjs +27 -121
  84. package/dist/admin/pages/Assets/components/FolderActionsMenu.mjs.map +1 -1
  85. package/dist/admin/pages/Assets/components/FolderContextMenu.js +67 -0
  86. package/dist/admin/pages/Assets/components/FolderContextMenu.js.map +1 -0
  87. package/dist/admin/pages/Assets/components/FolderContextMenu.mjs +65 -0
  88. package/dist/admin/pages/Assets/components/FolderContextMenu.mjs.map +1 -0
  89. package/dist/admin/pages/Assets/components/FolderTree/FolderTree.js +139 -45
  90. package/dist/admin/pages/Assets/components/FolderTree/FolderTree.js.map +1 -1
  91. package/dist/admin/pages/Assets/components/FolderTree/FolderTree.mjs +140 -47
  92. package/dist/admin/pages/Assets/components/FolderTree/FolderTree.mjs.map +1 -1
  93. package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.js +115 -0
  94. package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.js.map +1 -0
  95. package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.mjs +113 -0
  96. package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.mjs.map +1 -0
  97. package/dist/admin/pages/Assets/components/ItemContextMenu.js +125 -0
  98. package/dist/admin/pages/Assets/components/ItemContextMenu.js.map +1 -0
  99. package/dist/admin/pages/Assets/components/ItemContextMenu.mjs +122 -0
  100. package/dist/admin/pages/Assets/components/ItemContextMenu.mjs.map +1 -0
  101. package/dist/admin/pages/Assets/components/MainAreaContextMenu.js +88 -63
  102. package/dist/admin/pages/Assets/components/MainAreaContextMenu.js.map +1 -1
  103. package/dist/admin/pages/Assets/components/MainAreaContextMenu.mjs +88 -63
  104. package/dist/admin/pages/Assets/components/MainAreaContextMenu.mjs.map +1 -1
  105. package/dist/admin/pages/Assets/components/SelectionActions.js +111 -0
  106. package/dist/admin/pages/Assets/components/SelectionActions.js.map +1 -0
  107. package/dist/admin/pages/Assets/components/SelectionActions.mjs +109 -0
  108. package/dist/admin/pages/Assets/components/SelectionActions.mjs.map +1 -0
  109. package/dist/admin/pages/Assets/components/SelectionContextMenu.js +82 -0
  110. package/dist/admin/pages/Assets/components/SelectionContextMenu.js.map +1 -0
  111. package/dist/admin/pages/Assets/components/SelectionContextMenu.mjs +80 -0
  112. package/dist/admin/pages/Assets/components/SelectionContextMenu.mjs.map +1 -0
  113. package/dist/admin/pages/Assets/hooks/useAssetSelection.js +10 -0
  114. package/dist/admin/pages/Assets/hooks/useAssetSelection.js.map +1 -1
  115. package/dist/admin/pages/Assets/hooks/useAssetSelection.mjs +10 -0
  116. package/dist/admin/pages/Assets/hooks/useAssetSelection.mjs.map +1 -1
  117. package/dist/admin/pages/SettingsPage/SettingsPage.js +10 -3
  118. package/dist/admin/pages/SettingsPage/SettingsPage.js.map +1 -1
  119. package/dist/admin/pages/SettingsPage/SettingsPage.mjs +10 -3
  120. package/dist/admin/pages/SettingsPage/SettingsPage.mjs.map +1 -1
  121. package/dist/admin/services/api.js.map +1 -1
  122. package/dist/admin/services/api.mjs.map +1 -1
  123. package/dist/admin/src/hooks/useAIMetadataAvailability.d.ts +5 -0
  124. package/dist/admin/src/hooks/useAIMetadataEnabled.d.ts +5 -3
  125. package/dist/admin/src/hooks/useTracking.d.ts +0 -7
  126. package/dist/admin/src/legacy/components/EditAssetDialog/PreviewBox/PreviewComponents.d.ts +2 -2
  127. package/dist/admin/src/legacy/hooks/useAIMetadataAvailability.d.ts +5 -0
  128. package/dist/admin/src/legacy/hooks/useAIMetadataEnabled.d.ts +12 -0
  129. package/dist/admin/src/legacy/hooks/useSettings.d.ts +1 -0
  130. package/dist/admin/src/pages/Assets/components/ActionsMenuContent.d.ts +2 -2
  131. package/dist/admin/src/pages/Assets/components/AssetActions.d.ts +70 -0
  132. package/dist/admin/src/pages/Assets/components/AssetActionsMenu.d.ts +5 -14
  133. package/dist/admin/src/pages/Assets/components/AssetContextMenu.d.ts +27 -0
  134. package/dist/admin/src/pages/Assets/components/AssetDetails/useCropImg.d.ts +16 -0
  135. package/dist/admin/src/pages/Assets/components/CursorAnchoredMenu.d.ts +32 -0
  136. package/dist/admin/src/pages/Assets/components/FolderActions.d.ts +48 -0
  137. package/dist/admin/src/pages/Assets/components/FolderActionsMenu.d.ts +4 -8
  138. package/dist/admin/src/pages/Assets/components/FolderContextMenu.d.ts +21 -0
  139. package/dist/admin/src/pages/Assets/components/FolderTree/FolderTree.d.ts +5 -1
  140. package/dist/admin/src/pages/Assets/components/FolderTree/FolderTreeRowMenu.d.ts +25 -0
  141. package/dist/admin/src/pages/Assets/components/ItemContextMenu.d.ts +53 -0
  142. package/dist/admin/src/pages/Assets/components/MainAreaContextMenu.d.ts +15 -1
  143. package/dist/admin/src/pages/Assets/components/SelectionActions.d.ts +40 -0
  144. package/dist/admin/src/pages/Assets/components/SelectionContextMenu.d.ts +25 -0
  145. package/dist/admin/src/pages/Assets/hooks/useAssetSelection.d.ts +5 -0
  146. package/dist/admin/src/utils/mediaLibraryVersion.d.ts +18 -0
  147. package/dist/admin/translations/en.json.js +9 -3
  148. package/dist/admin/translations/en.json.js.map +1 -1
  149. package/dist/admin/translations/en.json.mjs +9 -3
  150. package/dist/admin/translations/en.json.mjs.map +1 -1
  151. package/dist/admin/utils/mediaLibraryVersion.js +24 -0
  152. package/dist/admin/utils/mediaLibraryVersion.js.map +1 -0
  153. package/dist/admin/utils/mediaLibraryVersion.mjs +20 -0
  154. package/dist/admin/utils/mediaLibraryVersion.mjs.map +1 -0
  155. package/dist/server/bootstrap.js +7 -0
  156. package/dist/server/bootstrap.js.map +1 -1
  157. package/dist/server/bootstrap.mjs +7 -0
  158. package/dist/server/bootstrap.mjs.map +1 -1
  159. package/dist/server/controllers/admin-file.js +2 -2
  160. package/dist/server/controllers/admin-file.js.map +1 -1
  161. package/dist/server/controllers/admin-file.mjs +2 -2
  162. package/dist/server/controllers/admin-file.mjs.map +1 -1
  163. package/dist/server/controllers/admin-folder-file.js +51 -4
  164. package/dist/server/controllers/admin-folder-file.js.map +1 -1
  165. package/dist/server/controllers/admin-folder-file.mjs +52 -5
  166. package/dist/server/controllers/admin-folder-file.mjs.map +1 -1
  167. package/dist/server/controllers/admin-folder.js +5 -5
  168. package/dist/server/controllers/admin-folder.js.map +1 -1
  169. package/dist/server/controllers/admin-folder.mjs +5 -5
  170. package/dist/server/controllers/admin-folder.mjs.map +1 -1
  171. package/dist/server/controllers/admin-settings.js +2 -1
  172. package/dist/server/controllers/admin-settings.js.map +1 -1
  173. package/dist/server/controllers/admin-settings.mjs +2 -1
  174. package/dist/server/controllers/admin-settings.mjs.map +1 -1
  175. package/dist/server/controllers/content-api.js +30 -7
  176. package/dist/server/controllers/content-api.js.map +1 -1
  177. package/dist/server/controllers/content-api.mjs +30 -7
  178. package/dist/server/controllers/content-api.mjs.map +1 -1
  179. package/dist/server/controllers/validation/admin/folder-file.js +4 -4
  180. package/dist/server/controllers/validation/admin/folder-file.js.map +1 -1
  181. package/dist/server/controllers/validation/admin/folder-file.mjs +2 -2
  182. package/dist/server/controllers/validation/admin/folder-file.mjs.map +1 -1
  183. package/dist/server/controllers/validation/admin/folder.js +3 -4
  184. package/dist/server/controllers/validation/admin/folder.js.map +1 -1
  185. package/dist/server/controllers/validation/admin/folder.mjs +3 -4
  186. package/dist/server/controllers/validation/admin/folder.mjs.map +1 -1
  187. package/dist/server/controllers/validation/admin/upload.js +1 -2
  188. package/dist/server/controllers/validation/admin/upload.js.map +1 -1
  189. package/dist/server/controllers/validation/admin/upload.mjs +1 -2
  190. package/dist/server/controllers/validation/admin/upload.mjs.map +1 -1
  191. package/dist/server/controllers/validation/admin/utils.js +1 -2
  192. package/dist/server/controllers/validation/admin/utils.js.map +1 -1
  193. package/dist/server/controllers/validation/admin/utils.mjs +1 -2
  194. package/dist/server/controllers/validation/admin/utils.mjs.map +1 -1
  195. package/dist/server/errors.js +12 -0
  196. package/dist/server/errors.js.map +1 -0
  197. package/dist/server/errors.mjs +10 -0
  198. package/dist/server/errors.mjs.map +1 -0
  199. package/dist/server/migrations/unsign-richtext-and-blocks-urls.js +2 -2
  200. package/dist/server/migrations/unsign-richtext-and-blocks-urls.js.map +1 -1
  201. package/dist/server/migrations/unsign-richtext-and-blocks-urls.mjs +1 -1
  202. package/dist/server/migrations/unsign-richtext-and-blocks-urls.mjs.map +1 -1
  203. package/dist/server/routes/content-api.js +3 -3
  204. package/dist/server/routes/content-api.js.map +1 -1
  205. package/dist/server/routes/content-api.mjs +3 -3
  206. package/dist/server/routes/content-api.mjs.map +1 -1
  207. package/dist/server/routes/validation/upload.js +8 -0
  208. package/dist/server/routes/validation/upload.js.map +1 -1
  209. package/dist/server/routes/validation/upload.mjs +8 -0
  210. package/dist/server/routes/validation/upload.mjs.map +1 -1
  211. package/dist/server/services/ai-metadata-provider.js +83 -0
  212. package/dist/server/services/ai-metadata-provider.js.map +1 -0
  213. package/dist/server/services/ai-metadata-provider.mjs +61 -0
  214. package/dist/server/services/ai-metadata-provider.mjs.map +1 -0
  215. package/dist/server/services/ai-metadata-strapi-managed.js +48 -0
  216. package/dist/server/services/ai-metadata-strapi-managed.js.map +1 -0
  217. package/dist/server/services/ai-metadata-strapi-managed.mjs +46 -0
  218. package/dist/server/services/ai-metadata-strapi-managed.mjs.map +1 -0
  219. package/dist/server/services/ai-metadata.js +5 -58
  220. package/dist/server/services/ai-metadata.js.map +1 -1
  221. package/dist/server/services/ai-metadata.mjs +6 -39
  222. package/dist/server/services/ai-metadata.mjs.map +1 -1
  223. package/dist/server/services/api-upload-folder.js +3 -3
  224. package/dist/server/services/api-upload-folder.js.map +1 -1
  225. package/dist/server/services/api-upload-folder.mjs +3 -3
  226. package/dist/server/services/api-upload-folder.mjs.map +1 -1
  227. package/dist/server/services/extensions/utils.js +19 -15
  228. package/dist/server/services/extensions/utils.js.map +1 -1
  229. package/dist/server/services/extensions/utils.mjs +19 -15
  230. package/dist/server/services/extensions/utils.mjs.map +1 -1
  231. package/dist/server/services/file.js +2 -2
  232. package/dist/server/services/file.js.map +1 -1
  233. package/dist/server/services/file.mjs +1 -1
  234. package/dist/server/services/file.mjs.map +1 -1
  235. package/dist/server/services/folder.js +9 -6
  236. package/dist/server/services/folder.js.map +1 -1
  237. package/dist/server/services/folder.mjs +9 -6
  238. package/dist/server/services/folder.mjs.map +1 -1
  239. package/dist/server/services/index.js +3 -1
  240. package/dist/server/services/index.js.map +1 -1
  241. package/dist/server/services/index.mjs +3 -1
  242. package/dist/server/services/index.mjs.map +1 -1
  243. package/dist/server/services/metrics.js +1 -1
  244. package/dist/server/services/metrics.js.map +1 -1
  245. package/dist/server/services/metrics.mjs +1 -1
  246. package/dist/server/services/metrics.mjs.map +1 -1
  247. package/dist/server/services/provider.js +3 -4
  248. package/dist/server/services/provider.js.map +1 -1
  249. package/dist/server/services/provider.mjs +3 -4
  250. package/dist/server/services/provider.mjs.map +1 -1
  251. package/dist/server/services/upload.js +5 -6
  252. package/dist/server/services/upload.js.map +1 -1
  253. package/dist/server/services/upload.mjs +4 -5
  254. package/dist/server/services/upload.mjs.map +1 -1
  255. package/dist/server/services/weekly-metrics.js +2 -2
  256. package/dist/server/services/weekly-metrics.js.map +1 -1
  257. package/dist/server/services/weekly-metrics.mjs +2 -2
  258. package/dist/server/services/weekly-metrics.mjs.map +1 -1
  259. package/dist/server/src/bootstrap.d.ts.map +1 -1
  260. package/dist/server/src/controllers/admin-folder-file.d.ts.map +1 -1
  261. package/dist/server/src/controllers/admin-folder.d.ts.map +1 -1
  262. package/dist/server/src/controllers/admin-settings.d.ts.map +1 -1
  263. package/dist/server/src/controllers/content-api.d.ts.map +1 -1
  264. package/dist/server/src/controllers/validation/admin/folder.d.ts.map +1 -1
  265. package/dist/server/src/controllers/validation/admin/upload.d.ts.map +1 -1
  266. package/dist/server/src/controllers/validation/admin/utils.d.ts.map +1 -1
  267. package/dist/server/src/errors.d.ts +6 -0
  268. package/dist/server/src/errors.d.ts.map +1 -0
  269. package/dist/server/src/index.d.ts +6 -1
  270. package/dist/server/src/index.d.ts.map +1 -1
  271. package/dist/server/src/mcp/ambient-instance.d.ts +3 -1
  272. package/dist/server/src/mcp/ambient-instance.d.ts.map +1 -1
  273. package/dist/server/src/routes/validation/upload.d.ts +4 -0
  274. package/dist/server/src/routes/validation/upload.d.ts.map +1 -1
  275. package/dist/server/src/services/ai-metadata-provider.d.ts +26 -0
  276. package/dist/server/src/services/ai-metadata-provider.d.ts.map +1 -0
  277. package/dist/server/src/services/ai-metadata-strapi-managed.d.ts +7 -0
  278. package/dist/server/src/services/ai-metadata-strapi-managed.d.ts.map +1 -0
  279. package/dist/server/src/services/ai-metadata.d.ts.map +1 -1
  280. package/dist/server/src/services/extensions/utils.d.ts +33 -1
  281. package/dist/server/src/services/extensions/utils.d.ts.map +1 -1
  282. package/dist/server/src/services/folder.d.ts +5 -2
  283. package/dist/server/src/services/folder.d.ts.map +1 -1
  284. package/dist/server/src/services/index.d.ts +6 -1
  285. package/dist/server/src/services/index.d.ts.map +1 -1
  286. package/dist/server/src/services/provider.d.ts.map +1 -1
  287. package/dist/server/src/services/upload.d.ts.map +1 -1
  288. package/dist/server/src/utils/images.d.ts +2 -2
  289. package/dist/server/src/utils/images.d.ts.map +1 -1
  290. package/dist/server/src/utils/index.d.ts +2 -0
  291. package/dist/server/src/utils/index.d.ts.map +1 -1
  292. package/dist/server/utils/images.js +6 -7
  293. package/dist/server/utils/images.js.map +1 -1
  294. package/dist/server/utils/images.mjs +6 -7
  295. package/dist/server/utils/images.mjs.map +1 -1
  296. package/dist/server/utils/index.js.map +1 -1
  297. package/dist/server/utils/index.mjs.map +1 -1
  298. package/dist/shared/contracts/settings.d.ts +6 -0
  299. package/package.json +11 -11
  300. package/dist/admin/legacy/hooks/useAiAvailability.js +0 -22
  301. package/dist/admin/legacy/hooks/useAiAvailability.js.map +0 -1
  302. package/dist/admin/legacy/hooks/useAiAvailability.mjs +0 -20
  303. package/dist/admin/legacy/hooks/useAiAvailability.mjs.map +0 -1
  304. 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: 1200;
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":"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"}
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, Flex, IconButton, Field, Button, Box, NumberInput } from '@strapi/design-system';
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: 1200;
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
  ]