canvu-react 0.3.26 → 0.3.28
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/dist/react.cjs +87 -29
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +87 -29
- package/dist/react.js.map +1 -1
- package/dist/realtime.cjs +190 -56
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.js +186 -56
- package/dist/realtime.js.map +1 -1
- package/package.json +1 -1
package/dist/realtime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MousePointer2, MessageSquare, Sparkles, Hand, Square, Circle, Minus, ArrowUpRight, PenLine, Highlighter, Eraser, Type, Image } from 'lucide-react';
|
|
2
|
+
import getStroke from 'perfect-freehand';
|
|
2
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
4
|
import { createContext, useState, useRef, useEffect, useMemo, useCallback, useLayoutEffect, useContext } from 'react';
|
|
4
5
|
|
|
@@ -71,6 +72,126 @@ function smoothFreehandPointsToPathD(points) {
|
|
|
71
72
|
return d;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// src/scene/custom-shape.ts
|
|
76
|
+
function expandCustomShapeTemplate(template, width, height) {
|
|
77
|
+
return template.replace(/\{\{w\}\}/g, String(width)).replace(/\{\{h\}\}/g, String(height)).replace(/\{\{width\}\}/g, String(width)).replace(/\{\{height\}\}/g, String(height));
|
|
78
|
+
}
|
|
79
|
+
function resolveCustomInner(content, size) {
|
|
80
|
+
if ("render" in content) {
|
|
81
|
+
return content.render(size);
|
|
82
|
+
}
|
|
83
|
+
return expandCustomShapeTemplate(content.svg, size.width, size.height);
|
|
84
|
+
}
|
|
85
|
+
function buildCustomShapeChildrenSvg(inner, intrinsic, bounds) {
|
|
86
|
+
const b = normalizeRect(bounds);
|
|
87
|
+
const sx = b.width / intrinsic.width;
|
|
88
|
+
const sy = b.height / intrinsic.height;
|
|
89
|
+
return `<g transform="scale(${sx},${sy})">${inner}</g>`;
|
|
90
|
+
}
|
|
91
|
+
function createCustomShapeItem(id, bounds, content) {
|
|
92
|
+
const r = normalizeRect(bounds);
|
|
93
|
+
const intrinsic = { width: r.width, height: r.height };
|
|
94
|
+
const inner = resolveCustomInner(content, intrinsic);
|
|
95
|
+
return {
|
|
96
|
+
id,
|
|
97
|
+
x: r.x,
|
|
98
|
+
y: r.y,
|
|
99
|
+
bounds: { ...r },
|
|
100
|
+
toolKind: "custom",
|
|
101
|
+
customIntrinsicSize: intrinsic,
|
|
102
|
+
customInnerSvg: inner,
|
|
103
|
+
childrenSvg: buildCustomShapeChildrenSvg(inner, intrinsic, r)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/scene/shape-builders.ts
|
|
108
|
+
function perfectFreehandOptions(toolKind, style, strokeComplete, pressureAware = false) {
|
|
109
|
+
const sw = style.strokeWidth;
|
|
110
|
+
const base2 = {
|
|
111
|
+
last: strokeComplete,
|
|
112
|
+
simulatePressure: true
|
|
113
|
+
};
|
|
114
|
+
if (toolKind === "draw" || toolKind === "pencil") {
|
|
115
|
+
if (pressureAware && toolKind === "draw") {
|
|
116
|
+
return {
|
|
117
|
+
...base2,
|
|
118
|
+
size: Math.max(2, sw * 1.05),
|
|
119
|
+
thinning: 0.42,
|
|
120
|
+
smoothing: 0.78,
|
|
121
|
+
streamline: 0.62,
|
|
122
|
+
simulatePressure: true
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
...base2,
|
|
127
|
+
size: Math.max(2, sw * 1.18),
|
|
128
|
+
thinning: 0.12,
|
|
129
|
+
smoothing: 0.85,
|
|
130
|
+
streamline: 0.78,
|
|
131
|
+
simulatePressure: true
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (toolKind === "brush") {
|
|
135
|
+
return {
|
|
136
|
+
...base2,
|
|
137
|
+
size: Math.max(4, sw * 1.22),
|
|
138
|
+
thinning: 0.52,
|
|
139
|
+
smoothing: 0.64,
|
|
140
|
+
streamline: 0.68
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
...base2,
|
|
145
|
+
size: Math.max(6, sw * 1.08),
|
|
146
|
+
thinning: 0.08,
|
|
147
|
+
smoothing: 0.88,
|
|
148
|
+
streamline: 0.84,
|
|
149
|
+
simulatePressure: true
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeComplete = true) {
|
|
153
|
+
if (pathPointsLocal.length === 0) return null;
|
|
154
|
+
if (pathPointsLocal.length === 1) {
|
|
155
|
+
const p = pathPointsLocal[0];
|
|
156
|
+
if (!p) return null;
|
|
157
|
+
return {
|
|
158
|
+
kind: "circle",
|
|
159
|
+
cx: p.x,
|
|
160
|
+
cy: p.y,
|
|
161
|
+
r: Math.max(0.5, style.strokeWidth / 2),
|
|
162
|
+
fill: style.stroke,
|
|
163
|
+
fillOpacity: style.strokeOpacity
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const hasPressure = pathPointsLocal.some(
|
|
167
|
+
(p) => p.pressure != null && Number.isFinite(p.pressure)
|
|
168
|
+
);
|
|
169
|
+
const input = hasPressure ? pathPointsLocal.map(
|
|
170
|
+
(p) => [p.x, p.y, Math.min(1, Math.max(0, p.pressure ?? 0.5))]
|
|
171
|
+
) : pathPointsLocal.map((p) => [p.x, p.y]);
|
|
172
|
+
const stroke = getStroke(
|
|
173
|
+
input,
|
|
174
|
+
perfectFreehandOptions(toolKind, style, strokeComplete, hasPressure)
|
|
175
|
+
);
|
|
176
|
+
if (stroke.length < 3) return null;
|
|
177
|
+
const first = stroke[0];
|
|
178
|
+
if (!first) return null;
|
|
179
|
+
let d = `M ${first[0]} ${first[1]} Q`;
|
|
180
|
+
for (let i = 0; i < stroke.length; i++) {
|
|
181
|
+
const a = stroke[i];
|
|
182
|
+
const b = stroke[(i + 1) % stroke.length];
|
|
183
|
+
if (!a || !b) continue;
|
|
184
|
+
d += ` ${a[0]} ${a[1]} ${(a[0] + b[0]) / 2} ${(a[1] + b[1]) / 2}`;
|
|
185
|
+
}
|
|
186
|
+
d += " Z";
|
|
187
|
+
return {
|
|
188
|
+
kind: "fillPath",
|
|
189
|
+
d,
|
|
190
|
+
fill: style.stroke,
|
|
191
|
+
fillOpacity: style.strokeOpacity
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
74
195
|
// src/react/presence/peer-color.ts
|
|
75
196
|
function defaultPresenceColorForId(id) {
|
|
76
197
|
let h = 2166136261;
|
|
@@ -95,6 +216,9 @@ function strokePaint(tool, fallback) {
|
|
|
95
216
|
return { stroke: fallback, strokeOpacity: 0.95, widthWorld: 3.5 };
|
|
96
217
|
}
|
|
97
218
|
}
|
|
219
|
+
function isFreehandTool(tool) {
|
|
220
|
+
return tool === "draw" || tool === "marker" || tool === "pencil" || tool === "brush";
|
|
221
|
+
}
|
|
98
222
|
function PresenceRemoteLayer({
|
|
99
223
|
camera,
|
|
100
224
|
cameraVersion: _cameraVersion,
|
|
@@ -136,34 +260,72 @@ function PresenceRemoteLayer({
|
|
|
136
260
|
strokeOpacity: markup.strokeOpacity ?? fallbackPaint.strokeOpacity,
|
|
137
261
|
widthWorld: markup.strokeWidth ?? fallbackPaint.widthWorld
|
|
138
262
|
};
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
263
|
+
if (markup.tool === "laser") {
|
|
264
|
+
const d = markup.points.length >= 2 ? smoothFreehandPointsToPathD([...markup.points]) : null;
|
|
265
|
+
if (d) {
|
|
266
|
+
strokeNode = /* @__PURE__ */ jsx(
|
|
267
|
+
"path",
|
|
268
|
+
{
|
|
269
|
+
d,
|
|
270
|
+
fill: "none",
|
|
271
|
+
stroke: paint.stroke,
|
|
272
|
+
strokeOpacity: paint.strokeOpacity,
|
|
273
|
+
strokeWidth: Math.max(paint.widthWorld / z, overlayStrokePx),
|
|
274
|
+
strokeLinecap: "round",
|
|
275
|
+
strokeLinejoin: "round",
|
|
276
|
+
shapeRendering: "geometricPrecision",
|
|
277
|
+
vectorEffect: "non-scaling-stroke"
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
} else if (isFreehandTool(markup.tool)) {
|
|
282
|
+
const style = {
|
|
283
|
+
stroke: paint.stroke,
|
|
284
|
+
strokeWidth: paint.widthWorld,
|
|
285
|
+
...paint.strokeOpacity != null ? { strokeOpacity: paint.strokeOpacity } : {}
|
|
286
|
+
};
|
|
287
|
+
const payload = computeFreehandSvgPayload(
|
|
288
|
+
markup.points.map((p) => ({ x: p.x, y: p.y })),
|
|
289
|
+
style,
|
|
290
|
+
markup.tool,
|
|
291
|
+
false
|
|
154
292
|
);
|
|
155
|
-
|
|
156
|
-
const p0 = markup.points[0];
|
|
157
|
-
if (p0) {
|
|
293
|
+
if (payload?.kind === "circle") {
|
|
158
294
|
strokeNode = /* @__PURE__ */ jsx(
|
|
159
295
|
"circle",
|
|
160
296
|
{
|
|
161
|
-
cx:
|
|
162
|
-
cy:
|
|
163
|
-
r:
|
|
164
|
-
fill:
|
|
165
|
-
fillOpacity:
|
|
166
|
-
|
|
297
|
+
cx: payload.cx,
|
|
298
|
+
cy: payload.cy,
|
|
299
|
+
r: payload.r,
|
|
300
|
+
fill: payload.fill,
|
|
301
|
+
...payload.fillOpacity != null ? { fillOpacity: payload.fillOpacity } : {},
|
|
302
|
+
shapeRendering: "geometricPrecision"
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
} else if (payload?.kind === "fillPath") {
|
|
306
|
+
strokeNode = /* @__PURE__ */ jsx(
|
|
307
|
+
"path",
|
|
308
|
+
{
|
|
309
|
+
d: payload.d,
|
|
310
|
+
fill: payload.fill,
|
|
311
|
+
fillRule: "nonzero",
|
|
312
|
+
stroke: "none",
|
|
313
|
+
...payload.fillOpacity != null ? { fillOpacity: payload.fillOpacity } : {},
|
|
314
|
+
shapeRendering: "geometricPrecision"
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
} else if (payload?.kind === "strokePath") {
|
|
318
|
+
strokeNode = /* @__PURE__ */ jsx(
|
|
319
|
+
"path",
|
|
320
|
+
{
|
|
321
|
+
d: payload.d,
|
|
322
|
+
fill: "none",
|
|
323
|
+
stroke: payload.stroke,
|
|
324
|
+
strokeWidth: payload.strokeWidth,
|
|
325
|
+
...payload.strokeOpacity != null ? { strokeOpacity: payload.strokeOpacity } : {},
|
|
326
|
+
strokeLinecap: "round",
|
|
327
|
+
strokeLinejoin: "round",
|
|
328
|
+
shapeRendering: "geometricPrecision"
|
|
167
329
|
}
|
|
168
330
|
);
|
|
169
331
|
}
|
|
@@ -795,38 +957,6 @@ var DEFAULT_VECTOR_TOOLS = [
|
|
|
795
957
|
shortcutHint: "I"
|
|
796
958
|
}
|
|
797
959
|
];
|
|
798
|
-
|
|
799
|
-
// src/scene/custom-shape.ts
|
|
800
|
-
function expandCustomShapeTemplate(template, width, height) {
|
|
801
|
-
return template.replace(/\{\{w\}\}/g, String(width)).replace(/\{\{h\}\}/g, String(height)).replace(/\{\{width\}\}/g, String(width)).replace(/\{\{height\}\}/g, String(height));
|
|
802
|
-
}
|
|
803
|
-
function resolveCustomInner(content, size) {
|
|
804
|
-
if ("render" in content) {
|
|
805
|
-
return content.render(size);
|
|
806
|
-
}
|
|
807
|
-
return expandCustomShapeTemplate(content.svg, size.width, size.height);
|
|
808
|
-
}
|
|
809
|
-
function buildCustomShapeChildrenSvg(inner, intrinsic, bounds) {
|
|
810
|
-
const b = normalizeRect(bounds);
|
|
811
|
-
const sx = b.width / intrinsic.width;
|
|
812
|
-
const sy = b.height / intrinsic.height;
|
|
813
|
-
return `<g transform="scale(${sx},${sy})">${inner}</g>`;
|
|
814
|
-
}
|
|
815
|
-
function createCustomShapeItem(id, bounds, content) {
|
|
816
|
-
const r = normalizeRect(bounds);
|
|
817
|
-
const intrinsic = { width: r.width, height: r.height };
|
|
818
|
-
const inner = resolveCustomInner(content, intrinsic);
|
|
819
|
-
return {
|
|
820
|
-
id,
|
|
821
|
-
x: r.x,
|
|
822
|
-
y: r.y,
|
|
823
|
-
bounds: { ...r },
|
|
824
|
-
toolKind: "custom",
|
|
825
|
-
customIntrinsicSize: intrinsic,
|
|
826
|
-
customInnerSvg: inner,
|
|
827
|
-
childrenSvg: buildCustomShapeChildrenSvg(inner, intrinsic, r)
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
960
|
var iconProps = { size: 20, strokeWidth: 2 };
|
|
831
961
|
var COMMENT_PLUGIN_DATA_KEY = "realtimeComment";
|
|
832
962
|
var REALTIME_COMMENT_TOOL = {
|