@nice2dev/ui-graphics 1.2.1 → 1.3.0
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 +464 -0
- package/dist/cjs/animation/AnimatedPerson.js +3 -1
- package/dist/cjs/core/NiceBrushEngine.js +5 -4
- package/dist/cjs/core/collaboration.js +11 -6
- package/dist/cjs/core/minimap.js +21 -20
- package/dist/cjs/font/NiceFontEditor.js +6 -6
- package/dist/cjs/game/GameAsset2dEditor.js +58 -39
- package/dist/cjs/photo/NiceAITools.js +3 -3
- package/dist/cjs/photo/PhotoEditorLeftPanel.js +16 -16
- package/dist/cjs/pixel/PixelEditor.js +1 -1
- package/dist/cjs/pixel/PixelEditorMenuBar.js +92 -27
- package/dist/cjs/pixel/PixelEditorRightPanel.js +9 -5
- package/dist/cjs/pixel/PixelEditorStatusBar.js +6 -2
- package/dist/cjs/pixel/PixelEditorTimeline.js +5 -1
- package/dist/cjs/pixel/PixelEditorToolbar.js +3 -1
- package/dist/cjs/pixel/SpriteGeneratorPanel.js +18 -11
- package/dist/cjs/texture/Nice3DTexturePainter.js +10 -6
- package/dist/cjs/ui/NiceUIDesigner.js +2 -2
- package/dist/cjs/vector/NiceSvgToCode.js +3 -3
- package/dist/cjs/vector/VectorEditor.js +1 -1
- package/dist/cjs/vector/VectorEditorMenuBar.js +7 -1
- package/dist/cjs/vector/VectorEditorRightPanel.js +3 -3
- package/dist/cjs/vector/useVectorEditor.js +26 -16
- package/dist/esm/animation/AnimatedPerson.js +3 -1
- package/dist/esm/core/NiceBrushEngine.js +5 -4
- package/dist/esm/core/collaboration.js +11 -6
- package/dist/esm/core/minimap.js +21 -20
- package/dist/esm/font/NiceFontEditor.js +7 -7
- package/dist/esm/game/GameAsset2dEditor.js +59 -40
- package/dist/esm/photo/NiceAITools.js +3 -3
- package/dist/esm/photo/PhotoEditorLeftPanel.js +17 -17
- package/dist/esm/pixel/PixelEditor.js +1 -1
- package/dist/esm/pixel/PixelEditorMenuBar.js +92 -27
- package/dist/esm/pixel/PixelEditorRightPanel.js +10 -6
- package/dist/esm/pixel/PixelEditorStatusBar.js +6 -2
- package/dist/esm/pixel/PixelEditorTimeline.js +6 -2
- package/dist/esm/pixel/PixelEditorToolbar.js +3 -1
- package/dist/esm/pixel/SpriteGeneratorPanel.js +18 -11
- package/dist/esm/texture/Nice3DTexturePainter.js +11 -7
- package/dist/esm/ui/NiceUIDesigner.js +3 -3
- package/dist/esm/vector/NiceSvgToCode.js +3 -3
- package/dist/esm/vector/VectorEditor.js +1 -1
- package/dist/esm/vector/VectorEditorMenuBar.js +7 -1
- package/dist/esm/vector/VectorEditorRightPanel.js +4 -4
- package/dist/esm/vector/useVectorEditor.js +26 -16
- package/package.json +4 -4
|
@@ -346,7 +346,7 @@ const Nice3DTexturePainter = ({ textureWidth = 1024, textureHeight = 1024, onSav
|
|
|
346
346
|
borderTop: '1px solid var(--nice-border, #373a40)',
|
|
347
347
|
width: '100%',
|
|
348
348
|
margin: '4px 0',
|
|
349
|
-
} }), jsxRuntime.
|
|
349
|
+
} }), jsxRuntime.jsx("label", { children: t('texturePainter3d.sizePx', 'Size: {size}px').replace('{size}', String(painter.brush.size)) }), jsxRuntime.jsx("input", { type: "range", min: 1, max: 100, value: painter.brush.size, onChange: (e) => painter.setBrush({ ...painter.brush, size: Number(e.target.value) }), style: { width: '100%' } }), jsxRuntime.jsx("label", { children: t('texturePainter3d.opacity', 'Opacity: {opacity}%').replace('{opacity}', String(Math.round(painter.brush.opacity * 100))) }), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: painter.brush.opacity, onChange: (e) => painter.setBrush({ ...painter.brush, opacity: Number(e.target.value) }), style: { width: '100%' } }), jsxRuntime.jsx("label", { children: t('texturePainter3d.hardness', 'Hardness: {hardness}%').replace('{hardness}', String(Math.round(painter.brush.hardness * 100))) }), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: painter.brush.hardness, onChange: (e) => painter.setBrush({ ...painter.brush, hardness: Number(e.target.value) }), style: { width: '100%' } }), jsxRuntime.jsx("label", { children: t('texturePainter3d.blendMode', 'Blend Mode') }), jsxRuntime.jsxs("select", { value: painter.brush.blendMode, onChange: (e) => painter.setBrush({
|
|
350
350
|
...painter.brush,
|
|
351
351
|
blendMode: e.target.value,
|
|
352
352
|
}), style: {
|
|
@@ -362,7 +362,7 @@ const Nice3DTexturePainter = ({ textureWidth = 1024, textureHeight = 1024, onSav
|
|
|
362
362
|
borderTop: '1px solid var(--nice-border, #373a40)',
|
|
363
363
|
width: '100%',
|
|
364
364
|
margin: '4px 0',
|
|
365
|
-
} }), jsxRuntime.
|
|
365
|
+
} }), jsxRuntime.jsx("label", { children: t('texturePainter3d.zoom', 'Zoom: {zoom}%').replace('{zoom}', String(Math.round(zoom * 100))) }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 2 }, children: [jsxRuntime.jsx("button", { onClick: () => setZoom((z) => Math.max(0.1, z - 0.1)), style: btnStyle(false), children: "\u2212" }), jsxRuntime.jsx("button", { onClick: () => setZoom(0.5), style: btnStyle(false), children: t('texturePainter3d.reset', 'Reset') }), jsxRuntime.jsx("button", { onClick: () => setZoom((z) => Math.min(4, z + 0.1)), style: btnStyle(false), children: "+" })] })] }), jsxRuntime.jsxs("div", { style: {
|
|
366
366
|
flex: 1,
|
|
367
367
|
overflow: 'hidden',
|
|
368
368
|
position: 'relative',
|
|
@@ -384,7 +384,7 @@ const Nice3DTexturePainter = ({ textureWidth = 1024, textureHeight = 1024, onSav
|
|
|
384
384
|
color: 'var(--nice-text-secondary, #868e96)',
|
|
385
385
|
display: 'flex',
|
|
386
386
|
gap: 12,
|
|
387
|
-
}, children: [jsxRuntime.jsxs("span", { children: [textureWidth, "\u00D7", textureHeight] }), cursorPos && (jsxRuntime.jsxs("span", { children: ["X: ", cursorPos.x, " Y: ", cursorPos.y] })), jsxRuntime.
|
|
387
|
+
}, children: [jsxRuntime.jsxs("span", { children: [textureWidth, "\u00D7", textureHeight] }), cursorPos && (jsxRuntime.jsxs("span", { children: ["X: ", cursorPos.x, " Y: ", cursorPos.y] })), jsxRuntime.jsx("span", { children: t('texturePainter3d.zoom', 'Zoom: {zoom}%').replace('{zoom}', String(Math.round(zoom * 100))) })] })] }), jsxRuntime.jsxs("div", { style: { width: 160, fontSize: 11, display: 'flex', flexDirection: 'column', gap: 4 }, children: [jsxRuntime.jsx("strong", { style: { fontSize: 12 }, children: t('texturePainter3d.layers', 'Layers') }), jsxRuntime.jsx("div", { style: { flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 2 }, children: [...painter.layers].reverse().map((l) => (jsxRuntime.jsxs("div", { style: {
|
|
388
388
|
padding: '4px 6px',
|
|
389
389
|
borderRadius: 3,
|
|
390
390
|
background: painter.activeLayerId === l.id
|
|
@@ -393,7 +393,7 @@ const Nice3DTexturePainter = ({ textureWidth = 1024, textureHeight = 1024, onSav
|
|
|
393
393
|
color: painter.activeLayerId === l.id
|
|
394
394
|
? 'var(--nice-color-white, #fff)'
|
|
395
395
|
: 'var(--nice-text, #c1c2c5)',
|
|
396
|
-
}, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' }, onClick: () => painter.setActiveLayerId(l.id), children: [jsxRuntime.jsx("button", { onClick: (e) => {
|
|
396
|
+
}, children: [jsxRuntime.jsxs("div", { role: "button", tabIndex: 0, onKeyDown: uiCore.clickOnActivationKey, style: { display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' }, onClick: () => painter.setActiveLayerId(l.id), children: [jsxRuntime.jsx("button", { onClick: (e) => {
|
|
397
397
|
e.stopPropagation();
|
|
398
398
|
painter.toggleLayerVisibility(l.id);
|
|
399
399
|
}, style: {
|
|
@@ -405,7 +405,11 @@ const Nice3DTexturePainter = ({ textureWidth = 1024, textureHeight = 1024, onSav
|
|
|
405
405
|
? 'var(--nice-text, #c1c2c5)'
|
|
406
406
|
: 'var(--nice-text-secondary, #5c5f66)',
|
|
407
407
|
fontSize: 12,
|
|
408
|
-
}, title: l.visible
|
|
408
|
+
}, title: l.visible
|
|
409
|
+
? t('texturePainter3d.hideLayer', 'Hide layer')
|
|
410
|
+
: t('texturePainter3d.showLayer', 'Show layer'), "aria-label": l.visible
|
|
411
|
+
? t('texturePainter3d.hideLayer', 'Hide layer')
|
|
412
|
+
: t('texturePainter3d.showLayer', 'Show layer'), children: jsxRuntime.jsx(uiCore.NiceIcon, { name: l.visible ? 'eye' : 'eye-off', size: 14 }) }), jsxRuntime.jsx("span", { style: {
|
|
409
413
|
flex: 1,
|
|
410
414
|
overflow: 'hidden',
|
|
411
415
|
textOverflow: 'ellipsis',
|
|
@@ -427,7 +431,7 @@ const Nice3DTexturePainter = ({ textureWidth = 1024, textureHeight = 1024, onSav
|
|
|
427
431
|
cursor: 'pointer',
|
|
428
432
|
background: 'var(--nice-bg-secondary, #2c2e33)',
|
|
429
433
|
color: 'var(--nice-text, #c1c2c5)',
|
|
430
|
-
}, children:
|
|
434
|
+
}, children: t('texturePainter3d.addLayer', '+ Add Layer') }), jsxRuntime.jsx("hr", { style: {
|
|
431
435
|
border: 'none',
|
|
432
436
|
borderTop: '1px solid var(--nice-border, #373a40)',
|
|
433
437
|
width: '100%',
|
|
@@ -375,7 +375,7 @@ const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
|
375
375
|
var _a, _b, _c, _d;
|
|
376
376
|
const isSelected = designer.selectedId === node.id;
|
|
377
377
|
const def = COMPONENT_DEFS.find((d) => d.type === node.type);
|
|
378
|
-
return (jsxRuntime.jsxs("div", { onClick: (e) => {
|
|
378
|
+
return (jsxRuntime.jsxs("div", { role: "presentation", onClick: (e) => {
|
|
379
379
|
e.stopPropagation();
|
|
380
380
|
designer.setSelectedId(node.id);
|
|
381
381
|
}, onDrop: (e) => ((def === null || def === void 0 ? void 0 : def.allowChildren) ? handleDrop(e, node.id) : undefined), onDragOver: (def === null || def === void 0 ? void 0 : def.allowChildren) ? handleDragOver : undefined, style: {
|
|
@@ -415,7 +415,7 @@ const NiceUIDesigner = ({ onChange, onExport, className = '', tutorial, }) => {
|
|
|
415
415
|
};
|
|
416
416
|
const renderTreeItem = (node, depth = 0) => {
|
|
417
417
|
const def = COMPONENT_DEFS.find((d) => d.type === node.type);
|
|
418
|
-
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsxs("div", { onClick: () => designer.setSelectedId(node.id), style: {
|
|
418
|
+
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsxs("div", { role: "button", tabIndex: 0, onKeyDown: uiCore.clickOnActivationKey, onClick: () => designer.setSelectedId(node.id), style: {
|
|
419
419
|
padding: '2px 4px',
|
|
420
420
|
paddingLeft: 4 + depth * 12,
|
|
421
421
|
fontSize: 10,
|
|
@@ -439,14 +439,14 @@ const NiceSvgToCode = ({ initialSvg = '', onConvert, className = '', }) => {
|
|
|
439
439
|
navigator.clipboard.writeText(result.code);
|
|
440
440
|
}
|
|
441
441
|
};
|
|
442
|
-
return (jsxRuntime.jsxs("div", { className: `nice-svg-to-code ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "refresh", size: 14 }), " SVG
|
|
442
|
+
return (jsxRuntime.jsxs("div", { className: `nice-svg-to-code ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "refresh", size: 14 }), " ", t('svgToCode.svgCode', 'SVG → Code')] }), jsxRuntime.jsx("textarea", { value: svgInput, onChange: (e) => setSvgInput(e.target.value), placeholder: t('svgToCode.pasteSvgMarkupHere', 'Paste SVG markup here…'), rows: 6, style: {
|
|
443
443
|
width: '100%',
|
|
444
444
|
fontFamily: 'monospace',
|
|
445
445
|
fontSize: 11,
|
|
446
446
|
padding: 6,
|
|
447
447
|
borderRadius: 4,
|
|
448
448
|
border: '1px solid var(--nice-border, #e2e8f0)',
|
|
449
|
-
} }), jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [
|
|
449
|
+
} }), jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('svgToCode.framework', 'Framework:'), jsxRuntime.jsxs("select", { value: config.framework, onChange: (e) => updateConfig({ framework: e.target.value }), style: { width: '100%' }, children: [jsxRuntime.jsx("option", { value: "react", children: "React (JSX/TSX)" }), jsxRuntime.jsx("option", { value: "vue", children: "Vue (SFC)" }), jsxRuntime.jsx("option", { value: "web-component", children: t('svgToCode.webComponent', 'Web Component') }), jsxRuntime.jsx("option", { value: "typescript", children: t('svgToCode.typescriptUtility', 'TypeScript (utility)') })] })] }), jsxRuntime.jsxs("label", { children: [t('svgToCode.componentName', 'Component name:'), jsxRuntime.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' } })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 8, flexWrap: 'wrap', fontSize: 11 }, children: [jsxRuntime.jsxs("label", { children: [jsxRuntime.jsx("input", { type: "checkbox", checked: config.typescript, onChange: (e) => updateConfig({ typescript: e.target.checked }) }), ' ', t('svgToCode.typescript', 'TypeScript')] }), jsxRuntime.jsxs("label", { children: [jsxRuntime.jsx("input", { type: "checkbox", checked: config.optimize, onChange: (e) => updateConfig({ optimize: e.target.checked }) }), ' ', t('svgToCode.optimize', 'Optimize')] }), jsxRuntime.jsxs("label", { children: [jsxRuntime.jsx("input", { type: "checkbox", checked: config.sizeProps, onChange: (e) => updateConfig({ sizeProps: e.target.checked }) }), ' ', t('svgToCode.sizeProps', 'Size props')] }), jsxRuntime.jsxs("label", { children: [jsxRuntime.jsx("input", { type: "checkbox", checked: config.useCurrentColor, onChange: (e) => updateConfig({ useCurrentColor: e.target.checked }) }), ' ', "currentColor"] }), jsxRuntime.jsxs("label", { children: [jsxRuntime.jsx("input", { type: "checkbox", checked: config.memo, onChange: (e) => updateConfig({ memo: e.target.checked }) }), ' ', "memo"] })] })] }), jsxRuntime.jsxs("button", { type: "button", onClick: handleConvert, disabled: !svgInput.trim(), style: {
|
|
450
450
|
background: 'var(--nice-color-accent, #6366f1)',
|
|
451
451
|
color: 'var(--nice-text-on-primary, #fff)',
|
|
452
452
|
border: 'none',
|
|
@@ -474,7 +474,7 @@ const NiceSvgToCode = ({ initialSvg = '', onConvert, className = '', }) => {
|
|
|
474
474
|
overflow: 'auto',
|
|
475
475
|
maxHeight: 300,
|
|
476
476
|
whiteSpace: 'pre-wrap',
|
|
477
|
-
}, children: result.code }), result.extractedColors.length > 0 && (jsxRuntime.jsxs("div", { style: { fontSize: 11, marginTop: 4 }, children: [
|
|
477
|
+
}, children: result.code }), result.extractedColors.length > 0 && (jsxRuntime.jsxs("div", { style: { fontSize: 11, marginTop: 4 }, children: [t('svgToCode.colors', 'Colors:'), ' ', result.extractedColors.map((c) => (jsxRuntime.jsx("span", { style: {
|
|
478
478
|
display: 'inline-block',
|
|
479
479
|
width: 12,
|
|
480
480
|
height: 12,
|
|
@@ -112,7 +112,7 @@ const NiceVectorEditor = ({ width: artW = 512, height: artH = 512, className, on
|
|
|
112
112
|
}
|
|
113
113
|
const node = (jsxRuntime.jsx(VectorEditorShapeRenderer.ShapeRenderer, { shape: s, selected: s.id === selId, zoom: zoom }, s.id));
|
|
114
114
|
return previewTr ? (jsxRuntime.jsx("g", { transform: previewTr, children: node }, s.id)) : (node);
|
|
115
|
-
})] }) }) }), jsxRuntime.jsxs(ui.NiceModal, { open: dlgSvg, onClose: () => setDlgSvg(false), title: t('vectorEditor.pasteSvg', 'Paste SVG'), children: [jsxRuntime.jsx("label", { children:
|
|
115
|
+
})] }) }) }), jsxRuntime.jsxs(ui.NiceModal, { open: dlgSvg, onClose: () => setDlgSvg(false), title: t('vectorEditor.pasteSvg', 'Paste SVG'), children: [jsxRuntime.jsx("label", { children: t('vectorEditor.pasteSvgSourceCodeBelow', '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: () => {
|
|
116
116
|
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
117
117
|
navigator.clipboard.writeText(snippetText).catch(() => { });
|
|
118
118
|
}
|
|
@@ -264,7 +264,13 @@ const VectorEditorMenuBar = ({ api, hasOnSaveToLibrary, onExportIconSnippet }) =
|
|
|
264
264
|
{ value: '', label: t('vectorEditor.menu.secondShape', '2nd Shape') },
|
|
265
265
|
...shapes
|
|
266
266
|
.filter((s) => s.id !== selId)
|
|
267
|
-
.map((s, i) => {
|
|
267
|
+
.map((s, i) => {
|
|
268
|
+
var _a;
|
|
269
|
+
return ({
|
|
270
|
+
value: s.id,
|
|
271
|
+
label: (_a = s.name) !== null && _a !== void 0 ? _a : t('vectorEditor.shape', 'Shape {i}').replace('{i}', String(i + 1)),
|
|
272
|
+
});
|
|
273
|
+
}),
|
|
268
274
|
], style: { minWidth: 100 } })), jsxRuntime.jsxs("label", { className: VectorEditor_module.menuLabel, children: [jsxRuntime.jsx(ui.NiceCheckbox, { checked: snapGrid, onChange: () => setSnapGrid((v) => !v) }), t('vectorEditor.menu.snap', 'Snap')] }), onExportIconSnippet && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: VectorEditor_module.menuSep }), jsxRuntime.jsxs(ui.NiceButton, { className: VectorEditor_module.menuBtn, variant: "primary", size: "sm", onClick: onExportIconSnippet, children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "clipboard", size: 14 }), ' ', t('vectorEditor.menu.exportIcon', 'Export Icon')] })] }))] }));
|
|
269
275
|
};
|
|
270
276
|
var VectorEditorMenuBar$1 = React.memo(VectorEditorMenuBar);
|
|
@@ -37,7 +37,7 @@ const VectorEditorRightPanel = ({ api, library, artW = 512, artH = 512 }) => {
|
|
|
37
37
|
} }) }), jsxRuntime.jsx(ui.NiceTextInput, { className: VectorEditor_module.propInput, value: (selShape === null || selShape === void 0 ? void 0 : selShape.fill) || fillCol, onChange: (val) => {
|
|
38
38
|
setFillCol(val);
|
|
39
39
|
updateSel({ fill: val });
|
|
40
|
-
} })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: VectorEditor_module.propRow, style: { flexWrap: 'wrap', gap: 4 }, children: vectorGradients.PRESET_GRADIENTS.map((grad, i) => (jsxRuntime.jsx("div", { style: {
|
|
40
|
+
} })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: VectorEditor_module.propRow, style: { flexWrap: 'wrap', gap: 4 }, children: vectorGradients.PRESET_GRADIENTS.map((grad, i) => (jsxRuntime.jsx("div", { role: "button", tabIndex: 0, onKeyDown: uiCore.clickOnActivationKey, style: {
|
|
41
41
|
width: 24,
|
|
42
42
|
height: 24,
|
|
43
43
|
borderRadius: 4,
|
|
@@ -46,7 +46,7 @@ const VectorEditorRightPanel = ({ api, library, artW = 512, artH = 512 }) => {
|
|
|
46
46
|
border: (selectedGradient === null || selectedGradient === void 0 ? void 0 : selectedGradient.id) === grad.id
|
|
47
47
|
? '2px solid var(--nice-bg, #FFF)'
|
|
48
48
|
: '1px solid var(--nice-border-color, #444)',
|
|
49
|
-
}, onClick: () => applyGradient(grad), title:
|
|
49
|
+
}, onClick: () => applyGradient(grad), title: t('vectorEditor.gradient', '{type} gradient').replace('{type}', String(grad.type)) }, i))) }), jsxRuntime.jsx("div", { className: VectorEditor_module.propRow, children: jsxRuntime.jsxs(ui.NiceButton, { className: VectorEditor_module.menuBtn, variant: "ghost", size: "sm", style: { width: '100%', fontSize: 10 }, onClick: () => setShowGradientEditor(!showGradientEditor), children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: showGradientEditor ? 'chevron-down' : 'chevron-right', size: 14 }), ' ', showGradientEditor
|
|
50
50
|
? t('vectorEditor.properties.hideEditor', 'Hide Editor')
|
|
51
51
|
: t('vectorEditor.properties.editGradient', 'Edit Gradient')] }) }), showGradientEditor && selectedGradient && (jsxRuntime.jsxs("div", { style: {
|
|
52
52
|
padding: 4,
|
|
@@ -233,7 +233,7 @@ const VectorEditorRightPanel = ({ api, library, artW = 512, artH = 512 }) => {
|
|
|
233
233
|
var _a;
|
|
234
234
|
const realIdx = shapes.length - 1 - revIdx;
|
|
235
235
|
const active = s.id === selId;
|
|
236
|
-
return (jsxRuntime.jsxs("div", { className: `${VectorEditor_module.layerRow} ${active ? VectorEditor_module.layerRowActive : ''} ${s.locked ? VectorEditor_module.layerRowLocked : ''}`, onClick: () => {
|
|
236
|
+
return (jsxRuntime.jsxs("div", { role: "button", tabIndex: 0, onKeyDown: uiCore.clickOnActivationKey, className: `${VectorEditor_module.layerRow} ${active ? VectorEditor_module.layerRowActive : ''} ${s.locked ? VectorEditor_module.layerRowLocked : ''}`, onClick: () => {
|
|
237
237
|
setSelId(s.id);
|
|
238
238
|
setTool('pointer');
|
|
239
239
|
}, children: [jsxRuntime.jsx("button", { type: "button", className: VectorEditor_module.layerIconBtn, title: s.hidden
|
|
@@ -94,8 +94,12 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
|
94
94
|
if (!r) {
|
|
95
95
|
return { x: 0, y: 0 };
|
|
96
96
|
}
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
// The <svg> fills the box and its viewBox fits the artboard into it (xMidYMid meet), so one
|
|
98
|
+
// art unit is `fit` screen px before the zoom. Dividing by the zoom alone put every shape
|
|
99
|
+
// closer to the centre than the pointer — off by half in the 900×520 showcase demo.
|
|
100
|
+
const fit = Math.min(r.width / artW, r.height / artH) || 1;
|
|
101
|
+
let x = (cx - r.left - r.width / 2 - px) / (zoom * fit) + artW / 2;
|
|
102
|
+
let y = (cy - r.top - r.height / 2 - py) / (zoom * fit) + artH / 2;
|
|
99
103
|
if (snapGrid) {
|
|
100
104
|
x = vectorEditorTypes.snapToGrid(x, gridSize);
|
|
101
105
|
y = vectorEditorTypes.snapToGrid(y, gridSize);
|
|
@@ -333,32 +337,37 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
|
333
337
|
}));
|
|
334
338
|
return;
|
|
335
339
|
}
|
|
336
|
-
|
|
340
|
+
const pending = pendingShape.current;
|
|
341
|
+
if (!pending) {
|
|
337
342
|
return;
|
|
338
343
|
}
|
|
339
|
-
|
|
344
|
+
// The pending shape's ref follows the drag as well. onUp sizes the result from the ref,
|
|
345
|
+
// which kept the 0×0 shape of the press — so every dragged rectangle, ellipse, line and
|
|
346
|
+
// arrow counted as "tiny" and was deleted on release: nothing appeared on the canvas.
|
|
347
|
+
const resize = (patch) => {
|
|
348
|
+
pendingShape.current = { ...pending, ...patch };
|
|
349
|
+
setShapes((prev) => prev.map((s) => (s.id === pending.id ? { ...s, ...patch } : s)));
|
|
350
|
+
};
|
|
351
|
+
const t = pending.type;
|
|
340
352
|
if (t === 'polygon' || t === 'star') {
|
|
341
353
|
const cx = startPt.current.x;
|
|
342
354
|
const cy = startPt.current.y;
|
|
343
355
|
const dx = p.x - cx;
|
|
344
356
|
const dy = p.y - cy;
|
|
345
357
|
const r2 = Math.sqrt(dx * dx + dy * dy);
|
|
346
|
-
|
|
347
|
-
? { ...s, x: cx - r2, y: cy - r2, w: r2 * 2, h: r2 * 2 }
|
|
348
|
-
: s));
|
|
358
|
+
resize({ x: cx - r2, y: cy - r2, w: r2 * 2, h: r2 * 2 });
|
|
349
359
|
return;
|
|
350
360
|
}
|
|
351
361
|
if (t === 'rect' || t === 'roundrect' || t === 'ellipse') {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
362
|
+
resize({
|
|
363
|
+
x: Math.min(startPt.current.x, p.x),
|
|
364
|
+
y: Math.min(startPt.current.y, p.y),
|
|
365
|
+
w: Math.abs(p.x - startPt.current.x),
|
|
366
|
+
h: Math.abs(p.y - startPt.current.y),
|
|
367
|
+
});
|
|
357
368
|
}
|
|
358
369
|
else if (t === 'line' || t === 'arrow') {
|
|
359
|
-
|
|
360
|
-
? { ...s, w: Math.abs(p.x - s.x), h: Math.abs(p.y - s.y), x2: p.x, y2: p.y }
|
|
361
|
-
: s));
|
|
370
|
+
resize({ w: Math.abs(p.x - pending.x), h: Math.abs(p.y - pending.y), x2: p.x, y2: p.y });
|
|
362
371
|
}
|
|
363
372
|
}, [tool, scr2art, selId]);
|
|
364
373
|
const onUp = React.useCallback(() => {
|
|
@@ -408,7 +417,8 @@ function useVectorEditor({ artW, artH, onSaveToLibrary }) {
|
|
|
408
417
|
dragStarted.current = false;
|
|
409
418
|
return;
|
|
410
419
|
}
|
|
411
|
-
|
|
420
|
+
// the ref holds the final geometry even when the last move has not rendered yet
|
|
421
|
+
snap(shapesRef.current.map((s) => (s.id === ps.id ? ps : s)));
|
|
412
422
|
setSelId(ps.id);
|
|
413
423
|
pendingShape.current = null;
|
|
414
424
|
dragStarted.current = false;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { motion } from 'framer-motion';
|
|
3
3
|
import { useMemo, useEffect } from 'react';
|
|
4
|
+
import { useNiceTranslation } from '../core/i18n.js';
|
|
4
5
|
import { usePersonControls, playPose } from './animationHelper.js';
|
|
5
6
|
import { DEFAULT_CHARACTER, pick } from './characterTypes.js';
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ const NiceAnimatedPerson = ({ id, name, size, score = 10, startPose = 'idle', on
|
|
|
9
10
|
// colors prop is destructured for API completeness but not yet fully wired into character defaults.
|
|
10
11
|
}) => {
|
|
11
12
|
var _a, _b, _c, _d, _e;
|
|
13
|
+
const { t } = useNiceTranslation();
|
|
12
14
|
const fx = usePersonControls();
|
|
13
15
|
const effectiveStartPose = pose !== null && pose !== void 0 ? pose : startPose;
|
|
14
16
|
const effectiveCharacter = character !== null && character !== void 0 ? character : config;
|
|
@@ -20,7 +22,7 @@ const NiceAnimatedPerson = ({ id, name, size, score = 10, startPose = 'idle', on
|
|
|
20
22
|
}, [fx, onReady, effectiveStartPose]);
|
|
21
23
|
const W = (_c = (_b = (_a = cfg.size) !== null && _a !== void 0 ? _a : effectiveSize) !== null && _b !== void 0 ? _b : DEFAULT_CHARACTER.size) !== null && _c !== void 0 ? _c : 180;
|
|
22
24
|
const skin = pick(cfg.face.colors, 0, 'var(--anim-skin, #FFD2B3)');
|
|
23
|
-
return (jsx(motion.div, { id: id, style: { width: W, height: W * 1.35, overflow: 'visible' }, animate: fx.root, children: jsxs("svg", { viewBox: "0 0 180 240", width: "100%", height: "100%", role: "img", "aria-label": (_e = (_d = cfg.name) !== null && _d !== void 0 ? _d : name) !== null && _e !== void 0 ? _e : 'Juror', children: [jsxs(motion.g, { animate: fx.body, children: [jsx("rect", { x: "68", y: "158", width: "14", height: "46", rx: "6", fill: pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg, #334155)') }), jsx("ellipse", { cx: "75", cy: "204", rx: "10", ry: "5", fill: pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg-shadow, #1E293B)') }), jsx("rect", { x: "98", y: "158", width: "14", height: "46", rx: "6", fill: pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg, #334155)') }), jsx("ellipse", { cx: "105", cy: "204", rx: "10", ry: "5", fill: pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg-shadow, #1E293B)') })] }), jsx(motion.g, { animate: fx.body, children: renderOutfit(cfg.outfit) }), jsx("rect", { x: "84", y: "82", width: "12", height: "12", rx: "4", fill: skin }), jsxs(motion.g, { animate: fx.leftArm, style: { transformOrigin: '64px 96px' }, children: [jsx("rect", { x: "36", y: "96", width: "16", height: "36", rx: "7", fill: skin }), jsx("circle", { cx: "44", cy: "136", r: "7", fill: skin })] }), jsxs(motion.g, { animate: fx.rightArm, style: { transformOrigin: '116px 96px' }, children: [jsx("rect", { x: "128", y: "96", width: "16", height: "36", rx: "7", fill: skin }), jsx("circle", { cx: "136", cy: "136", r: "7", fill: skin }), jsxs(motion.g, { animate: fx.card, children: [jsx("rect", { x: "126", y: "50", width: "42", height: "30", rx: "4", fill: "var(--nice-color-surface, #fff)", stroke: "var(--nice-text-primary, #111)", strokeWidth: 1.2 }), jsx("text", { x: "147", y: "70", textAnchor: "middle", fontSize: "16", fontWeight: 700, fill: "var(--nice-text-primary, #111)", children: String(score) })] }), renderProp(cfg.prop)] }), jsxs(motion.g, { animate: fx.head, children: [renderHairBack(cfg.hair), renderFace(cfg.face), jsx(motion.g, { animate: fx.eyes, children: renderEyes(cfg.eyes) }), renderNose(cfg.nose), jsx(motion.g, { animate: fx.mouth, children: renderMouth(cfg.mouth) }), renderHeadwear(cfg.headwear)] }), jsxs(motion.g, { animate: fx.curse, initial: { opacity: 0 }, children: [jsx("rect", { x: "62", y: "10", width: "56", height: "22", rx: "8", fill: "var(--nice-text-primary, #111)" }), jsx("text", { x: "90", y: "26", textAnchor: "middle", fontSize: "12", fill: "var(--nice-text-inverse, #fff)", children: "#!%&*" }), jsx("path", { d: "M82 32 l4 8 l4 -8", fill: "var(--nice-text-primary, #111)" })] })] }) }));
|
|
25
|
+
return (jsx(motion.div, { id: id, style: { width: W, height: W * 1.35, overflow: 'visible' }, animate: fx.root, children: jsxs("svg", { viewBox: "0 0 180 240", width: "100%", height: "100%", role: "img", "aria-label": (_e = (_d = cfg.name) !== null && _d !== void 0 ? _d : name) !== null && _e !== void 0 ? _e : t('animatedPerson.juror', 'Juror'), children: [jsxs(motion.g, { animate: fx.body, children: [jsx("rect", { x: "68", y: "158", width: "14", height: "46", rx: "6", fill: pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg, #334155)') }), jsx("ellipse", { cx: "75", cy: "204", rx: "10", ry: "5", fill: pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg-shadow, #1E293B)') }), jsx("rect", { x: "98", y: "158", width: "14", height: "46", rx: "6", fill: pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg, #334155)') }), jsx("ellipse", { cx: "105", cy: "204", rx: "10", ry: "5", fill: pick(cfg.outfit.colors, 1, 'var(--anim-outfit-leg-shadow, #1E293B)') })] }), jsx(motion.g, { animate: fx.body, children: renderOutfit(cfg.outfit) }), jsx("rect", { x: "84", y: "82", width: "12", height: "12", rx: "4", fill: skin }), jsxs(motion.g, { animate: fx.leftArm, style: { transformOrigin: '64px 96px' }, children: [jsx("rect", { x: "36", y: "96", width: "16", height: "36", rx: "7", fill: skin }), jsx("circle", { cx: "44", cy: "136", r: "7", fill: skin })] }), jsxs(motion.g, { animate: fx.rightArm, style: { transformOrigin: '116px 96px' }, children: [jsx("rect", { x: "128", y: "96", width: "16", height: "36", rx: "7", fill: skin }), jsx("circle", { cx: "136", cy: "136", r: "7", fill: skin }), jsxs(motion.g, { animate: fx.card, children: [jsx("rect", { x: "126", y: "50", width: "42", height: "30", rx: "4", fill: "var(--nice-color-surface, #fff)", stroke: "var(--nice-text-primary, #111)", strokeWidth: 1.2 }), jsx("text", { x: "147", y: "70", textAnchor: "middle", fontSize: "16", fontWeight: 700, fill: "var(--nice-text-primary, #111)", children: String(score) })] }), renderProp(cfg.prop)] }), jsxs(motion.g, { animate: fx.head, children: [renderHairBack(cfg.hair), renderFace(cfg.face), jsx(motion.g, { animate: fx.eyes, children: renderEyes(cfg.eyes) }), renderNose(cfg.nose), jsx(motion.g, { animate: fx.mouth, children: renderMouth(cfg.mouth) }), renderHeadwear(cfg.headwear)] }), jsxs(motion.g, { animate: fx.curse, initial: { opacity: 0 }, children: [jsx("rect", { x: "62", y: "10", width: "56", height: "22", rx: "8", fill: "var(--nice-text-primary, #111)" }), jsx("text", { x: "90", y: "26", textAnchor: "middle", fontSize: "12", fill: "var(--nice-text-inverse, #fff)", children: "#!%&*" }), jsx("path", { d: "M82 32 l4 8 l4 -8", fill: "var(--nice-text-primary, #111)" })] })] }) }));
|
|
24
26
|
};
|
|
25
27
|
/* ---------------- RENDERERS (redesigned proportions) ---------------- */
|
|
26
28
|
function renderOutfit(cfg) {
|
|
@@ -544,20 +544,21 @@ color = '#000000') {
|
|
|
544
544
|
const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '', }) => {
|
|
545
545
|
const { t } = useNiceTranslation();
|
|
546
546
|
const engine = useBrushEngine(canvas, color);
|
|
547
|
+
const { endStroke } = engine;
|
|
547
548
|
// Override endStroke to fire callback
|
|
548
549
|
useEffect(() => {
|
|
549
550
|
if (!canvas) {
|
|
550
551
|
return;
|
|
551
552
|
}
|
|
552
553
|
const onUp = () => {
|
|
553
|
-
const stroke =
|
|
554
|
+
const stroke = endStroke();
|
|
554
555
|
if (stroke && onStrokeEnd) {
|
|
555
556
|
onStrokeEnd(stroke);
|
|
556
557
|
}
|
|
557
558
|
};
|
|
558
559
|
canvas.addEventListener('pointerup', onUp);
|
|
559
560
|
return () => canvas.removeEventListener('pointerup', onUp);
|
|
560
|
-
}, [canvas,
|
|
561
|
+
}, [canvas, endStroke, onStrokeEnd]);
|
|
561
562
|
const s = engine.settings;
|
|
562
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) => {
|
|
563
564
|
var _a, _b;
|
|
@@ -572,13 +573,13 @@ const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '
|
|
|
572
573
|
: 'transparent',
|
|
573
574
|
color: s.name === ((_b = BRUSH_PRESETS[name]) === null || _b === void 0 ? void 0 : _b.name) ? 'var(--nice-color-white, #fff)' : 'inherit',
|
|
574
575
|
}, children: BRUSH_PRESETS[name].name }, name));
|
|
575
|
-
}) }), 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({
|
|
576
577
|
...s,
|
|
577
578
|
sizeDynamics: {
|
|
578
579
|
...s.sizeDynamics,
|
|
579
580
|
source: e.target.value,
|
|
580
581
|
},
|
|
581
|
-
}), 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')] })] }));
|
|
582
583
|
};
|
|
583
584
|
|
|
584
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 () => {
|
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);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { NiceTutorialButton, resolveTutorial } from '@nice2dev/ui';
|
|
3
|
-
import { NiceIcon } from '@nice2dev/ui-core';
|
|
3
|
+
import { clickOnActivationKey, NiceIcon } from '@nice2dev/ui-core';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { useNiceTranslation } from '../core/i18n.js';
|
|
6
6
|
|
|
@@ -237,17 +237,17 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
|
|
|
237
237
|
borderRadius: 3,
|
|
238
238
|
cursor: 'pointer',
|
|
239
239
|
fontSize: 11,
|
|
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: [
|
|
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: {
|
|
241
241
|
fontSize: 11,
|
|
242
242
|
marginBottom: 4,
|
|
243
243
|
color: 'var(--nice-text-secondary, #868e96)',
|
|
244
|
-
}, children:
|
|
244
|
+
}, children: t('fontEditor.glyphs2', '{glyphs} glyphs').replace('{glyphs}', String(editor.glyphs.length)) }), jsx("div", { style: {
|
|
245
245
|
display: 'grid',
|
|
246
246
|
gridTemplateColumns: 'repeat(auto-fill, 36px)',
|
|
247
247
|
gap: 2,
|
|
248
248
|
maxHeight: 300,
|
|
249
249
|
overflowY: 'auto',
|
|
250
|
-
}, children: editor.glyphs.map((g) => (jsx("div", { onClick: () => {
|
|
250
|
+
}, children: editor.glyphs.map((g) => (jsx("div", { role: "button", tabIndex: 0, "aria-pressed": editor.selectedGlyph === g.unicode, onKeyDown: clickOnActivationKey, onClick: () => {
|
|
251
251
|
editor.setSelectedGlyph(g.unicode);
|
|
252
252
|
setPathPoints([]);
|
|
253
253
|
}, title: `U+${g.unicode.toString(16).toUpperCase().padStart(4, '0')} - ${g.name}`, style: {
|
|
@@ -272,7 +272,7 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
|
|
|
272
272
|
border: g.path
|
|
273
273
|
? '1px solid var(--nice-border, #373a40)'
|
|
274
274
|
: '1px solid transparent',
|
|
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:
|
|
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: {
|
|
276
276
|
background: 'var(--nice-bg, #141517)',
|
|
277
277
|
borderRadius: 4,
|
|
278
278
|
cursor: drawMode === 'pen' ? 'crosshair' : 'default',
|
|
@@ -280,7 +280,7 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
|
|
|
280
280
|
Array.from({ length: 11 }, (_, i) => {
|
|
281
281
|
const pos = (i / 10) * em;
|
|
282
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));
|
|
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:
|
|
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, {
|
|
284
284
|
advanceWidth: Number(e.target.value),
|
|
285
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: {
|
|
286
286
|
color: 'var(--nice-text-secondary, #868e96)',
|
|
@@ -293,7 +293,7 @@ const NiceFontEditor = ({ fontName = 'MyFont', onSave, className = '', tutorial,
|
|
|
293
293
|
['lineGap', 'Line Gap'],
|
|
294
294
|
['xHeight', 'x-Height'],
|
|
295
295
|
['capHeight', 'Cap Height'],
|
|
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}` }),
|
|
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: () => {
|
|
297
297
|
if (newKernLeft && newKernRight) {
|
|
298
298
|
setKerningPairs((prev) => [
|
|
299
299
|
...prev,
|