@nice2dev/ui-graphics 1.1.10 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +173 -0
- package/README.md +60 -63
- package/dist/cjs/animation/AnimationEditor.js +0 -3
- package/dist/cjs/animation/Audience.js +3 -1
- package/dist/cjs/animation/BodyRenderer.js +1 -4
- package/dist/cjs/animation/rig/RigPlayer.js +1 -1
- package/dist/cjs/animation/rig/presets.js +67 -55
- package/dist/cjs/animation/shapes/heads.js +0 -3
- package/dist/cjs/animation/shapes/headwear.js +4 -1
- package/dist/cjs/core/NiceBrushEngine.js +6 -6
- package/dist/cjs/core/editorAsk.js +48 -0
- package/dist/cjs/core/i18n.js +7 -3
- package/dist/cjs/core/plugins.js +1 -1
- package/dist/cjs/core/rtl.js +1 -1
- package/dist/cjs/core/theme.js +1 -1
- package/dist/cjs/font/NiceFontEditor.js +16 -14
- package/dist/cjs/game/GameAsset2dEditor.js +28 -19
- package/dist/cjs/index.js +0 -11
- package/dist/cjs/nice2dev-ui-graphics.css +1 -1
- package/dist/cjs/photo/FilterThumb.js +3 -3
- package/dist/cjs/photo/NiceAITools.js +7 -5
- package/dist/cjs/photo/PhotoEditor.js +3 -1
- package/dist/cjs/photo/PhotoEditorCanvas.js +8 -6
- package/dist/cjs/photo/PhotoEditorLeftPanel.js +19 -19
- package/dist/cjs/photo/PhotoEditorToolbar.js +5 -5
- package/dist/cjs/photo/photoFilters.js +1 -0
- package/dist/cjs/pixel/PixelEditor.js +6 -6
- package/dist/cjs/pixel/PixelEditor.module.css.js +1 -1
- package/dist/cjs/pixel/PixelEditorCanvas.js +10 -6
- package/dist/cjs/pixel/PixelEditorMenuBar.js +3 -3
- package/dist/cjs/pixel/PixelEditorRightPanel.js +28 -24
- package/dist/cjs/pixel/PixelEditorStatusBar.js +8 -4
- package/dist/cjs/pixel/PixelEditorTimeline.js +11 -2
- package/dist/cjs/pixel/PixelEditorToolbar.js +10 -6
- package/dist/cjs/pixel/SpriteGeneratorPanel.js +12 -8
- package/dist/cjs/pixel/animalGeneratorPresets.js +31 -31
- package/dist/cjs/pixel/buildingGeneratorPresets.js +24 -24
- package/dist/cjs/pixel/characterGeneratorPresets.js +25 -25
- package/dist/cjs/pixel/usePixelEditor.js +13 -10
- package/dist/cjs/texture/Nice3DTexturePainter.js +12 -10
- package/dist/cjs/ui/NiceUIDesigner.js +18 -14
- package/dist/cjs/vector/NiceSvgToCode.js +6 -4
- package/dist/cjs/vector/VectorEditor.js +3 -5
- package/dist/cjs/vector/VectorEditorShapeRenderer.js +3 -1
- package/dist/cjs/vector/useVectorEditor.js +38 -29
- package/dist/cjs/vector/vectorEditorImport.js +1 -0
- package/dist/cjs/vector/vectorIconImport.js +1 -0
- package/dist/esm/animation/AnimationEditor.js +1 -3
- package/dist/esm/animation/Audience.js +3 -1
- package/dist/esm/animation/BodyRenderer.js +3 -5
- package/dist/esm/animation/rig/RigPlayer.js +1 -1
- package/dist/esm/animation/rig/presets.js +68 -56
- package/dist/esm/animation/shapes/heads.js +1 -3
- package/dist/esm/animation/shapes/headwear.js +4 -1
- package/dist/esm/animation/spineExport.js +1 -1
- package/dist/esm/core/NiceBrushEngine.js +6 -6
- package/dist/esm/core/editorAsk.js +45 -0
- package/dist/esm/core/i18n.js +8 -4
- package/dist/esm/core/plugins.js +1 -1
- package/dist/esm/core/rtl.js +1 -1
- package/dist/esm/core/theme.js +1 -1
- package/dist/esm/font/NiceFontEditor.js +16 -14
- package/dist/esm/game/GameAsset2dEditor.js +29 -19
- package/dist/esm/index.js +10 -10
- package/dist/esm/nice2dev-ui-graphics.css +1 -1
- package/dist/esm/photo/FilterThumb.js +3 -3
- package/dist/esm/photo/NiceAITools.js +7 -5
- package/dist/esm/photo/PhotoEditor.js +3 -1
- package/dist/esm/photo/PhotoEditorCanvas.js +8 -6
- package/dist/esm/photo/PhotoEditorLeftPanel.js +19 -19
- package/dist/esm/photo/PhotoEditorToolbar.js +5 -5
- package/dist/esm/photo/photoFilters.js +1 -0
- package/dist/esm/pixel/PixelEditor.js +7 -6
- package/dist/esm/pixel/PixelEditor.module.css.js +1 -1
- package/dist/esm/pixel/PixelEditorCanvas.js +10 -6
- package/dist/esm/pixel/PixelEditorMenuBar.js +3 -3
- package/dist/esm/pixel/PixelEditorRightPanel.js +28 -24
- package/dist/esm/pixel/PixelEditorStatusBar.js +8 -4
- package/dist/esm/pixel/PixelEditorTimeline.js +11 -2
- package/dist/esm/pixel/PixelEditorToolbar.js +10 -6
- package/dist/esm/pixel/SpriteGeneratorPanel.js +12 -8
- package/dist/esm/pixel/animalGeneratorPresets.js +32 -32
- package/dist/esm/pixel/buildingGeneratorPresets.js +25 -25
- package/dist/esm/pixel/characterGeneratorPresets.js +26 -26
- package/dist/esm/pixel/usePixelEditor.js +13 -10
- package/dist/esm/texture/Nice3DTexturePainter.js +12 -10
- package/dist/esm/ui/NiceUIDesigner.js +18 -14
- package/dist/esm/vector/NiceSvgToCode.js +6 -4
- package/dist/esm/vector/VectorEditor.js +4 -5
- package/dist/esm/vector/VectorEditorShapeRenderer.js +3 -1
- package/dist/esm/vector/useVectorEditor.js +38 -29
- package/dist/esm/vector/vectorEditorImport.js +1 -0
- package/dist/esm/vector/vectorIconImport.js +1 -0
- package/dist/types/animation/AnimatedPerson.d.ts +0 -4
- package/dist/types/animation/AnimationEditor.d.ts +1 -5
- package/dist/types/animation/Audience.d.ts +0 -4
- package/dist/types/animation/BodyRenderer.d.ts +0 -4
- package/dist/types/animation/rig/RigPlayer.d.ts +0 -7
- package/dist/types/animation/rig/RiggedBody.d.ts +0 -4
- package/dist/types/animation/shapes/heads.d.ts +0 -4
- package/dist/types/core/LocalUI.d.ts +1 -2
- package/dist/types/core/editorAsk.d.ts +26 -0
- package/dist/types/core/i18n.d.ts +1 -17
- package/dist/types/game/GameAsset2dEditor.d.ts +0 -4
- package/dist/types/game/index.d.ts +1 -1
- package/dist/types/index.d.ts +9 -19
- package/dist/types/photo/index.d.ts +0 -2
- package/dist/types/pixel/PixelEditor.d.ts +0 -4
- package/dist/types/pixel/SpriteGeneratorPanel.d.ts +0 -4
- package/dist/types/pixel/usePixelEditor.d.ts +3 -0
- package/dist/types/vector/VectorEditor.d.ts +0 -4
- package/dist/types/vector/useVectorEditor.d.ts +2 -0
- package/package.json +5 -5
|
@@ -4,6 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
4
4
|
var ui = require('@nice2dev/ui');
|
|
5
5
|
var uiCore = require('@nice2dev/ui-core');
|
|
6
6
|
var React = require('react');
|
|
7
|
+
var editorAsk = require('../core/editorAsk.js');
|
|
7
8
|
var i18n = require('../core/i18n.js');
|
|
8
9
|
var useVectorEditor = require('./useVectorEditor.js');
|
|
9
10
|
var vectorAnimation = require('./vectorAnimation.js');
|
|
@@ -65,7 +66,7 @@ const NiceVectorEditor = ({ width: artW = 512, height: artH = 512, className, on
|
|
|
65
66
|
}, [initialIcon, artW, artH, api]);
|
|
66
67
|
const { shapes, selId, tool, zoom, px, py, dlgSvg, svgText, gridSize, boxRef, fInp, animations, previewTime, isPlaying, onDown, onMove, onUp, onWheel, onDblClick, openFile, switchTool, importSVGString, setDlgSvg, setSvgText, } = api;
|
|
67
68
|
const toolRail = (jsxRuntime.jsx("div", { className: VectorEditor_module.toolbar, children: vectorEditorTypes.TOOLS.map((t) => (jsxRuntime.jsx(ui.NiceButton, { className: `${VectorEditor_module.toolBtn} ${tool === t.id ? VectorEditor_module.toolBtnActive : ''}`, onClick: () => switchTool(t.id), "aria-label": `${t.label} (${t.key})`, variant: tool === t.id ? 'primary' : 'ghost', size: "sm", children: jsxRuntime.jsx(uiCore.NiceIcon, { name: t.icon, size: "sm" }) }, t.id))) }));
|
|
68
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("input", { ref: fInp, type: "file", accept: ".svg", style: { display: 'none' }, onChange: (e) => {
|
|
69
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(editorAsk.EditorAskDialog, { ask: api.pendingAsk }), jsxRuntime.jsx("input", { ref: fInp, type: "file", accept: ".svg", style: { display: 'none' }, onChange: (e) => {
|
|
69
70
|
var _a;
|
|
70
71
|
const f = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
71
72
|
if (f) {
|
|
@@ -111,14 +112,11 @@ const NiceVectorEditor = ({ width: artW = 512, height: artH = 512, className, on
|
|
|
111
112
|
}
|
|
112
113
|
const node = (jsxRuntime.jsx(VectorEditorShapeRenderer.ShapeRenderer, { shape: s, selected: s.id === selId, zoom: zoom }, s.id));
|
|
113
114
|
return previewTr ? (jsxRuntime.jsx("g", { transform: previewTr, children: node }, s.id)) : (node);
|
|
114
|
-
})] }) }) }), jsxRuntime.jsxs(ui.NiceModal, { open: dlgSvg, onClose: () => setDlgSvg(false), title:
|
|
115
|
+
})] }) }) }), jsxRuntime.jsxs(ui.NiceModal, { open: dlgSvg, onClose: () => setDlgSvg(false), title: t('vectorEditor.pasteSvg', 'Paste SVG'), children: [jsxRuntime.jsx("label", { children: "Paste SVG source code below:" }), jsxRuntime.jsx(ui.NiceTextArea, { value: svgText, onChange: (val) => setSvgText(val), placeholder: t('vectorEditor.svgXmlnsHttpWwwW3', '<svg xmlns="http://www.w3.org/2000/svg" ...>') }), jsxRuntime.jsxs("div", { className: VectorEditor_module.dialogActions, children: [jsxRuntime.jsx(ui.NiceButton, { variant: "ghost", onClick: () => setDlgSvg(false), children: t('vectorEditor.cancel', 'Cancel') }), jsxRuntime.jsx(ui.NiceButton, { variant: "primary", onClick: () => importSVGString(svgText), children: t('vectorEditor.import', 'Import') })] })] }), jsxRuntime.jsxs(ui.NiceModal, { open: snippetOpen, onClose: () => setSnippetOpen(false), title: t('vectorEditor.snippet.title', 'Icon snippet copied to clipboard'), children: [jsxRuntime.jsxs("p", { style: { marginTop: 0 }, children: [t('vectorEditor.snippet.pasteHint', 'Paste into'), ' ', jsxRuntime.jsxs("code", { children: ["packages/icons/src/icons/", iconCategory ? `${iconCategory.toLowerCase()}` : '<category>', ".tsx"] }), ":"] }), jsxRuntime.jsx(ui.NiceTextArea, { value: snippetText, onChange: (val) => setSnippetText(val), rows: 16 }), jsxRuntime.jsxs("div", { className: VectorEditor_module.dialogActions, children: [jsxRuntime.jsx(ui.NiceButton, { variant: "primary", onClick: () => {
|
|
115
116
|
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
116
117
|
navigator.clipboard.writeText(snippetText).catch(() => { });
|
|
117
118
|
}
|
|
118
119
|
}, children: t('vectorEditor.snippet.copyAgain', 'Copy again') }), jsxRuntime.jsx(ui.NiceButton, { variant: "ghost", onClick: () => setSnippetOpen(false), children: t('vectorEditor.snippet.close', 'Close') })] })] })] }));
|
|
119
120
|
};
|
|
120
|
-
/** @deprecated Use NiceVectorEditor */
|
|
121
|
-
const VectorEditor = NiceVectorEditor;
|
|
122
121
|
|
|
123
122
|
exports.NiceVectorEditor = NiceVectorEditor;
|
|
124
|
-
exports.VectorEditor = VectorEditor;
|
|
@@ -8,7 +8,9 @@ const ShapeRenderer = ({ shape: s, selected, zoom }) => {
|
|
|
8
8
|
if (s.hidden) {
|
|
9
9
|
return null;
|
|
10
10
|
}
|
|
11
|
-
const selStroke = selected
|
|
11
|
+
const selStroke = selected
|
|
12
|
+
? 'color-mix(in srgb, var(--nice-color-primary, #5566cc) 60%, transparent)'
|
|
13
|
+
: undefined;
|
|
12
14
|
const gProps = {};
|
|
13
15
|
if (s.opacity < 1) {
|
|
14
16
|
gProps.opacity = s.opacity;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var React = require('react');
|
|
4
|
+
var editorAsk = require('../core/editorAsk.js');
|
|
4
5
|
var vectorBooleanOps = require('./vectorBooleanOps.js');
|
|
5
6
|
var vectorEditorExport = require('./vectorEditorExport.js');
|
|
6
7
|
var vectorEditorImport = require('./vectorEditorImport.js');
|
|
@@ -12,10 +13,13 @@ var vectorEditorTypes = require('./vectorEditorTypes.js');
|
|
|
12
13
|
* so the component file is pure layout.
|
|
13
14
|
*/
|
|
14
15
|
function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
16
|
+
// the library's dialogs for the text tool and the library name (not window.prompt)
|
|
17
|
+
const { pendingAsk, ask } = editorAsk.useEditorAsk();
|
|
15
18
|
/* ── state ── */
|
|
16
19
|
const [shapes, setShapes] = React.useState([]);
|
|
17
20
|
const [selId, setSelId] = React.useState(null);
|
|
18
21
|
const [tool, setTool] = React.useState('pointer');
|
|
22
|
+
// audit-allow: literal-color — the first fill of a new shape - document data
|
|
19
23
|
const [fillCol, setFillCol] = React.useState('#5566cc');
|
|
20
24
|
const [strokeCol, setStrokeCol] = React.useState('#000000');
|
|
21
25
|
const [strokeW, setStrokeW] = React.useState(2);
|
|
@@ -178,28 +182,30 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
|
178
182
|
return;
|
|
179
183
|
}
|
|
180
184
|
if (tool === 'text') {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
185
|
+
// the text comes from a dialog: the shape is added once it is entered
|
|
186
|
+
void ask('prompt', 'Enter text:', 'Hello').then((txt) => {
|
|
187
|
+
if (!txt) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const ns = {
|
|
191
|
+
id: vectorEditorTypes.sid(),
|
|
192
|
+
type: 'text',
|
|
193
|
+
x: p.x,
|
|
194
|
+
y: p.y,
|
|
195
|
+
w: txt.length * fontSize * 0.6,
|
|
196
|
+
h: fontSize,
|
|
197
|
+
...defaultBase,
|
|
198
|
+
stroke: 'none',
|
|
199
|
+
strokeWidth: 0,
|
|
200
|
+
text: txt,
|
|
201
|
+
fontSize,
|
|
202
|
+
fontFamily,
|
|
203
|
+
};
|
|
204
|
+
const next = [...shapes, ns];
|
|
205
|
+
snap(next);
|
|
206
|
+
setShapes(next);
|
|
207
|
+
setSelId(ns.id);
|
|
208
|
+
});
|
|
203
209
|
return;
|
|
204
210
|
}
|
|
205
211
|
if (tool === 'polygon') {
|
|
@@ -285,6 +291,7 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
|
285
291
|
starPoints2,
|
|
286
292
|
starInnerRatio,
|
|
287
293
|
cornerRadius,
|
|
294
|
+
ask,
|
|
288
295
|
]);
|
|
289
296
|
const onMove = React.useCallback((e) => {
|
|
290
297
|
if (panning.current) {
|
|
@@ -699,15 +706,15 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
|
699
706
|
const blob = new Blob([svg], { type: 'image/svg+xml' });
|
|
700
707
|
const reader = new FileReader();
|
|
701
708
|
reader.onload = () => {
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
709
|
+
const dataUrl = reader.result;
|
|
710
|
+
void ask('prompt', 'Save to library as:', 'drawing.svg').then((name) => {
|
|
711
|
+
if (name && name.trim()) {
|
|
712
|
+
onSaveToLibrary(dataUrl, name.trim(), 'image/svg+xml');
|
|
713
|
+
}
|
|
714
|
+
});
|
|
708
715
|
};
|
|
709
716
|
reader.readAsDataURL(blob);
|
|
710
|
-
}, [shapes, artW, artH, onSaveToLibrary]);
|
|
717
|
+
}, [shapes, artW, artH, onSaveToLibrary, ask]);
|
|
711
718
|
/* ── export shortcuts ── */
|
|
712
719
|
const exportSVG = React.useCallback(() => vectorEditorExport.exportSVGFile(shapes, artW, artH, animations), [shapes, artW, artH, animations]);
|
|
713
720
|
const exportPNG = React.useCallback(() => vectorEditorExport.exportPNGFile(shapes, artW, artH), [shapes, artW, artH]);
|
|
@@ -793,6 +800,8 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
|
793
800
|
/* ── return ── */
|
|
794
801
|
return {
|
|
795
802
|
// state
|
|
803
|
+
/** The question the editor waits on; render it with `EditorAskDialog`. */
|
|
804
|
+
pendingAsk,
|
|
796
805
|
shapes,
|
|
797
806
|
selId,
|
|
798
807
|
tool,
|
|
@@ -18,6 +18,7 @@ function parseSVGToShapes(raw) {
|
|
|
18
18
|
const elems = doc.querySelectorAll('rect, ellipse, circle, line, polyline, polygon, text, path');
|
|
19
19
|
elems.forEach((el) => {
|
|
20
20
|
const tag = el.tagName.toLowerCase();
|
|
21
|
+
// audit-allow: literal-color — an imported shape without a fill gets a visible default - document data
|
|
21
22
|
const fill = el.getAttribute('fill') || (tag === 'line' ? 'none' : '#cccccc');
|
|
22
23
|
const stroke = el.getAttribute('stroke') || 'none';
|
|
23
24
|
const sw = parseFloat(el.getAttribute('stroke-width') || '1');
|
|
@@ -39,6 +39,7 @@ function iconComponentToShapes(Icon, options) {
|
|
|
39
39
|
// Force a generous size and visible colors so the parser sees concrete strokes/fills.
|
|
40
40
|
const renderProps = {
|
|
41
41
|
size: 'lg',
|
|
42
|
+
// audit-allow: literal-color +2 — an imported icon's default colours - document data
|
|
42
43
|
primaryColor: '#5566cc',
|
|
43
44
|
secondaryColor: '#a29bfe',
|
|
44
45
|
strokeWidth: 2,
|
|
@@ -109,7 +109,5 @@ function NiceAnimationEditor({ initialConfig, onChange, storageKey = STORAGE_KEY
|
|
|
109
109
|
};
|
|
110
110
|
return (jsxs("div", { className: `ntd-animation-editor ${className !== null && className !== void 0 ? className : ''}`, style: style, children: [jsxs("div", { className: "ntd-ae-controls", children: [jsx(Row, { title: t('characterEditor.name', 'Name'), children: jsx(NiceTextInput, { className: "ntd-ae-input", value: cfg.name || '', onChange: (val) => setCfg({ ...cfg, name: val }), placeholder: t('characterEditor.namePlaceholder', 'Character name') }) }), jsxs(Row, { title: t('characters.size', 'Size'), children: [jsx(NiceSlider, { min: 140, max: 300, value: (_a = cfg.size) !== null && _a !== void 0 ? _a : 180, onChange: (val) => setCfg({ ...cfg, size: val }) }), jsxs("span", { className: "ntd-ae-size-value", children: [(_b = cfg.size) !== null && _b !== void 0 ? _b : 180, "px"] })] }), jsx(FeatureEditor, { title: t('characters.faceShape', 'Face'), feature: cfg.face, variants: VARIANTS.face, onChange: (v) => updateFeature('face', v) }), jsx(FeatureEditor, { title: t('characters.hairstyle', 'Hair'), feature: cfg.hair, variants: VARIANTS.hair, onChange: (v) => updateFeature('hair', v) }), jsx(FeatureEditor, { title: t('characters.eyes', 'Eyes'), feature: cfg.eyes, variants: VARIANTS.eyes, onChange: (v) => updateFeature('eyes', v) }), jsx(FeatureEditor, { title: t('characters.nose', 'Nose'), feature: cfg.nose, variants: VARIANTS.nose, onChange: (v) => updateFeature('nose', v) }), jsx(FeatureEditor, { title: t('characters.mouth', 'Mouth'), feature: cfg.mouth, variants: VARIANTS.mouth, onChange: (v) => updateFeature('mouth', v) }), jsx(FeatureEditor, { title: t('characters.clothing', 'Outfit'), feature: cfg.outfit, variants: VARIANTS.outfit, onChange: (v) => updateFeature('outfit', v) }), jsx(FeatureEditor, { title: t('characters.headwear', 'Headwear'), feature: cfg.headwear, variants: VARIANTS.headwear, onChange: (v) => updateFeature('headwear', v) }), jsx(FeatureEditor, { title: t('characters.prop', 'Prop'), feature: cfg.prop, variants: VARIANTS.prop, onChange: (v) => updateFeature('prop', v) }), jsxs("div", { className: "ntd-ae-action-row", children: [jsxs(NiceButton, { className: "ntd-ae-btn", variant: "ghost", onClick: randomizeColors, children: [jsx(NiceIcon, { name: "shuffle", size: 14 }), ' ', t('characterEditor.randomizeColors', 'Randomize colors')] }), jsxs(NiceButton, { className: "ntd-ae-btn", variant: "ghost", onClick: downloadJson, children: [jsx(NiceIcon, { name: "download", size: 14 }), " ", t('characters.downloadJson', 'Download JSON')] })] }), showJsonPanel && (jsxs("div", { className: "ntd-ae-json-panel", children: [jsx("div", { className: "ntd-ae-json-label", children: t('characterEditor.jsonSection', 'JSON') }), jsx(NiceTextArea, { className: "ntd-ae-textarea", value: json, onChange: (val) => setJson(val) }), jsxs("div", { className: "ntd-ae-json-actions", children: [jsx(NiceButton, { className: "ntd-ae-btn", variant: "primary", onClick: applyJson, children: t('characterEditor.applyJson', 'Apply JSON') }), jsx(NiceButton, { className: "ntd-ae-btn", variant: "ghost", onClick: () => navigator.clipboard.writeText(JSON.stringify(cfg, null, 2)), children: t('characterEditor.copyToClipboard', 'Copy') })] })] }))] }), showPreview && (jsxs("div", { className: "ntd-ae-preview", children: [jsx("div", { className: "ntd-ae-preview-label", children: t('characterEditor.preview', 'Preview') }), jsx(NiceAnimatedPerson, { character: cfg, score: 10, startPose: "idle" })] }))] }));
|
|
111
111
|
}
|
|
112
|
-
/** @deprecated Use NiceAnimationEditor */
|
|
113
|
-
const AnimationEditor = NiceAnimationEditor;
|
|
114
112
|
|
|
115
|
-
export {
|
|
113
|
+
export { NiceAnimationEditor };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { NiceButton } from '@nice2dev/ui';
|
|
3
3
|
import React from 'react';
|
|
4
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
4
5
|
import NiceAnimatedPerson from './AnimatedPerson.js';
|
|
5
6
|
import { DEFAULT_CHARACTER } from './characterTypes.js';
|
|
6
7
|
import { runRounds, seq } from './choreoDSL.js';
|
|
@@ -33,6 +34,7 @@ function makeAudience(n) {
|
|
|
33
34
|
}
|
|
34
35
|
/** Grid of animated audience members with wave/cheer actions. */
|
|
35
36
|
const NiceAudience = ({ count = 36, columns = 12 }) => {
|
|
37
|
+
const { t } = useNiceTranslation();
|
|
36
38
|
const people = React.useMemo(() => makeAudience(count), [count]);
|
|
37
39
|
const refs = React.useRef([]);
|
|
38
40
|
refs.current = [];
|
|
@@ -57,7 +59,7 @@ const NiceAudience = ({ count = 36, columns = 12 }) => {
|
|
|
57
59
|
display: 'grid',
|
|
58
60
|
gap: 8,
|
|
59
61
|
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
|
|
60
|
-
}, children: people.map((c, i) => (jsx(NiceAnimatedPerson, { character: c, score: 10, startPose: "idle", onReady: (fx) => (refs.current[i] = fx) }, i))) }), jsxs("div", { style: { display: 'flex', gap: 8 }, children: [jsx(NiceButton, { variant: "ghost", size: "sm", onClick: wave, children:
|
|
62
|
+
}, children: people.map((c, i) => (jsx(NiceAnimatedPerson, { character: c, score: 10, startPose: "idle", onReady: (fx) => (refs.current[i] = fx) }, i))) }), jsxs("div", { style: { display: 'flex', gap: 8 }, children: [jsx(NiceButton, { variant: "ghost", size: "sm", onClick: wave, children: t('audience.wave', 'Wave') }), jsx(NiceButton, { variant: "ghost", size: "sm", onClick: cheer, children: t('audience.cheer', 'Cheer') })] })] }));
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
export { NiceAudience as default };
|
|
@@ -3,7 +3,7 @@ import { pick, DEFAULT_COLORS } from './characterTypes.js';
|
|
|
3
3
|
import { Arm } from './shapes/arms.js';
|
|
4
4
|
import { Eyes } from './shapes/eyes.js';
|
|
5
5
|
import { Hair } from './shapes/hair.js';
|
|
6
|
-
import {
|
|
6
|
+
import { NiceHead } from './shapes/heads.js';
|
|
7
7
|
import { Headwear } from './shapes/headwear.js';
|
|
8
8
|
import { Leg } from './shapes/legs.js';
|
|
9
9
|
import { Mouth } from './shapes/mouths.js';
|
|
@@ -21,9 +21,7 @@ const NiceBodyRenderer = ({ character, size }) => {
|
|
|
21
21
|
const outfitBase = pick(character.outfit.colors, 0, DEFAULT_COLORS.shirt);
|
|
22
22
|
const armColors = [skin, outfitBase, stroke];
|
|
23
23
|
const legColors = [outfitBase, pick(character.outfit.colors, 1, outfitBase), stroke];
|
|
24
|
-
return (jsx("svg", { width: S, height: S, viewBox: `0 0 ${S} ${S}`, role: "img", children: jsxs("g", { transform: `translate(${S / 2} ${S * 0.86})`, children: [jsx(Leg, { colors: legColors, side: -1, variant: "pants", scale: k, transform: "translate(-16 0)" }), jsx(Leg, { colors: legColors, side: 1, variant: "pants", scale: k, transform: "translate(16 0)" }), jsx(Torso, { variant: "vShape", colors: [skin, 'var(--anim-transparent, #00000000)', stroke], scale: k }), jsx(Outfit, { variant: character.outfit.variant, colors: character.outfit.colors, scale: k }), jsx(Arm, { colors: armColors, side: -1, variant: "sleeveShort", scale: k, transform: "translate(-40 -8)" }), jsx(Arm, { colors: armColors, side: 1, variant: "sleeveShort", scale: k, transform: "translate(40 -8)" }), jsxs("g", { transform: "translate(0 -84)", children: [jsx(
|
|
24
|
+
return (jsx("svg", { width: S, height: S, viewBox: `0 0 ${S} ${S}`, role: "img", children: jsxs("g", { transform: `translate(${S / 2} ${S * 0.86})`, children: [jsx(Leg, { colors: legColors, side: -1, variant: "pants", scale: k, transform: "translate(-16 0)" }), jsx(Leg, { colors: legColors, side: 1, variant: "pants", scale: k, transform: "translate(16 0)" }), jsx(Torso, { variant: "vShape", colors: [skin, 'var(--anim-transparent, #00000000)', stroke], scale: k }), jsx(Outfit, { variant: character.outfit.variant, colors: character.outfit.colors, scale: k }), jsx(Arm, { colors: armColors, side: -1, variant: "sleeveShort", scale: k, transform: "translate(-40 -8)" }), jsx(Arm, { colors: armColors, side: 1, variant: "sleeveShort", scale: k, transform: "translate(40 -8)" }), jsxs("g", { transform: "translate(0 -84)", children: [jsx(NiceHead, { variant: character.face.variant, colors: character.face.colors, scale: k }), jsx(Hair, { variant: character.hair.variant, colors: character.hair.colors, scale: k }), jsx(Eyes, { variant: character.eyes.variant, colors: character.eyes.colors, scale: k }), jsx(Nose, { variant: character.nose.variant, colors: character.nose.colors, scale: k }), jsx(Mouth, { variant: character.mouth.variant, colors: character.mouth.colors, scale: k }), jsx(Headwear, { variant: character.headwear.variant, colors: character.headwear.colors, scale: k })] })] }) }));
|
|
25
25
|
};
|
|
26
|
-
/** @deprecated Use NiceBodyRenderer */
|
|
27
|
-
const BodyRenderer = NiceBodyRenderer;
|
|
28
26
|
|
|
29
|
-
export {
|
|
27
|
+
export { NiceBodyRenderer, NiceBodyRenderer as default };
|
|
@@ -7,7 +7,7 @@ import NiceRiggedBody from './RiggedBody.js';
|
|
|
7
7
|
import { solveFK } from './rigMath.js';
|
|
8
8
|
import { DEFAULT_POSE, DEFAULT_RIG } from './rigTypes.js';
|
|
9
9
|
|
|
10
|
-
const NiceRigPlayer = React.forwardRef(({ character, rig, pose, size, debug }, ref) => {
|
|
10
|
+
const NiceRigPlayer = /* @__PURE__ */ React.forwardRef(({ character, rig, pose, size, debug }, ref) => {
|
|
11
11
|
var _a;
|
|
12
12
|
const [current, setCurrent] = React.useState({ ...DEFAULT_POSE, ...(pose !== null && pose !== void 0 ? pose : {}) });
|
|
13
13
|
const r = rig !== null && rig !== void 0 ? rig : DEFAULT_RIG;
|
|
@@ -1,65 +1,77 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DEFAULT_POSE, DEFAULT_RIG } from './rigTypes.js';
|
|
2
2
|
|
|
3
3
|
/** Slim proportions preset. */
|
|
4
|
-
const RIG_SLIM = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
4
|
+
const RIG_SLIM = /* @__PURE__ */ (function () {
|
|
5
|
+
return {
|
|
6
|
+
...DEFAULT_RIG,
|
|
7
|
+
lUpperArm: { ...DEFAULT_RIG.lUpperArm, length: 0.11 },
|
|
8
|
+
lForearm: { ...DEFAULT_RIG.lForearm, length: 0.11 },
|
|
9
|
+
rUpperArm: { ...DEFAULT_RIG.rUpperArm, length: 0.11 },
|
|
10
|
+
rForearm: { ...DEFAULT_RIG.rForearm, length: 0.11 },
|
|
11
|
+
lThigh: { ...DEFAULT_RIG.lThigh, length: 0.14 },
|
|
12
|
+
lShin: { ...DEFAULT_RIG.lShin, length: 0.14 },
|
|
13
|
+
rThigh: { ...DEFAULT_RIG.rThigh, length: 0.14 },
|
|
14
|
+
rShin: { ...DEFAULT_RIG.rShin, length: 0.14 },
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
15
17
|
/** Broad shoulders preset. */
|
|
16
|
-
const RIG_HERO = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
18
|
+
const RIG_HERO = /* @__PURE__ */ (function () {
|
|
19
|
+
return {
|
|
20
|
+
...DEFAULT_RIG,
|
|
21
|
+
lShoulder: { ...DEFAULT_RIG.lShoulder, offset: [-0.13, 0] },
|
|
22
|
+
rShoulder: { ...DEFAULT_RIG.rShoulder, offset: [0.13, 0] },
|
|
23
|
+
lUpperArm: { ...DEFAULT_RIG.lUpperArm, length: 0.13 },
|
|
24
|
+
rUpperArm: { ...DEFAULT_RIG.rUpperArm, length: 0.13 },
|
|
25
|
+
};
|
|
26
|
+
})();
|
|
23
27
|
/** Robot-like short limbs preset. */
|
|
24
|
-
const RIG_ROBOT = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
28
|
+
const RIG_ROBOT = /* @__PURE__ */ (function () {
|
|
29
|
+
return {
|
|
30
|
+
...DEFAULT_RIG,
|
|
31
|
+
lUpperArm: { ...DEFAULT_RIG.lUpperArm, length: 0.1 },
|
|
32
|
+
lForearm: { ...DEFAULT_RIG.lForearm, length: 0.1 },
|
|
33
|
+
rUpperArm: { ...DEFAULT_RIG.rUpperArm, length: 0.1 },
|
|
34
|
+
rForearm: { ...DEFAULT_RIG.rForearm, length: 0.1 },
|
|
35
|
+
lThigh: { ...DEFAULT_RIG.lThigh, length: 0.13 },
|
|
36
|
+
lShin: { ...DEFAULT_RIG.lShin, length: 0.13 },
|
|
37
|
+
rThigh: { ...DEFAULT_RIG.rThigh, length: 0.13 },
|
|
38
|
+
rShin: { ...DEFAULT_RIG.rShin, length: 0.13 },
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
35
41
|
/** Happy pose. */
|
|
36
|
-
const POSE_HAPPY = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
const POSE_HAPPY = /* @__PURE__ */ (function () {
|
|
43
|
+
return {
|
|
44
|
+
...DEFAULT_POSE,
|
|
45
|
+
spineUpper: 80,
|
|
46
|
+
neck: 70,
|
|
47
|
+
head: 60,
|
|
48
|
+
lUpperArm: 60,
|
|
49
|
+
lForearm: 20,
|
|
50
|
+
rUpperArm: 120,
|
|
51
|
+
rForearm: 160,
|
|
52
|
+
};
|
|
53
|
+
})();
|
|
46
54
|
/** Angry pose. */
|
|
47
|
-
const POSE_ANGRY = {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
const POSE_ANGRY = /* @__PURE__ */ (function () {
|
|
56
|
+
return {
|
|
57
|
+
...DEFAULT_POSE,
|
|
58
|
+
head: 90,
|
|
59
|
+
lUpperArm: 150,
|
|
60
|
+
rUpperArm: 30,
|
|
61
|
+
lForearm: 160,
|
|
62
|
+
rForearm: 20,
|
|
63
|
+
};
|
|
64
|
+
})();
|
|
55
65
|
/** Dance pose A. */
|
|
56
|
-
const POSE_DANCE_A = {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
const POSE_DANCE_A = /* @__PURE__ */ (function () {
|
|
67
|
+
return {
|
|
68
|
+
...DEFAULT_POSE,
|
|
69
|
+
pelvis: 100,
|
|
70
|
+
lThigh: 120,
|
|
71
|
+
rThigh: 70,
|
|
72
|
+
lUpperArm: 30,
|
|
73
|
+
rUpperArm: 150,
|
|
74
|
+
};
|
|
75
|
+
})();
|
|
64
76
|
|
|
65
77
|
export { POSE_ANGRY, POSE_DANCE_A, POSE_HAPPY, RIG_HERO, RIG_ROBOT, RIG_SLIM };
|
|
@@ -65,7 +65,5 @@ const NiceHead = ({ variant, ...rest }) => {
|
|
|
65
65
|
const Comp = (_a = HEAD_RENDERERS[variant]) !== null && _a !== void 0 ? _a : HeadRound;
|
|
66
66
|
return jsx(Comp, { ...rest });
|
|
67
67
|
};
|
|
68
|
-
/** @deprecated Use NiceHead */
|
|
69
|
-
const Head = NiceHead;
|
|
70
68
|
|
|
71
|
-
export { HEAD_RENDERERS,
|
|
69
|
+
export { HEAD_RENDERERS, HeadChubby, HeadDiamond, HeadHeart, HeadLong, HeadOval, HeadRound, HeadSquare, NiceHead };
|
|
@@ -8,13 +8,16 @@ const HeadwearCap = ({ colors, scale = 1, transform }) => {
|
|
|
8
8
|
};
|
|
9
9
|
const HeadwearCrown = ({ colors, scale = 1, transform }) => {
|
|
10
10
|
const base = pick(colors, 0, 'var(--nice-warning, #FFD700)');
|
|
11
|
-
return (jsx("g", { transform: `${transform !== null && transform !== void 0 ? transform : ''} scale(${scale})`, children: jsx("path", { d: "M-22 -30 l6 -16 l8 10 l8 -14 l8 14 l8 -10 l6 16 z", fill: base,
|
|
11
|
+
return (jsx("g", { transform: `${transform !== null && transform !== void 0 ? transform : ''} scale(${scale})`, children: jsx("path", { d: "M-22 -30 l6 -16 l8 10 l8 -14 l8 14 l8 -10 l6 16 z", fill: base,
|
|
12
|
+
// audit-allow: literal-color — a character-generator shape: its own drawing colour
|
|
13
|
+
stroke: "#B8860B", strokeWidth: 1.2 }) }));
|
|
12
14
|
};
|
|
13
15
|
const HeadwearHeadphones = ({ colors, scale = 1, transform }) => {
|
|
14
16
|
const base = pick(colors, 0, 'var(--nice-text, #333)');
|
|
15
17
|
return (jsxs("g", { transform: `${transform !== null && transform !== void 0 ? transform : ''} scale(${scale})`, stroke: base, strokeWidth: 2.5, fill: "none", children: [jsx("path", { d: "M-30 0 q0 -40 30 -42 q30 2 30 42" }), jsx("rect", { x: -36, y: -6, width: 10, height: 16, rx: 3, fill: base }), jsx("rect", { x: 26, y: -6, width: 10, height: 16, rx: 3, fill: base })] }));
|
|
16
18
|
};
|
|
17
19
|
const HeadwearBeanie = ({ colors, scale = 1, transform }) => {
|
|
20
|
+
// audit-allow: literal-color — the hat's default colour when the character sets none
|
|
18
21
|
const base = pick(colors, 0, '#E53E3E');
|
|
19
22
|
return (jsxs("g", { transform: `${transform !== null && transform !== void 0 ? transform : ''} scale(${scale})`, children: [jsx("path", { d: "M-28 -14 q0 -22 28 -26 q28 4 28 26 z", fill: base }), jsx("circle", { cx: 0, cy: -42, r: 6, fill: base })] }));
|
|
20
23
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { NiceIcon } from '@nice2dev/ui-core';
|
|
3
3
|
import { useEffect, useState, useRef, useCallback } from 'react';
|
|
4
|
+
import { useNiceTranslation } from './i18n.js';
|
|
4
5
|
|
|
5
6
|
/* ── Brush Presets ── */
|
|
6
7
|
const DEFAULT_DYNAMICS = {
|
|
@@ -541,6 +542,7 @@ color = '#000000') {
|
|
|
541
542
|
}
|
|
542
543
|
/* ── Component ── */
|
|
543
544
|
const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '', }) => {
|
|
545
|
+
const { t } = useNiceTranslation();
|
|
544
546
|
const engine = useBrushEngine(canvas, color);
|
|
545
547
|
// Override endStroke to fire callback
|
|
546
548
|
useEffect(() => {
|
|
@@ -557,7 +559,7 @@ const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '
|
|
|
557
559
|
return () => canvas.removeEventListener('pointerup', onUp);
|
|
558
560
|
}, [canvas, engine.endStroke, onStrokeEnd]);
|
|
559
561
|
const s = engine.settings;
|
|
560
|
-
return (jsxs("div", { className: `nice-brush-engine ${className}`, style: { display: 'grid', gap: 6, fontSize: 13 }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "brush", size: 14 }), " Brush Engine v2
|
|
562
|
+
return (jsxs("div", { className: `nice-brush-engine ${className}`, style: { display: 'grid', gap: 6, fontSize: 13 }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "brush", size: 14 }), " ", t('brushEngine.brushEngineV2', 'Brush Engine v2')] }), jsx("div", { style: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: engine.presetNames.map((name) => {
|
|
561
563
|
var _a, _b;
|
|
562
564
|
return (jsx("button", { type: "button", onClick: () => engine.applyPreset(name), style: {
|
|
563
565
|
padding: '2px 6px',
|
|
@@ -568,17 +570,15 @@ const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '
|
|
|
568
570
|
background: s.name === ((_a = BRUSH_PRESETS[name]) === null || _a === void 0 ? void 0 : _a.name)
|
|
569
571
|
? 'var(--nice-color-accent, #6366f1)'
|
|
570
572
|
: 'transparent',
|
|
571
|
-
color: s.name === ((_b = BRUSH_PRESETS[name]) === null || _b === void 0 ? void 0 : _b.name)
|
|
572
|
-
? 'var(--nice-text-on-primary, #fff)'
|
|
573
|
-
: 'inherit',
|
|
573
|
+
color: s.name === ((_b = BRUSH_PRESETS[name]) === null || _b === void 0 ? void 0 : _b.name) ? 'var(--nice-color-white, #fff)' : 'inherit',
|
|
574
574
|
}, children: BRUSH_PRESETS[name].name }, name));
|
|
575
|
-
}) }), jsxs("label", { children: ["Size: ", s.size, "px", jsx("input", { type: "range", min: 1, max: 200, value: s.size, onChange: (e) => engine.setSettings({ ...s, size: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Opacity: ", Math.round(s.opacity * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.opacity, onChange: (e) => engine.setSettings({ ...s, opacity: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Flow: ", Math.round(s.flow * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.flow, onChange: (e) => engine.setSettings({ ...s, flow: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Hardness: ", Math.round(s.hardness * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.hardness, onChange: (e) => engine.setSettings({ ...s, hardness: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Smoothing: ", Math.round(s.smoothing * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.smoothing, onChange: (e) => engine.setSettings({ ...s, smoothing: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Tip:", jsxs("select", { value: s.tipShape, onChange: (e) => engine.setSettings({ ...s, tipShape: e.target.value }), style: { width: '100%' }, children: [jsx("option", { value: "circle", children:
|
|
575
|
+
}) }), jsxs("label", { children: ["Size: ", s.size, "px", jsx("input", { type: "range", min: 1, max: 200, value: s.size, onChange: (e) => engine.setSettings({ ...s, size: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Opacity: ", Math.round(s.opacity * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.opacity, onChange: (e) => engine.setSettings({ ...s, opacity: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Flow: ", Math.round(s.flow * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.flow, onChange: (e) => engine.setSettings({ ...s, flow: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Hardness: ", Math.round(s.hardness * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.hardness, onChange: (e) => engine.setSettings({ ...s, hardness: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Smoothing: ", Math.round(s.smoothing * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.smoothing, onChange: (e) => engine.setSettings({ ...s, smoothing: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Tip:", jsxs("select", { value: s.tipShape, onChange: (e) => engine.setSettings({ ...s, tipShape: e.target.value }), style: { width: '100%' }, children: [jsx("option", { value: "circle", children: t('brushEngine.circle', 'Circle') }), jsx("option", { value: "square", children: t('brushEngine.square', 'Square') }), jsx("option", { value: "diamond", children: t('brushEngine.diamond', 'Diamond') }), jsx("option", { value: "texture", children: t('brushEngine.texture', 'Texture') })] })] }), jsxs("label", { children: ["Roundness: ", Math.round(s.roundness * 100), "%", jsx("input", { type: "range", min: 0.05, max: 1, step: 0.05, value: s.roundness, onChange: (e) => engine.setSettings({ ...s, roundness: +e.target.value }), style: { width: '100%' } })] }), jsxs("details", { children: [jsxs("summary", { style: { cursor: 'pointer', fontSize: 12 }, children: [jsx(NiceIcon, { name: "bar-chart", size: 12 }), " ", t('brushEngine.sizeDynamics', 'Size Dynamics')] }), jsxs("label", { children: ["Source:", jsxs("select", { value: s.sizeDynamics.source, onChange: (e) => engine.setSettings({
|
|
576
576
|
...s,
|
|
577
577
|
sizeDynamics: {
|
|
578
578
|
...s.sizeDynamics,
|
|
579
579
|
source: e.target.value,
|
|
580
580
|
},
|
|
581
|
-
}), style: { width: '100%' }, children: [jsx("option", { value: "none", children: "Off" }), jsx("option", { value: "pressure", children:
|
|
581
|
+
}), style: { width: '100%' }, children: [jsx("option", { value: "none", children: "Off" }), jsx("option", { value: "pressure", children: t('brushEngine.pressure', 'Pressure') }), jsx("option", { value: "tilt", children: t('brushEngine.tilt', 'Tilt') }), jsx("option", { value: "speed", children: t('brushEngine.speed', 'Speed') }), jsx("option", { value: "random", children: t('brushEngine.random', 'Random') })] })] })] }), jsxs("label", { children: ["Scatter: ", Math.round(s.scatter * 100), "%", jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.scatter, onChange: (e) => engine.setSettings({ ...s, scatter: +e.target.value }), style: { width: '100%' } })] }), jsxs("label", { children: ["Blend:", jsx("select", { value: s.blendMode, onChange: (e) => engine.setSettings({ ...s, blendMode: e.target.value }), style: { width: '100%' }, children: ['normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten'].map((m) => (jsx("option", { value: m, children: m }, m))) })] }), jsxs("label", { style: { display: 'flex', gap: 4, alignItems: 'center' }, children: [jsx("input", { type: "checkbox", checked: s.wetEdges, onChange: (e) => engine.setSettings({ ...s, wetEdges: e.target.checked }) }), t('brushEngine.wetEdges', 'Wet Edges')] })] }));
|
|
582
582
|
};
|
|
583
583
|
|
|
584
584
|
export { BRUSH_PRESETS, NiceBrushEngine, useBrushEngine };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { NiceConfirmDialog, NicePromptDialog } from '@nice2dev/ui';
|
|
3
|
+
import { useState, useRef, useCallback } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The library's dialogs in place of `window.prompt` / `window.confirm` for the editor hooks,
|
|
7
|
+
* which hold state but render nothing: `ask` resolves with the answer (`null` when cancelled)
|
|
8
|
+
* and `pendingAsk` is the open question, for the editor component to pass to
|
|
9
|
+
* {@link EditorAskDialog}. One question at a time: a new one settles the open one as cancelled.
|
|
10
|
+
*/
|
|
11
|
+
function useEditorAsk() {
|
|
12
|
+
const [pendingAsk, setPendingAsk] = useState(null);
|
|
13
|
+
const pendingRef = useRef(null);
|
|
14
|
+
const ask = useCallback((kind, text, defaultValue) => new Promise((resolve) => {
|
|
15
|
+
var _a;
|
|
16
|
+
(_a = pendingRef.current) === null || _a === void 0 ? void 0 : _a.answer(null);
|
|
17
|
+
const next = {
|
|
18
|
+
kind,
|
|
19
|
+
text,
|
|
20
|
+
defaultValue,
|
|
21
|
+
answer: (value) => {
|
|
22
|
+
if (pendingRef.current === next) {
|
|
23
|
+
pendingRef.current = null;
|
|
24
|
+
setPendingAsk(null);
|
|
25
|
+
}
|
|
26
|
+
resolve(value);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
pendingRef.current = next;
|
|
30
|
+
setPendingAsk(next);
|
|
31
|
+
}), []);
|
|
32
|
+
return { pendingAsk, ask };
|
|
33
|
+
}
|
|
34
|
+
/** Shows the question {@link useEditorAsk} is waiting on, if any. */
|
|
35
|
+
const EditorAskDialog = ({ ask }) => {
|
|
36
|
+
if (!ask) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
if (ask.kind === 'confirm') {
|
|
40
|
+
return (jsx(NiceConfirmDialog, { visible: true, message: ask.text, onAccept: () => ask.answer(''), onReject: () => ask.answer(null) }));
|
|
41
|
+
}
|
|
42
|
+
return (jsx(NicePromptDialog, { open: true, title: ask.text, defaultValue: ask.defaultValue, onSubmit: (value) => ask.answer(value), onCancel: () => ask.answer(null) }));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export { EditorAskDialog, useEditorAsk };
|
package/dist/esm/core/i18n.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import {
|
|
2
|
+
import { useNiceTranslation as useNiceTranslation$1 } from '@nice2dev/ui-core';
|
|
3
|
+
import { useContext, useMemo, createContext } from 'react';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
const NiceI18nContext = createContext(
|
|
5
|
+
/** `null` outside this package's provider: the global translator applies. */
|
|
6
|
+
const NiceI18nContext = /* @__PURE__ */ createContext(null);
|
|
7
|
+
/** Overrides the app's global translator for the graphics controls below it. */
|
|
6
8
|
const NiceI18nProvider = ({ t, children }) => (jsx(NiceI18nContext.Provider, { value: t, children: children }));
|
|
7
9
|
function useNiceTranslation() {
|
|
8
|
-
const
|
|
10
|
+
const own = useContext(NiceI18nContext);
|
|
11
|
+
const { t: global } = useNiceTranslation$1();
|
|
12
|
+
const t = useMemo(() => own !== null && own !== void 0 ? own : ((key, defaultValue) => global(key, defaultValue)), [own, global]);
|
|
9
13
|
return { t };
|
|
10
14
|
}
|
|
11
15
|
|
package/dist/esm/core/plugins.js
CHANGED
|
@@ -110,7 +110,7 @@ function usePlugins(editor) {
|
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
112
|
// ── Plugin Context (React) ──────────────────────────────────────
|
|
113
|
-
const PluginContext = React.createContext(PluginRegistry);
|
|
113
|
+
const PluginContext = /* @__PURE__ */ React.createContext(PluginRegistry);
|
|
114
114
|
const NicePluginProvider = ({ plugins, children }) => {
|
|
115
115
|
React.useEffect(() => {
|
|
116
116
|
if (plugins) {
|
package/dist/esm/core/rtl.js
CHANGED
|
@@ -12,7 +12,7 @@ const defaultRTLConfig = {
|
|
|
12
12
|
mirrorToolbar: false,
|
|
13
13
|
mirrorPanels: false,
|
|
14
14
|
};
|
|
15
|
-
const RTLContext = React.createContext(defaultRTLConfig);
|
|
15
|
+
const RTLContext = /* @__PURE__ */ React.createContext(defaultRTLConfig);
|
|
16
16
|
/** Provider for RTL configuration. Wraps editors to enable RTL support. */
|
|
17
17
|
const NiceRTLProvider = ({ direction = 'ltr', mirrorToolbar = true, mirrorPanels = true, children }) => {
|
|
18
18
|
const config = React.useMemo(() => ({
|
package/dist/esm/core/theme.js
CHANGED