@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
|
@@ -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 };
|
|
@@ -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,23 +542,25 @@ 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);
|
|
547
|
+
const { endStroke } = engine;
|
|
545
548
|
// Override endStroke to fire callback
|
|
546
549
|
useEffect(() => {
|
|
547
550
|
if (!canvas) {
|
|
548
551
|
return;
|
|
549
552
|
}
|
|
550
553
|
const onUp = () => {
|
|
551
|
-
const stroke =
|
|
554
|
+
const stroke = endStroke();
|
|
552
555
|
if (stroke && onStrokeEnd) {
|
|
553
556
|
onStrokeEnd(stroke);
|
|
554
557
|
}
|
|
555
558
|
};
|
|
556
559
|
canvas.addEventListener('pointerup', onUp);
|
|
557
560
|
return () => canvas.removeEventListener('pointerup', onUp);
|
|
558
|
-
}, [canvas,
|
|
561
|
+
}, [canvas, endStroke, onStrokeEnd]);
|
|
559
562
|
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
|
|
563
|
+
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
564
|
var _a, _b;
|
|
562
565
|
return (jsx("button", { type: "button", onClick: () => engine.applyPreset(name), style: {
|
|
563
566
|
padding: '2px 6px',
|
|
@@ -570,13 +573,13 @@ const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '
|
|
|
570
573
|
: 'transparent',
|
|
571
574
|
color: s.name === ((_b = BRUSH_PRESETS[name]) === null || _b === void 0 ? void 0 : _b.name) ? 'var(--nice-color-white, #fff)' : 'inherit',
|
|
572
575
|
}, children: BRUSH_PRESETS[name].name }, name));
|
|
573
|
-
}) }), jsxs("label", { children: [
|
|
576
|
+
}) }), jsxs("label", { children: [t('brushEngine.sizePx', 'Size: {size}px').replace('{size}', String(s.size)), 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: [t('brushEngine.opacity', 'Opacity: {opacity}%').replace('{opacity}', String(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: [t('brushEngine.flow', 'Flow: {flow}%').replace('{flow}', String(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: [t('brushEngine.hardness', 'Hardness: {hardness}%').replace('{hardness}', String(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: [t('brushEngine.smoothing', 'Smoothing: {smoothing}%').replace('{smoothing}', String(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: [t('brushEngine.tip', '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: [t('brushEngine.roundness', 'Roundness: {roundness}%').replace('{roundness}', String(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: [t('brushEngine.source', 'Source:'), jsxs("select", { value: s.sizeDynamics.source, onChange: (e) => engine.setSettings({
|
|
574
577
|
...s,
|
|
575
578
|
sizeDynamics: {
|
|
576
579
|
...s.sizeDynamics,
|
|
577
580
|
source: e.target.value,
|
|
578
581
|
},
|
|
579
|
-
}), style: { width: '100%' }, children: [jsx("option", { value: "none", children:
|
|
582
|
+
}), style: { width: '100%' }, children: [jsx("option", { value: "none", children: t('brushEngine.off', '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: [t('brushEngine.scatter', 'Scatter: {scatter}%').replace('{scatter}', String(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: [t('brushEngine.blend', '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')] })] }));
|
|
580
583
|
};
|
|
581
584
|
|
|
582
585
|
export { BRUSH_PRESETS, NiceBrushEngine, useBrushEngine };
|
|
@@ -169,10 +169,14 @@ function useCollaboration(transport, roomId, currentUser) {
|
|
|
169
169
|
const [users, setUsers] = useState([]);
|
|
170
170
|
const [connected, setConnected] = useState(false);
|
|
171
171
|
const operationHandlers = useRef([]);
|
|
172
|
+
// the user as it is when the room is joined: a new object with the same id must not rejoin
|
|
173
|
+
const currentUserRef = useRef(currentUser);
|
|
174
|
+
currentUserRef.current = currentUser;
|
|
172
175
|
useEffect(() => {
|
|
173
|
-
|
|
176
|
+
const user = currentUserRef.current;
|
|
177
|
+
transport.connect(roomId, user.id);
|
|
174
178
|
setConnected(true);
|
|
175
|
-
transport.send({ type: 'join', user
|
|
179
|
+
transport.send({ type: 'join', user });
|
|
176
180
|
const unsubscribe = transport.onMessage((msg) => {
|
|
177
181
|
switch (msg.type) {
|
|
178
182
|
case 'join':
|
|
@@ -202,17 +206,18 @@ function useCollaboration(transport, roomId, currentUser) {
|
|
|
202
206
|
}
|
|
203
207
|
});
|
|
204
208
|
return () => {
|
|
205
|
-
transport.send({ type: 'leave', userId:
|
|
209
|
+
transport.send({ type: 'leave', userId: user.id });
|
|
206
210
|
unsubscribe();
|
|
207
211
|
transport.disconnect();
|
|
208
212
|
setConnected(false);
|
|
209
213
|
};
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
// a new transport reconnects (until 1.2.2 it was ignored and messages went to the old one)
|
|
215
|
+
}, [transport, roomId, currentUser.id]);
|
|
216
|
+
const sendCursor = useCallback((x, y) => transport.send({ type: 'cursor', userId: currentUser.id, x, y }), [transport, currentUser.id]);
|
|
212
217
|
const sendOperation = useCallback((op) => transport.send({
|
|
213
218
|
type: 'operation',
|
|
214
219
|
operation: { ...op, timestamp: Date.now(), userId: currentUser.id },
|
|
215
|
-
}), [currentUser.id]);
|
|
220
|
+
}), [transport, currentUser.id]);
|
|
216
221
|
const onOperation = useCallback((handler) => {
|
|
217
222
|
operationHandlers.current.push(handler);
|
|
218
223
|
return () => {
|
|
@@ -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/minimap.js
CHANGED
|
@@ -18,6 +18,27 @@ const Minimap = ({ canvasRef, imageDataUrl, documentWidth, documentHeight, viewp
|
|
|
18
18
|
const scale = Math.min(scaleX, scaleY);
|
|
19
19
|
// Draw minimap
|
|
20
20
|
React.useEffect(() => {
|
|
21
|
+
// inside the effect: it draws with this run's viewport, scale and size
|
|
22
|
+
const drawViewport = (ctx) => {
|
|
23
|
+
// Draw viewport rect
|
|
24
|
+
const vx = viewport.x * scale;
|
|
25
|
+
const vy = viewport.y * scale;
|
|
26
|
+
const vw = viewport.width * scale;
|
|
27
|
+
const vh = viewport.height * scale;
|
|
28
|
+
ctx.strokeStyle = resolveCssColor('var(--nice-primary-tint-80, rgba(59, 130, 246, 0.8))', ctx.canvas);
|
|
29
|
+
ctx.lineWidth = 1.5;
|
|
30
|
+
ctx.strokeRect(vx, vy, vw, vh);
|
|
31
|
+
// Dim areas outside viewport
|
|
32
|
+
ctx.fillStyle = resolveCssColor('var(--nice-overlay-30, rgba(0, 0, 0, 0.3))', ctx.canvas);
|
|
33
|
+
// Top
|
|
34
|
+
ctx.fillRect(0, 0, mapW, vy);
|
|
35
|
+
// Bottom
|
|
36
|
+
ctx.fillRect(0, vy + vh, mapW, mapH - vy - vh);
|
|
37
|
+
// Left
|
|
38
|
+
ctx.fillRect(0, vy, vx, vh);
|
|
39
|
+
// Right
|
|
40
|
+
ctx.fillRect(vx + vw, vy, mapW - vx - vw, vh);
|
|
41
|
+
};
|
|
21
42
|
if (!visible) {
|
|
22
43
|
return;
|
|
23
44
|
}
|
|
@@ -62,26 +83,6 @@ const Minimap = ({ canvasRef, imageDataUrl, documentWidth, documentHeight, viewp
|
|
|
62
83
|
mapW,
|
|
63
84
|
mapH,
|
|
64
85
|
]);
|
|
65
|
-
function drawViewport(ctx) {
|
|
66
|
-
// Draw viewport rect
|
|
67
|
-
const vx = viewport.x * scale;
|
|
68
|
-
const vy = viewport.y * scale;
|
|
69
|
-
const vw = viewport.width * scale;
|
|
70
|
-
const vh = viewport.height * scale;
|
|
71
|
-
ctx.strokeStyle = resolveCssColor('var(--nice-primary-tint-80, rgba(59, 130, 246, 0.8))', ctx.canvas);
|
|
72
|
-
ctx.lineWidth = 1.5;
|
|
73
|
-
ctx.strokeRect(vx, vy, vw, vh);
|
|
74
|
-
// Dim areas outside viewport
|
|
75
|
-
ctx.fillStyle = resolveCssColor('var(--nice-overlay-30, rgba(0, 0, 0, 0.3))', ctx.canvas);
|
|
76
|
-
// Top
|
|
77
|
-
ctx.fillRect(0, 0, mapW, vy);
|
|
78
|
-
// Bottom
|
|
79
|
-
ctx.fillRect(0, vy + vh, mapW, mapH - vy - vh);
|
|
80
|
-
// Left
|
|
81
|
-
ctx.fillRect(0, vy, vx, vh);
|
|
82
|
-
// Right
|
|
83
|
-
ctx.fillRect(vx + vw, vy, mapW - vx - vw, vh);
|
|
84
|
-
}
|
|
85
86
|
const handlePointerDown = (e) => {
|
|
86
87
|
isDragging.current = true;
|
|
87
88
|
updateViewportFromPointer(e);
|
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
|
@@ -2,6 +2,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { NiceTutorialButton, resolveTutorial } 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 FONT_EDITOR_TOUR = [
|
|
@@ -132,6 +133,7 @@ function useFontEditor(initialName) {
|
|
|
132
133
|
/** Font glyph editor with metrics, visual path editing, and preview. */
|
|
133
134
|
const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial, }) => {
|
|
134
135
|
var _a;
|
|
136
|
+
const { t } = useNiceTranslation();
|
|
135
137
|
const editor = useFontEditor(fontName);
|
|
136
138
|
const [drawMode, setDrawMode] = React.useState('select');
|
|
137
139
|
const [pathPoints, setPathPoints] = React.useState([]);
|
|
@@ -219,7 +221,7 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
|
|
|
219
221
|
color: 'var(--nice-text, #c1c2c5)',
|
|
220
222
|
padding: 12,
|
|
221
223
|
borderRadius: 8,
|
|
222
|
-
}, children: [jsx("span", { style: { position: 'absolute', top: 6, right: 6, zIndex: 20 }, children: jsx(NiceTutorialButton, { steps: resolveTutorial(tutorial, FONT_EDITOR_TOUR) }) }), jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 12 }, children: [jsx("strong", { style: { fontSize: 14 }, children:
|
|
224
|
+
}, children: [jsx("span", { style: { position: 'absolute', top: 6, right: 6, zIndex: 20 }, children: jsx(NiceTutorialButton, { steps: resolveTutorial(tutorial, FONT_EDITOR_TOUR) }) }), jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 12 }, children: [jsx("strong", { style: { fontSize: 14 }, children: t('fontEditor.fontEditor', 'Font Editor') }), jsx("input", { value: editor.fontName, onChange: (e) => editor.setFontName(e.target.value), placeholder: t('fontEditor.fontName', 'Font name'), style: { flex: 1, maxWidth: 200, ...fieldStyle, fontSize: 12 } }), jsx("button", { onClick: handleSave, style: {
|
|
223
225
|
padding: '4px 12px',
|
|
224
226
|
background: 'var(--nice-success, #2f9e44)',
|
|
225
227
|
color: 'var(--nice-text-on-primary, #fff)',
|
|
@@ -227,7 +229,7 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
|
|
|
227
229
|
borderRadius: 3,
|
|
228
230
|
cursor: 'pointer',
|
|
229
231
|
fontSize: 11,
|
|
230
|
-
}, children:
|
|
232
|
+
}, children: t('fontEditor.save', 'Save') }), jsx("button", { onClick: handleExportSvg, style: {
|
|
231
233
|
padding: '4px 12px',
|
|
232
234
|
background: '#228be6',
|
|
233
235
|
color: 'var(--nice-color-white, #fff)',
|
|
@@ -235,11 +237,11 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
|
|
|
235
237
|
borderRadius: 3,
|
|
236
238
|
cursor: 'pointer',
|
|
237
239
|
fontSize: 11,
|
|
238
|
-
}, children:
|
|
240
|
+
}, children: t('fontEditor.exportSvgFont', 'Export SVG Font') })] }), jsxs("div", { style: { display: 'flex', gap: 4, borderBottom: '1px solid var(--nice-border, #373a40)' }, children: [jsx("button", { onClick: () => setTab('glyph'), style: tabBtnStyle(tab === 'glyph'), children: t('fontEditor.glyphs', 'Glyphs') }), jsx("button", { onClick: () => setTab('metrics'), style: tabBtnStyle(tab === 'metrics'), children: t('fontEditor.metrics', 'Metrics') }), jsx("button", { onClick: () => setTab('kerning'), style: tabBtnStyle(tab === 'kerning'), children: t('fontEditor.kerning', 'Kerning') })] }), tab === 'glyph' && (jsxs("div", { style: { display: 'flex', gap: 8 }, children: [jsxs("div", { style: { flex: 1 }, children: [jsx("div", { style: {
|
|
239
241
|
fontSize: 11,
|
|
240
242
|
marginBottom: 4,
|
|
241
243
|
color: 'var(--nice-text-secondary, #868e96)',
|
|
242
|
-
}, children:
|
|
244
|
+
}, children: t('fontEditor.glyphs2', '{glyphs} glyphs').replace('{glyphs}', String(editor.glyphs.length)) }), jsx("div", { style: {
|
|
243
245
|
display: 'grid',
|
|
244
246
|
gridTemplateColumns: 'repeat(auto-fill, 36px)',
|
|
245
247
|
gap: 2,
|
|
@@ -270,7 +272,7 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
|
|
|
270
272
|
border: g.path
|
|
271
273
|
? '1px solid var(--nice-border, #373a40)'
|
|
272
274
|
: '1px solid transparent',
|
|
273
|
-
}, children: g.char }, g.unicode))) })] }), jsx("div", { style: { width: 320, fontSize: 11 }, children: selectedG ? (jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxs("strong", { children: ["\"", selectedG.char, "\" (U+", selectedG.unicode.toString(16).toUpperCase().padStart(4, '0'), ")"] }), jsxs("div", { style: { display: 'flex', gap: 2 }, children: [jsx("button", { onClick: () => setDrawMode('select'), style: btnStyle(drawMode === 'select'), children:
|
|
275
|
+
}, children: g.char }, g.unicode))) })] }), jsx("div", { style: { width: 320, fontSize: 11 }, children: selectedG ? (jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxs("strong", { children: ["\"", selectedG.char, "\" (U+", selectedG.unicode.toString(16).toUpperCase().padStart(4, '0'), ")"] }), jsxs("div", { style: { display: 'flex', gap: 2 }, children: [jsx("button", { onClick: () => setDrawMode('select'), style: btnStyle(drawMode === 'select'), children: t('fontEditor.select', 'Select') }), jsx("button", { onClick: () => setDrawMode('pen'), style: btnStyle(drawMode === 'pen'), children: t('fontEditor.pen', 'Pen') }), jsx("button", { onClick: clearPath, style: btnStyle(false), children: t('fontEditor.clear', 'Clear') }), jsx("button", { onClick: () => setShowGrid((g) => !g), style: btnStyle(showGrid), children: t('fontEditor.grid', 'Grid') })] })] }), jsxs("svg", { width: 280, height: 280, viewBox: `0 0 ${em} ${em}`, style: {
|
|
274
276
|
background: 'var(--nice-bg, #141517)',
|
|
275
277
|
borderRadius: 4,
|
|
276
278
|
cursor: drawMode === 'pen' ? 'crosshair' : 'default',
|
|
@@ -278,32 +280,32 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
|
|
|
278
280
|
Array.from({ length: 11 }, (_, i) => {
|
|
279
281
|
const pos = (i / 10) * em;
|
|
280
282
|
return (jsxs(React.Fragment, { children: [jsx("line", { x1: pos, y1: 0, x2: pos, y2: em, stroke: "var(--nice-overlay-light-5, rgba(255, 255, 255, 0.05))", strokeWidth: em * 0.002 }), jsx("line", { x1: 0, y1: pos, x2: em, y2: pos, stroke: "var(--nice-overlay-light-5, rgba(255, 255, 255, 0.05))", strokeWidth: em * 0.002 })] }, i));
|
|
281
|
-
}), jsx("line", { x1: 0, y1: em - editor.metrics.ascent, x2: em, y2: em - editor.metrics.ascent, stroke: "rgba(51,154,240,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsx("line", { x1: 0, y1: em, x2: em, y2: em, stroke: "rgba(47,158,68,0.3)", strokeWidth: em * 0.003 }), jsx("line", { x1: 0, y1: em - editor.metrics.xHeight, x2: em, y2: em - editor.metrics.xHeight, stroke: "rgba(249,226,175,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsx("line", { x1: 0, y1: em - editor.metrics.capHeight, x2: em, y2: em - editor.metrics.capHeight, stroke: "rgba(243,139,168,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsx("line", { x1: selectedG.leftBearing, y1: 0, x2: selectedG.leftBearing, y2: em, stroke: "rgba(166,227,161,0.2)", strokeWidth: em * 0.002 }), jsx("line", { x1: selectedG.advanceWidth, y1: 0, x2: selectedG.advanceWidth, y2: em, stroke: "rgba(243,139,168,0.2)", strokeWidth: em * 0.002 }), selectedG.path && (jsx("path", { d: selectedG.path, fill: "rgba(205,214,244,0.8)", stroke: "#89b4fa", strokeWidth: em * 0.005 })), pathPoints.map((p, i) => (jsx("circle", { cx: p.x, cy: p.y, r: em * 0.015, fill: i === 0 ? 'var(--nice-success, #2f9e44)' : 'var(--nice-info, #339af0)', stroke: "var(--nice-bg, #fff)", strokeWidth: em * 0.003 }, i))), jsx("text", { x: em * 0.02, y: em - editor.metrics.ascent - em * 0.01, fill: "rgba(51,154,240,0.5)", fontSize: em * 0.03, children:
|
|
283
|
+
}), jsx("line", { x1: 0, y1: em - editor.metrics.ascent, x2: em, y2: em - editor.metrics.ascent, stroke: "rgba(51,154,240,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsx("line", { x1: 0, y1: em, x2: em, y2: em, stroke: "rgba(47,158,68,0.3)", strokeWidth: em * 0.003 }), jsx("line", { x1: 0, y1: em - editor.metrics.xHeight, x2: em, y2: em - editor.metrics.xHeight, stroke: "rgba(249,226,175,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsx("line", { x1: 0, y1: em - editor.metrics.capHeight, x2: em, y2: em - editor.metrics.capHeight, stroke: "rgba(243,139,168,0.3)", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.01}` }), jsx("line", { x1: selectedG.leftBearing, y1: 0, x2: selectedG.leftBearing, y2: em, stroke: "rgba(166,227,161,0.2)", strokeWidth: em * 0.002 }), jsx("line", { x1: selectedG.advanceWidth, y1: 0, x2: selectedG.advanceWidth, y2: em, stroke: "rgba(243,139,168,0.2)", strokeWidth: em * 0.002 }), selectedG.path && (jsx("path", { d: selectedG.path, fill: "rgba(205,214,244,0.8)", stroke: "#89b4fa", strokeWidth: em * 0.005 })), pathPoints.map((p, i) => (jsx("circle", { cx: p.x, cy: p.y, r: em * 0.015, fill: i === 0 ? 'var(--nice-success, #2f9e44)' : 'var(--nice-info, #339af0)', stroke: "var(--nice-bg, #fff)", strokeWidth: em * 0.003 }, i))), jsx("text", { x: em * 0.02, y: em - editor.metrics.ascent - em * 0.01, fill: "rgba(51,154,240,0.5)", fontSize: em * 0.03, children: t('fontEditor.ascent2', 'ascent') }), jsx("text", { x: em * 0.02, y: em - editor.metrics.xHeight - em * 0.01, fill: "rgba(249,226,175,0.5)", fontSize: em * 0.03, children: t('fontEditor.xHeight2', 'x-height') }), jsx("text", { x: em * 0.02, y: em - editor.metrics.capHeight - em * 0.01, fill: "rgba(243,139,168,0.5)", fontSize: em * 0.03, children: t('fontEditor.cap2', 'cap') })] }), jsxs("div", { children: [jsx("label", { children: t('fontEditor.svgPath', 'SVG Path') }), jsx("textarea", { value: selectedG.path, onChange: (e) => editor.updateGlyph(selectedG.unicode, { path: e.target.value }), rows: 3, style: { ...fieldStyle, fontFamily: 'monospace', resize: 'vertical' } })] }), jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }, children: [jsx("label", { children: t('fontEditor.advanceWidth', 'Advance Width') }), jsx("input", { type: "number", value: selectedG.advanceWidth, onChange: (e) => editor.updateGlyph(selectedG.unicode, {
|
|
282
284
|
advanceWidth: Number(e.target.value),
|
|
283
|
-
}), style: fieldStyle }), jsx("label", { children:
|
|
285
|
+
}), style: fieldStyle }), jsx("label", { children: t('fontEditor.leftBearing', 'Left Bearing') }), jsx("input", { type: "number", value: selectedG.leftBearing, onChange: (e) => editor.updateGlyph(selectedG.unicode, { leftBearing: Number(e.target.value) }), style: fieldStyle })] })] })) : (jsx("div", { style: {
|
|
284
286
|
color: 'var(--nice-text-secondary, #868e96)',
|
|
285
287
|
padding: 20,
|
|
286
288
|
textAlign: 'center',
|
|
287
|
-
}, children:
|
|
289
|
+
}, children: t('fontEditor.selectAGlyphToEdit', 'Select a glyph to edit') })) })] })), tab === 'metrics' && (jsxs("div", { style: { maxWidth: 400 }, children: [jsx("strong", { style: { fontSize: 12, display: 'block', marginBottom: 8 }, children: t('fontEditor.fontMetrics', 'Font Metrics') }), [
|
|
288
290
|
['unitsPerEm', 'Units per Em'],
|
|
289
291
|
['ascent', 'Ascent'],
|
|
290
292
|
['descent', 'Descent'],
|
|
291
293
|
['lineGap', 'Line Gap'],
|
|
292
294
|
['xHeight', 'x-Height'],
|
|
293
295
|
['capHeight', 'Cap Height'],
|
|
294
|
-
].map(([key, label]) => (jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }, children: [jsx("span", { style: { minWidth: 100, fontSize: 11 }, children: label }), jsx("input", { type: "number", value: editor.metrics[key], onChange: (e) => editor.setMetrics({ ...editor.metrics, [key]: Number(e.target.value) }), style: fieldStyle })] }, key))), jsxs("svg", { width: 300, height: 200, viewBox: `0 0 ${em} ${em}`, style: { background: 'var(--nice-bg, #141517)', borderRadius: 4, marginTop: 12 }, children: [jsx("rect", { x: 0, y: em - editor.metrics.ascent, width: em, height: editor.metrics.ascent - editor.metrics.descent, fill: "rgba(51,154,240,0.05)", stroke: "rgba(51,154,240,0.2)", strokeWidth: em * 0.003 }), jsx("line", { x1: 0, y1: em - editor.metrics.ascent, x2: em, y2: em - editor.metrics.ascent, stroke: "var(--nice-info, #339af0)", strokeWidth: em * 0.004 }), jsx("line", { x1: 0, y1: em, x2: em, y2: em, stroke: "var(--nice-success, #2f9e44)", strokeWidth: em * 0.004 }), jsx("line", { x1: 0, y1: em + Math.abs(editor.metrics.descent), x2: em, y2: em + Math.abs(editor.metrics.descent), stroke: "#e03131", strokeWidth: em * 0.004 }), jsx("line", { x1: 0, y1: em - editor.metrics.xHeight, x2: em, y2: em - editor.metrics.xHeight, stroke: "#f9e2af", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.015}` }), jsx("line", { x1: 0, y1: em - editor.metrics.capHeight, x2: em, y2: em - editor.metrics.capHeight, stroke: "#f38ba8", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.015}` }),
|
|
296
|
+
].map(([key, label]) => (jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }, children: [jsx("span", { style: { minWidth: 100, fontSize: 11 }, children: label }), jsx("input", { type: "number", value: editor.metrics[key], onChange: (e) => editor.setMetrics({ ...editor.metrics, [key]: Number(e.target.value) }), style: fieldStyle })] }, key))), jsxs("svg", { width: 300, height: 200, viewBox: `0 0 ${em} ${em}`, style: { background: 'var(--nice-bg, #141517)', borderRadius: 4, marginTop: 12 }, children: [jsx("rect", { x: 0, y: em - editor.metrics.ascent, width: em, height: editor.metrics.ascent - editor.metrics.descent, fill: "rgba(51,154,240,0.05)", stroke: "rgba(51,154,240,0.2)", strokeWidth: em * 0.003 }), jsx("line", { x1: 0, y1: em - editor.metrics.ascent, x2: em, y2: em - editor.metrics.ascent, stroke: "var(--nice-info, #339af0)", strokeWidth: em * 0.004 }), jsx("line", { x1: 0, y1: em, x2: em, y2: em, stroke: "var(--nice-success, #2f9e44)", strokeWidth: em * 0.004 }), jsx("line", { x1: 0, y1: em + Math.abs(editor.metrics.descent), x2: em, y2: em + Math.abs(editor.metrics.descent), stroke: "#e03131", strokeWidth: em * 0.004 }), jsx("line", { x1: 0, y1: em - editor.metrics.xHeight, x2: em, y2: em - editor.metrics.xHeight, stroke: "#f9e2af", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.015}` }), jsx("line", { x1: 0, y1: em - editor.metrics.capHeight, x2: em, y2: em - editor.metrics.capHeight, stroke: "#f38ba8", strokeWidth: em * 0.003, strokeDasharray: `${em * 0.015}` }), jsx("text", { x: em * 0.02, y: em - editor.metrics.ascent + em * 0.05, fill: "var(--nice-info, #339af0)", fontSize: em * 0.04, children: t('fontEditor.ascent', 'Ascent ({ascent})').replace('{ascent}', String(editor.metrics.ascent)) }), jsx("text", { x: em * 0.02, y: em + em * 0.05, fill: "var(--nice-success, #2f9e44)", fontSize: em * 0.04, children: t('fontEditor.baseline', 'Baseline') }), jsx("text", { x: em * 0.02, y: em - editor.metrics.xHeight + em * 0.05, fill: "#f9e2af", fontSize: em * 0.04, children: t('fontEditor.xHeight', 'x-Height ({xHeight})').replace('{xHeight}', String(editor.metrics.xHeight)) }), jsx("text", { x: em * 0.02, y: em - editor.metrics.capHeight + em * 0.05, fill: "#f38ba8", fontSize: em * 0.04, children: t('fontEditor.cap', 'Cap ({capHeight})').replace('{capHeight}', String(editor.metrics.capHeight)) })] })] })), tab === 'kerning' && (jsxs("div", { style: { maxWidth: 500 }, children: [jsx("strong", { style: { fontSize: 12, display: 'block', marginBottom: 8 }, children: t('fontEditor.kerningPairs', 'Kerning Pairs') }), jsxs("div", { style: { display: 'flex', gap: 4, marginBottom: 8, alignItems: 'center', fontSize: 11 }, children: [jsx("input", { value: newKernLeft, onChange: (e) => setNewKernLeft(e.target.value), maxLength: 1, style: { ...fieldStyle, width: 30, textAlign: 'center' } }), jsx("input", { value: newKernRight, onChange: (e) => setNewKernRight(e.target.value), maxLength: 1, style: { ...fieldStyle, width: 30, textAlign: 'center' } }), jsx("button", { onClick: () => {
|
|
295
297
|
if (newKernLeft && newKernRight) {
|
|
296
298
|
setKerningPairs((prev) => [
|
|
297
299
|
...prev,
|
|
298
300
|
{ left: newKernLeft, right: newKernRight, value: -50 },
|
|
299
301
|
]);
|
|
300
302
|
}
|
|
301
|
-
}, style: btnStyle(false), children:
|
|
303
|
+
}, style: btnStyle(false), children: t('fontEditor.addPair', 'Add Pair') })] }), jsx("div", { style: { maxHeight: 200, overflowY: 'auto' }, children: kerningPairs.length === 0 ? (jsx("div", { style: {
|
|
302
304
|
color: 'var(--nice-text-secondary, #868e96)',
|
|
303
305
|
fontSize: 11,
|
|
304
306
|
padding: 8,
|
|
305
307
|
textAlign: 'center',
|
|
306
|
-
}, children:
|
|
308
|
+
}, children: t('fontEditor.noKerningPairsDefined', 'No kerning pairs defined') })) : (kerningPairs.map((kp, i) => (jsxs("div", { style: {
|
|
307
309
|
display: 'flex',
|
|
308
310
|
gap: 8,
|
|
309
311
|
alignItems: 'center',
|
|
@@ -314,7 +316,7 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
|
|
|
314
316
|
border: 'none',
|
|
315
317
|
color: 'var(--nice-danger, #ff6b6b)',
|
|
316
318
|
cursor: 'pointer',
|
|
317
|
-
}, "aria-label":
|
|
319
|
+
}, "aria-label": t('fontEditor.removeKerningPair', 'Remove kerning pair'), title: t('fontEditor.removeKerningPair', 'Remove kerning pair'), children: jsx(NiceIcon, { name: "close", size: 14 }) })] }, i)))) })] })), jsxs("div", { style: { marginTop: 8, borderTop: '1px solid var(--nice-border, #373a40)', paddingTop: 8 }, children: [jsx("div", { style: { fontSize: 11, color: 'var(--nice-text-secondary, #868e96)', marginBottom: 4 }, children: t('fontEditor.preview', 'Preview') }), jsx("input", { value: editor.previewText, onChange: (e) => editor.setPreviewText(e.target.value), style: { width: '100%', padding: '6px 8px', ...fieldStyle, fontSize: 12 } }), jsx("div", { style: {
|
|
318
320
|
marginTop: 8,
|
|
319
321
|
padding: 16,
|
|
320
322
|
background: 'var(--nice-bg, #fff)',
|