@nice2dev/ui-graphics 1.3.15 → 1.3.17

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 (294) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/animation/AnimatedPerson.js +470 -143
  3. package/dist/cjs/animation/AnimationEditor.js +289 -101
  4. package/dist/cjs/animation/Audience.js +83 -61
  5. package/dist/cjs/animation/BodyRenderer.js +69 -28
  6. package/dist/cjs/animation/animationHelper.js +281 -288
  7. package/dist/cjs/animation/characterTypes.js +200 -203
  8. package/dist/cjs/animation/choreoDSL.js +205 -219
  9. package/dist/cjs/animation/choreography.js +4 -32
  10. package/dist/cjs/animation/karaokeIntegration.js +68 -77
  11. package/dist/cjs/animation/lottieExport.js +108 -114
  12. package/dist/cjs/animation/motionPath.js +225 -248
  13. package/dist/cjs/animation/physics.js +227 -254
  14. package/dist/cjs/animation/rig/RigPlayer.js +53 -56
  15. package/dist/cjs/animation/rig/RiggedBody.js +71 -26
  16. package/dist/cjs/animation/rig/choreoRigBridge.js +72 -77
  17. package/dist/cjs/animation/rig/ik.js +13 -18
  18. package/dist/cjs/animation/rig/poseOps.js +34 -41
  19. package/dist/cjs/animation/rig/presets.js +64 -72
  20. package/dist/cjs/animation/rig/rigMath.js +25 -32
  21. package/dist/cjs/animation/rig/rigTypes.js +59 -63
  22. package/dist/cjs/animation/shapes/arms.js +31 -13
  23. package/dist/cjs/animation/shapes/eyes.js +129 -47
  24. package/dist/cjs/animation/shapes/hair.js +72 -45
  25. package/dist/cjs/animation/shapes/heads.js +131 -57
  26. package/dist/cjs/animation/shapes/headwear.js +47 -38
  27. package/dist/cjs/animation/shapes/legs.js +31 -13
  28. package/dist/cjs/animation/shapes/mouths.js +120 -44
  29. package/dist/cjs/animation/shapes/noses.js +80 -25
  30. package/dist/cjs/animation/shapes/outfits.js +101 -39
  31. package/dist/cjs/animation/shapes/torsos.js +15 -11
  32. package/dist/cjs/animation/spineExport.js +103 -121
  33. package/dist/cjs/core/LocalUI.js +108 -43
  34. package/dist/cjs/core/NiceBrushEngine.js +758 -552
  35. package/dist/cjs/core/collaboration.js +249 -251
  36. package/dist/cjs/core/colorBlindness.js +100 -115
  37. package/dist/cjs/core/dragDrop.js +155 -158
  38. package/dist/cjs/core/editorAsk.js +51 -41
  39. package/dist/cjs/core/fixes.js +140 -171
  40. package/dist/cjs/core/gridGuides.js +166 -201
  41. package/dist/cjs/core/historyVisual.js +156 -135
  42. package/dist/cjs/core/i18n.js +13 -14
  43. package/dist/cjs/core/integrations.js +238 -249
  44. package/dist/cjs/core/minimap.js +149 -151
  45. package/dist/cjs/core/plugins.js +98 -113
  46. package/dist/cjs/core/rtl.js +71 -83
  47. package/dist/cjs/core/shortcuts.js +196 -225
  48. package/dist/cjs/core/theme.js +142 -149
  49. package/dist/cjs/core/yjsCollaboration.js +232 -293
  50. package/dist/cjs/design/design-tools.js +12 -22
  51. package/dist/cjs/font/NiceFontEditor.js +877 -316
  52. package/dist/cjs/font/fontFormatUtils.js +388 -448
  53. package/dist/cjs/game/GameAsset2dEditor.js +1201 -392
  54. package/dist/cjs/game/GameAsset2dEditor.module.css.js +146 -5
  55. package/dist/cjs/game/gameAssetTypes.js +7 -16
  56. package/dist/cjs/game/gameAssetUtils.js +390 -413
  57. package/dist/cjs/game/useGameAssetEditor.js +820 -763
  58. package/dist/cjs/index.js +81 -84
  59. package/dist/cjs/nice2dev-ui-graphics.css +1 -1
  60. package/dist/cjs/node_modules/lib0/array.js +8 -70
  61. package/dist/cjs/node_modules/lib0/binary.js +3 -20
  62. package/dist/cjs/node_modules/lib0/broadcastchannel.js +24 -86
  63. package/dist/cjs/node_modules/lib0/buffer.js +15 -82
  64. package/dist/cjs/node_modules/lib0/conditions.js +3 -16
  65. package/dist/cjs/node_modules/lib0/decoding.js +108 -287
  66. package/dist/cjs/node_modules/lib0/dom.js +14 -44
  67. package/dist/cjs/node_modules/lib0/encoding.js +68 -418
  68. package/dist/cjs/node_modules/lib0/environment.js +26 -92
  69. package/dist/cjs/node_modules/lib0/error.js +5 -29
  70. package/dist/cjs/node_modules/lib0/function.js +27 -66
  71. package/dist/cjs/node_modules/lib0/iterator.js +8 -33
  72. package/dist/cjs/node_modules/lib0/logging.common.js +12 -22
  73. package/dist/cjs/node_modules/lib0/logging.js +32 -71
  74. package/dist/cjs/node_modules/lib0/map.js +16 -86
  75. package/dist/cjs/node_modules/lib0/math.js +3 -32
  76. package/dist/cjs/node_modules/lib0/number.js +4 -15
  77. package/dist/cjs/node_modules/lib0/object.js +14 -88
  78. package/dist/cjs/node_modules/lib0/observable.js +38 -78
  79. package/dist/cjs/node_modules/lib0/pair.js +3 -20
  80. package/dist/cjs/node_modules/lib0/prng.js +8 -77
  81. package/dist/cjs/node_modules/lib0/promise.js +6 -30
  82. package/dist/cjs/node_modules/lib0/random.js +8 -23
  83. package/dist/cjs/node_modules/lib0/schema.js +313 -687
  84. package/dist/cjs/node_modules/lib0/set.js +3 -10
  85. package/dist/cjs/node_modules/lib0/storage.js +20 -55
  86. package/dist/cjs/node_modules/lib0/string.js +17 -72
  87. package/dist/cjs/node_modules/lib0/symbol.js +2 -12
  88. package/dist/cjs/node_modules/lib0/time.js +2 -15
  89. package/dist/cjs/node_modules/lib0/trait/equality.js +3 -28
  90. package/dist/cjs/node_modules/lib0/url.js +4 -18
  91. package/dist/cjs/node_modules/lib0/webcrypto.js +2 -5
  92. package/dist/cjs/node_modules/y-protocols/auth.js +5 -19
  93. package/dist/cjs/node_modules/y-protocols/awareness.js +53 -116
  94. package/dist/cjs/node_modules/y-protocols/sync.js +16 -103
  95. package/dist/cjs/node_modules/y-websocket/src/y-websocket.js +77 -219
  96. package/dist/cjs/node_modules/yjs/dist/yjs.js +2128 -4868
  97. package/dist/cjs/photo/FilterThumb.js +37 -25
  98. package/dist/cjs/photo/NiceAITools.js +679 -467
  99. package/dist/cjs/photo/PhotoEditor.js +34 -20
  100. package/dist/cjs/photo/PhotoEditor.module.css.js +296 -5
  101. package/dist/cjs/photo/PhotoEditorCanvas.js +250 -91
  102. package/dist/cjs/photo/PhotoEditorLeftPanel.js +1086 -224
  103. package/dist/cjs/photo/PhotoEditorToolbar.js +135 -19
  104. package/dist/cjs/photo/photoEditorActions.js +89 -72
  105. package/dist/cjs/photo/photoEditorTypes.js +29 -32
  106. package/dist/cjs/photo/photoFilters.js +959 -985
  107. package/dist/cjs/photo/photoOverlays.js +899 -945
  108. package/dist/cjs/photo/photoSelection.js +427 -499
  109. package/dist/cjs/photo/usePhotoEditor.js +739 -741
  110. package/dist/cjs/pixel/HSVPicker.js +191 -175
  111. package/dist/cjs/pixel/PixelEditor.js +159 -48
  112. package/dist/cjs/pixel/PixelEditor.module.css.js +209 -5
  113. package/dist/cjs/pixel/PixelEditorCanvas.js +29 -16
  114. package/dist/cjs/pixel/PixelEditorMenuBar.js +160 -162
  115. package/dist/cjs/pixel/PixelEditorRightPanel.js +377 -45
  116. package/dist/cjs/pixel/PixelEditorStatusBar.js +47 -20
  117. package/dist/cjs/pixel/PixelEditorTimeline.js +106 -23
  118. package/dist/cjs/pixel/PixelEditorToolbar.js +99 -21
  119. package/dist/cjs/pixel/SpriteGeneratorPanel.js +483 -303
  120. package/dist/cjs/pixel/animalGeneratorPresets.js +776 -601
  121. package/dist/cjs/pixel/asepriteFormat.js +511 -560
  122. package/dist/cjs/pixel/buildingGeneratorPresets.js +693 -520
  123. package/dist/cjs/pixel/characterGeneratorPresets.js +644 -544
  124. package/dist/cjs/pixel/pixel-pro.js +52 -63
  125. package/dist/cjs/pixel/pixelEditorExports.js +388 -428
  126. package/dist/cjs/pixel/pixelEditorTypes.js +91 -108
  127. package/dist/cjs/pixel/pixelEditorUtils.js +519 -561
  128. package/dist/cjs/pixel/spriteGeneratorCore.js +206 -257
  129. package/dist/cjs/pixel/usePixelEditor.js +1321 -1343
  130. package/dist/cjs/texture/Nice3DTexturePainter.js +707 -440
  131. package/dist/cjs/ui/NiceUIDesigner.js +954 -527
  132. package/dist/cjs/vector/NiceSvgToCode.js +586 -446
  133. package/dist/cjs/vector/VectorEditor.js +290 -117
  134. package/dist/cjs/vector/VectorEditor.module.css.js +86 -5
  135. package/dist/cjs/vector/VectorEditorMenuBar.js +333 -275
  136. package/dist/cjs/vector/VectorEditorRightPanel.js +1094 -371
  137. package/dist/cjs/vector/VectorEditorShapeRenderer.js +198 -60
  138. package/dist/cjs/vector/VectorEditorStatusBar.js +39 -13
  139. package/dist/cjs/vector/useVectorEditor.js +848 -889
  140. package/dist/cjs/vector/vectorAnimation.js +110 -137
  141. package/dist/cjs/vector/vectorBooleanOps.js +398 -485
  142. package/dist/cjs/vector/vectorEditorExport.js +201 -203
  143. package/dist/cjs/vector/vectorEditorImport.js +173 -189
  144. package/dist/cjs/vector/vectorEditorTypes.js +42 -49
  145. package/dist/cjs/vector/vectorGradients.js +151 -174
  146. package/dist/cjs/vector/vectorIconExport.js +52 -59
  147. package/dist/cjs/vector/vectorIconImport.js +169 -219
  148. package/dist/esm/animation/AnimatedPerson.js +470 -141
  149. package/dist/esm/animation/AnimationEditor.js +290 -100
  150. package/dist/esm/animation/Audience.js +83 -59
  151. package/dist/esm/animation/BodyRenderer.js +71 -25
  152. package/dist/esm/animation/animationHelper.js +284 -287
  153. package/dist/esm/animation/characterTypes.js +204 -202
  154. package/dist/esm/animation/choreoDSL.js +212 -218
  155. package/dist/esm/animation/choreography.js +8 -31
  156. package/dist/esm/animation/karaokeIntegration.js +74 -76
  157. package/dist/esm/animation/lottieExport.js +112 -113
  158. package/dist/esm/animation/motionPath.js +235 -247
  159. package/dist/esm/animation/physics.js +235 -253
  160. package/dist/esm/animation/rig/RigPlayer.js +53 -54
  161. package/dist/esm/animation/rig/RiggedBody.js +71 -24
  162. package/dist/esm/animation/rig/choreoRigBridge.js +74 -76
  163. package/dist/esm/animation/rig/ik.js +14 -17
  164. package/dist/esm/animation/rig/poseOps.js +37 -40
  165. package/dist/esm/animation/rig/presets.js +70 -71
  166. package/dist/esm/animation/rig/rigMath.js +28 -31
  167. package/dist/esm/animation/rig/rigTypes.js +62 -62
  168. package/dist/esm/animation/shapes/arms.js +32 -12
  169. package/dist/esm/animation/shapes/eyes.js +135 -46
  170. package/dist/esm/animation/shapes/hair.js +80 -44
  171. package/dist/esm/animation/shapes/heads.js +140 -56
  172. package/dist/esm/animation/shapes/headwear.js +54 -37
  173. package/dist/esm/animation/shapes/legs.js +32 -12
  174. package/dist/esm/animation/shapes/mouths.js +127 -43
  175. package/dist/esm/animation/shapes/noses.js +87 -24
  176. package/dist/esm/animation/shapes/outfits.js +106 -38
  177. package/dist/esm/animation/shapes/torsos.js +16 -10
  178. package/dist/esm/animation/spineExport.js +106 -120
  179. package/dist/esm/core/LocalUI.js +111 -42
  180. package/dist/esm/core/NiceBrushEngine.js +761 -551
  181. package/dist/esm/core/collaboration.js +255 -250
  182. package/dist/esm/core/colorBlindness.js +106 -114
  183. package/dist/esm/core/dragDrop.js +157 -157
  184. package/dist/esm/core/editorAsk.js +53 -40
  185. package/dist/esm/core/fixes.js +145 -170
  186. package/dist/esm/core/gridGuides.js +178 -200
  187. package/dist/esm/core/historyVisual.js +158 -134
  188. package/dist/esm/core/i18n.js +15 -13
  189. package/dist/esm/core/integrations.js +244 -248
  190. package/dist/esm/core/minimap.js +151 -150
  191. package/dist/esm/core/plugins.js +102 -112
  192. package/dist/esm/core/rtl.js +77 -82
  193. package/dist/esm/core/shortcuts.js +200 -224
  194. package/dist/esm/core/theme.js +150 -148
  195. package/dist/esm/core/yjsCollaboration.js +240 -292
  196. package/dist/esm/design/design-tools.js +13 -21
  197. package/dist/esm/font/NiceFontEditor.js +880 -315
  198. package/dist/esm/font/fontFormatUtils.js +394 -447
  199. package/dist/esm/game/GameAsset2dEditor.js +1202 -391
  200. package/dist/esm/game/GameAsset2dEditor.module.css.js +146 -3
  201. package/dist/esm/game/gameAssetTypes.js +8 -15
  202. package/dist/esm/game/gameAssetUtils.js +408 -412
  203. package/dist/esm/game/useGameAssetEditor.js +821 -762
  204. package/dist/esm/index.js +332 -69
  205. package/dist/esm/nice2dev-ui-graphics.css +1 -1
  206. package/dist/esm/node_modules/lib0/array.js +15 -69
  207. package/dist/esm/node_modules/lib0/binary.js +14 -19
  208. package/dist/esm/node_modules/lib0/broadcastchannel.js +27 -85
  209. package/dist/esm/node_modules/lib0/buffer.js +20 -80
  210. package/dist/esm/node_modules/lib0/conditions.js +4 -15
  211. package/dist/esm/node_modules/lib0/decoding.js +128 -286
  212. package/dist/esm/node_modules/lib0/dom.js +20 -43
  213. package/dist/esm/node_modules/lib0/encoding.js +90 -415
  214. package/dist/esm/node_modules/lib0/environment.js +33 -91
  215. package/dist/esm/node_modules/lib0/error.js +8 -28
  216. package/dist/esm/node_modules/lib0/function.js +31 -65
  217. package/dist/esm/node_modules/lib0/iterator.js +11 -32
  218. package/dist/esm/node_modules/lib0/logging.common.js +22 -21
  219. package/dist/esm/node_modules/lib0/logging.js +44 -70
  220. package/dist/esm/node_modules/lib0/map.js +21 -85
  221. package/dist/esm/node_modules/lib0/math.js +9 -31
  222. package/dist/esm/node_modules/lib0/number.js +7 -14
  223. package/dist/esm/node_modules/lib0/object.js +26 -87
  224. package/dist/esm/node_modules/lib0/observable.js +40 -77
  225. package/dist/esm/node_modules/lib0/pair.js +5 -19
  226. package/dist/esm/node_modules/lib0/prng.js +15 -76
  227. package/dist/esm/node_modules/lib0/promise.js +7 -29
  228. package/dist/esm/node_modules/lib0/random.js +10 -22
  229. package/dist/esm/node_modules/lib0/schema.js +367 -686
  230. package/dist/esm/node_modules/lib0/set.js +4 -9
  231. package/dist/esm/node_modules/lib0/storage.js +23 -54
  232. package/dist/esm/node_modules/lib0/string.js +26 -71
  233. package/dist/esm/node_modules/lib0/symbol.js +3 -11
  234. package/dist/esm/node_modules/lib0/time.js +3 -14
  235. package/dist/esm/node_modules/lib0/trait/equality.js +5 -27
  236. package/dist/esm/node_modules/lib0/url.js +5 -17
  237. package/dist/esm/node_modules/lib0/webcrypto.js +3 -4
  238. package/dist/esm/node_modules/y-protocols/auth.js +7 -18
  239. package/dist/esm/node_modules/y-protocols/awareness.js +58 -115
  240. package/dist/esm/node_modules/y-protocols/sync.js +26 -102
  241. package/dist/esm/node_modules/y-websocket/src/y-websocket.js +82 -218
  242. package/dist/esm/node_modules/yjs/dist/yjs.js +2263 -4899
  243. package/dist/esm/photo/FilterThumb.js +37 -23
  244. package/dist/esm/photo/NiceAITools.js +685 -466
  245. package/dist/esm/photo/PhotoEditor.js +34 -18
  246. package/dist/esm/photo/PhotoEditor.module.css.js +296 -3
  247. package/dist/esm/photo/PhotoEditorCanvas.js +250 -89
  248. package/dist/esm/photo/PhotoEditorLeftPanel.js +1086 -222
  249. package/dist/esm/photo/PhotoEditorToolbar.js +135 -17
  250. package/dist/esm/photo/photoEditorActions.js +93 -71
  251. package/dist/esm/photo/photoEditorTypes.js +32 -31
  252. package/dist/esm/photo/photoFilters.js +967 -984
  253. package/dist/esm/photo/photoOverlays.js +913 -944
  254. package/dist/esm/photo/photoSelection.js +438 -498
  255. package/dist/esm/photo/usePhotoEditor.js +740 -740
  256. package/dist/esm/pixel/HSVPicker.js +192 -174
  257. package/dist/esm/pixel/PixelEditor.js +160 -47
  258. package/dist/esm/pixel/PixelEditor.module.css.js +209 -3
  259. package/dist/esm/pixel/PixelEditorCanvas.js +29 -14
  260. package/dist/esm/pixel/PixelEditorMenuBar.js +160 -160
  261. package/dist/esm/pixel/PixelEditorRightPanel.js +377 -43
  262. package/dist/esm/pixel/PixelEditorStatusBar.js +47 -18
  263. package/dist/esm/pixel/PixelEditorTimeline.js +106 -21
  264. package/dist/esm/pixel/PixelEditorToolbar.js +99 -19
  265. package/dist/esm/pixel/SpriteGeneratorPanel.js +483 -301
  266. package/dist/esm/pixel/animalGeneratorPresets.js +782 -600
  267. package/dist/esm/pixel/asepriteFormat.js +517 -559
  268. package/dist/esm/pixel/buildingGeneratorPresets.js +699 -519
  269. package/dist/esm/pixel/characterGeneratorPresets.js +650 -543
  270. package/dist/esm/pixel/pixel-pro.js +53 -62
  271. package/dist/esm/pixel/pixelEditorExports.js +397 -427
  272. package/dist/esm/pixel/pixelEditorTypes.js +103 -107
  273. package/dist/esm/pixel/pixelEditorUtils.js +551 -560
  274. package/dist/esm/pixel/spriteGeneratorCore.js +223 -256
  275. package/dist/esm/pixel/usePixelEditor.js +1322 -1342
  276. package/dist/esm/texture/Nice3DTexturePainter.js +709 -439
  277. package/dist/esm/ui/NiceUIDesigner.js +957 -526
  278. package/dist/esm/vector/NiceSvgToCode.js +589 -445
  279. package/dist/esm/vector/VectorEditor.js +291 -116
  280. package/dist/esm/vector/VectorEditor.module.css.js +86 -3
  281. package/dist/esm/vector/VectorEditorMenuBar.js +333 -273
  282. package/dist/esm/vector/VectorEditorRightPanel.js +1094 -369
  283. package/dist/esm/vector/VectorEditorShapeRenderer.js +199 -59
  284. package/dist/esm/vector/VectorEditorStatusBar.js +39 -11
  285. package/dist/esm/vector/useVectorEditor.js +849 -888
  286. package/dist/esm/vector/vectorAnimation.js +115 -136
  287. package/dist/esm/vector/vectorBooleanOps.js +401 -484
  288. package/dist/esm/vector/vectorEditorExport.js +207 -202
  289. package/dist/esm/vector/vectorEditorImport.js +174 -188
  290. package/dist/esm/vector/vectorEditorTypes.js +50 -48
  291. package/dist/esm/vector/vectorGradients.js +158 -173
  292. package/dist/esm/vector/vectorIconExport.js +53 -58
  293. package/dist/esm/vector/vectorIconImport.js +171 -218
  294. package/package.json +9 -15
