canvu-react 0.3.33 → 0.3.35
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/index.cjs +250 -169
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +250 -169
- package/dist/index.js.map +1 -1
- package/dist/native.cjs +233 -159
- package/dist/native.cjs.map +1 -1
- package/dist/native.js +233 -159
- package/dist/native.js.map +1 -1
- package/dist/react.cjs +314 -235
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +314 -235
- package/dist/react.js.map +1 -1
- package/dist/realtime.cjs +2 -2
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.js +2 -2
- package/dist/realtime.js.map +1 -1
- package/dist/tldraw.cjs +235 -161
- package/dist/tldraw.cjs.map +1 -1
- package/dist/tldraw.js +235 -161
- package/dist/tldraw.js.map +1 -1
- package/package.json +1 -1
package/dist/react.cjs
CHANGED
|
@@ -291,153 +291,197 @@ function svgNumber(value) {
|
|
|
291
291
|
const rounded = Math.round(value * 100) / 100;
|
|
292
292
|
return Number.isInteger(rounded) ? String(rounded) : String(rounded);
|
|
293
293
|
}
|
|
294
|
-
function
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return count;
|
|
299
|
-
}
|
|
300
|
-
function roundedRectMetrics(width, height, inset, radius) {
|
|
301
|
-
const left = inset;
|
|
302
|
-
const top = inset;
|
|
303
|
-
const right = width - inset;
|
|
304
|
-
const bottom = height - inset;
|
|
305
|
-
const rectWidth = Math.max(0, right - left);
|
|
306
|
-
const rectHeight = Math.max(0, bottom - top);
|
|
307
|
-
const normalizedRadius = Math.max(
|
|
308
|
-
0,
|
|
309
|
-
Math.min(radius, rectWidth / 2, rectHeight / 2)
|
|
294
|
+
function architecturalCloudCenterCount(edgeLength, radius) {
|
|
295
|
+
const targetSpacing = Math.min(
|
|
296
|
+
ARCHITECTURAL_CLOUD_TARGET_SPACING,
|
|
297
|
+
Math.max(1, radius * 1.3)
|
|
310
298
|
);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const
|
|
299
|
+
return Math.max(2, Math.round(edgeLength / targetSpacing) + 1);
|
|
300
|
+
}
|
|
301
|
+
function distributeRange(start, end, count) {
|
|
302
|
+
if (count <= 1) return [start];
|
|
303
|
+
const step = (end - start) / (count - 1);
|
|
304
|
+
return Array.from({ length: count }, (_, index) => start + step * index);
|
|
305
|
+
}
|
|
306
|
+
function lineCloudPathSegment(start, end) {
|
|
307
|
+
const dx = end[0] - start[0];
|
|
308
|
+
const dy = end[1] - start[1];
|
|
309
|
+
const length = Math.hypot(dx, dy);
|
|
316
310
|
return {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
right,
|
|
320
|
-
bottom,
|
|
321
|
-
radius: normalizedRadius,
|
|
322
|
-
centerX,
|
|
323
|
-
topHalfLength,
|
|
324
|
-
horizontalLength,
|
|
325
|
-
verticalLength,
|
|
326
|
-
arcLength,
|
|
327
|
-
perimeter: horizontalLength * 2 + verticalLength * 2 + Math.PI * 2 * normalizedRadius
|
|
311
|
+
length,
|
|
312
|
+
pointAt: (t) => [start[0] + dx * t, start[1] + dy * t]
|
|
328
313
|
};
|
|
329
314
|
}
|
|
330
|
-
function
|
|
331
|
-
return [
|
|
315
|
+
function ellipsePoint(centerX, centerY, radiusX, radiusY, angle) {
|
|
316
|
+
return [centerX + Math.cos(angle) * radiusX, centerY + Math.sin(angle) * radiusY];
|
|
332
317
|
}
|
|
333
|
-
function
|
|
334
|
-
const
|
|
335
|
-
|
|
318
|
+
function approximateEllipseArcLength(radiusX, radiusY, startAngle, endAngle) {
|
|
319
|
+
const steps = Math.max(
|
|
320
|
+
4,
|
|
321
|
+
Math.ceil(Math.abs(endAngle - startAngle) / (Math.PI / 16))
|
|
322
|
+
);
|
|
323
|
+
let length = 0;
|
|
324
|
+
let previous = ellipsePoint(0, 0, radiusX, radiusY, startAngle);
|
|
325
|
+
for (let index = 1; index <= steps; index += 1) {
|
|
326
|
+
const angle = startAngle + (endAngle - startAngle) * index / steps;
|
|
327
|
+
const next = ellipsePoint(0, 0, radiusX, radiusY, angle);
|
|
328
|
+
length += Math.hypot(next[0] - previous[0], next[1] - previous[1]);
|
|
329
|
+
previous = next;
|
|
330
|
+
}
|
|
331
|
+
return length;
|
|
336
332
|
}
|
|
337
|
-
function
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
333
|
+
function ellipseCloudPathSegment(centerX, centerY, radiusX, radiusY, startAngle, endAngle) {
|
|
334
|
+
return {
|
|
335
|
+
length: approximateEllipseArcLength(radiusX, radiusY, startAngle, endAngle),
|
|
336
|
+
pointAt: (t) => ellipsePoint(
|
|
337
|
+
centerX,
|
|
338
|
+
centerY,
|
|
339
|
+
radiusX,
|
|
340
|
+
radiusY,
|
|
341
|
+
startAngle + (endAngle - startAngle) * t
|
|
342
|
+
)
|
|
345
343
|
};
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
344
|
+
}
|
|
345
|
+
function cloudPathPerimeter(segments) {
|
|
346
|
+
const usableSegments = segments.filter((segment) => segment.length > 1e-9);
|
|
347
|
+
return usableSegments.reduce((sum, segment) => sum + segment.length, 0);
|
|
348
|
+
}
|
|
349
|
+
function pointOnCloudPath(segments, distance) {
|
|
350
|
+
const perimeter = cloudPathPerimeter(segments);
|
|
351
|
+
if (perimeter <= 0) return [0, 0];
|
|
352
|
+
let remaining = (distance % perimeter + perimeter) % perimeter;
|
|
353
|
+
for (const segment of segments) {
|
|
354
|
+
if (segment.length <= 1e-9) continue;
|
|
355
|
+
if (remaining <= segment.length) {
|
|
356
|
+
const t = remaining / segment.length;
|
|
357
|
+
return segment.pointAt(t);
|
|
358
|
+
}
|
|
359
|
+
remaining -= segment.length;
|
|
355
360
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
361
|
+
const fallback = segments.find((segment) => segment.length > 1e-9);
|
|
362
|
+
return fallback?.pointAt(0) ?? [0, 0];
|
|
363
|
+
}
|
|
364
|
+
function buildRoundedCapsulePathSegments(width, height, inset) {
|
|
365
|
+
const left = inset;
|
|
366
|
+
const top = inset;
|
|
367
|
+
const right = width - inset;
|
|
368
|
+
const bottom = height - inset;
|
|
369
|
+
const capsuleWidth = Math.max(0, right - left);
|
|
370
|
+
const capsuleHeight = Math.max(0, bottom - top);
|
|
371
|
+
const radius = Math.min(capsuleWidth, capsuleHeight) / 2;
|
|
372
|
+
if (radius <= 0) return [];
|
|
373
|
+
const leftCenterX = left + radius;
|
|
374
|
+
const rightCenterX = right - radius;
|
|
375
|
+
const topCenterY = top + radius;
|
|
376
|
+
const bottomCenterY = bottom - radius;
|
|
377
|
+
return [
|
|
378
|
+
lineCloudPathSegment([leftCenterX, top], [rightCenterX, top]),
|
|
379
|
+
ellipseCloudPathSegment(
|
|
380
|
+
rightCenterX,
|
|
381
|
+
topCenterY,
|
|
382
|
+
radius,
|
|
383
|
+
radius,
|
|
362
384
|
-Math.PI / 2,
|
|
385
|
+
0
|
|
386
|
+
),
|
|
387
|
+
lineCloudPathSegment([right, topCenterY], [right, bottomCenterY]),
|
|
388
|
+
ellipseCloudPathSegment(
|
|
389
|
+
rightCenterX,
|
|
390
|
+
bottomCenterY,
|
|
391
|
+
radius,
|
|
392
|
+
radius,
|
|
363
393
|
0,
|
|
364
|
-
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
metrics.right,
|
|
373
|
-
metrics.bottom - metrics.radius,
|
|
374
|
-
t
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
t = consume(metrics.arcLength);
|
|
378
|
-
if (t != null) {
|
|
379
|
-
return pointOnArc(
|
|
380
|
-
metrics.right - metrics.radius,
|
|
381
|
-
metrics.bottom - metrics.radius,
|
|
382
|
-
metrics.radius,
|
|
383
|
-
0,
|
|
384
|
-
Math.PI / 2,
|
|
385
|
-
t
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
t = consume(metrics.horizontalLength);
|
|
389
|
-
if (t != null) {
|
|
390
|
-
return pointOnLine(
|
|
391
|
-
metrics.right - metrics.radius,
|
|
392
|
-
metrics.bottom,
|
|
393
|
-
metrics.left + metrics.radius,
|
|
394
|
-
metrics.bottom,
|
|
395
|
-
t
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
t = consume(metrics.arcLength);
|
|
399
|
-
if (t != null) {
|
|
400
|
-
return pointOnArc(
|
|
401
|
-
metrics.left + metrics.radius,
|
|
402
|
-
metrics.bottom - metrics.radius,
|
|
403
|
-
metrics.radius,
|
|
394
|
+
Math.PI / 2
|
|
395
|
+
),
|
|
396
|
+
lineCloudPathSegment([rightCenterX, bottom], [leftCenterX, bottom]),
|
|
397
|
+
ellipseCloudPathSegment(
|
|
398
|
+
leftCenterX,
|
|
399
|
+
bottomCenterY,
|
|
400
|
+
radius,
|
|
401
|
+
radius,
|
|
404
402
|
Math.PI / 2,
|
|
403
|
+
Math.PI
|
|
404
|
+
),
|
|
405
|
+
lineCloudPathSegment([left, bottomCenterY], [left, topCenterY]),
|
|
406
|
+
ellipseCloudPathSegment(
|
|
407
|
+
leftCenterX,
|
|
408
|
+
topCenterY,
|
|
409
|
+
radius,
|
|
410
|
+
radius,
|
|
405
411
|
Math.PI,
|
|
406
|
-
|
|
407
|
-
)
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
412
|
+
Math.PI * 1.5
|
|
413
|
+
)
|
|
414
|
+
];
|
|
415
|
+
}
|
|
416
|
+
function buildRoundedArcCloudPathD(cloudWidth, cloudHeight, center) {
|
|
417
|
+
const minDimension = Math.min(cloudWidth, cloudHeight);
|
|
418
|
+
const radius = Math.min(
|
|
419
|
+
ARCHITECTURAL_CLOUD_ROUNDED_RADIUS,
|
|
420
|
+
Math.max(ARCHITECTURAL_CLOUD_ROUNDED_MIN_RADIUS, minDimension * 0.16)
|
|
421
|
+
);
|
|
422
|
+
const centerPath = buildRoundedCapsulePathSegments(
|
|
423
|
+
cloudWidth,
|
|
424
|
+
cloudHeight,
|
|
425
|
+
radius
|
|
426
|
+
);
|
|
427
|
+
const centerPerimeter = cloudPathPerimeter(centerPath);
|
|
428
|
+
if (centerPerimeter <= 0) return "";
|
|
429
|
+
const lobeCount = Math.max(
|
|
430
|
+
8,
|
|
431
|
+
Math.round(centerPerimeter / ARCHITECTURAL_CLOUD_ROUNDED_TARGET_SPACING)
|
|
432
|
+
);
|
|
433
|
+
const centers = Array.from(
|
|
434
|
+
{ length: lobeCount },
|
|
435
|
+
(_, index) => pointOnCloudPath(centerPath, centerPerimeter * index / lobeCount)
|
|
436
|
+
);
|
|
437
|
+
const points = centers.map((point, index) => {
|
|
438
|
+
const previous = centers[(index - 1 + centers.length) % centers.length] ?? point;
|
|
439
|
+
return cloudCircleIntersection(previous, point, radius, center);
|
|
440
|
+
});
|
|
441
|
+
const [startX, startY] = points[0] ?? [0, 0];
|
|
442
|
+
const segments = [`M${svgNumber(startX)} ${svgNumber(startY)}`];
|
|
443
|
+
for (const [endX, endY] of points.slice(1)) {
|
|
444
|
+
segments.push(
|
|
445
|
+
`A ${svgNumber(radius)} ${svgNumber(radius)} 0 0 1 ${svgNumber(endX)} ${svgNumber(endY)}`
|
|
438
446
|
);
|
|
439
447
|
}
|
|
440
|
-
|
|
448
|
+
segments.push(
|
|
449
|
+
`A ${svgNumber(radius)} ${svgNumber(radius)} 0 0 1 ${svgNumber(startX)} ${svgNumber(startY)}`
|
|
450
|
+
);
|
|
451
|
+
segments.push("Z");
|
|
452
|
+
return segments.join(" ");
|
|
453
|
+
}
|
|
454
|
+
function cloudCircleIntersection(a, b, radius, center) {
|
|
455
|
+
const midX = (a[0] + b[0]) / 2;
|
|
456
|
+
const midY = (a[1] + b[1]) / 2;
|
|
457
|
+
const dx = b[0] - a[0];
|
|
458
|
+
const dy = b[1] - a[1];
|
|
459
|
+
const distance = Math.hypot(dx, dy);
|
|
460
|
+
if (distance <= 1e-9) return [midX, midY];
|
|
461
|
+
const halfDistance = distance / 2;
|
|
462
|
+
const offset = Math.sqrt(
|
|
463
|
+
Math.max(0, radius * radius - halfDistance * halfDistance)
|
|
464
|
+
);
|
|
465
|
+
const normalX = -dy / distance;
|
|
466
|
+
const normalY = dx / distance;
|
|
467
|
+
const first = [midX + normalX * offset, midY + normalY * offset];
|
|
468
|
+
const second = [
|
|
469
|
+
midX - normalX * offset,
|
|
470
|
+
midY - normalY * offset
|
|
471
|
+
];
|
|
472
|
+
const firstDistance = (first[0] - center[0]) * (first[0] - center[0]) + (first[1] - center[1]) * (first[1] - center[1]);
|
|
473
|
+
const secondDistance = (second[0] - center[0]) * (second[0] - center[0]) + (second[1] - center[1]) * (second[1] - center[1]);
|
|
474
|
+
return firstDistance >= secondDistance ? first : second;
|
|
475
|
+
}
|
|
476
|
+
function cloudEllipseIntersection(a, b, radiusX, radiusY, center) {
|
|
477
|
+
const scaleY = radiusX / radiusY;
|
|
478
|
+
const [x, y] = cloudCircleIntersection(
|
|
479
|
+
[a[0], a[1] * scaleY],
|
|
480
|
+
[b[0], b[1] * scaleY],
|
|
481
|
+
radiusX,
|
|
482
|
+
[center[0], center[1] * scaleY]
|
|
483
|
+
);
|
|
484
|
+
return [x, y / scaleY];
|
|
441
485
|
}
|
|
442
486
|
function buildRectSvg(width, height, style = DEFAULT_STROKE_STYLE) {
|
|
443
487
|
return `<rect width="${width}" height="${height}" fill="none" stroke="${style.stroke}" stroke-width="${style.strokeWidth}" rx="4"${strokeOpacityAttr(style)} />`;
|
|
@@ -451,39 +495,63 @@ function buildArchitecturalCloudPathD(width, height, strokeWidth = DEFAULT_STROK
|
|
|
451
495
|
const w = Math.max(0, width);
|
|
452
496
|
const h = Math.max(0, height);
|
|
453
497
|
if (w <= 0 || h <= 0) return "";
|
|
454
|
-
const
|
|
455
|
-
const
|
|
456
|
-
const
|
|
457
|
-
if (
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
Math.max(5, Math.min(14, Math.min(w, h) * 0.07))
|
|
498
|
+
const padding = Math.max(0, strokeWidth * 2);
|
|
499
|
+
const cloudWidth = Math.max(0, w - padding);
|
|
500
|
+
const cloudHeight = Math.max(0, h - padding);
|
|
501
|
+
if (cloudWidth <= 0 || cloudHeight <= 0) return "";
|
|
502
|
+
const radiusX = Math.min(
|
|
503
|
+
ARCHITECTURAL_CLOUD_RADIUS,
|
|
504
|
+
cloudWidth * ARCHITECTURAL_CLOUD_FLAT_RADIUS_RATIO
|
|
462
505
|
);
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
506
|
+
const radiusY = Math.min(
|
|
507
|
+
ARCHITECTURAL_CLOUD_RADIUS,
|
|
508
|
+
cloudHeight * ARCHITECTURAL_CLOUD_FLAT_RADIUS_RATIO
|
|
509
|
+
);
|
|
510
|
+
if (radiusX <= 0 || radiusY <= 0) return "";
|
|
511
|
+
const center = [cloudWidth / 2, cloudHeight / 2];
|
|
512
|
+
const leftCenterX = radiusX;
|
|
513
|
+
const rightCenterX = cloudWidth - radiusX;
|
|
514
|
+
const topCenterY = radiusY;
|
|
515
|
+
const bottomCenterY = cloudHeight - radiusY;
|
|
516
|
+
const horizontalCenters = distributeRange(
|
|
517
|
+
leftCenterX,
|
|
518
|
+
rightCenterX,
|
|
519
|
+
architecturalCloudCenterCount(Math.max(0, rightCenterX - leftCenterX), radiusX)
|
|
467
520
|
);
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
inset + amplitude,
|
|
473
|
-
Math.max(0, radius - amplitude)
|
|
521
|
+
const verticalCenters = distributeRange(
|
|
522
|
+
topCenterY,
|
|
523
|
+
bottomCenterY,
|
|
524
|
+
architecturalCloudCenterCount(Math.max(0, bottomCenterY - topCenterY), radiusY)
|
|
474
525
|
);
|
|
475
|
-
|
|
476
|
-
|
|
526
|
+
if (horizontalCenters.length > 3 && verticalCenters.length > 3) {
|
|
527
|
+
const roundedArcCloudPath = buildRoundedArcCloudPathD(
|
|
528
|
+
cloudWidth,
|
|
529
|
+
cloudHeight,
|
|
530
|
+
center
|
|
531
|
+
);
|
|
532
|
+
if (roundedArcCloudPath !== "") return roundedArcCloudPath;
|
|
533
|
+
}
|
|
534
|
+
const rectangularCenters = [
|
|
535
|
+
...horizontalCenters.map((x) => [x, topCenterY]),
|
|
536
|
+
...verticalCenters.slice(1).map((y) => [rightCenterX, y]),
|
|
537
|
+
...horizontalCenters.slice(0, -1).reverse().map((x) => [x, bottomCenterY]),
|
|
538
|
+
...verticalCenters.slice(1, -1).reverse().map((y) => [leftCenterX, y])
|
|
539
|
+
];
|
|
540
|
+
const centers = rectangularCenters;
|
|
541
|
+
const points = centers.map((point, index) => {
|
|
542
|
+
const previous = centers[(index - 1 + centers.length) % centers.length] ?? point;
|
|
543
|
+
return cloudEllipseIntersection(previous, point, radiusX, radiusY, center);
|
|
544
|
+
});
|
|
545
|
+
const [startX, startY] = points[0] ?? [0, 0];
|
|
477
546
|
const segments = [`M${svgNumber(startX)} ${svgNumber(startY)}`];
|
|
478
|
-
for (
|
|
479
|
-
const controlDistance = (index + 0.5) / scallopCount * outer.perimeter;
|
|
480
|
-
const endDistance = (index + 1) / scallopCount * inner.perimeter;
|
|
481
|
-
const [controlX, controlY] = pointOnRoundedRectPath(outer, controlDistance);
|
|
482
|
-
const [endX, endY] = pointOnRoundedRectPath(inner, endDistance);
|
|
547
|
+
for (const [endX, endY] of points.slice(1)) {
|
|
483
548
|
segments.push(
|
|
484
|
-
`
|
|
549
|
+
`A ${svgNumber(radiusX)} ${svgNumber(radiusY)} 0 0 1 ${svgNumber(endX)} ${svgNumber(endY)}`
|
|
485
550
|
);
|
|
486
551
|
}
|
|
552
|
+
segments.push(
|
|
553
|
+
`A ${svgNumber(radiusX)} ${svgNumber(radiusY)} 0 0 1 ${svgNumber(startX)} ${svgNumber(startY)}`
|
|
554
|
+
);
|
|
487
555
|
segments.push("Z");
|
|
488
556
|
return segments.join(" ");
|
|
489
557
|
}
|
|
@@ -921,7 +989,7 @@ function createImageFromVectorTrace(id, bounds, imageVectorInnerSvg, imageVector
|
|
|
921
989
|
childrenSvg
|
|
922
990
|
};
|
|
923
991
|
}
|
|
924
|
-
var DEFAULT_STROKE_STYLE, TOOL_FREEHAND_DEFAULTS;
|
|
992
|
+
var DEFAULT_STROKE_STYLE, TOOL_FREEHAND_DEFAULTS, ARCHITECTURAL_CLOUD_RADIUS, ARCHITECTURAL_CLOUD_TARGET_SPACING, ARCHITECTURAL_CLOUD_FLAT_RADIUS_RATIO, ARCHITECTURAL_CLOUD_ROUNDED_RADIUS, ARCHITECTURAL_CLOUD_ROUNDED_MIN_RADIUS, ARCHITECTURAL_CLOUD_ROUNDED_TARGET_SPACING;
|
|
925
993
|
var init_shape_builders = __esm({
|
|
926
994
|
"src/scene/shape-builders.ts"() {
|
|
927
995
|
init_rect();
|
|
@@ -937,6 +1005,12 @@ var init_shape_builders = __esm({
|
|
|
937
1005
|
brush: { strokeWidth: 10 },
|
|
938
1006
|
marker: { stroke: "#fde047", strokeWidth: 16, strokeOpacity: 0.5 }
|
|
939
1007
|
};
|
|
1008
|
+
ARCHITECTURAL_CLOUD_RADIUS = 38;
|
|
1009
|
+
ARCHITECTURAL_CLOUD_TARGET_SPACING = 50;
|
|
1010
|
+
ARCHITECTURAL_CLOUD_FLAT_RADIUS_RATIO = 0.38;
|
|
1011
|
+
ARCHITECTURAL_CLOUD_ROUNDED_RADIUS = 72;
|
|
1012
|
+
ARCHITECTURAL_CLOUD_ROUNDED_MIN_RADIUS = 44;
|
|
1013
|
+
ARCHITECTURAL_CLOUD_ROUNDED_TARGET_SPACING = 98;
|
|
940
1014
|
}
|
|
941
1015
|
});
|
|
942
1016
|
|
|
@@ -1751,6 +1825,67 @@ function useCanvuChromeContext() {
|
|
|
1751
1825
|
return react.useContext(CanvuChromeContext);
|
|
1752
1826
|
}
|
|
1753
1827
|
|
|
1828
|
+
// src/math/item-transform.ts
|
|
1829
|
+
init_rect();
|
|
1830
|
+
function getItemRotationRad(item) {
|
|
1831
|
+
return item.rotation ?? 0;
|
|
1832
|
+
}
|
|
1833
|
+
function itemLocalToWorld(lx, ly, itemX, itemY, w, h, rotationRad) {
|
|
1834
|
+
const c = { x: w / 2, y: h / 2 };
|
|
1835
|
+
const dlx = lx - c.x;
|
|
1836
|
+
const dly = ly - c.y;
|
|
1837
|
+
const cos = Math.cos(rotationRad);
|
|
1838
|
+
const sin = Math.sin(rotationRad);
|
|
1839
|
+
return {
|
|
1840
|
+
x: itemX + c.x + cos * dlx - sin * dly,
|
|
1841
|
+
y: itemY + c.y + sin * dlx + cos * dly
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
function worldToItemLocal(wx, wy, itemX, itemY, w, h, rotationRad) {
|
|
1845
|
+
const c = { x: w / 2, y: h / 2 };
|
|
1846
|
+
const vx = wx - itemX;
|
|
1847
|
+
const vy = wy - itemY;
|
|
1848
|
+
const dx = vx - c.x;
|
|
1849
|
+
const dy = vy - c.y;
|
|
1850
|
+
const cos = Math.cos(-rotationRad);
|
|
1851
|
+
const sin = Math.sin(-rotationRad);
|
|
1852
|
+
const lx = cos * dx - sin * dy;
|
|
1853
|
+
const ly = sin * dx + cos * dy;
|
|
1854
|
+
return { x: c.x + lx, y: c.y + ly };
|
|
1855
|
+
}
|
|
1856
|
+
function itemPivotWorld(item) {
|
|
1857
|
+
const r = normalizeRect(item.bounds);
|
|
1858
|
+
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
|
1859
|
+
}
|
|
1860
|
+
function boundsAabbForRotatedItem(item) {
|
|
1861
|
+
const rot = getItemRotationRad(item);
|
|
1862
|
+
if (Math.abs(rot) < 1e-12 && item.bounds.width >= 0 && item.bounds.height >= 0) {
|
|
1863
|
+
return item.bounds;
|
|
1864
|
+
}
|
|
1865
|
+
const r = normalizeRect(item.bounds);
|
|
1866
|
+
if (Math.abs(rot) < 1e-12) {
|
|
1867
|
+
return r;
|
|
1868
|
+
}
|
|
1869
|
+
const corners = [
|
|
1870
|
+
[0, 0],
|
|
1871
|
+
[r.width, 0],
|
|
1872
|
+
[r.width, r.height],
|
|
1873
|
+
[0, r.height]
|
|
1874
|
+
];
|
|
1875
|
+
let minX = Infinity;
|
|
1876
|
+
let minY = Infinity;
|
|
1877
|
+
let maxX = -Infinity;
|
|
1878
|
+
let maxY = -Infinity;
|
|
1879
|
+
for (const [lx, ly] of corners) {
|
|
1880
|
+
const p = itemLocalToWorld(lx, ly, item.x, item.y, r.width, r.height, rot);
|
|
1881
|
+
minX = Math.min(minX, p.x);
|
|
1882
|
+
minY = Math.min(minY, p.y);
|
|
1883
|
+
maxX = Math.max(maxX, p.x);
|
|
1884
|
+
maxY = Math.max(maxY, p.y);
|
|
1885
|
+
}
|
|
1886
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1754
1889
|
// src/scene/clone-item.ts
|
|
1755
1890
|
init_shape_builders();
|
|
1756
1891
|
function cloneVectorSceneItemWithNewId(item) {
|
|
@@ -1790,20 +1925,25 @@ function markImageAsManaged(item) {
|
|
|
1790
1925
|
};
|
|
1791
1926
|
}
|
|
1792
1927
|
function restackManagedImages(items) {
|
|
1793
|
-
let
|
|
1928
|
+
let anchorAabbY = Infinity;
|
|
1929
|
+
let anchorCenterX = 0;
|
|
1794
1930
|
for (const item of items) {
|
|
1795
1931
|
if (!isManagedImage(item)) continue;
|
|
1796
|
-
|
|
1932
|
+
const aabb = boundsAabbForRotatedItem(item);
|
|
1933
|
+
if (aabb.y < anchorAabbY) {
|
|
1934
|
+
anchorAabbY = aabb.y;
|
|
1935
|
+
anchorCenterX = aabb.x + aabb.width / 2;
|
|
1936
|
+
}
|
|
1797
1937
|
}
|
|
1798
|
-
if (!
|
|
1799
|
-
|
|
1800
|
-
const anchorTopY = anchor.bounds.y;
|
|
1801
|
-
let cursorY = anchorTopY;
|
|
1938
|
+
if (!Number.isFinite(anchorAabbY)) return [...items];
|
|
1939
|
+
let cursorY = anchorAabbY;
|
|
1802
1940
|
return items.map((item) => {
|
|
1803
1941
|
if (!isManagedImage(item)) return item;
|
|
1942
|
+
const aabb = boundsAabbForRotatedItem(item);
|
|
1943
|
+
const centerY = cursorY + aabb.height / 2;
|
|
1804
1944
|
const newX = anchorCenterX - item.bounds.width / 2;
|
|
1805
|
-
const newY =
|
|
1806
|
-
cursorY
|
|
1945
|
+
const newY = centerY - item.bounds.height / 2;
|
|
1946
|
+
cursorY += aabb.height + STACK_GAP_WORLD;
|
|
1807
1947
|
if (item.bounds.x === newX && item.bounds.y === newY) return item;
|
|
1808
1948
|
return {
|
|
1809
1949
|
...item,
|
|
@@ -1823,8 +1963,10 @@ function copyManagedImage(items, id) {
|
|
|
1823
1963
|
return restackManagedImages(inserted);
|
|
1824
1964
|
}
|
|
1825
1965
|
function rotateManagedImage(items, id) {
|
|
1826
|
-
return
|
|
1827
|
-
|
|
1966
|
+
return restackManagedImages(
|
|
1967
|
+
items.map(
|
|
1968
|
+
(i) => i.id === id ? { ...i, rotation: ((i.rotation ?? 0) + Math.PI / 2) % (Math.PI * 2) } : i
|
|
1969
|
+
)
|
|
1828
1970
|
);
|
|
1829
1971
|
}
|
|
1830
1972
|
function deleteManagedImage(items, id) {
|
|
@@ -2271,69 +2413,6 @@ function getBoardPositionStyle(position, inset = 12, zIndex = 40) {
|
|
|
2271
2413
|
return base2;
|
|
2272
2414
|
}
|
|
2273
2415
|
}
|
|
2274
|
-
|
|
2275
|
-
// src/math/item-transform.ts
|
|
2276
|
-
init_rect();
|
|
2277
|
-
function getItemRotationRad(item) {
|
|
2278
|
-
return item.rotation ?? 0;
|
|
2279
|
-
}
|
|
2280
|
-
function itemLocalToWorld(lx, ly, itemX, itemY, w, h, rotationRad) {
|
|
2281
|
-
const c = { x: w / 2, y: h / 2 };
|
|
2282
|
-
const dlx = lx - c.x;
|
|
2283
|
-
const dly = ly - c.y;
|
|
2284
|
-
const cos = Math.cos(rotationRad);
|
|
2285
|
-
const sin = Math.sin(rotationRad);
|
|
2286
|
-
return {
|
|
2287
|
-
x: itemX + c.x + cos * dlx - sin * dly,
|
|
2288
|
-
y: itemY + c.y + sin * dlx + cos * dly
|
|
2289
|
-
};
|
|
2290
|
-
}
|
|
2291
|
-
function worldToItemLocal(wx, wy, itemX, itemY, w, h, rotationRad) {
|
|
2292
|
-
const c = { x: w / 2, y: h / 2 };
|
|
2293
|
-
const vx = wx - itemX;
|
|
2294
|
-
const vy = wy - itemY;
|
|
2295
|
-
const dx = vx - c.x;
|
|
2296
|
-
const dy = vy - c.y;
|
|
2297
|
-
const cos = Math.cos(-rotationRad);
|
|
2298
|
-
const sin = Math.sin(-rotationRad);
|
|
2299
|
-
const lx = cos * dx - sin * dy;
|
|
2300
|
-
const ly = sin * dx + cos * dy;
|
|
2301
|
-
return { x: c.x + lx, y: c.y + ly };
|
|
2302
|
-
}
|
|
2303
|
-
function itemPivotWorld(item) {
|
|
2304
|
-
const r = normalizeRect(item.bounds);
|
|
2305
|
-
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
|
2306
|
-
}
|
|
2307
|
-
function boundsAabbForRotatedItem(item) {
|
|
2308
|
-
const rot = getItemRotationRad(item);
|
|
2309
|
-
if (Math.abs(rot) < 1e-12 && item.bounds.width >= 0 && item.bounds.height >= 0) {
|
|
2310
|
-
return item.bounds;
|
|
2311
|
-
}
|
|
2312
|
-
const r = normalizeRect(item.bounds);
|
|
2313
|
-
if (Math.abs(rot) < 1e-12) {
|
|
2314
|
-
return r;
|
|
2315
|
-
}
|
|
2316
|
-
const corners = [
|
|
2317
|
-
[0, 0],
|
|
2318
|
-
[r.width, 0],
|
|
2319
|
-
[r.width, r.height],
|
|
2320
|
-
[0, r.height]
|
|
2321
|
-
];
|
|
2322
|
-
let minX = Infinity;
|
|
2323
|
-
let minY = Infinity;
|
|
2324
|
-
let maxX = -Infinity;
|
|
2325
|
-
let maxY = -Infinity;
|
|
2326
|
-
for (const [lx, ly] of corners) {
|
|
2327
|
-
const p = itemLocalToWorld(lx, ly, item.x, item.y, r.width, r.height, rot);
|
|
2328
|
-
minX = Math.min(minX, p.x);
|
|
2329
|
-
minY = Math.min(minY, p.y);
|
|
2330
|
-
maxX = Math.max(maxX, p.x);
|
|
2331
|
-
maxY = Math.max(maxY, p.y);
|
|
2332
|
-
}
|
|
2333
|
-
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
2334
|
-
}
|
|
2335
|
-
|
|
2336
|
-
// src/react/navmenu/minimap.tsx
|
|
2337
2416
|
init_rect();
|
|
2338
2417
|
var NavMenuMinimapSlotContext = react.createContext(null);
|
|
2339
2418
|
function noop() {
|
|
@@ -3241,7 +3320,7 @@ function ShapeContextMenu({
|
|
|
3241
3320
|
}
|
|
3242
3321
|
return reactDom.createPortal(menu, document.body);
|
|
3243
3322
|
}
|
|
3244
|
-
var architecturalCloudIconPath = "
|
|
3323
|
+
var architecturalCloudIconPath = "M1.5 12 A 4.94 5.23 0 0 1 9.07 6 A 4.94 5.23 0 0 1 14.93 6 A 4.94 5.23 0 0 1 22.5 12 A 4.94 5.23 0 0 1 14.93 18 A 4.94 5.23 0 0 1 9.07 18 A 4.94 5.23 0 0 1 1.5 12 Z";
|
|
3245
3324
|
var base = {
|
|
3246
3325
|
width: 20,
|
|
3247
3326
|
height: 20,
|
|
@@ -3273,7 +3352,7 @@ function IconEllipse(props) {
|
|
|
3273
3352
|
return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...base, ...props, "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("ellipse", { cx: "12", cy: "12", rx: "9", ry: "6" }) });
|
|
3274
3353
|
}
|
|
3275
3354
|
function IconArchitecturalCloud(props) {
|
|
3276
|
-
return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...base, ...props, "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: architecturalCloudIconPath
|
|
3355
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...base, ...props, "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: architecturalCloudIconPath }) });
|
|
3277
3356
|
}
|
|
3278
3357
|
function IconLine(props) {
|
|
3279
3358
|
return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...base, ...props, "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "5", y1: "19", x2: "19", y2: "5" }) });
|