@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,448 +1,718 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { resolveCssColor, NiceIcon, clickOnActivationKey } from '@nice2dev/ui-core';
3
- import React from 'react';
4
- import { useNiceTranslation } from '../core/i18n.js';
5
-
6
- // ── Default Config ──────────────────────────────────────────────
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { resolveCssColor, NiceIcon, clickOnActivationKey } from "@nice2dev/ui-core";
3
+ import React from "react";
4
+ import { useNiceTranslation } from "../core/i18n.js";
7
5
  const DEFAULT_BRUSH = {
8
- size: 20,
9
- opacity: 1,
10
- hardness: 0.8,
11
- // Paint, and the value of a colour input (which needs #rrggbb): a var() here made the
12
- // browser warn and show black.
13
- color: '#ffffff',
14
- blendMode: 'normal',
6
+ size: 20,
7
+ opacity: 1,
8
+ hardness: 0.8,
9
+ // Paint, and the value of a colour input (which needs #rrggbb): a var() here made the
10
+ // browser warn and show black.
11
+ color: "#ffffff",
12
+ blendMode: "normal"
15
13
  };
16
- // ── Hook ────────────────────────────────────────────────────────
17
14
  function useTexturePainter(width, height) {
18
- const [brush, setBrush] = React.useState(DEFAULT_BRUSH);
19
- const [layers, setLayers] = React.useState([]);
20
- const [activeLayerId, setActiveLayerId] = React.useState(null);
21
- const [tool, setTool] = React.useState('brush');
22
- const [renderVersion, setRenderVersion] = React.useState(0);
23
- const undoStackRef = React.useRef([]);
24
- React.useEffect(() => {
25
- if (typeof document === 'undefined') {
26
- return;
27
- }
28
- const canvas = document.createElement('canvas');
29
- canvas.width = width;
30
- canvas.height = height;
31
- const ctx = canvas.getContext('2d');
32
- if (ctx) {
33
- ctx.fillStyle = resolveCssColor('var(--nice-text-secondary, #808080)', ctx.canvas);
34
- ctx.fillRect(0, 0, width, height);
35
- }
36
- const layer = {
37
- id: 'base',
38
- name: 'Base Color',
39
- visible: true,
40
- opacity: 1,
41
- canvas,
42
- };
43
- setLayers([layer]);
44
- setActiveLayerId('base');
45
- }, [width, height]);
46
- const addLayer = React.useCallback((name) => {
47
- if (typeof document === 'undefined') {
48
- return;
49
- }
50
- const canvas = document.createElement('canvas');
51
- canvas.width = width;
52
- canvas.height = height;
53
- const id = `layer-${Date.now()}`;
54
- setLayers((prev) => [...prev, { id, name, visible: true, opacity: 1, canvas }]);
55
- setActiveLayerId(id);
56
- }, [width, height]);
57
- const removeLayer = React.useCallback((id) => {
58
- setLayers((prev) => prev.filter((l) => l.id !== id));
59
- setActiveLayerId((prev) => (prev === id ? null : prev));
60
- }, []);
61
- const toggleLayerVisibility = React.useCallback((id) => {
62
- setLayers((prev) => prev.map((l) => (l.id === id ? { ...l, visible: !l.visible } : l)));
63
- setRenderVersion((v) => v + 1);
64
- }, []);
65
- const setLayerOpacity = React.useCallback((id, opacity) => {
66
- setLayers((prev) => prev.map((l) => (l.id === id ? { ...l, opacity } : l)));
67
- setRenderVersion((v) => v + 1);
68
- }, []);
69
- const beginStroke = React.useCallback(() => {
70
- const layer = layers.find((l) => l.id === activeLayerId);
71
- if (!layer) {
72
- return;
73
- }
74
- const ctx = layer.canvas.getContext('2d');
75
- if (!ctx) {
76
- return;
77
- }
78
- undoStackRef.current.push({
79
- layerId: layer.id,
80
- imageData: ctx.getImageData(0, 0, layer.canvas.width, layer.canvas.height),
81
- });
82
- if (undoStackRef.current.length > 40) {
83
- undoStackRef.current.shift();
84
- }
85
- }, [layers, activeLayerId]);
86
- const undo = React.useCallback(() => {
87
- const snapshot = undoStackRef.current.pop();
88
- if (!snapshot) {
89
- return;
90
- }
91
- const layer = layers.find((l) => l.id === snapshot.layerId);
92
- if (!layer) {
93
- return;
94
- }
95
- const ctx = layer.canvas.getContext('2d');
96
- if (!ctx) {
97
- return;
98
- }
99
- ctx.putImageData(snapshot.imageData, 0, 0);
100
- setRenderVersion((v) => v + 1);
101
- }, [layers]);
102
- const eyedrop = React.useCallback((x, y) => {
103
- // Sample composite color from all visible layers
104
- if (typeof document === 'undefined') {
105
- return;
106
- }
107
- const tmp = document.createElement('canvas');
108
- tmp.width = width;
109
- tmp.height = height;
110
- const ctx = tmp.getContext('2d');
111
- if (!ctx) {
112
- return;
113
- }
114
- for (const layer of layers) {
115
- if (!layer.visible) {
116
- continue;
117
- }
118
- ctx.globalAlpha = layer.opacity;
119
- ctx.drawImage(layer.canvas, 0, 0);
120
- }
121
- const px = ctx.getImageData(Math.round(x), Math.round(y), 1, 1).data;
122
- const hex = `#${px[0].toString(16).padStart(2, '0')}${px[1].toString(16).padStart(2, '0')}${px[2].toString(16).padStart(2, '0')}`;
123
- setBrush((b) => ({ ...b, color: hex }));
124
- setTool('brush');
125
- }, [layers, width, height]);
126
- const paintStroke = React.useCallback((x, y) => {
127
- const layer = layers.find((l) => l.id === activeLayerId);
128
- if (!layer) {
129
- return;
130
- }
131
- const ctx = layer.canvas.getContext('2d');
132
- if (!ctx) {
133
- return;
134
- }
135
- ctx.globalAlpha = brush.opacity;
136
- if (tool === 'eraser') {
137
- ctx.globalCompositeOperation = 'destination-out';
138
- ctx.beginPath();
139
- ctx.arc(x, y, brush.size / 2, 0, Math.PI * 2);
140
- ctx.fill();
141
- ctx.globalCompositeOperation = 'source-over';
142
- }
143
- else if (tool === 'brush') {
144
- const gradient = ctx.createRadialGradient(x, y, 0, x, y, brush.size / 2);
145
- gradient.addColorStop(0, brush.color);
146
- gradient.addColorStop(brush.hardness, brush.color);
147
- gradient.addColorStop(1, 'transparent');
148
- ctx.fillStyle = gradient;
149
- ctx.beginPath();
150
- ctx.arc(x, y, brush.size / 2, 0, Math.PI * 2);
151
- ctx.fill();
152
- }
153
- else if (tool === 'fill') {
154
- ctx.fillStyle = brush.color;
155
- ctx.fillRect(0, 0, width, height);
156
- }
157
- ctx.globalAlpha = 1;
158
- setRenderVersion((v) => v + 1);
159
- }, [layers, activeLayerId, brush, tool, width, height]);
160
- const flattenToBlob = React.useCallback(async () => {
161
- if (typeof document === 'undefined') {
162
- return null;
163
- }
164
- const canvas = document.createElement('canvas');
165
- canvas.width = width;
166
- canvas.height = height;
167
- const ctx = canvas.getContext('2d');
168
- if (!ctx) {
169
- return null;
170
- }
171
- for (const layer of layers) {
172
- if (!layer.visible) {
173
- continue;
174
- }
175
- ctx.globalAlpha = layer.opacity;
176
- ctx.drawImage(layer.canvas, 0, 0);
15
+ const [brush, setBrush] = React.useState(DEFAULT_BRUSH);
16
+ const [layers, setLayers] = React.useState([]);
17
+ const [activeLayerId, setActiveLayerId] = React.useState(null);
18
+ const [tool, setTool] = React.useState("brush");
19
+ const [renderVersion, setRenderVersion] = React.useState(0);
20
+ const undoStackRef = React.useRef([]);
21
+ React.useEffect(() => {
22
+ if (typeof document === "undefined") {
23
+ return;
24
+ }
25
+ const canvas = document.createElement("canvas");
26
+ canvas.width = width;
27
+ canvas.height = height;
28
+ const ctx = canvas.getContext("2d");
29
+ if (ctx) {
30
+ ctx.fillStyle = resolveCssColor("var(--nice-text-secondary, #808080)", ctx.canvas);
31
+ ctx.fillRect(0, 0, width, height);
32
+ }
33
+ const layer = {
34
+ id: "base",
35
+ name: "Base Color",
36
+ visible: true,
37
+ opacity: 1,
38
+ canvas
39
+ };
40
+ setLayers([layer]);
41
+ setActiveLayerId("base");
42
+ }, [width, height]);
43
+ const addLayer = React.useCallback(
44
+ (name) => {
45
+ if (typeof document === "undefined") {
46
+ return;
47
+ }
48
+ const canvas = document.createElement("canvas");
49
+ canvas.width = width;
50
+ canvas.height = height;
51
+ const id = `layer-${Date.now()}`;
52
+ setLayers((prev) => [...prev, { id, name, visible: true, opacity: 1, canvas }]);
53
+ setActiveLayerId(id);
54
+ },
55
+ [width, height]
56
+ );
57
+ const removeLayer = React.useCallback((id) => {
58
+ setLayers((prev) => prev.filter((l) => l.id !== id));
59
+ setActiveLayerId((prev) => prev === id ? null : prev);
60
+ }, []);
61
+ const toggleLayerVisibility = React.useCallback((id) => {
62
+ setLayers((prev) => prev.map((l) => l.id === id ? { ...l, visible: !l.visible } : l));
63
+ setRenderVersion((v) => v + 1);
64
+ }, []);
65
+ const setLayerOpacity = React.useCallback((id, opacity) => {
66
+ setLayers((prev) => prev.map((l) => l.id === id ? { ...l, opacity } : l));
67
+ setRenderVersion((v) => v + 1);
68
+ }, []);
69
+ const beginStroke = React.useCallback(() => {
70
+ const layer = layers.find((l) => l.id === activeLayerId);
71
+ if (!layer) {
72
+ return;
73
+ }
74
+ const ctx = layer.canvas.getContext("2d");
75
+ if (!ctx) {
76
+ return;
77
+ }
78
+ undoStackRef.current.push({
79
+ layerId: layer.id,
80
+ imageData: ctx.getImageData(0, 0, layer.canvas.width, layer.canvas.height)
81
+ });
82
+ if (undoStackRef.current.length > 40) {
83
+ undoStackRef.current.shift();
84
+ }
85
+ }, [layers, activeLayerId]);
86
+ const undo = React.useCallback(() => {
87
+ const snapshot = undoStackRef.current.pop();
88
+ if (!snapshot) {
89
+ return;
90
+ }
91
+ const layer = layers.find((l) => l.id === snapshot.layerId);
92
+ if (!layer) {
93
+ return;
94
+ }
95
+ const ctx = layer.canvas.getContext("2d");
96
+ if (!ctx) {
97
+ return;
98
+ }
99
+ ctx.putImageData(snapshot.imageData, 0, 0);
100
+ setRenderVersion((v) => v + 1);
101
+ }, [layers]);
102
+ const eyedrop = React.useCallback(
103
+ (x, y) => {
104
+ if (typeof document === "undefined") {
105
+ return;
106
+ }
107
+ const tmp = document.createElement("canvas");
108
+ tmp.width = width;
109
+ tmp.height = height;
110
+ const ctx = tmp.getContext("2d");
111
+ if (!ctx) {
112
+ return;
113
+ }
114
+ for (const layer of layers) {
115
+ if (!layer.visible) {
116
+ continue;
177
117
  }
178
- ctx.globalAlpha = 1;
179
- return new Promise((resolve) => canvas.toBlob(resolve, 'image/png'));
180
- }, [layers, width, height]);
118
+ ctx.globalAlpha = layer.opacity;
119
+ ctx.drawImage(layer.canvas, 0, 0);
120
+ }
121
+ const px = ctx.getImageData(Math.round(x), Math.round(y), 1, 1).data;
122
+ const hex = `#${px[0].toString(16).padStart(2, "0")}${px[1].toString(16).padStart(2, "0")}${px[2].toString(16).padStart(2, "0")}`;
123
+ setBrush((b) => ({ ...b, color: hex }));
124
+ setTool("brush");
125
+ },
126
+ [layers, width, height]
127
+ );
128
+ const paintStroke = React.useCallback(
129
+ (x, y) => {
130
+ const layer = layers.find((l) => l.id === activeLayerId);
131
+ if (!layer) {
132
+ return;
133
+ }
134
+ const ctx = layer.canvas.getContext("2d");
135
+ if (!ctx) {
136
+ return;
137
+ }
138
+ ctx.globalAlpha = brush.opacity;
139
+ if (tool === "eraser") {
140
+ ctx.globalCompositeOperation = "destination-out";
141
+ ctx.beginPath();
142
+ ctx.arc(x, y, brush.size / 2, 0, Math.PI * 2);
143
+ ctx.fill();
144
+ ctx.globalCompositeOperation = "source-over";
145
+ } else if (tool === "brush") {
146
+ const gradient = ctx.createRadialGradient(x, y, 0, x, y, brush.size / 2);
147
+ gradient.addColorStop(0, brush.color);
148
+ gradient.addColorStop(brush.hardness, brush.color);
149
+ gradient.addColorStop(1, "transparent");
150
+ ctx.fillStyle = gradient;
151
+ ctx.beginPath();
152
+ ctx.arc(x, y, brush.size / 2, 0, Math.PI * 2);
153
+ ctx.fill();
154
+ } else if (tool === "fill") {
155
+ ctx.fillStyle = brush.color;
156
+ ctx.fillRect(0, 0, width, height);
157
+ }
158
+ ctx.globalAlpha = 1;
159
+ setRenderVersion((v) => v + 1);
160
+ },
161
+ [layers, activeLayerId, brush, tool, width, height]
162
+ );
163
+ const flattenToBlob = React.useCallback(async () => {
164
+ if (typeof document === "undefined") {
165
+ return null;
166
+ }
167
+ const canvas = document.createElement("canvas");
168
+ canvas.width = width;
169
+ canvas.height = height;
170
+ const ctx = canvas.getContext("2d");
171
+ if (!ctx) {
172
+ return null;
173
+ }
174
+ for (const layer of layers) {
175
+ if (!layer.visible) {
176
+ continue;
177
+ }
178
+ ctx.globalAlpha = layer.opacity;
179
+ ctx.drawImage(layer.canvas, 0, 0);
180
+ }
181
+ ctx.globalAlpha = 1;
182
+ return new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
183
+ }, [layers, width, height]);
184
+ return {
185
+ brush,
186
+ setBrush,
187
+ layers,
188
+ addLayer,
189
+ removeLayer,
190
+ activeLayerId,
191
+ setActiveLayerId,
192
+ tool,
193
+ setTool,
194
+ paintStroke,
195
+ flattenToBlob,
196
+ beginStroke,
197
+ undo,
198
+ canUndo: undoStackRef.current.length > 0,
199
+ toggleLayerVisibility,
200
+ setLayerOpacity,
201
+ eyedrop,
202
+ renderVersion
203
+ };
204
+ }
205
+ const Nice3DTexturePainter = ({
206
+ textureWidth = 1024,
207
+ textureHeight = 1024,
208
+ onSave,
209
+ className = ""
210
+ }) => {
211
+ const { t } = useNiceTranslation();
212
+ const painter = useTexturePainter(textureWidth, textureHeight);
213
+ const canvasRef = React.useRef(null);
214
+ const isPainting = React.useRef(false);
215
+ const [zoom, setZoom] = React.useState(0.5);
216
+ const [panOffset, setPanOffset] = React.useState({ x: 0, y: 0 });
217
+ const isPanning = React.useRef(false);
218
+ const lastPan = React.useRef({ x: 0, y: 0 });
219
+ const [cursorPos, setCursorPos] = React.useState(null);
220
+ const pw = Math.round(textureWidth * zoom);
221
+ const ph = Math.round(textureHeight * zoom);
222
+ React.useEffect(() => {
223
+ const canvas = canvasRef.current;
224
+ if (!canvas) {
225
+ return;
226
+ }
227
+ const ctx = canvas.getContext("2d");
228
+ if (!ctx) {
229
+ return;
230
+ }
231
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
232
+ const size = 8;
233
+ const checkA = resolveCssColor("var(--nice-border, #ccc)", canvas);
234
+ const checkB = resolveCssColor("var(--nice-bg, #fff)", canvas);
235
+ for (let y = 0; y < canvas.height; y += size) {
236
+ for (let x = 0; x < canvas.width; x += size) {
237
+ ctx.fillStyle = (x + y) / size % 2 === 0 ? checkA : checkB;
238
+ ctx.fillRect(x, y, size, size);
239
+ }
240
+ }
241
+ for (const layer of painter.layers) {
242
+ if (!layer.visible) {
243
+ continue;
244
+ }
245
+ ctx.globalAlpha = layer.opacity;
246
+ ctx.drawImage(layer.canvas, 0, 0, canvas.width, canvas.height);
247
+ }
248
+ ctx.globalAlpha = 1;
249
+ }, [painter.layers, painter.renderVersion, zoom]);
250
+ const screenToTexture = (e) => {
251
+ const rect = canvasRef.current?.getBoundingClientRect();
252
+ if (!rect) {
253
+ return { x: 0, y: 0 };
254
+ }
181
255
  return {
182
- brush,
183
- setBrush,
184
- layers,
185
- addLayer,
186
- removeLayer,
187
- activeLayerId,
188
- setActiveLayerId,
189
- tool,
190
- setTool,
191
- paintStroke,
192
- flattenToBlob,
193
- beginStroke,
194
- undo,
195
- canUndo: undoStackRef.current.length > 0,
196
- toggleLayerVisibility,
197
- setLayerOpacity,
198
- eyedrop,
199
- renderVersion,
256
+ x: (e.clientX - rect.left) / rect.width * textureWidth,
257
+ y: (e.clientY - rect.top) / rect.height * textureHeight
200
258
  };
201
- }
202
- // ── Component ───────────────────────────────────────────────────
203
- /** 3D texture painting tool with UV-space brush painting, zoom/pan, and layers. */
204
- const Nice3DTexturePainter = ({ textureWidth = 1024, textureHeight = 1024, onSave, className = '', }) => {
205
- const { t } = useNiceTranslation();
206
- const painter = useTexturePainter(textureWidth, textureHeight);
207
- const canvasRef = React.useRef(null);
208
- const isPainting = React.useRef(false);
209
- const [zoom, setZoom] = React.useState(0.5);
210
- const [panOffset, setPanOffset] = React.useState({ x: 0, y: 0 });
211
- const isPanning = React.useRef(false);
212
- const lastPan = React.useRef({ x: 0, y: 0 });
213
- const [cursorPos, setCursorPos] = React.useState(null);
214
- const pw = Math.round(textureWidth * zoom);
215
- const ph = Math.round(textureHeight * zoom);
216
- // Render composite texture to preview
217
- React.useEffect(() => {
218
- const canvas = canvasRef.current;
219
- if (!canvas) {
220
- return;
221
- }
222
- const ctx = canvas.getContext('2d');
223
- if (!ctx) {
224
- return;
225
- }
226
- ctx.clearRect(0, 0, canvas.width, canvas.height);
227
- const size = 8;
228
- // Canvas ignores var(): resolve the checker colours once per paint.
229
- const checkA = resolveCssColor('var(--nice-border, #ccc)', canvas);
230
- const checkB = resolveCssColor('var(--nice-bg, #fff)', canvas);
231
- for (let y = 0; y < canvas.height; y += size) {
232
- for (let x = 0; x < canvas.width; x += size) {
233
- ctx.fillStyle = ((x + y) / size) % 2 === 0 ? checkA : checkB;
234
- ctx.fillRect(x, y, size, size);
259
+ };
260
+ const handlePointerDown = (e) => {
261
+ if (e.button === 1) {
262
+ isPanning.current = true;
263
+ lastPan.current = { x: e.clientX, y: e.clientY };
264
+ e.preventDefault();
265
+ return;
266
+ }
267
+ if (painter.tool === "eyedropper") {
268
+ const { x: x2, y: y2 } = screenToTexture(e);
269
+ painter.eyedrop(x2, y2);
270
+ return;
271
+ }
272
+ painter.beginStroke();
273
+ isPainting.current = true;
274
+ const { x, y } = screenToTexture(e);
275
+ painter.paintStroke(x, y);
276
+ e.target.setPointerCapture(e.pointerId);
277
+ };
278
+ const handlePointerMove = (e) => {
279
+ const { x, y } = screenToTexture(e);
280
+ setCursorPos({ x: Math.round(x), y: Math.round(y) });
281
+ if (isPanning.current) {
282
+ setPanOffset((prev) => ({
283
+ x: prev.x + e.clientX - lastPan.current.x,
284
+ y: prev.y + e.clientY - lastPan.current.y
285
+ }));
286
+ lastPan.current = { x: e.clientX, y: e.clientY };
287
+ return;
288
+ }
289
+ if (!isPainting.current) {
290
+ return;
291
+ }
292
+ painter.paintStroke(x, y);
293
+ };
294
+ const handlePointerUp = () => {
295
+ isPainting.current = false;
296
+ isPanning.current = false;
297
+ };
298
+ const handleWheel = (e) => {
299
+ e.preventDefault();
300
+ setZoom((z) => Math.max(0.1, Math.min(4, z + (e.deltaY > 0 ? -0.05 : 0.05))));
301
+ };
302
+ const handleSave = async () => {
303
+ const blob = await painter.flattenToBlob();
304
+ if (blob) {
305
+ onSave?.(blob);
306
+ }
307
+ };
308
+ const toolIcons = {
309
+ brush: "brush",
310
+ eraser: "eraser",
311
+ eyedropper: "eyedropper",
312
+ fill: "bucket"
313
+ };
314
+ const btnStyle = (active) => ({
315
+ padding: "4px 8px",
316
+ border: "none",
317
+ borderRadius: 3,
318
+ cursor: "pointer",
319
+ fontSize: 11,
320
+ background: active ? "var(--nice-info, #339af0)" : "var(--nice-bg-secondary, #2c2e33)",
321
+ color: active ? "var(--nice-color-white, #fff)" : "var(--nice-text, #c1c2c5)",
322
+ display: "flex",
323
+ alignItems: "center",
324
+ gap: 4
325
+ });
326
+ const toolCursor = painter.tool === "eyedropper" ? "crosshair" : painter.tool === "brush" ? "crosshair" : painter.tool === "eraser" ? "cell" : "crosshair";
327
+ return /* @__PURE__ */ jsxs(
328
+ "div",
329
+ {
330
+ className: `nice-3d-texture-painter ${className}`,
331
+ style: {
332
+ display: "flex",
333
+ gap: 8,
334
+ background: "var(--nice-bg, #1a1b1e)",
335
+ color: "var(--nice-text, #c1c2c5)",
336
+ padding: 12,
337
+ borderRadius: 8
338
+ },
339
+ children: [
340
+ /* @__PURE__ */ jsxs("div", { style: { width: 140, fontSize: 11, display: "flex", flexDirection: "column", gap: 4 }, children: [
341
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: 12 }, children: t("texturePainter3d.tools", "Tools") }),
342
+ ["brush", "eraser", "eyedropper", "fill"].map((t2) => /* @__PURE__ */ jsxs("button", { onClick: () => painter.setTool(t2), style: btnStyle(painter.tool === t2), children: [
343
+ /* @__PURE__ */ jsx(NiceIcon, { name: toolIcons[t2], size: 14 }),
344
+ " ",
345
+ t2.charAt(0).toUpperCase() + t2.slice(1),
346
+ " ",
347
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--nice-text-secondary, #868e96)", marginLeft: "auto" }, children: t2 === "brush" ? "B" : t2 === "eraser" ? "E" : t2 === "eyedropper" ? "I" : "G" })
348
+ ] }, t2)),
349
+ /* @__PURE__ */ jsx(
350
+ "hr",
351
+ {
352
+ style: {
353
+ border: "none",
354
+ borderTop: "1px solid var(--nice-border, #373a40)",
355
+ width: "100%",
356
+ margin: "4px 0"
357
+ }
235
358
  }
236
- }
237
- for (const layer of painter.layers) {
238
- if (!layer.visible) {
239
- continue;
359
+ ),
360
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "space-between" }, children: /* @__PURE__ */ jsxs(
361
+ "button",
362
+ {
363
+ onClick: () => painter.undo(),
364
+ style: btnStyle(false),
365
+ title: t("texturePainter3d.undoCtrlZ", "Undo (Ctrl+Z)"),
366
+ children: [
367
+ /* @__PURE__ */ jsx(NiceIcon, { name: "reply", size: 14 }),
368
+ " ",
369
+ t("texturePainter3d.undo", "Undo")
370
+ ]
240
371
  }
241
- ctx.globalAlpha = layer.opacity;
242
- ctx.drawImage(layer.canvas, 0, 0, canvas.width, canvas.height);
243
- }
244
- ctx.globalAlpha = 1;
245
- }, [painter.layers, painter.renderVersion, zoom]);
246
- const screenToTexture = (e) => {
247
- var _a;
248
- const rect = (_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
249
- if (!rect) {
250
- return { x: 0, y: 0 };
251
- }
252
- return {
253
- x: ((e.clientX - rect.left) / rect.width) * textureWidth,
254
- y: ((e.clientY - rect.top) / rect.height) * textureHeight,
255
- };
256
- };
257
- const handlePointerDown = (e) => {
258
- if (e.button === 1) {
259
- isPanning.current = true;
260
- lastPan.current = { x: e.clientX, y: e.clientY };
261
- e.preventDefault();
262
- return;
263
- }
264
- if (painter.tool === 'eyedropper') {
265
- const { x, y } = screenToTexture(e);
266
- painter.eyedrop(x, y);
267
- return;
268
- }
269
- painter.beginStroke();
270
- isPainting.current = true;
271
- const { x, y } = screenToTexture(e);
272
- painter.paintStroke(x, y);
273
- e.target.setPointerCapture(e.pointerId);
274
- };
275
- const handlePointerMove = (e) => {
276
- const { x, y } = screenToTexture(e);
277
- setCursorPos({ x: Math.round(x), y: Math.round(y) });
278
- if (isPanning.current) {
279
- setPanOffset((prev) => ({
280
- x: prev.x + e.clientX - lastPan.current.x,
281
- y: prev.y + e.clientY - lastPan.current.y,
282
- }));
283
- lastPan.current = { x: e.clientX, y: e.clientY };
284
- return;
285
- }
286
- if (!isPainting.current) {
287
- return;
288
- }
289
- painter.paintStroke(x, y);
290
- };
291
- const handlePointerUp = () => {
292
- isPainting.current = false;
293
- isPanning.current = false;
294
- };
295
- const handleWheel = (e) => {
296
- e.preventDefault();
297
- setZoom((z) => Math.max(0.1, Math.min(4, z + (e.deltaY > 0 ? -0.05 : 0.05))));
298
- };
299
- const handleSave = async () => {
300
- const blob = await painter.flattenToBlob();
301
- if (blob) {
302
- onSave === null || onSave === void 0 ? void 0 : onSave(blob);
303
- }
304
- };
305
- const toolIcons = {
306
- brush: 'brush',
307
- eraser: 'eraser',
308
- eyedropper: 'eyedropper',
309
- fill: 'bucket',
310
- };
311
- const btnStyle = (active) => ({
312
- padding: '4px 8px',
313
- border: 'none',
314
- borderRadius: 3,
315
- cursor: 'pointer',
316
- fontSize: 11,
317
- background: active ? 'var(--nice-info, #339af0)' : 'var(--nice-bg-secondary, #2c2e33)',
318
- color: active ? 'var(--nice-color-white, #fff)' : 'var(--nice-text, #c1c2c5)',
319
- display: 'flex',
320
- alignItems: 'center',
321
- gap: 4,
322
- });
323
- const toolCursor = painter.tool === 'eyedropper'
324
- ? 'crosshair'
325
- : painter.tool === 'brush'
326
- ? 'crosshair'
327
- : painter.tool === 'eraser'
328
- ? 'cell'
329
- : 'crosshair';
330
- return (jsxs("div", { className: `nice-3d-texture-painter ${className}`, style: {
331
- display: 'flex',
332
- gap: 8,
333
- background: 'var(--nice-bg, #1a1b1e)',
334
- color: 'var(--nice-text, #c1c2c5)',
335
- padding: 12,
336
- borderRadius: 8,
337
- }, children: [jsxs("div", { style: { width: 140, fontSize: 11, display: 'flex', flexDirection: 'column', gap: 4 }, children: [jsx("strong", { style: { fontSize: 12 }, children: t('texturePainter3d.tools', 'Tools') }), ['brush', 'eraser', 'eyedropper', 'fill'].map((t) => (jsxs("button", { onClick: () => painter.setTool(t), style: btnStyle(painter.tool === t), children: [jsx(NiceIcon, { name: toolIcons[t], size: 14 }), " ", t.charAt(0).toUpperCase() + t.slice(1), ' ', jsx("span", { style: { color: 'var(--nice-text-secondary, #868e96)', marginLeft: 'auto' }, children: t === 'brush' ? 'B' : t === 'eraser' ? 'E' : t === 'eyedropper' ? 'I' : 'G' })] }, t))), jsx("hr", { style: {
338
- border: 'none',
339
- borderTop: '1px solid var(--nice-border, #373a40)',
340
- width: '100%',
341
- margin: '4px 0',
342
- } }), jsx("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: jsxs("button", { onClick: () => painter.undo(), style: btnStyle(false), title: t('texturePainter3d.undoCtrlZ', 'Undo (Ctrl+Z)'), children: [jsx(NiceIcon, { name: "reply", size: 14 }), " ", t('texturePainter3d.undo', 'Undo')] }) }), jsx("hr", { style: {
343
- border: 'none',
344
- borderTop: '1px solid var(--nice-border, #373a40)',
345
- width: '100%',
346
- margin: '4px 0',
347
- } }), jsx("label", { children: t('texturePainter3d.sizePx', 'Size: {size}px').replace('{size}', String(painter.brush.size)) }), jsx("input", { type: "range", min: 1, max: 100, value: painter.brush.size, onChange: (e) => painter.setBrush({ ...painter.brush, size: Number(e.target.value) }), style: { width: '100%' } }), jsx("label", { children: t('texturePainter3d.opacity', 'Opacity: {opacity}%').replace('{opacity}', String(Math.round(painter.brush.opacity * 100))) }), jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: painter.brush.opacity, onChange: (e) => painter.setBrush({ ...painter.brush, opacity: Number(e.target.value) }), style: { width: '100%' } }), jsx("label", { children: t('texturePainter3d.hardness', 'Hardness: {hardness}%').replace('{hardness}', String(Math.round(painter.brush.hardness * 100))) }), jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: painter.brush.hardness, onChange: (e) => painter.setBrush({ ...painter.brush, hardness: Number(e.target.value) }), style: { width: '100%' } }), jsx("label", { children: t('texturePainter3d.blendMode', 'Blend Mode') }), jsxs("select", { value: painter.brush.blendMode, onChange: (e) => painter.setBrush({
348
- ...painter.brush,
349
- blendMode: e.target.value,
350
- }), style: {
351
- width: '100%',
352
- padding: 2,
353
- background: 'var(--nice-bg-secondary, #2c2e33)',
354
- color: 'var(--nice-text, #c1c2c5)',
355
- border: '1px solid var(--nice-border, #373a40)',
356
- borderRadius: 3,
357
- fontSize: 10,
358
- }, children: [jsx("option", { value: "normal", children: t('texturePainter3d.normal', 'Normal') }), jsx("option", { value: "multiply", children: t('texturePainter3d.multiply', 'Multiply') }), jsx("option", { value: "screen", children: t('texturePainter3d.screen', 'Screen') }), jsx("option", { value: "overlay", children: t('texturePainter3d.overlay', 'Overlay') })] }), jsx("label", { children: t('texturePainter3d.color', 'Color') }), jsxs("div", { style: { display: 'flex', gap: 4, alignItems: 'center' }, children: [jsx("input", { type: "color", value: painter.brush.color, onChange: (e) => painter.setBrush({ ...painter.brush, color: e.target.value }), style: { width: 32, height: 24, border: 'none', cursor: 'pointer' } }), jsx("span", { style: { fontFamily: 'monospace', fontSize: 10 }, children: painter.brush.color })] }), jsx("hr", { style: {
359
- border: 'none',
360
- borderTop: '1px solid var(--nice-border, #373a40)',
361
- width: '100%',
362
- margin: '4px 0',
363
- } }), jsx("label", { children: t('texturePainter3d.zoom', 'Zoom: {zoom}%').replace('{zoom}', String(Math.round(zoom * 100))) }), jsxs("div", { style: { display: 'flex', gap: 2 }, children: [jsx("button", { onClick: () => setZoom((z) => Math.max(0.1, z - 0.1)), style: btnStyle(false), children: "\u2212" }), jsx("button", { onClick: () => setZoom(0.5), style: btnStyle(false), children: t('texturePainter3d.reset', 'Reset') }), jsx("button", { onClick: () => setZoom((z) => Math.min(4, z + 0.1)), style: btnStyle(false), children: "+" })] })] }), jsxs("div", { style: {
364
- flex: 1,
365
- overflow: 'hidden',
366
- position: 'relative',
367
- background: 'var(--nice-bg, #141517)',
368
- borderRadius: 6,
369
- }, onWheel: handleWheel, children: [jsx("div", { style: {
370
- transform: `translate(${panOffset.x}px, ${panOffset.y}px)`,
371
- display: 'inline-block',
372
- padding: 16,
373
- }, children: jsx("canvas", { ref: canvasRef, width: pw, height: ph, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, style: {
374
- cursor: toolCursor,
375
- borderRadius: 4,
376
- border: '1px solid var(--nice-border, #373a40)',
377
- } }) }), jsxs("div", { style: {
378
- position: 'absolute',
379
- bottom: 4,
380
- left: 8,
381
- fontSize: 10,
382
- color: 'var(--nice-text-secondary, #868e96)',
383
- display: 'flex',
384
- gap: 12,
385
- }, children: [jsxs("span", { children: [textureWidth, "\u00D7", textureHeight] }), cursorPos && (jsxs("span", { children: ["X: ", cursorPos.x, " Y: ", cursorPos.y] })), jsx("span", { children: t('texturePainter3d.zoom', 'Zoom: {zoom}%').replace('{zoom}', String(Math.round(zoom * 100))) })] })] }), jsxs("div", { style: { width: 160, fontSize: 11, display: 'flex', flexDirection: 'column', gap: 4 }, children: [jsx("strong", { style: { fontSize: 12 }, children: t('texturePainter3d.layers', 'Layers') }), jsx("div", { style: { flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 2 }, children: [...painter.layers].reverse().map((l) => (jsxs("div", { style: {
386
- padding: '4px 6px',
387
- borderRadius: 3,
388
- background: painter.activeLayerId === l.id
389
- ? 'var(--nice-info, #339af0)'
390
- : 'var(--nice-bg-secondary, #2c2e33)',
391
- color: painter.activeLayerId === l.id
392
- ? 'var(--nice-color-white, #fff)'
393
- : 'var(--nice-text, #c1c2c5)',
394
- }, children: [jsxs("div", { role: "button", tabIndex: 0, onKeyDown: clickOnActivationKey, style: { display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' }, onClick: () => painter.setActiveLayerId(l.id), children: [jsx("button", { onClick: (e) => {
395
- e.stopPropagation();
396
- painter.toggleLayerVisibility(l.id);
397
- }, style: {
398
- background: 'transparent',
399
- border: 'none',
400
- cursor: 'pointer',
401
- padding: 0,
402
- color: l.visible
403
- ? 'var(--nice-text, #c1c2c5)'
404
- : 'var(--nice-text-secondary, #5c5f66)',
405
- fontSize: 12,
406
- }, title: l.visible
407
- ? t('texturePainter3d.hideLayer', 'Hide layer')
408
- : t('texturePainter3d.showLayer', 'Show layer'), "aria-label": l.visible
409
- ? t('texturePainter3d.hideLayer', 'Hide layer')
410
- : t('texturePainter3d.showLayer', 'Show layer'), children: jsx(NiceIcon, { name: l.visible ? 'eye' : 'eye-off', size: 14 }) }), jsx("span", { style: {
411
- flex: 1,
412
- overflow: 'hidden',
413
- textOverflow: 'ellipsis',
414
- whiteSpace: 'nowrap',
415
- }, children: l.name }), l.id !== 'base' && (jsx("button", { onClick: (e) => {
416
- e.stopPropagation();
417
- painter.removeLayer(l.id);
418
- }, style: {
419
- background: 'transparent',
420
- border: 'none',
421
- color: 'var(--nice-danger, #ff6b6b)',
422
- cursor: 'pointer',
423
- padding: 0,
424
- fontSize: 10,
425
- }, "aria-label": t('texturePainter3d.removeLayer', 'Remove layer'), title: t('texturePainter3d.removeLayer', 'Remove layer'), children: jsx(NiceIcon, { name: "close", size: 10 }) }))] }), jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginTop: 2 }, children: [jsx("span", { style: { fontSize: 9, color: 'var(--nice-text-secondary, #868e96)' }, children: "\u03B1" }), jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: l.opacity, onChange: (e) => painter.setLayerOpacity(l.id, Number(e.target.value)), style: { flex: 1, height: 12 } }), jsxs("span", { style: { fontSize: 9, minWidth: 24, textAlign: 'right' }, children: [Math.round(l.opacity * 100), "%"] })] })] }, l.id))) }), jsx("button", { onClick: () => painter.addLayer(`Layer ${painter.layers.length + 1}`), style: {
426
- padding: '4px 8px',
427
- border: 'none',
428
- borderRadius: 3,
429
- cursor: 'pointer',
430
- background: 'var(--nice-bg-secondary, #2c2e33)',
431
- color: 'var(--nice-text, #c1c2c5)',
432
- }, children: t('texturePainter3d.addLayer', '+ Add Layer') }), jsx("hr", { style: {
433
- border: 'none',
434
- borderTop: '1px solid var(--nice-border, #373a40)',
435
- width: '100%',
436
- margin: '4px 0',
437
- } }), jsx("button", { onClick: handleSave, style: {
438
- padding: '6px 8px',
439
- background: 'var(--nice-success, #2f9e44)',
440
- color: 'var(--nice-text-on-primary, #fff)',
441
- border: 'none',
442
- borderRadius: 3,
443
- cursor: 'pointer',
444
- fontWeight: 'bold',
445
- }, children: t('texturePainter3d.saveTexture', 'Save Texture') })] })] }));
372
+ ) }),
373
+ /* @__PURE__ */ jsx(
374
+ "hr",
375
+ {
376
+ style: {
377
+ border: "none",
378
+ borderTop: "1px solid var(--nice-border, #373a40)",
379
+ width: "100%",
380
+ margin: "4px 0"
381
+ }
382
+ }
383
+ ),
384
+ /* @__PURE__ */ jsx("label", { children: t("texturePainter3d.sizePx", "Size: {size}px").replace(
385
+ "{size}",
386
+ String(painter.brush.size)
387
+ ) }),
388
+ /* @__PURE__ */ jsx(
389
+ "input",
390
+ {
391
+ type: "range",
392
+ min: 1,
393
+ max: 100,
394
+ value: painter.brush.size,
395
+ onChange: (e) => painter.setBrush({ ...painter.brush, size: Number(e.target.value) }),
396
+ style: { width: "100%" }
397
+ }
398
+ ),
399
+ /* @__PURE__ */ jsx("label", { children: t("texturePainter3d.opacity", "Opacity: {opacity}%").replace(
400
+ "{opacity}",
401
+ String(Math.round(painter.brush.opacity * 100))
402
+ ) }),
403
+ /* @__PURE__ */ jsx(
404
+ "input",
405
+ {
406
+ type: "range",
407
+ min: 0,
408
+ max: 1,
409
+ step: 0.01,
410
+ value: painter.brush.opacity,
411
+ onChange: (e) => painter.setBrush({ ...painter.brush, opacity: Number(e.target.value) }),
412
+ style: { width: "100%" }
413
+ }
414
+ ),
415
+ /* @__PURE__ */ jsx("label", { children: t("texturePainter3d.hardness", "Hardness: {hardness}%").replace(
416
+ "{hardness}",
417
+ String(Math.round(painter.brush.hardness * 100))
418
+ ) }),
419
+ /* @__PURE__ */ jsx(
420
+ "input",
421
+ {
422
+ type: "range",
423
+ min: 0,
424
+ max: 1,
425
+ step: 0.01,
426
+ value: painter.brush.hardness,
427
+ onChange: (e) => painter.setBrush({ ...painter.brush, hardness: Number(e.target.value) }),
428
+ style: { width: "100%" }
429
+ }
430
+ ),
431
+ /* @__PURE__ */ jsx("label", { children: t("texturePainter3d.blendMode", "Blend Mode") }),
432
+ /* @__PURE__ */ jsxs(
433
+ "select",
434
+ {
435
+ value: painter.brush.blendMode,
436
+ onChange: (e) => painter.setBrush({
437
+ ...painter.brush,
438
+ blendMode: e.target.value
439
+ }),
440
+ style: {
441
+ width: "100%",
442
+ padding: 2,
443
+ background: "var(--nice-bg-secondary, #2c2e33)",
444
+ color: "var(--nice-text, #c1c2c5)",
445
+ border: "1px solid var(--nice-border, #373a40)",
446
+ borderRadius: 3,
447
+ fontSize: 10
448
+ },
449
+ children: [
450
+ /* @__PURE__ */ jsx("option", { value: "normal", children: t("texturePainter3d.normal", "Normal") }),
451
+ /* @__PURE__ */ jsx("option", { value: "multiply", children: t("texturePainter3d.multiply", "Multiply") }),
452
+ /* @__PURE__ */ jsx("option", { value: "screen", children: t("texturePainter3d.screen", "Screen") }),
453
+ /* @__PURE__ */ jsx("option", { value: "overlay", children: t("texturePainter3d.overlay", "Overlay") })
454
+ ]
455
+ }
456
+ ),
457
+ /* @__PURE__ */ jsx("label", { children: t("texturePainter3d.color", "Color") }),
458
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [
459
+ /* @__PURE__ */ jsx(
460
+ "input",
461
+ {
462
+ type: "color",
463
+ value: painter.brush.color,
464
+ onChange: (e) => painter.setBrush({ ...painter.brush, color: e.target.value }),
465
+ style: { width: 32, height: 24, border: "none", cursor: "pointer" }
466
+ }
467
+ ),
468
+ /* @__PURE__ */ jsx("span", { style: { fontFamily: "monospace", fontSize: 10 }, children: painter.brush.color })
469
+ ] }),
470
+ /* @__PURE__ */ jsx(
471
+ "hr",
472
+ {
473
+ style: {
474
+ border: "none",
475
+ borderTop: "1px solid var(--nice-border, #373a40)",
476
+ width: "100%",
477
+ margin: "4px 0"
478
+ }
479
+ }
480
+ ),
481
+ /* @__PURE__ */ jsx("label", { children: t("texturePainter3d.zoom", "Zoom: {zoom}%").replace(
482
+ "{zoom}",
483
+ String(Math.round(zoom * 100))
484
+ ) }),
485
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 2 }, children: [
486
+ /* @__PURE__ */ jsx("button", { onClick: () => setZoom((z) => Math.max(0.1, z - 0.1)), style: btnStyle(false), children: "−" }),
487
+ /* @__PURE__ */ jsx("button", { onClick: () => setZoom(0.5), style: btnStyle(false), children: t("texturePainter3d.reset", "Reset") }),
488
+ /* @__PURE__ */ jsx("button", { onClick: () => setZoom((z) => Math.min(4, z + 0.1)), style: btnStyle(false), children: "+" })
489
+ ] })
490
+ ] }),
491
+ /* @__PURE__ */ jsxs(
492
+ "div",
493
+ {
494
+ style: {
495
+ flex: 1,
496
+ overflow: "hidden",
497
+ position: "relative",
498
+ background: "var(--nice-bg, #141517)",
499
+ borderRadius: 6
500
+ },
501
+ onWheel: handleWheel,
502
+ children: [
503
+ /* @__PURE__ */ jsx(
504
+ "div",
505
+ {
506
+ style: {
507
+ transform: `translate(${panOffset.x}px, ${panOffset.y}px)`,
508
+ display: "inline-block",
509
+ padding: 16
510
+ },
511
+ children: /* @__PURE__ */ jsx(
512
+ "canvas",
513
+ {
514
+ ref: canvasRef,
515
+ width: pw,
516
+ height: ph,
517
+ onPointerDown: handlePointerDown,
518
+ onPointerMove: handlePointerMove,
519
+ onPointerUp: handlePointerUp,
520
+ style: {
521
+ cursor: toolCursor,
522
+ borderRadius: 4,
523
+ border: "1px solid var(--nice-border, #373a40)"
524
+ }
525
+ }
526
+ )
527
+ }
528
+ ),
529
+ /* @__PURE__ */ jsxs(
530
+ "div",
531
+ {
532
+ style: {
533
+ position: "absolute",
534
+ bottom: 4,
535
+ left: 8,
536
+ fontSize: 10,
537
+ color: "var(--nice-text-secondary, #868e96)",
538
+ display: "flex",
539
+ gap: 12
540
+ },
541
+ children: [
542
+ /* @__PURE__ */ jsxs("span", { children: [
543
+ textureWidth,
544
+ "×",
545
+ textureHeight
546
+ ] }),
547
+ cursorPos && /* @__PURE__ */ jsxs("span", { children: [
548
+ "X: ",
549
+ cursorPos.x,
550
+ " Y: ",
551
+ cursorPos.y
552
+ ] }),
553
+ /* @__PURE__ */ jsx("span", { children: t("texturePainter3d.zoom", "Zoom: {zoom}%").replace(
554
+ "{zoom}",
555
+ String(Math.round(zoom * 100))
556
+ ) })
557
+ ]
558
+ }
559
+ )
560
+ ]
561
+ }
562
+ ),
563
+ /* @__PURE__ */ jsxs("div", { style: { width: 160, fontSize: 11, display: "flex", flexDirection: "column", gap: 4 }, children: [
564
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: 12 }, children: t("texturePainter3d.layers", "Layers") }),
565
+ /* @__PURE__ */ jsx(
566
+ "div",
567
+ {
568
+ style: { flex: 1, overflowY: "auto", display: "flex", flexDirection: "column", gap: 2 },
569
+ children: [...painter.layers].reverse().map((l) => /* @__PURE__ */ jsxs(
570
+ "div",
571
+ {
572
+ style: {
573
+ padding: "4px 6px",
574
+ borderRadius: 3,
575
+ background: painter.activeLayerId === l.id ? "var(--nice-info, #339af0)" : "var(--nice-bg-secondary, #2c2e33)",
576
+ color: painter.activeLayerId === l.id ? "var(--nice-color-white, #fff)" : "var(--nice-text, #c1c2c5)"
577
+ },
578
+ children: [
579
+ /* @__PURE__ */ jsxs(
580
+ "div",
581
+ {
582
+ role: "button",
583
+ tabIndex: 0,
584
+ onKeyDown: clickOnActivationKey,
585
+ style: { display: "flex", alignItems: "center", gap: 4, cursor: "pointer" },
586
+ onClick: () => painter.setActiveLayerId(l.id),
587
+ children: [
588
+ /* @__PURE__ */ jsx(
589
+ "button",
590
+ {
591
+ onClick: (e) => {
592
+ e.stopPropagation();
593
+ painter.toggleLayerVisibility(l.id);
594
+ },
595
+ style: {
596
+ background: "transparent",
597
+ border: "none",
598
+ cursor: "pointer",
599
+ padding: 0,
600
+ color: l.visible ? "var(--nice-text, #c1c2c5)" : "var(--nice-text-secondary, #5c5f66)",
601
+ fontSize: 12
602
+ },
603
+ title: l.visible ? t("texturePainter3d.hideLayer", "Hide layer") : t("texturePainter3d.showLayer", "Show layer"),
604
+ "aria-label": l.visible ? t("texturePainter3d.hideLayer", "Hide layer") : t("texturePainter3d.showLayer", "Show layer"),
605
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: l.visible ? "eye" : "eye-off", size: 14 })
606
+ }
607
+ ),
608
+ /* @__PURE__ */ jsx(
609
+ "span",
610
+ {
611
+ style: {
612
+ flex: 1,
613
+ overflow: "hidden",
614
+ textOverflow: "ellipsis",
615
+ whiteSpace: "nowrap"
616
+ },
617
+ children: l.name
618
+ }
619
+ ),
620
+ l.id !== "base" && /* @__PURE__ */ jsx(
621
+ "button",
622
+ {
623
+ onClick: (e) => {
624
+ e.stopPropagation();
625
+ painter.removeLayer(l.id);
626
+ },
627
+ style: {
628
+ background: "transparent",
629
+ border: "none",
630
+ color: "var(--nice-danger, #ff6b6b)",
631
+ cursor: "pointer",
632
+ padding: 0,
633
+ fontSize: 10
634
+ },
635
+ "aria-label": t("texturePainter3d.removeLayer", "Remove layer"),
636
+ title: t("texturePainter3d.removeLayer", "Remove layer"),
637
+ children: /* @__PURE__ */ jsx(NiceIcon, { name: "close", size: 10 })
638
+ }
639
+ )
640
+ ]
641
+ }
642
+ ),
643
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4, marginTop: 2 }, children: [
644
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 9, color: "var(--nice-text-secondary, #868e96)" }, children: "α" }),
645
+ /* @__PURE__ */ jsx(
646
+ "input",
647
+ {
648
+ type: "range",
649
+ min: 0,
650
+ max: 1,
651
+ step: 0.01,
652
+ value: l.opacity,
653
+ onChange: (e) => painter.setLayerOpacity(l.id, Number(e.target.value)),
654
+ style: { flex: 1, height: 12 }
655
+ }
656
+ ),
657
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 9, minWidth: 24, textAlign: "right" }, children: [
658
+ Math.round(l.opacity * 100),
659
+ "%"
660
+ ] })
661
+ ] })
662
+ ]
663
+ },
664
+ l.id
665
+ ))
666
+ }
667
+ ),
668
+ /* @__PURE__ */ jsx(
669
+ "button",
670
+ {
671
+ onClick: () => painter.addLayer(`Layer ${painter.layers.length + 1}`),
672
+ style: {
673
+ padding: "4px 8px",
674
+ border: "none",
675
+ borderRadius: 3,
676
+ cursor: "pointer",
677
+ background: "var(--nice-bg-secondary, #2c2e33)",
678
+ color: "var(--nice-text, #c1c2c5)"
679
+ },
680
+ children: t("texturePainter3d.addLayer", "+ Add Layer")
681
+ }
682
+ ),
683
+ /* @__PURE__ */ jsx(
684
+ "hr",
685
+ {
686
+ style: {
687
+ border: "none",
688
+ borderTop: "1px solid var(--nice-border, #373a40)",
689
+ width: "100%",
690
+ margin: "4px 0"
691
+ }
692
+ }
693
+ ),
694
+ /* @__PURE__ */ jsx(
695
+ "button",
696
+ {
697
+ onClick: handleSave,
698
+ style: {
699
+ padding: "6px 8px",
700
+ background: "var(--nice-success, #2f9e44)",
701
+ color: "var(--nice-text-on-primary, #fff)",
702
+ border: "none",
703
+ borderRadius: 3,
704
+ cursor: "pointer",
705
+ fontWeight: "bold"
706
+ },
707
+ children: t("texturePainter3d.saveTexture", "Save Texture")
708
+ }
709
+ )
710
+ ] })
711
+ ]
712
+ }
713
+ );
714
+ };
715
+ export {
716
+ Nice3DTexturePainter,
717
+ useTexturePainter
446
718
  };
447
-
448
- export { Nice3DTexturePainter, useTexturePainter };