@@ -1,749 +1,749 @@
1
- import { useState, useRef, useMemo, useCallback, useEffect } from 'react';
2
- import { useNiceTranslation } from '../core/i18n.js';
3
- import { getExportBlob, handleDownloadBlob, handleCopyImageToClipboard, handleNativeShare } from './photoEditorActions.js';
4
- import { DEFAULT_ADJUSTMENTS, renderToCanvas, PHOTO_FILTERS, canvasToFile } from './photoFilters.js';
5
- import { EMOJI_CATALOG, deserializeEditorState, renderAllOverlays, renderLiveStroke, hitTestOverlay, createDrawingOverlay, serializeEditorState, createFrameOverlay, createShapeOverlay, createTextOverlay, createEmojiOverlay } from './photoOverlays.js';
6
-
7
- /**
8
- * usePhotoEditor.ts — Custom hook encapsulating all PhotoEditor state & logic.
9
- *
10
- * Extracts ~500 lines of state, effects, callbacks, and memoised values
11
- * out of the god-component so that the main component file is pure composition.
12
- */
13
- // ── Hook ──
14
- function usePhotoEditor({ src,
15
- // Hex: `accentDim` appends an alpha byte to it, and var(...)55 is not a colour.
16
- // audit-allow: literal-color
17
- playerColor = '#00aaff', onSave, onCancel, initialState, }) {
18
- const { t: _t } = useNiceTranslation();
19
- const t = (key, def) => _t(key, def);
20
- // ── State ──
21
- // Image
22
- const [image, setImage] = useState(null);
23
- const [loading, setLoading] = useState(true);
24
- // Tabs
25
- const [activeTab, setActiveTabRaw] = useState('filters');
26
- const [cropMode, setCropMode] = useState(false);
27
- // Filter
28
- const [selectedFilter, setSelectedFilter] = useState('none');
29
- const [filterIntensity, setFilterIntensity] = useState(1);
30
- const [filterCategory, setFilterCategory] = useState('all');
31
- const [filterParams] = useState({});
32
- // Adjustments
33
- const [adjustments, setAdjustments] = useState({ ...DEFAULT_ADJUSTMENTS });
34
- // Transform
35
- const [rotation, setRotation] = useState(0);
36
- const [flipH, setFlipH] = useState(false);
37
- const [flipV, setFlipV] = useState(false);
38
- // Crop
39
- const [cropRect, setCropRect] = useState(null);
40
- const [cropAspect, setCropAspect] = useState(null);
41
- const [cropDragging, setCropDragging] = useState(null);
42
- const [cropStart, setCropStart] = useState(null);
43
- // Compare
44
- const [showCompare, setShowCompare] = useState(false);
45
- const [comparePosition, setComparePosition] = useState(50);
46
- const [compareDragging, setCompareDragging] = useState(false);
47
- // Zoom
48
- const [zoom, setZoom] = useState(1);
49
- // Overlays
50
- const [overlays, setOverlays] = useState([]);
51
- const [selectedOverlayId, setSelectedOverlayId] = useState(null);
52
- const [overlayDragging, setOverlayDragging] = useState(false);
53
- const [overlayDragStart, setOverlayDragStart] = useState(null);
54
- // Sticker / emoji
55
- const [emojiCat, setEmojiCat] = useState(EMOJI_CATALOG[0].id);
56
- // Text overlay
57
- const [textDraft, setTextDraft] = useState('Tekst');
58
- // Tool colours are paint drawn INTO the photo (and fed to colour inputs, which need
59
- // #rrggbb), so literal hex.
60
- const [textColor, setTextColor] = useState('#ffffff');
61
- const [textBold, setTextBold] = useState(true);
62
- const [textItalic, setTextItalic] = useState(false);
63
- const [textOutline, setTextOutline] = useState(true);
64
- const [textFontSize, setTextFontSize] = useState(36);
65
- const [textBg, setTextBg] = useState(false);
66
- // Shape
67
- const [shapeStroke, setShapeStroke] = useState('#ffffff');
68
- const [shapeFill, setShapeFill] = useState('');
69
- const [shapeStrokeW, setShapeStrokeW] = useState(3);
70
- // Frame
71
- const [frameColor, setFrameColor] = useState('#ffffff');
72
- const [frameThick, setFrameThick] = useState(3);
73
- // Drawing
74
- // audit-allow: literal-color — paint (see textColor above)
75
- const [drawColor, setDrawColor] = useState('#ff0000');
76
- const [drawWidth, setDrawWidth] = useState(4);
77
- const [isDrawing, setIsDrawing] = useState(false);
78
- const [livePoints, setLivePoints] = useState([]);
79
- const drawingOverlayRef = useRef(null);
80
- // History
81
- const [history, setHistory] = useState([]);
82
- const [historyIdx, setHistoryIdx] = useState(-1);
83
- const suppressHistoryRef = useRef(false);
84
- // Canvas refs
85
- const mainCanvasRef = useRef(null);
86
- const compareCanvasRef = useRef(null);
87
- const containerRef = useRef(null);
88
- // ── Derived ──
89
- const accent = playerColor;
90
- const accentDim = playerColor + '55';
91
- const selectedOverlay = useMemo(() => { var _a; return (_a = overlays.find((o) => o.id === selectedOverlayId)) !== null && _a !== void 0 ? _a : null; }, [overlays, selectedOverlayId]);
92
- // Tab setter (auto-toggle cropMode)
93
- const setActiveTab = useCallback((tab) => {
94
- setActiveTabRaw(tab);
95
- setCropMode(tab === 'crop');
96
- }, []);
97
- // ── History ──
98
- const pushHistory = useCallback(() => {
99
- if (suppressHistoryRef.current) {
100
- return;
101
- }
102
- const state = {
103
- selectedFilter,
104
- filterIntensity,
105
- adjustments: { ...adjustments },
106
- rotation,
107
- flipH,
108
- flipV,
109
- cropRect: cropRect ? { ...cropRect } : null,
110
- overlays: overlays.map((o) => ({ ...o })),
111
- };
112
- setHistory((prev) => [...prev.slice(0, historyIdx + 1), state]);
113
- setHistoryIdx((prev) => prev + 1);
114
- }, [
115
- selectedFilter,
116
- filterIntensity,
117
- adjustments,
118
- rotation,
119
- flipH,
120
- flipV,
121
- cropRect,
122
- overlays,
123
- historyIdx,
124
- ]);
125
- const restoreState = useCallback((s) => {
126
- suppressHistoryRef.current = true;
127
- setSelectedFilter(s.selectedFilter);
128
- setFilterIntensity(s.filterIntensity);
129
- setAdjustments({ ...s.adjustments });
130
- setRotation(s.rotation);
131
- setFlipH(s.flipH);
132
- setFlipV(s.flipV);
133
- setCropRect(s.cropRect ? { ...s.cropRect } : null);
134
- setOverlays(s.overlays.map((o) => ({ ...o })));
135
- setSelectedOverlayId(null);
136
- setTimeout(() => {
1
+ import { useState, useRef, useMemo, useCallback, useEffect } from "react";
2
+ import { useNiceTranslation } from "../core/i18n.js";
3
+ import { getExportBlob, handleDownloadBlob, handleCopyImageToClipboard, handleNativeShare } from "./photoEditorActions.js";
4
+ import { DEFAULT_ADJUSTMENTS, renderToCanvas, PHOTO_FILTERS, canvasToFile } from "./photoFilters.js";
5
+ import { EMOJI_CATALOG, deserializeEditorState, renderAllOverlays, renderLiveStroke, hitTestOverlay, createDrawingOverlay, serializeEditorState, createFrameOverlay, createShapeOverlay, createTextOverlay, createEmojiOverlay } from "./photoOverlays.js";
6
+ function usePhotoEditor({
7
+ src,
8
+ // Hex: `accentDim` appends an alpha byte to it, and var(...)55 is not a colour.
9
+ // audit-allow: literal-color
10
+ playerColor = "#00aaff",
11
+ onSave,
12
+ onCancel,
13
+ initialState
14
+ }) {
15
+ const { t: _t } = useNiceTranslation();
16
+ const t = (key, def) => _t(key, def);
17
+ const [image, setImage] = useState(null);
18
+ const [loading, setLoading] = useState(true);
19
+ const [activeTab, setActiveTabRaw] = useState("filters");
20
+ const [cropMode, setCropMode] = useState(false);
21
+ const [selectedFilter, setSelectedFilter] = useState("none");
22
+ const [filterIntensity, setFilterIntensity] = useState(1);
23
+ const [filterCategory, setFilterCategory] = useState("all");
24
+ const [filterParams] = useState({});
25
+ const [adjustments, setAdjustments] = useState({ ...DEFAULT_ADJUSTMENTS });
26
+ const [rotation, setRotation] = useState(0);
27
+ const [flipH, setFlipH] = useState(false);
28
+ const [flipV, setFlipV] = useState(false);
29
+ const [cropRect, setCropRect] = useState(
30
+ null
31
+ );
32
+ const [cropAspect, setCropAspect] = useState(null);
33
+ const [cropDragging, setCropDragging] = useState(null);
34
+ const [cropStart, setCropStart] = useState(null);
35
+ const [showCompare, setShowCompare] = useState(false);
36
+ const [comparePosition, setComparePosition] = useState(50);
37
+ const [compareDragging, setCompareDragging] = useState(false);
38
+ const [zoom, setZoom] = useState(1);
39
+ const [overlays, setOverlays] = useState([]);
40
+ const [selectedOverlayId, setSelectedOverlayId] = useState(null);
41
+ const [overlayDragging, setOverlayDragging] = useState(false);
42
+ const [overlayDragStart, setOverlayDragStart] = useState(null);
43
+ const [emojiCat, setEmojiCat] = useState(EMOJI_CATALOG[0].id);
44
+ const [textDraft, setTextDraft] = useState("Tekst");
45
+ const [textColor, setTextColor] = useState("#ffffff");
46
+ const [textBold, setTextBold] = useState(true);
47
+ const [textItalic, setTextItalic] = useState(false);
48
+ const [textOutline, setTextOutline] = useState(true);
49
+ const [textFontSize, setTextFontSize] = useState(36);
50
+ const [textBg, setTextBg] = useState(false);
51
+ const [shapeStroke, setShapeStroke] = useState("#ffffff");
52
+ const [shapeFill, setShapeFill] = useState("");
53
+ const [shapeStrokeW, setShapeStrokeW] = useState(3);
54
+ const [frameColor, setFrameColor] = useState("#ffffff");
55
+ const [frameThick, setFrameThick] = useState(3);
56
+ const [drawColor, setDrawColor] = useState("#ff0000");
57
+ const [drawWidth, setDrawWidth] = useState(4);
58
+ const [isDrawing, setIsDrawing] = useState(false);
59
+ const [livePoints, setLivePoints] = useState([]);
60
+ const drawingOverlayRef = useRef(null);
61
+ const [history, setHistory] = useState([]);
62
+ const [historyIdx, setHistoryIdx] = useState(-1);
63
+ const suppressHistoryRef = useRef(false);
64
+ const mainCanvasRef = useRef(null);
65
+ const compareCanvasRef = useRef(null);
66
+ const containerRef = useRef(null);
67
+ const accent = playerColor;
68
+ const accentDim = playerColor + "55";
69
+ const selectedOverlay = useMemo(
70
+ () => overlays.find((o) => o.id === selectedOverlayId) ?? null,
71
+ [overlays, selectedOverlayId]
72
+ );
73
+ const setActiveTab = useCallback((tab) => {
74
+ setActiveTabRaw(tab);
75
+ setCropMode(tab === "crop");
76
+ }, []);
77
+ const pushHistory = useCallback(() => {
78
+ if (suppressHistoryRef.current) {
79
+ return;
80
+ }
81
+ const state = {
82
+ selectedFilter,
83
+ filterIntensity,
84
+ adjustments: { ...adjustments },
85
+ rotation,
86
+ flipH,
87
+ flipV,
88
+ cropRect: cropRect ? { ...cropRect } : null,
89
+ overlays: overlays.map((o) => ({ ...o }))
90
+ };
91
+ setHistory((prev) => [...prev.slice(0, historyIdx + 1), state]);
92
+ setHistoryIdx((prev) => prev + 1);
93
+ }, [
94
+ selectedFilter,
95
+ filterIntensity,
96
+ adjustments,
97
+ rotation,
98
+ flipH,
99
+ flipV,
100
+ cropRect,
101
+ overlays,
102
+ historyIdx
103
+ ]);
104
+ const restoreState = useCallback((s) => {
105
+ suppressHistoryRef.current = true;
106
+ setSelectedFilter(s.selectedFilter);
107
+ setFilterIntensity(s.filterIntensity);
108
+ setAdjustments({ ...s.adjustments });
109
+ setRotation(s.rotation);
110
+ setFlipH(s.flipH);
111
+ setFlipV(s.flipV);
112
+ setCropRect(s.cropRect ? { ...s.cropRect } : null);
113
+ setOverlays(s.overlays.map((o) => ({ ...o })));
114
+ setSelectedOverlayId(null);
115
+ setTimeout(() => {
116
+ suppressHistoryRef.current = false;
117
+ }, 50);
118
+ }, []);
119
+ const undo = useCallback(() => {
120
+ if (historyIdx > 0) {
121
+ const newIdx = historyIdx - 1;
122
+ setHistoryIdx(newIdx);
123
+ restoreState(history[newIdx]);
124
+ }
125
+ }, [historyIdx, history, restoreState]);
126
+ const redo = useCallback(() => {
127
+ if (historyIdx < history.length - 1) {
128
+ const newIdx = historyIdx + 1;
129
+ setHistoryIdx(newIdx);
130
+ restoreState(history[newIdx]);
131
+ }
132
+ }, [historyIdx, history, restoreState]);
133
+ useEffect(() => {
134
+ const img = new Image();
135
+ img.onload = () => {
136
+ setImage(img);
137
+ setLoading(false);
138
+ setCropRect({ x: 0, y: 0, w: img.naturalWidth, h: img.naturalHeight });
139
+ };
140
+ img.onerror = () => setLoading(false);
141
+ if (src instanceof File || src instanceof Blob) {
142
+ img.src = URL.createObjectURL(src);
143
+ } else {
144
+ img.crossOrigin = "anonymous";
145
+ img.src = src;
146
+ }
147
+ return () => {
148
+ if (img.src.startsWith("blob:")) {
149
+ URL.revokeObjectURL(img.src);
150
+ }
151
+ };
152
+ }, [src]);
153
+ useEffect(() => {
154
+ if (image && history.length === 0) {
155
+ if (initialState) {
156
+ const prev = deserializeEditorState(initialState);
157
+ if (prev) {
158
+ suppressHistoryRef.current = true;
159
+ setSelectedFilter(prev.filter);
160
+ setFilterIntensity(prev.filterIntensity);
161
+ setAdjustments({ ...prev.adjustments });
162
+ setRotation(prev.rotation);
163
+ setFlipH(prev.flipH);
164
+ setFlipV(prev.flipV);
165
+ if (prev.cropRect) {
166
+ setCropRect(prev.cropRect);
167
+ }
168
+ if (prev.overlays) {
169
+ setOverlays(prev.overlays);
170
+ }
171
+ setTimeout(() => {
137
172
  suppressHistoryRef.current = false;
138
- }, 50);
139
- }, []);
140
- const undo = useCallback(() => {
141
- if (historyIdx > 0) {
142
- const newIdx = historyIdx - 1;
143
- setHistoryIdx(newIdx);
144
- restoreState(history[newIdx]);
145
- }
146
- }, [historyIdx, history, restoreState]);
147
- const redo = useCallback(() => {
148
- if (historyIdx < history.length - 1) {
149
- const newIdx = historyIdx + 1;
150
- setHistoryIdx(newIdx);
151
- restoreState(history[newIdx]);
152
- }
153
- }, [historyIdx, history, restoreState]);
154
- // ── Load image ──
155
- useEffect(() => {
156
- const img = new Image();
157
- img.onload = () => {
158
- setImage(img);
159
- setLoading(false);
160
- setCropRect({ x: 0, y: 0, w: img.naturalWidth, h: img.naturalHeight });
161
- };
162
- img.onerror = () => setLoading(false);
163
- if (src instanceof File || src instanceof Blob) {
164
- img.src = URL.createObjectURL(src);
165
- }
166
- else {
167
- img.crossOrigin = 'anonymous';
168
- img.src = src;
169
- }
170
- return () => {
171
- if (img.src.startsWith('blob:')) {
172
- URL.revokeObjectURL(img.src);
173
- }
174
- };
175
- }, [src]);
176
- // Push initial history once image loads
177
- useEffect(() => {
178
- if (image && history.length === 0) {
179
- if (initialState) {
180
- const prev = deserializeEditorState(initialState);
181
- if (prev) {
182
- suppressHistoryRef.current = true;
183
- setSelectedFilter(prev.filter);
184
- setFilterIntensity(prev.filterIntensity);
185
- setAdjustments({ ...prev.adjustments });
186
- setRotation(prev.rotation);
187
- setFlipH(prev.flipH);
188
- setFlipV(prev.flipV);
189
- if (prev.cropRect) {
190
- setCropRect(prev.cropRect);
191
- }
192
- if (prev.overlays) {
193
- setOverlays(prev.overlays);
194
- }
195
- setTimeout(() => {
196
- suppressHistoryRef.current = false;
197
- }, 50);
198
- }
199
- }
200
- pushHistory();
201
- }
202
- // eslint-disable-next-line react-hooks/exhaustive-deps
203
- }, [image]);
204
- // ── Render main canvas ──
205
- const renderMain = useCallback(() => {
206
- if (!image || !mainCanvasRef.current) {
207
- return;
208
- }
209
- renderToCanvas(mainCanvasRef.current, image, selectedFilter, { ...filterParams, intensity: filterIntensity }, adjustments, cropMode ? null : cropRect, rotation, flipH, flipV);
210
- if (overlays.length > 0 || livePoints.length > 1) {
211
- const ctx = mainCanvasRef.current.getContext('2d');
212
- if (ctx) {
213
- const cw = mainCanvasRef.current.width;
214
- const ch = mainCanvasRef.current.height;
215
- if (overlays.length > 0) {
216
- renderAllOverlays(ctx, overlays, cw, ch);
217
- }
218
- if (livePoints.length > 1) {
219
- renderLiveStroke(ctx, livePoints, drawColor, drawWidth, cw, ch);
220
- }
221
- }
222
- }
223
- }, [
224
- image,
225
- selectedFilter,
226
- filterParams,
227
- filterIntensity,
228
- adjustments,
229
- cropRect,
230
- cropMode,
231
- rotation,
232
- flipH,
233
- flipV,
234
- overlays,
235
- livePoints,
236
- drawColor,
237
- drawWidth,
238
- ]);
239
- useEffect(() => {
240
- renderMain();
241
- }, [renderMain]);
242
- // Render compare canvas (original)
243
- useEffect(() => {
244
- if (!image || !compareCanvasRef.current || !showCompare) {
245
- return;
246
- }
247
- const canvas = compareCanvasRef.current;
248
- const ctx = canvas.getContext('2d');
249
- if (!ctx) {
250
- return;
251
- }
252
- const crop = cropRect !== null && cropRect !== void 0 ? cropRect : { x: 0, y: 0, w: image.naturalWidth, h: image.naturalHeight };
253
- canvas.width = crop.w;
254
- canvas.height = crop.h;
255
- ctx.drawImage(image, crop.x, crop.y, crop.w, crop.h, 0, 0, crop.w, crop.h);
256
- }, [image, showCompare, cropRect]);
257
- // ── Filter thumbnails ──
258
- const filteredList = useMemo(() => {
259
- if (filterCategory === 'all') {
260
- return PHOTO_FILTERS;
261
- }
262
- return PHOTO_FILTERS.filter((f) => f.category === filterCategory || f.id === 'none');
263
- }, [filterCategory]);
264
- const generateThumbnail = useCallback((canvas, filter) => {
265
- if (!image) {
266
- return;
267
- }
268
- const size = 64;
269
- canvas.width = size;
270
- canvas.height = size;
271
- const ctx = canvas.getContext('2d', { willReadFrequently: true });
272
- if (!ctx) {
273
- return;
274
- }
275
- const s = Math.min(image.naturalWidth, image.naturalHeight);
276
- const sx = (image.naturalWidth - s) / 2;
277
- const sy = (image.naturalHeight - s) / 2;
278
- ctx.drawImage(image, sx, sy, s, s, 0, 0, size, size);
279
- if (filter.id !== 'none') {
280
- const imgData = ctx.getImageData(0, 0, size, size);
281
- const filtered = filter.apply(imgData, { intensity: 1 });
282
- ctx.putImageData(filtered, 0, 0);
283
- }
284
- }, [image]);
285
- // ── Crop interaction ──
286
- const getCanvasScale = useCallback(() => {
287
- if (!mainCanvasRef.current || !containerRef.current || !image) {
288
- return { sx: 1, sy: 1, ox: 0, oy: 0 };
289
- }
290
- const rect = mainCanvasRef.current.getBoundingClientRect();
173
+ }, 50);
174
+ }
175
+ }
176
+ pushHistory();
177
+ }
178
+ }, [image]);
179
+ const renderMain = useCallback(() => {
180
+ if (!image || !mainCanvasRef.current) {
181
+ return;
182
+ }
183
+ renderToCanvas(
184
+ mainCanvasRef.current,
185
+ image,
186
+ selectedFilter,
187
+ { ...filterParams, intensity: filterIntensity },
188
+ adjustments,
189
+ cropMode ? null : cropRect,
190
+ rotation,
191
+ flipH,
192
+ flipV
193
+ );
194
+ if (overlays.length > 0 || livePoints.length > 1) {
195
+ const ctx = mainCanvasRef.current.getContext("2d");
196
+ if (ctx) {
291
197
  const cw = mainCanvasRef.current.width;
292
198
  const ch = mainCanvasRef.current.height;
293
- return { sx: cw / rect.width, sy: ch / rect.height, ox: rect.left, oy: rect.top };
294
- }, [image]);
295
- const handleCropMouseDown = useCallback((e, handle) => {
199
+ if (overlays.length > 0) {
200
+ renderAllOverlays(ctx, overlays, cw, ch);
201
+ }
202
+ if (livePoints.length > 1) {
203
+ renderLiveStroke(ctx, livePoints, drawColor, drawWidth, cw, ch);
204
+ }
205
+ }
206
+ }
207
+ }, [
208
+ image,
209
+ selectedFilter,
210
+ filterParams,
211
+ filterIntensity,
212
+ adjustments,
213
+ cropRect,
214
+ cropMode,
215
+ rotation,
216
+ flipH,
217
+ flipV,
218
+ overlays,
219
+ livePoints,
220
+ drawColor,
221
+ drawWidth
222
+ ]);
223
+ useEffect(() => {
224
+ renderMain();
225
+ }, [renderMain]);
226
+ useEffect(() => {
227
+ if (!image || !compareCanvasRef.current || !showCompare) {
228
+ return;
229
+ }
230
+ const canvas = compareCanvasRef.current;
231
+ const ctx = canvas.getContext("2d");
232
+ if (!ctx) {
233
+ return;
234
+ }
235
+ const crop = cropRect ?? { x: 0, y: 0, w: image.naturalWidth, h: image.naturalHeight };
236
+ canvas.width = crop.w;
237
+ canvas.height = crop.h;
238
+ ctx.drawImage(image, crop.x, crop.y, crop.w, crop.h, 0, 0, crop.w, crop.h);
239
+ }, [image, showCompare, cropRect]);
240
+ const filteredList = useMemo(() => {
241
+ if (filterCategory === "all") {
242
+ return PHOTO_FILTERS;
243
+ }
244
+ return PHOTO_FILTERS.filter((f) => f.category === filterCategory || f.id === "none");
245
+ }, [filterCategory]);
246
+ const generateThumbnail = useCallback(
247
+ (canvas, filter) => {
248
+ if (!image) {
249
+ return;
250
+ }
251
+ const size = 64;
252
+ canvas.width = size;
253
+ canvas.height = size;
254
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
255
+ if (!ctx) {
256
+ return;
257
+ }
258
+ const s = Math.min(image.naturalWidth, image.naturalHeight);
259
+ const sx = (image.naturalWidth - s) / 2;
260
+ const sy = (image.naturalHeight - s) / 2;
261
+ ctx.drawImage(image, sx, sy, s, s, 0, 0, size, size);
262
+ if (filter.id !== "none") {
263
+ const imgData = ctx.getImageData(0, 0, size, size);
264
+ const filtered = filter.apply(imgData, { intensity: 1 });
265
+ ctx.putImageData(filtered, 0, 0);
266
+ }
267
+ },
268
+ [image]
269
+ );
270
+ const getCanvasScale = useCallback(() => {
271
+ if (!mainCanvasRef.current || !containerRef.current || !image) {
272
+ return { sx: 1, sy: 1, ox: 0, oy: 0 };
273
+ }
274
+ const rect = mainCanvasRef.current.getBoundingClientRect();
275
+ const cw = mainCanvasRef.current.width;
276
+ const ch = mainCanvasRef.current.height;
277
+ return { sx: cw / rect.width, sy: ch / rect.height, ox: rect.left, oy: rect.top };
278
+ }, [image]);
279
+ const handleCropMouseDown = useCallback(
280
+ (e, handle) => {
281
+ e.preventDefault();
282
+ e.stopPropagation();
283
+ setCropDragging(handle);
284
+ setCropStart({ mx: e.clientX, my: e.clientY, rect: { ...cropRect } });
285
+ },
286
+ [cropRect]
287
+ );
288
+ useEffect(() => {
289
+ if (!cropDragging || !cropStart || !cropRect || !image) {
290
+ return;
291
+ }
292
+ const handleMove = (e) => {
293
+ const scale = getCanvasScale();
294
+ const dx = (e.clientX - cropStart.mx) * scale.sx;
295
+ const dy = (e.clientY - cropStart.my) * scale.sy;
296
+ const r = { ...cropStart.rect };
297
+ const minSize = 20;
298
+ if (cropDragging === "move") {
299
+ r.x = Math.max(0, Math.min(image.naturalWidth - r.w, r.x + dx));
300
+ r.y = Math.max(0, Math.min(image.naturalHeight - r.h, r.y + dy));
301
+ } else {
302
+ if (cropDragging.includes("w")) {
303
+ r.x += dx;
304
+ r.w -= dx;
305
+ }
306
+ if (cropDragging.includes("e")) {
307
+ r.w += dx;
308
+ }
309
+ if (cropDragging.includes("n")) {
310
+ r.y += dy;
311
+ r.h -= dy;
312
+ }
313
+ if (cropDragging.includes("s")) {
314
+ r.h += dy;
315
+ }
316
+ if (r.w < minSize) {
317
+ r.w = minSize;
318
+ }
319
+ if (r.h < minSize) {
320
+ r.h = minSize;
321
+ }
322
+ if (cropAspect) {
323
+ if (cropDragging.includes("w") || cropDragging.includes("e")) {
324
+ r.h = r.w / cropAspect;
325
+ } else {
326
+ r.w = r.h * cropAspect;
327
+ }
328
+ }
329
+ r.x = Math.max(0, r.x);
330
+ r.y = Math.max(0, r.y);
331
+ r.w = Math.min(r.w, image.naturalWidth - r.x);
332
+ r.h = Math.min(r.h, image.naturalHeight - r.y);
333
+ }
334
+ setCropRect(r);
335
+ setCropStart({ mx: e.clientX, my: e.clientY, rect: r });
336
+ };
337
+ const handleUp = () => {
338
+ setCropDragging(null);
339
+ setCropStart(null);
340
+ pushHistory();
341
+ };
342
+ window.addEventListener("mousemove", handleMove);
343
+ window.addEventListener("mouseup", handleUp);
344
+ return () => {
345
+ window.removeEventListener("mousemove", handleMove);
346
+ window.removeEventListener("mouseup", handleUp);
347
+ };
348
+ }, [cropDragging, cropStart, cropRect, image, cropAspect, getCanvasScale, pushHistory]);
349
+ useEffect(() => {
350
+ if (!compareDragging) {
351
+ return;
352
+ }
353
+ const handleMove = (e) => {
354
+ if (!containerRef.current) {
355
+ return;
356
+ }
357
+ const rect = containerRef.current.getBoundingClientRect();
358
+ const pct = Math.max(0, Math.min(100, (e.clientX - rect.left) / rect.width * 100));
359
+ setComparePosition(pct);
360
+ };
361
+ const handleUp = () => setCompareDragging(false);
362
+ window.addEventListener("mousemove", handleMove);
363
+ window.addEventListener("mouseup", handleUp);
364
+ return () => {
365
+ window.removeEventListener("mousemove", handleMove);
366
+ window.removeEventListener("mouseup", handleUp);
367
+ };
368
+ }, [compareDragging]);
369
+ useEffect(() => {
370
+ const handleKey = (e) => {
371
+ if (e.ctrlKey && e.key === "z") {
296
372
  e.preventDefault();
297
- e.stopPropagation();
298
- setCropDragging(handle);
299
- setCropStart({ mx: e.clientX, my: e.clientY, rect: { ...cropRect } });
300
- }, [cropRect]);
301
- useEffect(() => {
302
- if (!cropDragging || !cropStart || !cropRect || !image) {
303
- return;
304
- }
305
- const handleMove = (e) => {
306
- const scale = getCanvasScale();
307
- const dx = (e.clientX - cropStart.mx) * scale.sx;
308
- const dy = (e.clientY - cropStart.my) * scale.sy;
309
- const r = { ...cropStart.rect };
310
- const minSize = 20;
311
- if (cropDragging === 'move') {
312
- r.x = Math.max(0, Math.min(image.naturalWidth - r.w, r.x + dx));
313
- r.y = Math.max(0, Math.min(image.naturalHeight - r.h, r.y + dy));
314
- }
315
- else {
316
- if (cropDragging.includes('w')) {
317
- r.x += dx;
318
- r.w -= dx;
319
- }
320
- if (cropDragging.includes('e')) {
321
- r.w += dx;
322
- }
323
- if (cropDragging.includes('n')) {
324
- r.y += dy;
325
- r.h -= dy;
326
- }
327
- if (cropDragging.includes('s')) {
328
- r.h += dy;
329
- }
330
- if (r.w < minSize) {
331
- r.w = minSize;
332
- }
333
- if (r.h < minSize) {
334
- r.h = minSize;
335
- }
336
- if (cropAspect) {
337
- if (cropDragging.includes('w') || cropDragging.includes('e')) {
338
- r.h = r.w / cropAspect;
339
- }
340
- else {
341
- r.w = r.h * cropAspect;
342
- }
343
- }
344
- r.x = Math.max(0, r.x);
345
- r.y = Math.max(0, r.y);
346
- r.w = Math.min(r.w, image.naturalWidth - r.x);
347
- r.h = Math.min(r.h, image.naturalHeight - r.y);
348
- }
349
- setCropRect(r);
350
- setCropStart({ mx: e.clientX, my: e.clientY, rect: r });
351
- };
352
- const handleUp = () => {
353
- setCropDragging(null);
354
- setCropStart(null);
355
- pushHistory();
356
- };
357
- window.addEventListener('mousemove', handleMove);
358
- window.addEventListener('mouseup', handleUp);
359
- return () => {
360
- window.removeEventListener('mousemove', handleMove);
361
- window.removeEventListener('mouseup', handleUp);
362
- };
363
- }, [cropDragging, cropStart, cropRect, image, cropAspect, getCanvasScale, pushHistory]);
364
- // Compare slider drag
365
- useEffect(() => {
366
- if (!compareDragging) {
367
- return;
368
- }
369
- const handleMove = (e) => {
370
- if (!containerRef.current) {
371
- return;
372
- }
373
- const rect = containerRef.current.getBoundingClientRect();
374
- const pct = Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100));
375
- setComparePosition(pct);
376
- };
377
- const handleUp = () => setCompareDragging(false);
378
- window.addEventListener('mousemove', handleMove);
379
- window.addEventListener('mouseup', handleUp);
380
- return () => {
381
- window.removeEventListener('mousemove', handleMove);
382
- window.removeEventListener('mouseup', handleUp);
383
- };
384
- }, [compareDragging]);
385
- // ── Keyboard shortcuts ──
386
- useEffect(() => {
387
- const handleKey = (e) => {
388
- if (e.ctrlKey && e.key === 'z') {
389
- e.preventDefault();
390
- undo();
391
- }
392
- if (e.ctrlKey && e.key === 'y') {
393
- e.preventDefault();
394
- redo();
395
- }
396
- };
397
- window.addEventListener('keydown', handleKey);
398
- return () => window.removeEventListener('keydown', handleKey);
399
- }, [undo, redo]);
400
- // ── Adjustments ──
401
- const handleAdjChange = useCallback((key, value) => {
402
- setAdjustments((prev) => ({ ...prev, [key]: value }));
403
- }, []);
404
- const handleAdjChangeEnd = useCallback(() => pushHistory(), [pushHistory]);
405
- // ── Overlay helpers ──
406
- const addOverlay = useCallback((ov) => {
407
- setOverlays((prev) => [...prev, ov]);
408
- setSelectedOverlayId(ov.id);
409
- setTimeout(() => pushHistory(), 30);
410
- }, [pushHistory]);
411
- const updateOverlay = useCallback((id, patch) => {
412
- setOverlays((prev) => prev.map((o) => (o.id === id ? { ...o, ...patch } : o)));
413
- }, []);
414
- const deleteOverlay = useCallback((id) => {
415
- setOverlays((prev) => prev.filter((o) => o.id !== id));
416
- if (selectedOverlayId === id) {
417
- setSelectedOverlayId(null);
418
- }
419
- setTimeout(() => pushHistory(), 30);
420
- }, [selectedOverlayId, pushHistory]);
421
- const moveOverlayForward = useCallback((id) => {
422
- setOverlays((prev) => {
423
- const idx = prev.findIndex((o) => o.id === id);
424
- if (idx < 0 || idx >= prev.length - 1) {
425
- return prev;
426
- }
427
- const copy = [...prev];
428
- [copy[idx], copy[idx + 1]] = [copy[idx + 1], copy[idx]];
429
- return copy;
430
- });
431
- }, []);
432
- const moveOverlayBack = useCallback((id) => {
433
- setOverlays((prev) => {
434
- const idx = prev.findIndex((o) => o.id === id);
435
- if (idx <= 0) {
436
- return prev;
437
- }
438
- const copy = [...prev];
439
- [copy[idx - 1], copy[idx]] = [copy[idx], copy[idx - 1]];
440
- return copy;
441
- });
442
- }, []);
443
- // ── Canvas fraction from mouse event ──
444
- const getCanvasFraction = useCallback((e) => {
445
- if (!mainCanvasRef.current) {
446
- return { fx: 0.5, fy: 0.5 };
447
- }
448
- const rect = mainCanvasRef.current.getBoundingClientRect();
449
- return {
450
- fx: Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)),
451
- fy: Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height)),
452
- };
453
- }, []);
454
- // ── Canvas mouse handlers ──
455
- const handleCanvasMouseDown = useCallback((e) => {
456
- var _a, _b, _c, _d;
457
- if (cropMode) {
458
- return;
459
- }
460
- const { fx, fy } = getCanvasFraction(e);
461
- if (activeTab === 'draw') {
462
- e.preventDefault();
463
- setIsDrawing(true);
464
- setLivePoints([[fx, fy]]);
465
- return;
466
- }
467
- for (let i = overlays.length - 1; i >= 0; i--) {
468
- const cw = (_b = (_a = mainCanvasRef.current) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : 1;
469
- const ch = (_d = (_c = mainCanvasRef.current) === null || _c === void 0 ? void 0 : _c.height) !== null && _d !== void 0 ? _d : 1;
470
- if (hitTestOverlay(overlays[i], fx, fy, cw, ch)) {
471
- setSelectedOverlayId(overlays[i].id);
472
- setOverlayDragging(true);
473
- setOverlayDragStart({ mx: fx, my: fy, ox: overlays[i].x, oy: overlays[i].y });
474
- e.preventDefault();
475
- return;
476
- }
477
- }
373
+ undo();
374
+ }
375
+ if (e.ctrlKey && e.key === "y") {
376
+ e.preventDefault();
377
+ redo();
378
+ }
379
+ };
380
+ window.addEventListener("keydown", handleKey);
381
+ return () => window.removeEventListener("keydown", handleKey);
382
+ }, [undo, redo]);
383
+ const handleAdjChange = useCallback((key, value) => {
384
+ setAdjustments((prev) => ({ ...prev, [key]: value }));
385
+ }, []);
386
+ const handleAdjChangeEnd = useCallback(() => pushHistory(), [pushHistory]);
387
+ const addOverlay = useCallback(
388
+ (ov) => {
389
+ setOverlays((prev) => [...prev, ov]);
390
+ setSelectedOverlayId(ov.id);
391
+ setTimeout(() => pushHistory(), 30);
392
+ },
393
+ [pushHistory]
394
+ );
395
+ const updateOverlay = useCallback((id, patch) => {
396
+ setOverlays((prev) => prev.map((o) => o.id === id ? { ...o, ...patch } : o));
397
+ }, []);
398
+ const deleteOverlay = useCallback(
399
+ (id) => {
400
+ setOverlays((prev) => prev.filter((o) => o.id !== id));
401
+ if (selectedOverlayId === id) {
478
402
  setSelectedOverlayId(null);
479
- }, [cropMode, activeTab, getCanvasFraction, overlays]);
480
- const handleCanvasMouseMove = useCallback((e) => {
481
- const { fx, fy } = getCanvasFraction(e);
482
- if (isDrawing) {
483
- setLivePoints((prev) => [...prev, [fx, fy]]);
484
- return;
485
- }
486
- if (overlayDragging && overlayDragStart && selectedOverlayId) {
487
- const dx = fx - overlayDragStart.mx;
488
- const dy = fy - overlayDragStart.my;
489
- updateOverlay(selectedOverlayId, {
490
- x: Math.max(0, Math.min(1, overlayDragStart.ox + dx)),
491
- y: Math.max(0, Math.min(1, overlayDragStart.oy + dy)),
492
- });
493
- }
494
- }, [
495
- getCanvasFraction,
496
- isDrawing,
497
- overlayDragging,
498
- overlayDragStart,
499
- selectedOverlayId,
500
- updateOverlay,
501
- ]);
502
- const handleCanvasMouseUp = useCallback(() => {
503
- if (isDrawing && livePoints.length > 1) {
504
- const stroke = {
505
- points: [...livePoints],
506
- color: drawColor,
507
- width: drawWidth,
508
- opacity: 1,
509
- };
510
- if (!drawingOverlayRef.current) {
511
- const dOv = createDrawingOverlay();
512
- dOv.strokes.push(stroke);
513
- drawingOverlayRef.current = dOv;
514
- setOverlays((prev) => [...prev, dOv]);
515
- }
516
- else {
517
- const id = drawingOverlayRef.current.id;
518
- setOverlays((prev) => prev.map((o) => o.id === id && o.type === 'drawing'
519
- ? { ...o, strokes: [...o.strokes, stroke] }
520
- : o));
521
- }
522
- setLivePoints([]);
523
- setIsDrawing(false);
524
- pushHistory();
525
- return;
526
- }
527
- setIsDrawing(false);
528
- setLivePoints([]);
529
- if (overlayDragging) {
530
- setOverlayDragging(false);
531
- setOverlayDragStart(null);
532
- pushHistory();
533
- }
534
- }, [isDrawing, livePoints, drawColor, drawWidth, overlayDragging, pushHistory]);
535
- // ── Actions ──
536
- const buildExportParams = useCallback(() => {
537
- if (!image) {
538
- return null;
539
- }
540
- return {
541
- image,
542
- selectedFilter,
543
- filterParams,
544
- filterIntensity,
545
- adjustments,
546
- cropRect,
547
- rotation,
548
- flipH,
549
- flipV,
550
- overlays,
551
- };
552
- }, [
553
- image,
554
- selectedFilter,
555
- filterParams,
556
- filterIntensity,
557
- adjustments,
558
- cropRect,
559
- rotation,
560
- flipH,
561
- flipV,
562
- overlays,
563
- ]);
564
- const handleSave = useCallback(async () => {
565
- if (!image || !mainCanvasRef.current) {
566
- return;
567
- }
568
- const exportCanvas = document.createElement('canvas');
569
- renderToCanvas(exportCanvas, image, selectedFilter, { ...filterParams, intensity: filterIntensity }, adjustments, cropRect, rotation, flipH, flipV);
570
- if (overlays.length > 0) {
571
- const ctx = exportCanvas.getContext('2d');
572
- if (ctx) {
573
- renderAllOverlays(ctx, overlays, exportCanvas.width, exportCanvas.height);
574
- }
575
- }
576
- const file = await canvasToFile(exportCanvas, 'player-photo.jpg', 0.92);
577
- const editorState = serializeEditorState({
578
- filter: selectedFilter,
579
- filterIntensity,
580
- adjustments: { ...adjustments },
581
- rotation,
582
- flipH,
583
- flipV,
584
- cropRect: cropRect ? { ...cropRect } : null,
585
- overlays,
403
+ }
404
+ setTimeout(() => pushHistory(), 30);
405
+ },
406
+ [selectedOverlayId, pushHistory]
407
+ );
408
+ const moveOverlayForward = useCallback((id) => {
409
+ setOverlays((prev) => {
410
+ const idx = prev.findIndex((o) => o.id === id);
411
+ if (idx < 0 || idx >= prev.length - 1) {
412
+ return prev;
413
+ }
414
+ const copy = [...prev];
415
+ [copy[idx], copy[idx + 1]] = [copy[idx + 1], copy[idx]];
416
+ return copy;
417
+ });
418
+ }, []);
419
+ const moveOverlayBack = useCallback((id) => {
420
+ setOverlays((prev) => {
421
+ const idx = prev.findIndex((o) => o.id === id);
422
+ if (idx <= 0) {
423
+ return prev;
424
+ }
425
+ const copy = [...prev];
426
+ [copy[idx - 1], copy[idx]] = [copy[idx], copy[idx - 1]];
427
+ return copy;
428
+ });
429
+ }, []);
430
+ const getCanvasFraction = useCallback((e) => {
431
+ if (!mainCanvasRef.current) {
432
+ return { fx: 0.5, fy: 0.5 };
433
+ }
434
+ const rect = mainCanvasRef.current.getBoundingClientRect();
435
+ return {
436
+ fx: Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)),
437
+ fy: Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height))
438
+ };
439
+ }, []);
440
+ const handleCanvasMouseDown = useCallback(
441
+ (e) => {
442
+ if (cropMode) {
443
+ return;
444
+ }
445
+ const { fx, fy } = getCanvasFraction(e);
446
+ if (activeTab === "draw") {
447
+ e.preventDefault();
448
+ setIsDrawing(true);
449
+ setLivePoints([[fx, fy]]);
450
+ return;
451
+ }
452
+ for (let i = overlays.length - 1; i >= 0; i--) {
453
+ const cw = mainCanvasRef.current?.width ?? 1;
454
+ const ch = mainCanvasRef.current?.height ?? 1;
455
+ if (hitTestOverlay(overlays[i], fx, fy, cw, ch)) {
456
+ setSelectedOverlayId(overlays[i].id);
457
+ setOverlayDragging(true);
458
+ setOverlayDragStart({ mx: fx, my: fy, ox: overlays[i].x, oy: overlays[i].y });
459
+ e.preventDefault();
460
+ return;
461
+ }
462
+ }
463
+ setSelectedOverlayId(null);
464
+ },
465
+ [cropMode, activeTab, getCanvasFraction, overlays]
466
+ );
467
+ const handleCanvasMouseMove = useCallback(
468
+ (e) => {
469
+ const { fx, fy } = getCanvasFraction(e);
470
+ if (isDrawing) {
471
+ setLivePoints((prev) => [...prev, [fx, fy]]);
472
+ return;
473
+ }
474
+ if (overlayDragging && overlayDragStart && selectedOverlayId) {
475
+ const dx = fx - overlayDragStart.mx;
476
+ const dy = fy - overlayDragStart.my;
477
+ updateOverlay(selectedOverlayId, {
478
+ x: Math.max(0, Math.min(1, overlayDragStart.ox + dx)),
479
+ y: Math.max(0, Math.min(1, overlayDragStart.oy + dy))
586
480
  });
587
- onSave(file, editorState);
588
- }, [
589
- image,
590
- selectedFilter,
591
- filterParams,
592
- filterIntensity,
593
- adjustments,
594
- cropRect,
595
- rotation,
596
- flipH,
597
- flipV,
598
- overlays,
599
- onSave,
600
- ]);
601
- const handleReset = useCallback(() => {
602
- setSelectedFilter('none');
603
- setFilterIntensity(1);
604
- setAdjustments({ ...DEFAULT_ADJUSTMENTS });
605
- setRotation(0);
606
- setFlipH(false);
607
- setFlipV(false);
608
- setOverlays([]);
609
- setSelectedOverlayId(null);
610
- if (image) {
611
- setCropRect({ x: 0, y: 0, w: image.naturalWidth, h: image.naturalHeight });
612
- }
613
- setCropMode(false);
614
- setZoom(1);
615
- pushHistory();
616
- }, [image, pushHistory]);
617
- const handleDownload = useCallback(async () => {
618
- const p = buildExportParams();
619
- if (!p) {
620
- return;
621
- }
622
- const blob = await getExportBlob(p);
623
- if (blob) {
624
- handleDownloadBlob(blob);
625
- }
626
- }, [buildExportParams]);
627
- const handleCopyToClipboard = useCallback(async () => {
628
- const p = buildExportParams();
629
- if (!p) {
630
- return;
631
- }
632
- await handleCopyImageToClipboard(p);
633
- }, [buildExportParams]);
634
- const handleShareNative = useCallback(async () => {
635
- const p = buildExportParams();
636
- if (!p) {
637
- return;
638
- }
639
- const blob = await getExportBlob(p);
640
- if (blob) {
641
- await handleNativeShare(blob);
642
- }
643
- }, [buildExportParams]);
644
- // ── Return ──
481
+ }
482
+ },
483
+ [
484
+ getCanvasFraction,
485
+ isDrawing,
486
+ overlayDragging,
487
+ overlayDragStart,
488
+ selectedOverlayId,
489
+ updateOverlay
490
+ ]
491
+ );
492
+ const handleCanvasMouseUp = useCallback(() => {
493
+ if (isDrawing && livePoints.length > 1) {
494
+ const stroke = {
495
+ points: [...livePoints],
496
+ color: drawColor,
497
+ width: drawWidth,
498
+ opacity: 1
499
+ };
500
+ if (!drawingOverlayRef.current) {
501
+ const dOv = createDrawingOverlay();
502
+ dOv.strokes.push(stroke);
503
+ drawingOverlayRef.current = dOv;
504
+ setOverlays((prev) => [...prev, dOv]);
505
+ } else {
506
+ const id = drawingOverlayRef.current.id;
507
+ setOverlays(
508
+ (prev) => prev.map(
509
+ (o) => o.id === id && o.type === "drawing" ? { ...o, strokes: [...o.strokes, stroke] } : o
510
+ )
511
+ );
512
+ }
513
+ setLivePoints([]);
514
+ setIsDrawing(false);
515
+ pushHistory();
516
+ return;
517
+ }
518
+ setIsDrawing(false);
519
+ setLivePoints([]);
520
+ if (overlayDragging) {
521
+ setOverlayDragging(false);
522
+ setOverlayDragStart(null);
523
+ pushHistory();
524
+ }
525
+ }, [isDrawing, livePoints, drawColor, drawWidth, overlayDragging, pushHistory]);
526
+ const buildExportParams = useCallback(() => {
527
+ if (!image) {
528
+ return null;
529
+ }
645
530
  return {
646
- t,
647
- playerColor,
648
- onCancel,
649
- accent,
650
- accentDim,
651
- image,
652
- loading,
653
- activeTab,
654
- setActiveTab,
655
- setCropMode,
656
- selectedFilter,
657
- setSelectedFilter,
658
- filterIntensity,
659
- setFilterIntensity,
660
- filterCategory,
661
- setFilterCategory,
662
- filteredList,
663
- generateThumbnail,
664
- adjustments,
665
- handleAdjChange,
666
- handleAdjChangeEnd,
667
- rotation,
668
- setRotation,
669
- flipH,
670
- setFlipH,
671
- flipV,
672
- setFlipV,
673
- zoom,
674
- setZoom,
675
- cropMode,
676
- cropRect,
677
- setCropRect,
678
- cropAspect,
679
- setCropAspect,
680
- handleCropMouseDown,
681
- showCompare,
682
- setShowCompare,
683
- comparePosition,
684
- setCompareDragging,
685
- overlays,
686
- setOverlays,
687
- selectedOverlayId,
688
- setSelectedOverlayId,
689
- selectedOverlay,
690
- addOverlay,
691
- updateOverlay,
692
- deleteOverlay,
693
- moveOverlayForward,
694
- moveOverlayBack,
695
- emojiCat,
696
- setEmojiCat,
697
- textDraft,
698
- setTextDraft,
699
- textColor,
700
- setTextColor,
701
- textBold,
702
- setTextBold,
703
- textItalic,
704
- setTextItalic,
705
- textOutline,
706
- setTextOutline,
707
- textFontSize,
708
- setTextFontSize,
709
- textBg,
710
- setTextBg,
711
- shapeStroke,
712
- setShapeStroke,
713
- shapeFill,
714
- setShapeFill,
715
- shapeStrokeW,
716
- setShapeStrokeW,
717
- frameColor,
718
- setFrameColor,
719
- frameThick,
720
- setFrameThick,
721
- drawColor,
722
- setDrawColor,
723
- drawWidth,
724
- setDrawWidth,
725
- drawingOverlayRef,
726
- historyIdx,
727
- historyLength: history.length,
728
- undo,
729
- redo,
730
- pushHistory,
731
- handleSave,
732
- handleReset,
733
- handleDownload,
734
- handleCopyToClipboard,
735
- handleShareNative,
736
- mainCanvasRef,
737
- compareCanvasRef,
738
- containerRef,
739
- handleCanvasMouseDown,
740
- handleCanvasMouseMove,
741
- handleCanvasMouseUp,
742
- createEmojiOverlay,
743
- createTextOverlay,
744
- createShapeOverlay,
745
- createFrameOverlay,
531
+ image,
532
+ selectedFilter,
533
+ filterParams,
534
+ filterIntensity,
535
+ adjustments,
536
+ cropRect,
537
+ rotation,
538
+ flipH,
539
+ flipV,
540
+ overlays
746
541
  };
542
+ }, [
543
+ image,
544
+ selectedFilter,
545
+ filterParams,
546
+ filterIntensity,
547
+ adjustments,
548
+ cropRect,
549
+ rotation,
550
+ flipH,
551
+ flipV,
552
+ overlays
553
+ ]);
554
+ const handleSave = useCallback(async () => {
555
+ if (!image || !mainCanvasRef.current) {
556
+ return;
557
+ }
558
+ const exportCanvas = document.createElement("canvas");
559
+ renderToCanvas(
560
+ exportCanvas,
561
+ image,
562
+ selectedFilter,
563
+ { ...filterParams, intensity: filterIntensity },
564
+ adjustments,
565
+ cropRect,
566
+ rotation,
567
+ flipH,
568
+ flipV
569
+ );
570
+ if (overlays.length > 0) {
571
+ const ctx = exportCanvas.getContext("2d");
572
+ if (ctx) {
573
+ renderAllOverlays(ctx, overlays, exportCanvas.width, exportCanvas.height);
574
+ }
575
+ }
576
+ const file = await canvasToFile(exportCanvas, "player-photo.jpg", 0.92);
577
+ const editorState = serializeEditorState({
578
+ filter: selectedFilter,
579
+ filterIntensity,
580
+ adjustments: { ...adjustments },
581
+ rotation,
582
+ flipH,
583
+ flipV,
584
+ cropRect: cropRect ? { ...cropRect } : null,
585
+ overlays
586
+ });
587
+ onSave(file, editorState);
588
+ }, [
589
+ image,
590
+ selectedFilter,
591
+ filterParams,
592
+ filterIntensity,
593
+ adjustments,
594
+ cropRect,
595
+ rotation,
596
+ flipH,
597
+ flipV,
598
+ overlays,
599
+ onSave
600
+ ]);
601
+ const handleReset = useCallback(() => {
602
+ setSelectedFilter("none");
603
+ setFilterIntensity(1);
604
+ setAdjustments({ ...DEFAULT_ADJUSTMENTS });
605
+ setRotation(0);
606
+ setFlipH(false);
607
+ setFlipV(false);
608
+ setOverlays([]);
609
+ setSelectedOverlayId(null);
610
+ if (image) {
611
+ setCropRect({ x: 0, y: 0, w: image.naturalWidth, h: image.naturalHeight });
612
+ }
613
+ setCropMode(false);
614
+ setZoom(1);
615
+ pushHistory();
616
+ }, [image, pushHistory]);
617
+ const handleDownload = useCallback(async () => {
618
+ const p = buildExportParams();
619
+ if (!p) {
620
+ return;
621
+ }
622
+ const blob = await getExportBlob(p);
623
+ if (blob) {
624
+ handleDownloadBlob(blob);
625
+ }
626
+ }, [buildExportParams]);
627
+ const handleCopyToClipboard = useCallback(async () => {
628
+ const p = buildExportParams();
629
+ if (!p) {
630
+ return;
631
+ }
632
+ await handleCopyImageToClipboard(p);
633
+ }, [buildExportParams]);
634
+ const handleShareNative = useCallback(async () => {
635
+ const p = buildExportParams();
636
+ if (!p) {
637
+ return;
638
+ }
639
+ const blob = await getExportBlob(p);
640
+ if (blob) {
641
+ await handleNativeShare(blob);
642
+ }
643
+ }, [buildExportParams]);
644
+ return {
645
+ t,
646
+ playerColor,
647
+ onCancel,
648
+ accent,
649
+ accentDim,
650
+ image,
651
+ loading,
652
+ activeTab,
653
+ setActiveTab,
654
+ setCropMode,
655
+ selectedFilter,
656
+ setSelectedFilter,
657
+ filterIntensity,
658
+ setFilterIntensity,
659
+ filterCategory,
660
+ setFilterCategory,
661
+ filteredList,
662
+ generateThumbnail,
663
+ adjustments,
664
+ handleAdjChange,
665
+ handleAdjChangeEnd,
666
+ rotation,
667
+ setRotation,
668
+ flipH,
669
+ setFlipH,
670
+ flipV,
671
+ setFlipV,
672
+ zoom,
673
+ setZoom,
674
+ cropMode,
675
+ cropRect,
676
+ setCropRect,
677
+ cropAspect,
678
+ setCropAspect,
679
+ handleCropMouseDown,
680
+ showCompare,
681
+ setShowCompare,
682
+ comparePosition,
683
+ setCompareDragging,
684
+ overlays,
685
+ setOverlays,
686
+ selectedOverlayId,
687
+ setSelectedOverlayId,
688
+ selectedOverlay,
689
+ addOverlay,
690
+ updateOverlay,
691
+ deleteOverlay,
692
+ moveOverlayForward,
693
+ moveOverlayBack,
694
+ emojiCat,
695
+ setEmojiCat,
696
+ textDraft,
697
+ setTextDraft,
698
+ textColor,
699
+ setTextColor,
700
+ textBold,
701
+ setTextBold,
702
+ textItalic,
703
+ setTextItalic,
704
+ textOutline,
705
+ setTextOutline,
706
+ textFontSize,
707
+ setTextFontSize,
708
+ textBg,
709
+ setTextBg,
710
+ shapeStroke,
711
+ setShapeStroke,
712
+ shapeFill,
713
+ setShapeFill,
714
+ shapeStrokeW,
715
+ setShapeStrokeW,
716
+ frameColor,
717
+ setFrameColor,
718
+ frameThick,
719
+ setFrameThick,
720
+ drawColor,
721
+ setDrawColor,
722
+ drawWidth,
723
+ setDrawWidth,
724
+ drawingOverlayRef,
725
+ historyIdx,
726
+ historyLength: history.length,
727
+ undo,
728
+ redo,
729
+ pushHistory,
730
+ handleSave,
731
+ handleReset,
732
+ handleDownload,
733
+ handleCopyToClipboard,
734
+ handleShareNative,
735
+ mainCanvasRef,
736
+ compareCanvasRef,
737
+ containerRef,
738
+ handleCanvasMouseDown,
739
+ handleCanvasMouseMove,
740
+ handleCanvasMouseUp,
741
+ createEmojiOverlay,
742
+ createTextOverlay,
743
+ createShapeOverlay,
744
+ createFrameOverlay
745
+ };
747
746
  }
748
-
749
- export { usePhotoEditor };
747
+ export {
748
+ usePhotoEditor
749
+ };