@nice2dev/ui-graphics 1.3.14 → 1.3.16

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