@nice2dev/ui-graphics 1.2.0 → 1.2.2
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 +142 -0
- package/dist/cjs/animation/AnimatedPerson.js +3 -1
- package/dist/cjs/animation/Audience.js +3 -1
- package/dist/cjs/animation/rig/RigPlayer.js +1 -1
- package/dist/cjs/animation/rig/presets.js +67 -55
- package/dist/cjs/animation/shapes/headwear.js +4 -1
- package/dist/cjs/core/NiceBrushEngine.js +8 -5
- package/dist/cjs/core/collaboration.js +11 -6
- package/dist/cjs/core/editorAsk.js +48 -0
- package/dist/cjs/core/i18n.js +7 -3
- package/dist/cjs/core/minimap.js +21 -20
- 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 +14 -12
- package/dist/cjs/game/GameAsset2dEditor.js +75 -44
- package/dist/cjs/nice2dev-ui-graphics.css +1 -1
- package/dist/cjs/photo/FilterThumb.js +3 -3
- package/dist/cjs/photo/NiceAITools.js +6 -4
- package/dist/cjs/photo/PhotoEditor.js +3 -1
- package/dist/cjs/photo/PhotoEditorCanvas.js +8 -6
- package/dist/cjs/photo/PhotoEditorLeftPanel.js +29 -29
- package/dist/cjs/photo/PhotoEditorToolbar.js +4 -4
- package/dist/cjs/photo/photoFilters.js +1 -0
- package/dist/cjs/pixel/PixelEditor.js +6 -3
- package/dist/cjs/pixel/PixelEditorCanvas.js +10 -6
- package/dist/cjs/pixel/PixelEditorMenuBar.js +95 -30
- package/dist/cjs/pixel/PixelEditorRightPanel.js +32 -24
- package/dist/cjs/pixel/PixelEditorStatusBar.js +12 -4
- package/dist/cjs/pixel/PixelEditorTimeline.js +15 -2
- package/dist/cjs/pixel/PixelEditorToolbar.js +12 -6
- package/dist/cjs/pixel/SpriteGeneratorPanel.js +28 -17
- 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 +16 -10
- package/dist/cjs/ui/NiceUIDesigner.js +17 -13
- package/dist/cjs/vector/NiceSvgToCode.js +7 -5
- package/dist/cjs/vector/VectorEditor.js +3 -2
- package/dist/cjs/vector/VectorEditorMenuBar.js +7 -1
- package/dist/cjs/vector/VectorEditorRightPanel.js +1 -1
- 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/AnimatedPerson.js +3 -1
- package/dist/esm/animation/Audience.js +3 -1
- package/dist/esm/animation/rig/RigPlayer.js +1 -1
- package/dist/esm/animation/rig/presets.js +68 -56
- package/dist/esm/animation/shapes/headwear.js +4 -1
- package/dist/esm/animation/spineExport.js +1 -1
- package/dist/esm/core/NiceBrushEngine.js +8 -5
- package/dist/esm/core/collaboration.js +11 -6
- package/dist/esm/core/editorAsk.js +45 -0
- package/dist/esm/core/i18n.js +8 -4
- package/dist/esm/core/minimap.js +21 -20
- 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 +14 -12
- package/dist/esm/game/GameAsset2dEditor.js +75 -44
- package/dist/esm/nice2dev-ui-graphics.css +1 -1
- package/dist/esm/photo/FilterThumb.js +3 -3
- package/dist/esm/photo/NiceAITools.js +6 -4
- package/dist/esm/photo/PhotoEditor.js +3 -1
- package/dist/esm/photo/PhotoEditorCanvas.js +8 -6
- package/dist/esm/photo/PhotoEditorLeftPanel.js +29 -29
- package/dist/esm/photo/PhotoEditorToolbar.js +4 -4
- package/dist/esm/photo/photoFilters.js +1 -0
- package/dist/esm/pixel/PixelEditor.js +6 -3
- package/dist/esm/pixel/PixelEditorCanvas.js +10 -6
- package/dist/esm/pixel/PixelEditorMenuBar.js +95 -30
- package/dist/esm/pixel/PixelEditorRightPanel.js +32 -24
- package/dist/esm/pixel/PixelEditorStatusBar.js +12 -4
- package/dist/esm/pixel/PixelEditorTimeline.js +15 -2
- package/dist/esm/pixel/PixelEditorToolbar.js +12 -6
- package/dist/esm/pixel/SpriteGeneratorPanel.js +28 -17
- 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 +16 -10
- package/dist/esm/ui/NiceUIDesigner.js +17 -13
- package/dist/esm/vector/NiceSvgToCode.js +7 -5
- package/dist/esm/vector/VectorEditor.js +3 -2
- package/dist/esm/vector/VectorEditorMenuBar.js +7 -1
- package/dist/esm/vector/VectorEditorRightPanel.js +1 -1
- 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/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/pixel/usePixelEditor.d.ts +3 -0
- package/dist/types/vector/useVectorEditor.d.ts +2 -0
- package/package.json +5 -5
|
@@ -2,6 +2,7 @@ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
|
2
2
|
import { NiceTutorialButton, resolveTutorial, NiceAppShell } from '@nice2dev/ui';
|
|
3
3
|
import { NiceIcon } from '@nice2dev/ui-core';
|
|
4
4
|
import React from 'react';
|
|
5
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
5
6
|
|
|
6
7
|
/** Built-in guided tour (EN inline + i18n keys → PL and other locales). */
|
|
7
8
|
const UI_DESIGNER_TOUR = [
|
|
@@ -310,6 +311,7 @@ function updateInTree(nodes, id, updates) {
|
|
|
310
311
|
/** Visual drag & drop UI builder that generates React code. */
|
|
311
312
|
const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
312
313
|
var _a, _b, _c, _d, _e;
|
|
314
|
+
const { t } = useNiceTranslation();
|
|
313
315
|
const designer = useUIDesigner();
|
|
314
316
|
const [breakpoint, setBreakpoint] = React.useState('desktop');
|
|
315
317
|
const [dragType, setDragType] = React.useState(null);
|
|
@@ -393,7 +395,7 @@ const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
|
393
395
|
color: 'var(--nice-text-secondary, #868e96)',
|
|
394
396
|
fontSize: 11,
|
|
395
397
|
gap: 4,
|
|
396
|
-
}, children: [jsx(NiceIcon, { name: "image", size: 14 }), " Image
|
|
398
|
+
}, children: [jsx(NiceIcon, { name: "image", size: 14 }), " ", t('uiDesigner.image', 'Image')] })) : node.type === 'List' ? (jsx("ul", { style: { margin: 0, ...node.style }, children: ((_d = node.props.items) !== null && _d !== void 0 ? _d : []).map((item, i) => (jsx("li", { children: item }, i))) })) : node.type === 'Divider' ? (jsx("hr", { style: {
|
|
397
399
|
margin: 0,
|
|
398
400
|
border: 'none',
|
|
399
401
|
borderTop: '1px solid var(--nice-border, #dee2e6)',
|
|
@@ -419,7 +421,9 @@ const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
|
419
421
|
display: 'flex',
|
|
420
422
|
alignItems: 'center',
|
|
421
423
|
gap: 4,
|
|
422
|
-
background: designer.selectedId === node.id
|
|
424
|
+
background: designer.selectedId === node.id
|
|
425
|
+
? 'color-mix(in srgb, var(--nice-color-primary, #339af0) 20%, transparent)'
|
|
426
|
+
: 'transparent',
|
|
423
427
|
borderRadius: 2,
|
|
424
428
|
}, children: [(def === null || def === void 0 ? void 0 : def.icon) && jsx(NiceIcon, { name: def.icon, size: 12 }), jsxs("span", { style: { flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: [node.type, node.props.text ? `: ${String(node.props.text).slice(0, 15)}` : ''] })] }), node.children.map((c) => renderTreeItem(c, depth + 1))] }, node.id));
|
|
425
429
|
};
|
|
@@ -433,7 +437,7 @@ const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
|
433
437
|
height: '100%',
|
|
434
438
|
overflowY: 'auto',
|
|
435
439
|
boxSizing: 'border-box',
|
|
436
|
-
}, children: [jsxs("div", { children: [jsx("strong", { style: { fontSize: 12, display: 'block', marginBottom: 4 }, children:
|
|
440
|
+
}, children: [jsxs("div", { children: [jsx("strong", { style: { fontSize: 12, display: 'block', marginBottom: 4 }, children: t('uiDesigner.components', 'Components') }), jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 2 }, children: COMPONENT_DEFS.map((def) => (jsxs("button", { draggable: true, onDragStart: () => setDragType(def.type), onDragEnd: () => setDragType(null), onClick: () => { var _a; return designer.addNode(def.type, (_a = designer.selectedId) !== null && _a !== void 0 ? _a : undefined); }, style: {
|
|
437
441
|
display: 'flex',
|
|
438
442
|
alignItems: 'center',
|
|
439
443
|
gap: 4,
|
|
@@ -445,8 +449,8 @@ const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
|
445
449
|
cursor: 'grab',
|
|
446
450
|
textAlign: 'left',
|
|
447
451
|
fontSize: 11,
|
|
448
|
-
}, children: [jsx(NiceIcon, { name: def.icon, size: 14 }), " ", def.label] }, def.type))) })] }), jsxs("div", { children: [jsx("strong", { style: { fontSize: 12, display: 'block', marginBottom: 4 }, children:
|
|
449
|
-
const toolbarContent = (jsxs("div", { style: { display: 'flex', gap: 4, alignItems: 'center', fontSize: 11, padding: '4px 8px' }, children: [jsx("button", { onClick: () => designer.undo(), disabled: !designer.canUndo, style: { ...btnStyle(false), opacity: designer.canUndo ? 1 : 0.4 }, title:
|
|
452
|
+
}, children: [jsx(NiceIcon, { name: def.icon, size: 14 }), " ", def.label] }, def.type))) })] }), jsxs("div", { children: [jsx("strong", { style: { fontSize: 12, display: 'block', marginBottom: 4 }, children: t('uiDesigner.tree', 'Tree') }), jsx("div", { style: { maxHeight: 200, overflowY: 'auto' }, children: designer.tree.length === 0 ? (jsx("div", { style: { color: 'var(--nice-text-secondary, #5c5f66)', padding: 4 }, children: t('uiDesigner.empty', 'Empty') })) : (designer.tree.map((n) => renderTreeItem(n))) })] })] }));
|
|
453
|
+
const toolbarContent = (jsxs("div", { style: { display: 'flex', gap: 4, alignItems: 'center', fontSize: 11, padding: '4px 8px' }, children: [jsx("button", { onClick: () => designer.undo(), disabled: !designer.canUndo, style: { ...btnStyle(false), opacity: designer.canUndo ? 1 : 0.4 }, title: t('uiDesigner.undoCtrlZ', 'Undo (Ctrl+Z)'), "aria-label": t('uiDesigner.undo', 'Undo'), children: jsx(NiceIcon, { name: "reply", size: 14 }) }), jsx("button", { onClick: () => designer.redo(), disabled: !designer.canRedo, style: { ...btnStyle(false), opacity: designer.canRedo ? 1 : 0.4 }, title: t('uiDesigner.redoCtrlY', 'Redo (Ctrl+Y)'), "aria-label": t('uiDesigner.redo', 'Redo'), children: jsx(NiceIcon, { name: "forward", size: 14 }) }), jsx("div", { style: { width: 1, height: 16, background: 'var(--nice-border, #373a40)' } }), jsxs("button", { onClick: () => setBreakpoint('desktop'), style: btnStyle(breakpoint === 'desktop'), children: [jsx(NiceIcon, { name: "monitor", size: 14 }), " ", t('uiDesigner.desktop', 'Desktop')] }), jsxs("button", { onClick: () => setBreakpoint('tablet'), style: btnStyle(breakpoint === 'tablet'), children: [jsx(NiceIcon, { name: "window", size: 14 }), " ", t('uiDesigner.tablet', 'Tablet')] }), jsxs("button", { onClick: () => setBreakpoint('mobile'), style: btnStyle(breakpoint === 'mobile'), children: [jsx(NiceIcon, { name: "phone", size: 14 }), " ", t('uiDesigner.mobile', 'Mobile')] }), jsx("div", { style: { flex: 1 } }), designer.selectedId && (jsxs("button", { onClick: () => designer.removeNode(designer.selectedId), style: { ...btnStyle(false), color: 'var(--nice-danger, #ff6b6b)' }, children: [jsx(NiceIcon, { name: "trash", size: 14 }), " ", t('uiDesigner.delete', 'Delete')] })), jsxs("button", { onClick: () => setShowCode((v) => !v), style: btnStyle(showCode), children: ['</>', " ", t('uiDesigner.code', 'Code')] }), jsx("button", { onClick: () => onExport === null || onExport === void 0 ? void 0 : onExport(designer.exportCode()), style: {
|
|
450
454
|
padding: '4px 8px',
|
|
451
455
|
background: 'var(--nice-primary, #228be6)',
|
|
452
456
|
color: 'var(--nice-text-on-primary, #fff)',
|
|
@@ -454,7 +458,7 @@ const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
|
454
458
|
borderRadius: 3,
|
|
455
459
|
cursor: 'pointer',
|
|
456
460
|
fontSize: 11,
|
|
457
|
-
}, children:
|
|
461
|
+
}, children: t('uiDesigner.export', 'Export') })] }));
|
|
458
462
|
const mainContent = (jsx("div", { style: {
|
|
459
463
|
flex: 1,
|
|
460
464
|
display: 'flex',
|
|
@@ -470,7 +474,7 @@ const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
|
470
474
|
margin: 0,
|
|
471
475
|
overflow: 'auto',
|
|
472
476
|
fontSize: 11,
|
|
473
|
-
color: '#a6e3a1',
|
|
477
|
+
color: 'var(--nice-color-success, #a6e3a1)',
|
|
474
478
|
fontFamily: 'monospace',
|
|
475
479
|
}, children: designer.exportCode() })) : (jsx("div", { style: {
|
|
476
480
|
flex: 1,
|
|
@@ -487,7 +491,7 @@ const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
|
487
491
|
textAlign: 'center',
|
|
488
492
|
padding: 40,
|
|
489
493
|
color: 'var(--nice-text-secondary, #868e96)',
|
|
490
|
-
}, children:
|
|
494
|
+
}, children: t('uiDesigner.dragOrClickAComponent', 'Drag or click a component to add it') })) : (designer.tree.map((n) => renderNode(n))) })) }));
|
|
491
495
|
const rightPanel = (jsxs("div", { style: {
|
|
492
496
|
width: 200,
|
|
493
497
|
fontSize: 11,
|
|
@@ -498,7 +502,7 @@ const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
|
498
502
|
overflowY: 'auto',
|
|
499
503
|
padding: 8,
|
|
500
504
|
boxSizing: 'border-box',
|
|
501
|
-
}, children: [jsx("strong", { style: { fontSize: 12 }, children:
|
|
505
|
+
}, children: [jsx("strong", { style: { fontSize: 12 }, children: t('uiDesigner.properties', 'Properties') }), selectedNode ? (jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [jsxs("div", { style: {
|
|
502
506
|
color: 'var(--nice-text-secondary, #868e96)',
|
|
503
507
|
display: 'flex',
|
|
504
508
|
alignItems: 'center',
|
|
@@ -507,13 +511,13 @@ const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
|
507
511
|
var _a;
|
|
508
512
|
const di = (_a = COMPONENT_DEFS.find((d) => d.type === selectedNode.type)) === null || _a === void 0 ? void 0 : _a.icon;
|
|
509
513
|
return di ? jsx(NiceIcon, { name: di, size: 14 }) : null;
|
|
510
|
-
})(), ' ', selectedNode.type] }), selectedNode.props.text !== undefined && (jsxs("div", { children: [jsx("label", { children:
|
|
514
|
+
})(), ' ', selectedNode.type] }), selectedNode.props.text !== undefined && (jsxs("div", { children: [jsx("label", { children: t('uiDesigner.text', 'Text') }), jsx("input", { value: String(selectedNode.props.text), onChange: (e) => designer.updateNode(selectedNode.id, { props: { text: e.target.value } }), style: fieldStyle })] })), selectedNode.props.placeholder !== undefined && (jsxs("div", { children: [jsx("label", { children: t('uiDesigner.placeholder', 'Placeholder') }), jsx("input", { value: String(selectedNode.props.placeholder), onChange: (e) => designer.updateNode(selectedNode.id, { props: { placeholder: e.target.value } }), style: fieldStyle })] })), selectedNode.props.level !== undefined && (jsxs("div", { children: [jsx("label", { children: t('uiDesigner.headingLevel', 'Heading Level') }), jsx("select", { value: Number(selectedNode.props.level), onChange: (e) => designer.updateNode(selectedNode.id, {
|
|
511
515
|
props: { level: Number(e.target.value) },
|
|
512
|
-
}), style: fieldStyle, children: [1, 2, 3, 4, 5, 6].map((v) => (jsxs("option", { value: v, children: ["H", v] }, v))) })] })), selectedNode.props.variant !== undefined && (jsxs("div", { children: [jsx("label", { children:
|
|
516
|
+
}), style: fieldStyle, children: [1, 2, 3, 4, 5, 6].map((v) => (jsxs("option", { value: v, children: ["H", v] }, v))) })] })), selectedNode.props.variant !== undefined && (jsxs("div", { children: [jsx("label", { children: t('uiDesigner.variant', 'Variant') }), jsx("select", { value: String(selectedNode.props.variant), onChange: (e) => designer.updateNode(selectedNode.id, { props: { variant: e.target.value } }), style: fieldStyle, children: ['primary', 'secondary', 'outline', 'ghost'].map((v) => (jsx("option", { value: v, children: v }, v))) })] })), selectedNode.props.src !== undefined && (jsxs("div", { children: [jsx("label", { children: t('uiDesigner.imageUrl', 'Image URL') }), jsx("input", { value: String(selectedNode.props.src), onChange: (e) => designer.updateNode(selectedNode.id, { props: { src: e.target.value } }), style: fieldStyle })] })), selectedNode.props.alt !== undefined && (jsxs("div", { children: [jsx("label", { children: t('uiDesigner.altText', 'Alt Text') }), jsx("input", { value: String(selectedNode.props.alt), onChange: (e) => designer.updateNode(selectedNode.id, { props: { alt: e.target.value } }), style: fieldStyle })] })), jsx("hr", { style: {
|
|
513
517
|
border: 'none',
|
|
514
518
|
borderTop: '1px solid var(--nice-border, #373a40)',
|
|
515
519
|
margin: '4px 0',
|
|
516
|
-
} }), jsx("strong", { style: { color: 'var(--nice-text-secondary, #868e96)' }, children:
|
|
520
|
+
} }), jsx("strong", { style: { color: 'var(--nice-text-secondary, #868e96)' }, children: t('uiDesigner.style', 'Style') }), ['padding', 'margin', 'gap', 'borderRadius'].map((prop) => (jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [jsx("label", { style: { minWidth: 60 }, children: prop }), jsx("input", { type: "number", value: typeof selectedNode.style[prop] === 'number'
|
|
517
521
|
? selectedNode.style[prop]
|
|
518
522
|
: 0, onChange: (e) => designer.updateNode(selectedNode.id, {
|
|
519
523
|
style: { [prop]: Number(e.target.value) },
|
|
@@ -543,7 +547,7 @@ const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
|
543
547
|
color: 'var(--nice-text-secondary, #5c5f66)',
|
|
544
548
|
padding: 8,
|
|
545
549
|
textAlign: 'center',
|
|
546
|
-
}, children:
|
|
550
|
+
}, children: t('uiDesigner.selectAnElement', 'Select an element') }))] }));
|
|
547
551
|
return (jsxs("div", { className: `nice-ui-designer ${className}`, style: { position: 'relative', height: '100%' }, children: [jsx("span", { style: { position: 'absolute', top: 6, right: 6, zIndex: 20 }, children: jsx(NiceTutorialButton, { steps: resolveTutorial(tutorial, UI_DESIGNER_TOUR) }) }), jsx(NiceAppShell, { flush: true, toolbar: toolbarContent, sidebar: leftPanel, rightSidebar: rightPanel, children: mainContent })] }));
|
|
548
552
|
};
|
|
549
553
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { NiceIcon } from '@nice2dev/ui-core';
|
|
3
3
|
import React, { useState, useCallback } from 'react';
|
|
4
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
4
5
|
|
|
5
6
|
/* ── Default config ── */
|
|
6
7
|
const DEFAULT_CONFIG = {
|
|
@@ -417,6 +418,7 @@ function useSvgToCode() {
|
|
|
417
418
|
}
|
|
418
419
|
/* ── Component ── */
|
|
419
420
|
const NiceSvgToCode = ({ initialSvg = '', onConvert, className = '', }) => {
|
|
421
|
+
const { t } = useNiceTranslation();
|
|
420
422
|
const { svgInput, setSvgInput, config, updateConfig, convert, result } = useSvgToCode();
|
|
421
423
|
// Initialize with provided SVG
|
|
422
424
|
React.useEffect(() => {
|
|
@@ -435,21 +437,21 @@ const NiceSvgToCode = ({ initialSvg = '', onConvert, className = '', }) => {
|
|
|
435
437
|
navigator.clipboard.writeText(result.code);
|
|
436
438
|
}
|
|
437
439
|
};
|
|
438
|
-
return (jsxs("div", { className: `nice-svg-to-code ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "refresh", size: 14 }), " SVG
|
|
440
|
+
return (jsxs("div", { className: `nice-svg-to-code ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxs("strong", { children: [jsx(NiceIcon, { name: "refresh", size: 14 }), " ", t('svgToCode.svgCode', 'SVG → Code')] }), jsx("textarea", { value: svgInput, onChange: (e) => setSvgInput(e.target.value), placeholder: t('svgToCode.pasteSvgMarkupHere', 'Paste SVG markup here…'), rows: 6, style: {
|
|
439
441
|
width: '100%',
|
|
440
442
|
fontFamily: 'monospace',
|
|
441
443
|
fontSize: 11,
|
|
442
444
|
padding: 6,
|
|
443
445
|
borderRadius: 4,
|
|
444
446
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
445
|
-
} }), jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxs("label", { children: [
|
|
447
|
+
} }), jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxs("label", { children: [t('svgToCode.framework', 'Framework:'), jsxs("select", { value: config.framework, onChange: (e) => updateConfig({ framework: e.target.value }), style: { width: '100%' }, children: [jsx("option", { value: "react", children: "React (JSX/TSX)" }), jsx("option", { value: "vue", children: "Vue (SFC)" }), jsx("option", { value: "web-component", children: t('svgToCode.webComponent', 'Web Component') }), jsx("option", { value: "typescript", children: t('svgToCode.typescriptUtility', 'TypeScript (utility)') })] })] }), jsxs("label", { children: [t('svgToCode.componentName', 'Component name:'), jsx("input", { type: "text", value: config.componentName, onChange: (e) => updateConfig({ componentName: e.target.value.replace(/[^a-zA-Z0-9]/g, '') }), style: { width: '100%', padding: '3px 6px' } })] }), jsxs("div", { style: { display: 'flex', gap: 8, flexWrap: 'wrap', fontSize: 11 }, children: [jsxs("label", { children: [jsx("input", { type: "checkbox", checked: config.typescript, onChange: (e) => updateConfig({ typescript: e.target.checked }) }), ' ', t('svgToCode.typescript', 'TypeScript')] }), jsxs("label", { children: [jsx("input", { type: "checkbox", checked: config.optimize, onChange: (e) => updateConfig({ optimize: e.target.checked }) }), ' ', t('svgToCode.optimize', 'Optimize')] }), jsxs("label", { children: [jsx("input", { type: "checkbox", checked: config.sizeProps, onChange: (e) => updateConfig({ sizeProps: e.target.checked }) }), ' ', t('svgToCode.sizeProps', 'Size props')] }), jsxs("label", { children: [jsx("input", { type: "checkbox", checked: config.useCurrentColor, onChange: (e) => updateConfig({ useCurrentColor: e.target.checked }) }), ' ', "currentColor"] }), jsxs("label", { children: [jsx("input", { type: "checkbox", checked: config.memo, onChange: (e) => updateConfig({ memo: e.target.checked }) }), ' ', "memo"] })] })] }), jsxs("button", { type: "button", onClick: handleConvert, disabled: !svgInput.trim(), style: {
|
|
446
448
|
background: 'var(--nice-color-accent, #6366f1)',
|
|
447
449
|
color: 'var(--nice-text-on-primary, #fff)',
|
|
448
450
|
border: 'none',
|
|
449
451
|
borderRadius: 4,
|
|
450
452
|
padding: '8px 12px',
|
|
451
453
|
cursor: 'pointer',
|
|
452
|
-
}, children: [jsx(NiceIcon, { name: "refresh", size: 14 }),
|
|
454
|
+
}, children: [jsx(NiceIcon, { name: "refresh", size: 14 }), ' ', t('svgToCode.convertTo', 'Convert to {framework}').replace('{framework}', config.framework.charAt(0).toUpperCase() + config.framework.slice(1))] }), result && (jsxs("div", { children: [jsxs("div", { style: {
|
|
453
455
|
display: 'flex',
|
|
454
456
|
justifyContent: 'space-between',
|
|
455
457
|
alignItems: 'center',
|
|
@@ -461,7 +463,7 @@ const NiceSvgToCode = ({ initialSvg = '', onConvert, className = '', }) => {
|
|
|
461
463
|
borderRadius: 3,
|
|
462
464
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
463
465
|
background: 'transparent',
|
|
464
|
-
}, children: [jsx(NiceIcon, { name: "clipboard", size: 12 }), " Copy
|
|
466
|
+
}, children: [jsx(NiceIcon, { name: "clipboard", size: 12 }), " ", t('svgToCode.copy', 'Copy')] })] }), jsx("pre", { style: {
|
|
465
467
|
background: 'var(--nice-code-bg, #1e293b)',
|
|
466
468
|
color: 'var(--nice-code-color, #e2e8f0)',
|
|
467
469
|
padding: 8,
|
|
@@ -470,7 +472,7 @@ const NiceSvgToCode = ({ initialSvg = '', onConvert, className = '', }) => {
|
|
|
470
472
|
overflow: 'auto',
|
|
471
473
|
maxHeight: 300,
|
|
472
474
|
whiteSpace: 'pre-wrap',
|
|
473
|
-
}, children: result.code }), result.extractedColors.length > 0 && (jsxs("div", { style: { fontSize: 11, marginTop: 4 }, children: [
|
|
475
|
+
}, children: result.code }), result.extractedColors.length > 0 && (jsxs("div", { style: { fontSize: 11, marginTop: 4 }, children: [t('svgToCode.colors', 'Colors:'), ' ', result.extractedColors.map((c) => (jsx("span", { style: {
|
|
474
476
|
display: 'inline-block',
|
|
475
477
|
width: 12,
|
|
476
478
|
height: 12,
|
|
@@ -2,6 +2,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
|
2
2
|
import { NiceButton, NiceAppShell, NiceModal, NiceTextArea } from '@nice2dev/ui';
|
|
3
3
|
import { NiceIcon } from '@nice2dev/ui-core';
|
|
4
4
|
import { useRef, useState, useCallback, useEffect } from 'react';
|
|
5
|
+
import { EditorAskDialog } from '../core/editorAsk.js';
|
|
5
6
|
import { useNiceTranslation } from '../core/i18n.js';
|
|
6
7
|
import { useVectorEditor } from './useVectorEditor.js';
|
|
7
8
|
import { frameToTransform, sampleAnimation } from './vectorAnimation.js';
|
|
@@ -63,7 +64,7 @@ const NiceVectorEditor = ({ width: artW = 512, height: artH = 512, className, on
|
|
|
63
64
|
}, [initialIcon, artW, artH, api]);
|
|
64
65
|
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;
|
|
65
66
|
const toolRail = (jsx("div", { className: styles.toolbar, children: TOOLS.map((t) => (jsx(NiceButton, { className: `${styles.toolBtn} ${tool === t.id ? styles.toolBtnActive : ''}`, onClick: () => switchTool(t.id), "aria-label": `${t.label} (${t.key})`, variant: tool === t.id ? 'primary' : 'ghost', size: "sm", children: jsx(NiceIcon, { name: t.icon, size: "sm" }) }, t.id))) }));
|
|
66
|
-
return (jsxs(Fragment, { children: [jsx("input", { ref: fInp, type: "file", accept: ".svg", style: { display: 'none' }, onChange: (e) => {
|
|
67
|
+
return (jsxs(Fragment, { children: [jsx(EditorAskDialog, { ask: api.pendingAsk }), jsx("input", { ref: fInp, type: "file", accept: ".svg", style: { display: 'none' }, onChange: (e) => {
|
|
67
68
|
var _a;
|
|
68
69
|
const f = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
|
|
69
70
|
if (f) {
|
|
@@ -109,7 +110,7 @@ const NiceVectorEditor = ({ width: artW = 512, height: artH = 512, className, on
|
|
|
109
110
|
}
|
|
110
111
|
const node = (jsx(ShapeRenderer, { shape: s, selected: s.id === selId, zoom: zoom }, s.id));
|
|
111
112
|
return previewTr ? (jsx("g", { transform: previewTr, children: node }, s.id)) : (node);
|
|
112
|
-
})] }) }) }), jsxs(NiceModal, { open: dlgSvg, onClose: () => setDlgSvg(false), title:
|
|
113
|
+
})] }) }) }), jsxs(NiceModal, { open: dlgSvg, onClose: () => setDlgSvg(false), title: t('vectorEditor.pasteSvg', 'Paste SVG'), children: [jsx("label", { children: t('vectorEditor.pasteSvgSourceCodeBelow', 'Paste SVG source code below:') }), jsx(NiceTextArea, { value: svgText, onChange: (val) => setSvgText(val), placeholder: t('vectorEditor.svgXmlnsHttpWwwW3', '<svg xmlns="http://www.w3.org/2000/svg" ...>') }), jsxs("div", { className: styles.dialogActions, children: [jsx(NiceButton, { variant: "ghost", onClick: () => setDlgSvg(false), children: t('vectorEditor.cancel', 'Cancel') }), jsx(NiceButton, { variant: "primary", onClick: () => importSVGString(svgText), children: t('vectorEditor.import', 'Import') })] })] }), jsxs(NiceModal, { open: snippetOpen, onClose: () => setSnippetOpen(false), title: t('vectorEditor.snippet.title', 'Icon snippet copied to clipboard'), children: [jsxs("p", { style: { marginTop: 0 }, children: [t('vectorEditor.snippet.pasteHint', 'Paste into'), ' ', jsxs("code", { children: ["packages/icons/src/icons/", iconCategory ? `${iconCategory.toLowerCase()}` : '<category>', ".tsx"] }), ":"] }), jsx(NiceTextArea, { value: snippetText, onChange: (val) => setSnippetText(val), rows: 16 }), jsxs("div", { className: styles.dialogActions, children: [jsx(NiceButton, { variant: "primary", onClick: () => {
|
|
113
114
|
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
114
115
|
navigator.clipboard.writeText(snippetText).catch(() => { });
|
|
115
116
|
}
|
|
@@ -262,7 +262,13 @@ const VectorEditorMenuBar$1 = ({ api, hasOnSaveToLibrary, onExportIconSnippet })
|
|
|
262
262
|
{ value: '', label: t('vectorEditor.menu.secondShape', '2nd Shape') },
|
|
263
263
|
...shapes
|
|
264
264
|
.filter((s) => s.id !== selId)
|
|
265
|
-
.map((s, i) => {
|
|
265
|
+
.map((s, i) => {
|
|
266
|
+
var _a;
|
|
267
|
+
return ({
|
|
268
|
+
value: s.id,
|
|
269
|
+
label: (_a = s.name) !== null && _a !== void 0 ? _a : t('vectorEditor.shape', 'Shape {i}').replace('{i}', String(i + 1)),
|
|
270
|
+
});
|
|
271
|
+
}),
|
|
266
272
|
], style: { minWidth: 100 } })), jsxs("label", { className: styles.menuLabel, children: [jsx(NiceCheckbox, { checked: snapGrid, onChange: () => setSnapGrid((v) => !v) }), t('vectorEditor.menu.snap', 'Snap')] }), onExportIconSnippet && (jsxs(Fragment, { children: [jsx("div", { className: styles.menuSep }), jsxs(NiceButton, { className: styles.menuBtn, variant: "primary", size: "sm", onClick: onExportIconSnippet, children: [jsx(NiceIcon, { name: "clipboard", size: 14 }), ' ', t('vectorEditor.menu.exportIcon', 'Export Icon')] })] }))] }));
|
|
267
273
|
};
|
|
268
274
|
var VectorEditorMenuBar = React.memo(VectorEditorMenuBar$1);
|
|
@@ -44,7 +44,7 @@ const VectorEditorRightPanel$1 = ({ api, library, artW = 512, artH = 512 }) => {
|
|
|
44
44
|
border: (selectedGradient === null || selectedGradient === void 0 ? void 0 : selectedGradient.id) === grad.id
|
|
45
45
|
? '2px solid var(--nice-bg, #FFF)'
|
|
46
46
|
: '1px solid var(--nice-border-color, #444)',
|
|
47
|
-
}, onClick: () => applyGradient(grad), title:
|
|
47
|
+
}, onClick: () => applyGradient(grad), title: t('vectorEditor.gradient', '{type} gradient').replace('{type}', String(grad.type)) }, i))) }), jsx("div", { className: styles.propRow, children: jsxs(NiceButton, { className: styles.menuBtn, variant: "ghost", size: "sm", style: { width: '100%', fontSize: 10 }, onClick: () => setShowGradientEditor(!showGradientEditor), children: [jsx(NiceIcon, { name: showGradientEditor ? 'chevron-down' : 'chevron-right', size: 14 }), ' ', showGradientEditor
|
|
48
48
|
? t('vectorEditor.properties.hideEditor', 'Hide Editor')
|
|
49
49
|
: t('vectorEditor.properties.editGradient', 'Edit Gradient')] }) }), showGradientEditor && selectedGradient && (jsxs("div", { style: {
|
|
50
50
|
padding: 4,
|
|
@@ -6,7 +6,9 @@ const ShapeRenderer = ({ shape: s, selected, zoom }) => {
|
|
|
6
6
|
if (s.hidden) {
|
|
7
7
|
return null;
|
|
8
8
|
}
|
|
9
|
-
const selStroke = selected
|
|
9
|
+
const selStroke = selected
|
|
10
|
+
? 'color-mix(in srgb, var(--nice-color-primary, #5566cc) 60%, transparent)'
|
|
11
|
+
: undefined;
|
|
10
12
|
const gProps = {};
|
|
11
13
|
if (s.opacity < 1) {
|
|
12
14
|
gProps.opacity = s.opacity;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState, useRef, useMemo, useCallback, useEffect } from 'react';
|
|
2
|
+
import { useEditorAsk } from '../core/editorAsk.js';
|
|
2
3
|
import { booleanOperation, flattenToOutline } from './vectorBooleanOps.js';
|
|
3
4
|
import { buildSVGString, exportSVGFile, exportPNGFile, exportJPGFile, exportWebPFile, exportBMPFile } from './vectorEditorExport.js';
|
|
4
5
|
import { parseSVGToShapes } from './vectorEditorImport.js';
|
|
@@ -10,10 +11,13 @@ import { cloneShapes, sid, snapToGrid } from './vectorEditorTypes.js';
|
|
|
10
11
|
* so the component file is pure layout.
|
|
11
12
|
*/
|
|
12
13
|
function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
14
|
+
// the library's dialogs for the text tool and the library name (not window.prompt)
|
|
15
|
+
const { pendingAsk, ask } = useEditorAsk();
|
|
13
16
|
/* ── state ── */
|
|
14
17
|
const [shapes, setShapes] = useState([]);
|
|
15
18
|
const [selId, setSelId] = useState(null);
|
|
16
19
|
const [tool, setTool] = useState('pointer');
|
|
20
|
+
// audit-allow: literal-color — the first fill of a new shape - document data
|
|
17
21
|
const [fillCol, setFillCol] = useState('#5566cc');
|
|
18
22
|
const [strokeCol, setStrokeCol] = useState('#000000');
|
|
19
23
|
const [strokeW, setStrokeW] = useState(2);
|
|
@@ -176,28 +180,30 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
|
176
180
|
return;
|
|
177
181
|
}
|
|
178
182
|
if (tool === 'text') {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
183
|
+
// the text comes from a dialog: the shape is added once it is entered
|
|
184
|
+
void ask('prompt', 'Enter text:', 'Hello').then((txt) => {
|
|
185
|
+
if (!txt) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const ns = {
|
|
189
|
+
id: sid(),
|
|
190
|
+
type: 'text',
|
|
191
|
+
x: p.x,
|
|
192
|
+
y: p.y,
|
|
193
|
+
w: txt.length * fontSize * 0.6,
|
|
194
|
+
h: fontSize,
|
|
195
|
+
...defaultBase,
|
|
196
|
+
stroke: 'none',
|
|
197
|
+
strokeWidth: 0,
|
|
198
|
+
text: txt,
|
|
199
|
+
fontSize,
|
|
200
|
+
fontFamily,
|
|
201
|
+
};
|
|
202
|
+
const next = [...shapes, ns];
|
|
203
|
+
snap(next);
|
|
204
|
+
setShapes(next);
|
|
205
|
+
setSelId(ns.id);
|
|
206
|
+
});
|
|
201
207
|
return;
|
|
202
208
|
}
|
|
203
209
|
if (tool === 'polygon') {
|
|
@@ -283,6 +289,7 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
|
283
289
|
starPoints2,
|
|
284
290
|
starInnerRatio,
|
|
285
291
|
cornerRadius,
|
|
292
|
+
ask,
|
|
286
293
|
]);
|
|
287
294
|
const onMove = useCallback((e) => {
|
|
288
295
|
if (panning.current) {
|
|
@@ -697,15 +704,15 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
|
697
704
|
const blob = new Blob([svg], { type: 'image/svg+xml' });
|
|
698
705
|
const reader = new FileReader();
|
|
699
706
|
reader.onload = () => {
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
707
|
+
const dataUrl = reader.result;
|
|
708
|
+
void ask('prompt', 'Save to library as:', 'drawing.svg').then((name) => {
|
|
709
|
+
if (name && name.trim()) {
|
|
710
|
+
onSaveToLibrary(dataUrl, name.trim(), 'image/svg+xml');
|
|
711
|
+
}
|
|
712
|
+
});
|
|
706
713
|
};
|
|
707
714
|
reader.readAsDataURL(blob);
|
|
708
|
-
}, [shapes, artW, artH, onSaveToLibrary]);
|
|
715
|
+
}, [shapes, artW, artH, onSaveToLibrary, ask]);
|
|
709
716
|
/* ── export shortcuts ── */
|
|
710
717
|
const exportSVG = useCallback(() => exportSVGFile(shapes, artW, artH, animations), [shapes, artW, artH, animations]);
|
|
711
718
|
const exportPNG = useCallback(() => exportPNGFile(shapes, artW, artH), [shapes, artW, artH]);
|
|
@@ -791,6 +798,8 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
|
791
798
|
/* ── return ── */
|
|
792
799
|
return {
|
|
793
800
|
// state
|
|
801
|
+
/** The question the editor waits on; render it with `EditorAskDialog`. */
|
|
802
|
+
pendingAsk,
|
|
794
803
|
shapes,
|
|
795
804
|
selId,
|
|
796
805
|
tool,
|
|
@@ -16,6 +16,7 @@ function parseSVGToShapes(raw) {
|
|
|
16
16
|
const elems = doc.querySelectorAll('rect, ellipse, circle, line, polyline, polygon, text, path');
|
|
17
17
|
elems.forEach((el) => {
|
|
18
18
|
const tag = el.tagName.toLowerCase();
|
|
19
|
+
// audit-allow: literal-color — an imported shape without a fill gets a visible default - document data
|
|
19
20
|
const fill = el.getAttribute('fill') || (tag === 'line' ? 'none' : '#cccccc');
|
|
20
21
|
const stroke = el.getAttribute('stroke') || 'none';
|
|
21
22
|
const sw = parseFloat(el.getAttribute('stroke-width') || '1');
|
|
@@ -37,6 +37,7 @@ function iconComponentToShapes(Icon, options) {
|
|
|
37
37
|
// Force a generous size and visible colors so the parser sees concrete strokes/fills.
|
|
38
38
|
const renderProps = {
|
|
39
39
|
size: 'lg',
|
|
40
|
+
// audit-allow: literal-color +2 — an imported icon's default colours - document data
|
|
40
41
|
primaryColor: '#5566cc',
|
|
41
42
|
secondaryColor: '#a29bfe',
|
|
42
43
|
strokeWidth: 2,
|
|
@@ -16,6 +16,5 @@ export interface RangeInputProps extends Omit<React.InputHTMLAttributes<HTMLInpu
|
|
|
16
16
|
onChange?: React.ChangeEventHandler<HTMLInputElement>;
|
|
17
17
|
}
|
|
18
18
|
export declare const RangeInput: React.FC<RangeInputProps>;
|
|
19
|
-
export
|
|
20
|
-
}
|
|
19
|
+
export type TextAreaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
|
21
20
|
export declare const TextArea: React.FC<TextAreaProps>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/** A question an editor hook waits on; {@link EditorAskDialog} shows it. */
|
|
3
|
+
export interface EditorAsk {
|
|
4
|
+
/** `prompt` asks for a value, `confirm` for a yes / no. */
|
|
5
|
+
kind: 'prompt' | 'confirm';
|
|
6
|
+
/** The prompt's title, or the question to confirm. */
|
|
7
|
+
text: string;
|
|
8
|
+
/** Initial value of a prompt. */
|
|
9
|
+
defaultValue?: string;
|
|
10
|
+
/** Settles it: the entered value (`''` for an accepted confirm), or `null` when cancelled. */
|
|
11
|
+
answer: (value: string | null) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The library's dialogs in place of `window.prompt` / `window.confirm` for the editor hooks,
|
|
15
|
+
* which hold state but render nothing: `ask` resolves with the answer (`null` when cancelled)
|
|
16
|
+
* and `pendingAsk` is the open question, for the editor component to pass to
|
|
17
|
+
* {@link EditorAskDialog}. One question at a time: a new one settles the open one as cancelled.
|
|
18
|
+
*/
|
|
19
|
+
export declare function useEditorAsk(): {
|
|
20
|
+
pendingAsk: EditorAsk | null;
|
|
21
|
+
ask: (kind: EditorAsk["kind"], text: string, defaultValue?: string) => Promise<string | null>;
|
|
22
|
+
};
|
|
23
|
+
/** Shows the question {@link useEditorAsk} is waiting on, if any. */
|
|
24
|
+
export declare const EditorAskDialog: React.FC<{
|
|
25
|
+
ask: EditorAsk | null;
|
|
26
|
+
}>;
|
|
@@ -1,26 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* i18n — Lightweight translation context for @nice2dev/ui-graphics.
|
|
3
|
-
*
|
|
4
|
-
* Drop-in replacement for react-i18next's useTranslation, requires NO
|
|
5
|
-
* external dependencies. Consumers can optionally wrap with NiceI18nProvider
|
|
6
|
-
* to supply their own translation function. Falls back to the default
|
|
7
|
-
* (English key strings) when no provider is present.
|
|
8
|
-
*
|
|
9
|
-
* Usage in components:
|
|
10
|
-
* const { t } = useNiceTranslation();
|
|
11
|
-
* <button>{t('actions.save', 'Save')}</button>
|
|
12
|
-
*
|
|
13
|
-
* Usage in apps:
|
|
14
|
-
* <NiceI18nProvider t={(key, fallback) => myI18n(key) ?? fallback}>
|
|
15
|
-
* <App />
|
|
16
|
-
* </NiceI18nProvider>
|
|
17
|
-
*/
|
|
18
1
|
import React from 'react';
|
|
19
2
|
export type NiceTranslateFn = (key: string, defaultValue: string) => string;
|
|
20
3
|
export interface NiceI18nProviderProps {
|
|
21
4
|
t: NiceTranslateFn;
|
|
22
5
|
children: React.ReactNode;
|
|
23
6
|
}
|
|
7
|
+
/** Overrides the app's global translator for the graphics controls below it. */
|
|
24
8
|
export declare const NiceI18nProvider: React.FC<NiceI18nProviderProps>;
|
|
25
9
|
export declare function useNiceTranslation(): {
|
|
26
10
|
t: NiceTranslateFn;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* usePixelEditor — custom hook encapsulating ALL state, refs, callbacks,
|
|
3
3
|
* side-effects and memos for the PixelEditor component.
|
|
4
4
|
*/
|
|
5
|
+
import { type EditorAsk } from '../core/editorAsk';
|
|
5
6
|
import { type Tool, type Layer, type BlendMode, type Selection } from './pixelEditorTypes';
|
|
6
7
|
import { type BrushConfig, type SymmetryMode } from './pixelEditorUtils';
|
|
7
8
|
export interface UsePixelEditorProps {
|
|
@@ -81,6 +82,8 @@ export interface PixelEditorAPI {
|
|
|
81
82
|
exportSVG: () => void;
|
|
82
83
|
exportSheet: () => void;
|
|
83
84
|
saveToLibrary: () => void;
|
|
85
|
+
/** The question the editor waits on (library name); render it with `EditorAskDialog`. */
|
|
86
|
+
pendingAsk: EditorAsk | null;
|
|
84
87
|
addLayer: () => void;
|
|
85
88
|
rmLayer: () => void;
|
|
86
89
|
toggleVis: (i: number) => void;
|
|
@@ -11,6 +11,8 @@ export interface UseVectorEditorOptions {
|
|
|
11
11
|
onSaveToLibrary?: (dataUrl: string, name: string, mimeType: string) => void;
|
|
12
12
|
}
|
|
13
13
|
export declare function useVectorEditor({ artW, artH, onSaveToLibrary }: UseVectorEditorOptions): {
|
|
14
|
+
/** The question the editor waits on; render it with `EditorAskDialog`. */
|
|
15
|
+
readonly pendingAsk: import("../core/editorAsk").EditorAsk | null;
|
|
14
16
|
readonly shapes: VShape[];
|
|
15
17
|
readonly selId: string | null;
|
|
16
18
|
readonly tool: VTool;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nice2dev/ui-graphics",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Nice2Dev Graphics Editors — Pixel, Vector, Photo and Animation editors for React",
|
|
5
5
|
"author": "NiceToDev <contact@nicetodev.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -64,15 +64,15 @@
|
|
|
64
64
|
"nicetodev"
|
|
65
65
|
],
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"@nice2dev/ui-core": "^1.0
|
|
68
|
-
"@nice2dev/ui": "^1.0
|
|
67
|
+
"@nice2dev/ui-core": "^1.2.0",
|
|
68
|
+
"@nice2dev/ui": "^1.2.0",
|
|
69
69
|
"framer-motion": ">=10.0.0",
|
|
70
70
|
"react": ">=17.0.0",
|
|
71
71
|
"react-dom": ">=17.0.0"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@nice2dev/ui": "^1.2.
|
|
75
|
-
"@nice2dev/ui-core": "^1.2.
|
|
74
|
+
"@nice2dev/ui": "^1.2.2",
|
|
75
|
+
"@nice2dev/ui-core": "^1.2.2",
|
|
76
76
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
77
77
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
78
78
|
"@rollup/plugin-typescript": "^12.3.0",
|