@pixerate/editor-svelte 0.109.1 → 0.110.1
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.
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { imageChipClass, imageColorInputClass } from './styles.js';
|
|
3
3
|
import type { ImageEditorController } from '@pixerate/editor/image-editor';
|
|
4
4
|
import {
|
|
5
|
+
userErrorMessage,
|
|
5
6
|
BRUSH_OPTION_CONTROLS,
|
|
6
7
|
MIXER_MIXING_OPTIONS,
|
|
7
8
|
MIXER_OPTION_CONTROLS,
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
error = null;
|
|
50
51
|
fn();
|
|
51
52
|
} catch (err) {
|
|
52
|
-
error =
|
|
53
|
+
error = userErrorMessage(err);
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
</script>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
canvasToImagePoint,
|
|
6
6
|
imageToCanvasPoint,
|
|
7
7
|
createImagePlaybackLocator,
|
|
8
|
+
agentDragDraft,
|
|
8
9
|
hitTestCrop,
|
|
9
10
|
getCropCursor,
|
|
10
11
|
calculateCropDrag,
|
|
@@ -16,6 +17,8 @@
|
|
|
16
17
|
pointerPressure,
|
|
17
18
|
FreeTransformTool,
|
|
18
19
|
getViewportProjection,
|
|
20
|
+
measureCanvasViewport,
|
|
21
|
+
watchCanvasViewport,
|
|
19
22
|
ShapeDrawGesture,
|
|
20
23
|
TypeToolGesture,
|
|
21
24
|
DEFAULT_SHAPE_TOOL_OPTIONS,
|
|
@@ -26,6 +29,7 @@
|
|
|
26
29
|
type TypeToolOptions,
|
|
27
30
|
} from '@pixerate/editor/image-editor';
|
|
28
31
|
import { untrack } from 'svelte';
|
|
32
|
+
import { trackPresenceDrag } from '@pixerate/editor/tools';
|
|
29
33
|
import { createPlaybackSurface, getAgentPlayback } from '../playback/playback.svelte.js';
|
|
30
34
|
import PresenceLayer from '../playback/PresenceLayer.svelte';
|
|
31
35
|
import type { ReactiveImageEditor } from './imageEditorState.svelte.js';
|
|
@@ -115,6 +119,11 @@
|
|
|
115
119
|
let canvasEl = $state<HTMLCanvasElement | null>(null);
|
|
116
120
|
let containerEl = $state<HTMLDivElement | null>(null);
|
|
117
121
|
let rootEl = $state<HTMLDivElement | null>(null);
|
|
122
|
+
/**
|
|
123
|
+
* The viewport in CSS pixels as last rendered. The backing store is larger (device pixel ratio,
|
|
124
|
+
* CSS transforms), so pointer mapping and the projection use this, never `canvasEl.width`.
|
|
125
|
+
*/
|
|
126
|
+
let view = { width: 0, height: 0 };
|
|
118
127
|
const report = (source: ImageEditorCanvasErrorSource, message: string | null) => onerror?.(source, message);
|
|
119
128
|
|
|
120
129
|
// Agent playback: the agent's cursor in document pixels, drawn over the canvas without clipping so
|
|
@@ -123,19 +132,19 @@
|
|
|
123
132
|
const playing = $derived((playback === undefined ? ambientPlayback : playback) ?? null);
|
|
124
133
|
/** Document pixels → pixels in the canvas root, through the canvas projection. */
|
|
125
134
|
const docToRoot = (p: PlaybackPoint): PlaybackPoint => {
|
|
126
|
-
if (!canvasEl || !rootEl || !
|
|
135
|
+
if (!canvasEl || !rootEl || !view.width) return p;
|
|
127
136
|
const r = canvasEl.getBoundingClientRect();
|
|
128
137
|
const box = rootEl.getBoundingClientRect();
|
|
129
|
-
const c = imageToCanvasPoint(p, editor.controller.getState(),
|
|
130
|
-
return { x: r.left - box.left + (c.x * r.width) /
|
|
138
|
+
const c = imageToCanvasPoint(p, editor.controller.getState(), view.width, view.height, mode === 'crop');
|
|
139
|
+
return { x: r.left - box.left + (c.x * r.width) / view.width, y: r.top - box.top + (c.y * r.height) / view.height };
|
|
131
140
|
};
|
|
132
141
|
/** Viewport (client) pixels → document pixels. */
|
|
133
142
|
const clientToDoc = (p: PlaybackPoint): PlaybackPoint => {
|
|
134
|
-
if (!canvasEl || !
|
|
143
|
+
if (!canvasEl || !view.width) return p;
|
|
135
144
|
const r = canvasEl.getBoundingClientRect();
|
|
136
|
-
const sx = r.width > 0 ?
|
|
137
|
-
const sy = r.height > 0 ?
|
|
138
|
-
return canvasToImagePoint({ x: (p.x - r.left) * sx, y: (p.y - r.top) * sy }, editor.controller.getState(),
|
|
145
|
+
const sx = r.width > 0 ? view.width / r.width : 1;
|
|
146
|
+
const sy = r.height > 0 ? view.height / r.height : 1;
|
|
147
|
+
return canvasToImagePoint({ x: (p.x - r.left) * sx, y: (p.y - r.top) * sy }, editor.controller.getState(), view.width, view.height, mode === 'crop');
|
|
139
148
|
};
|
|
140
149
|
/** A client box as a rectangle in document pixels. */
|
|
141
150
|
const clientRect = (r: DOMRect | null | undefined): PlaybackRect | null => {
|
|
@@ -272,12 +281,12 @@
|
|
|
272
281
|
/** The document point of a pointer event and the hit tolerance (6 screen px). */
|
|
273
282
|
function transformPoint(e: PointerEvent) {
|
|
274
283
|
const rect = canvasEl!.getBoundingClientRect();
|
|
275
|
-
const sx = rect.width > 0 ?
|
|
276
|
-
const sy = rect.height > 0 ?
|
|
284
|
+
const sx = rect.width > 0 ? view.width / rect.width : 1;
|
|
285
|
+
const sy = rect.height > 0 ? view.height / rect.height : 1;
|
|
277
286
|
const s = editor.controller.getState();
|
|
278
287
|
const cropping = mode === 'crop';
|
|
279
|
-
const projection = getViewportProjection(s,
|
|
280
|
-
const pt = canvasToImagePoint({ x: (e.clientX - rect.left) * sx, y: (e.clientY - rect.top) * sy }, s,
|
|
288
|
+
const projection = getViewportProjection(s, view.width, view.height, cropping);
|
|
289
|
+
const pt = canvasToImagePoint({ x: (e.clientX - rect.left) * sx, y: (e.clientY - rect.top) * sy }, s, view.width, view.height, cropping);
|
|
281
290
|
transformTool.setView(projection.scale);
|
|
282
291
|
return { pt, tolerance: 6 / (projection.scale || 1) };
|
|
283
292
|
}
|
|
@@ -325,14 +334,44 @@
|
|
|
325
334
|
let currentPath: ImagePoint[] = [];
|
|
326
335
|
let dragStartPoint: ImagePoint | null = null;
|
|
327
336
|
|
|
337
|
+
// The agent's drags pull out the crop box or the marquee as it goes, like a user's drag; the call
|
|
338
|
+
// that follows sets the real crop or selection.
|
|
339
|
+
let agentDrafting = false;
|
|
340
|
+
$effect(() => {
|
|
341
|
+
const presence = agentPresence;
|
|
342
|
+
return untrack(() =>
|
|
343
|
+
presence.subscribe(
|
|
344
|
+
trackPresenceDrag({
|
|
345
|
+
onDrag: (box) => {
|
|
346
|
+
if (cropDragInfo || selectionGesture.active) return;
|
|
347
|
+
const draft = agentDragDraft(mode, box, editor.controller.getState().imageDimensions);
|
|
348
|
+
if (!draft) return;
|
|
349
|
+
agentDrafting = true;
|
|
350
|
+
if ('crop' in draft) draftCrop = draft.crop;
|
|
351
|
+
else selectionDraft = draft.selectionPath;
|
|
352
|
+
void renderCanvas();
|
|
353
|
+
},
|
|
354
|
+
onEnd: () => {
|
|
355
|
+
if (!agentDrafting) return;
|
|
356
|
+
agentDrafting = false;
|
|
357
|
+
draftCrop = null;
|
|
358
|
+
selectionDraft = null;
|
|
359
|
+
void renderCanvas();
|
|
360
|
+
},
|
|
361
|
+
}),
|
|
362
|
+
),
|
|
363
|
+
);
|
|
364
|
+
});
|
|
365
|
+
|
|
328
366
|
async function renderCanvas(overrideAnnotation?: Annotation | null, overrideCrop?: CropBox | null) {
|
|
329
367
|
if (!canvasEl || !containerEl) return;
|
|
330
|
-
const
|
|
331
|
-
|
|
368
|
+
const viewport = measureCanvasViewport(containerEl);
|
|
369
|
+
view = { width: viewport.width, height: viewport.height };
|
|
332
370
|
|
|
333
371
|
await editor.controller.render(canvasEl, {
|
|
334
|
-
viewportWidth:
|
|
335
|
-
viewportHeight:
|
|
372
|
+
viewportWidth: viewport.width,
|
|
373
|
+
viewportHeight: viewport.height,
|
|
374
|
+
pixelRatio: viewport.pixelRatio,
|
|
336
375
|
fit,
|
|
337
376
|
showCropOverlay: mode === 'crop',
|
|
338
377
|
showSelectionOverlay: true,
|
|
@@ -354,11 +393,11 @@
|
|
|
354
393
|
const events = typeof e.getCoalescedEvents === 'function' ? e.getCoalescedEvents() : [];
|
|
355
394
|
const list = events.length ? events : [e];
|
|
356
395
|
const rect = canvasEl!.getBoundingClientRect();
|
|
357
|
-
const sx = rect.width > 0 ?
|
|
358
|
-
const sy = rect.height > 0 ?
|
|
396
|
+
const sx = rect.width > 0 ? view.width / rect.width : 1;
|
|
397
|
+
const sy = rect.height > 0 ? view.height / rect.height : 1;
|
|
359
398
|
const s = editor.controller.getState();
|
|
360
399
|
return list.map((ev) => ({
|
|
361
|
-
...canvasToImagePoint({ x: (ev.clientX - rect.left) * sx, y: (ev.clientY - rect.top) * sy }, s,
|
|
400
|
+
...canvasToImagePoint({ x: (ev.clientX - rect.left) * sx, y: (ev.clientY - rect.top) * sy }, s, view.width, view.height, false),
|
|
362
401
|
pressure: pointerPressure(ev),
|
|
363
402
|
}));
|
|
364
403
|
}
|
|
@@ -419,24 +458,17 @@
|
|
|
419
458
|
|
|
420
459
|
$effect(() => {
|
|
421
460
|
if (!containerEl) return;
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
observer.observe(containerEl);
|
|
427
|
-
return () => observer.disconnect();
|
|
428
|
-
} else {
|
|
429
|
-
const handleResize = () => renderCanvas();
|
|
430
|
-
window.addEventListener('resize', handleResize);
|
|
431
|
-
return () => window.removeEventListener('resize', handleResize);
|
|
432
|
-
}
|
|
461
|
+
const { width, height } = measureCanvasViewport(containerEl);
|
|
462
|
+
view = { width, height };
|
|
463
|
+
// Re-render when the layout size, the device pixel ratio or a host's CSS transform scale changes.
|
|
464
|
+
return watchCanvasViewport(containerEl, () => renderCanvas());
|
|
433
465
|
});
|
|
434
466
|
|
|
435
467
|
function getCanvasEventPoint(e: MouseEvent): ImagePoint {
|
|
436
468
|
if (!canvasEl) return { x: 0, y: 0 };
|
|
437
469
|
const rect = canvasEl.getBoundingClientRect();
|
|
438
|
-
const scaleX = rect.width > 0 ?
|
|
439
|
-
const scaleY = rect.height > 0 ?
|
|
470
|
+
const scaleX = rect.width > 0 ? view.width / rect.width : 1;
|
|
471
|
+
const scaleY = rect.height > 0 ? view.height / rect.height : 1;
|
|
440
472
|
return {
|
|
441
473
|
x: (e.clientX - rect.left) * scaleX,
|
|
442
474
|
y: (e.clientY - rect.top) * scaleY,
|
|
@@ -449,7 +481,7 @@
|
|
|
449
481
|
|
|
450
482
|
if (mode === 'select') {
|
|
451
483
|
const gesture = selectionGesture;
|
|
452
|
-
const imgPt = canvasToImagePoint(canvasPt, editor.state,
|
|
484
|
+
const imgPt = canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false);
|
|
453
485
|
void gesture.begin(imgPt, e).then(() => report('select', gesture.error));
|
|
454
486
|
return;
|
|
455
487
|
}
|
|
@@ -461,8 +493,8 @@
|
|
|
461
493
|
width: editor.state.imageDimensions.width,
|
|
462
494
|
height: editor.state.imageDimensions.height,
|
|
463
495
|
};
|
|
464
|
-
const handle = hitTestCrop(canvasPt, editor.state,
|
|
465
|
-
const startImgPt = canvasToImagePoint(canvasPt, editor.state,
|
|
496
|
+
const handle = hitTestCrop(canvasPt, editor.state, view.width, view.height, 14, activeCrop);
|
|
497
|
+
const startImgPt = canvasToImagePoint(canvasPt, editor.state, view.width, view.height, true);
|
|
466
498
|
|
|
467
499
|
if (handle) {
|
|
468
500
|
cropDragInfo = {
|
|
@@ -484,7 +516,7 @@
|
|
|
484
516
|
}
|
|
485
517
|
|
|
486
518
|
if (mode === 'annotate' && (editor.state.activeTool === 'shape' || editor.state.activeTool === 'type')) {
|
|
487
|
-
const imgPt = canvasToImagePoint(canvasPt, editor.state,
|
|
519
|
+
const imgPt = canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false);
|
|
488
520
|
report(editor.state.activeTool, null);
|
|
489
521
|
if (editor.state.activeTool === 'shape') shapeGesture.down(imgPt);
|
|
490
522
|
else {
|
|
@@ -496,7 +528,7 @@
|
|
|
496
528
|
}
|
|
497
529
|
|
|
498
530
|
if (mode === 'annotate') {
|
|
499
|
-
const imgPt = canvasToImagePoint(canvasPt, editor.state,
|
|
531
|
+
const imgPt = canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false);
|
|
500
532
|
dragStartPoint = imgPt;
|
|
501
533
|
|
|
502
534
|
if (editor.state.activeTool === 'select') {
|
|
@@ -540,7 +572,7 @@
|
|
|
540
572
|
|
|
541
573
|
if (mode === 'select') {
|
|
542
574
|
const gesture = selectionGesture;
|
|
543
|
-
if (gesture.active && gesture.move(canvasToImagePoint(canvasPt, editor.state,
|
|
575
|
+
if (gesture.active && gesture.move(canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false))) {
|
|
544
576
|
selectionDraft = gesture.draftPath;
|
|
545
577
|
renderCanvas();
|
|
546
578
|
}
|
|
@@ -549,7 +581,7 @@
|
|
|
549
581
|
|
|
550
582
|
if (mode === 'crop') {
|
|
551
583
|
if (cropDragInfo) {
|
|
552
|
-
const currentImgPt = canvasToImagePoint(canvasPt, editor.state,
|
|
584
|
+
const currentImgPt = canvasToImagePoint(canvasPt, editor.state, view.width, view.height, true);
|
|
553
585
|
let newCrop: CropBox;
|
|
554
586
|
|
|
555
587
|
if (cropDragInfo.handle === 'new') {
|
|
@@ -585,7 +617,7 @@
|
|
|
585
617
|
width: editor.state.imageDimensions.width,
|
|
586
618
|
height: editor.state.imageDimensions.height,
|
|
587
619
|
};
|
|
588
|
-
const handle = hitTestCrop(canvasPt, editor.state,
|
|
620
|
+
const handle = hitTestCrop(canvasPt, editor.state, view.width, view.height, 14, activeCrop);
|
|
589
621
|
canvasEl.style.cursor = getCropCursor(handle, editor.state.transform);
|
|
590
622
|
}
|
|
591
623
|
return;
|
|
@@ -593,7 +625,7 @@
|
|
|
593
625
|
|
|
594
626
|
if (mode === 'annotate' && editor.state.activeTool === 'shape') {
|
|
595
627
|
if (shapeGesture.active) {
|
|
596
|
-
shapeGesture.move(canvasToImagePoint(canvasPt, editor.state,
|
|
628
|
+
shapeGesture.move(canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false), { shift: e.shiftKey, alt: e.altKey });
|
|
597
629
|
if (shapeGesture.error) report('shape', shapeGesture.error);
|
|
598
630
|
}
|
|
599
631
|
return;
|
|
@@ -601,7 +633,7 @@
|
|
|
601
633
|
|
|
602
634
|
if (mode === 'annotate') {
|
|
603
635
|
if (annotationDragInfo) {
|
|
604
|
-
const currentImgPt = canvasToImagePoint(canvasPt, editor.state,
|
|
636
|
+
const currentImgPt = canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false);
|
|
605
637
|
const dx = currentImgPt.x - annotationDragInfo.startImgPt.x;
|
|
606
638
|
const dy = currentImgPt.y - annotationDragInfo.startImgPt.y;
|
|
607
639
|
editor.updateAnnotation(annotationDragInfo.annId, {
|
|
@@ -613,7 +645,7 @@
|
|
|
613
645
|
|
|
614
646
|
if (!dragStartPoint) {
|
|
615
647
|
if (editor.state.activeTool === 'select') {
|
|
616
|
-
const imgPt = canvasToImagePoint(canvasPt, editor.state,
|
|
648
|
+
const imgPt = canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false);
|
|
617
649
|
let hovered = false;
|
|
618
650
|
for (let i = editor.state.annotations.length - 1; i >= 0; i--) {
|
|
619
651
|
if (hitTestAnnotation(imgPt, editor.state.annotations[i])) {
|
|
@@ -628,7 +660,7 @@
|
|
|
628
660
|
return;
|
|
629
661
|
}
|
|
630
662
|
|
|
631
|
-
const currentImgPt = canvasToImagePoint(canvasPt, editor.state,
|
|
663
|
+
const currentImgPt = canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false);
|
|
632
664
|
const stroke = { strokeColor: annotationColor, strokeWidth: annotationWidth };
|
|
633
665
|
|
|
634
666
|
if (editor.state.activeTool === 'pen' && isDrawing) {
|
|
@@ -687,7 +719,7 @@
|
|
|
687
719
|
if (mode === 'select') {
|
|
688
720
|
const gesture = selectionGesture;
|
|
689
721
|
if (gesture.active) {
|
|
690
|
-
gesture.end(canvasToImagePoint(canvasPt, editor.state,
|
|
722
|
+
gesture.end(canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false), e);
|
|
691
723
|
selectionDraft = null;
|
|
692
724
|
renderCanvas();
|
|
693
725
|
}
|
|
@@ -711,7 +743,7 @@
|
|
|
711
743
|
return;
|
|
712
744
|
}
|
|
713
745
|
if (mode === 'annotate' && editor.state.activeTool === 'type') {
|
|
714
|
-
typeTool.up(canvasToImagePoint(canvasPt, editor.state,
|
|
746
|
+
typeTool.up(canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false));
|
|
715
747
|
typeSession = typeTool.session;
|
|
716
748
|
if (typeTool.error) report('type', typeTool.error);
|
|
717
749
|
void renderCanvas();
|
|
@@ -727,7 +759,7 @@
|
|
|
727
759
|
}
|
|
728
760
|
|
|
729
761
|
if (dragStartPoint) {
|
|
730
|
-
const currentImgPt = canvasToImagePoint(canvasPt, editor.state,
|
|
762
|
+
const currentImgPt = canvasToImagePoint(canvasPt, editor.state, view.width, view.height, false);
|
|
731
763
|
const stroke = { strokeColor: annotationColor, strokeWidth: annotationWidth };
|
|
732
764
|
|
|
733
765
|
if (editor.state.activeTool === 'pen' && isDrawing) {
|
|
@@ -802,7 +834,7 @@
|
|
|
802
834
|
onpointerup={handlePaintPointerUp}
|
|
803
835
|
onpointercancel={() => paintGesture.cancel()}
|
|
804
836
|
data-testid="image-editor-canvas"
|
|
805
|
-
style={transformActive ? `cursor: ${transformCursor}
|
|
837
|
+
style="width: 100%; height: 100%;{transformActive ? ` cursor: ${transformCursor};` : ''}"
|
|
806
838
|
class="absolute inset-0 w-full h-full block {transformActive ? 'touch-none' : mode === 'paint' ? 'touch-none cursor-crosshair' : mode === 'annotate' && editor.state.activeTool === 'type' ? 'cursor-text' : mode === 'annotate' && editor.state.activeTool === 'shape' ? 'cursor-crosshair' : ''}"
|
|
807
839
|
></canvas>
|
|
808
840
|
{#if typeSession}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.110.1",
|
|
4
4
|
"description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
|
|
5
5
|
"author": "Pixerate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
"@tiptap/extension-bubble-menu": "^2.27.3",
|
|
161
161
|
"@tiptap/extension-placeholder": "^2.27.3",
|
|
162
162
|
"@tiptap/starter-kit": "^2.27.3",
|
|
163
|
-
"@pixerate/editor": "0.
|
|
163
|
+
"@pixerate/editor": "0.150.1"
|
|
164
164
|
},
|
|
165
165
|
"peerDependencies": {
|
|
166
166
|
"@dagrejs/dagre": "^1.1.4",
|
|
@@ -179,14 +179,14 @@
|
|
|
179
179
|
},
|
|
180
180
|
"devDependencies": {
|
|
181
181
|
"@dagrejs/dagre": "^1.1.4",
|
|
182
|
-
"@sveltejs/package": "^3.0.
|
|
182
|
+
"@sveltejs/package": "^3.0.1",
|
|
183
183
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
|
184
184
|
"@tiptap/core": "^2.27.3",
|
|
185
185
|
"@tiptap/pm": "^2.27.3",
|
|
186
186
|
"@types/dagre": "^0.7.52",
|
|
187
187
|
"@xyflow/svelte": "^1.7.0",
|
|
188
188
|
"svelte": "^5.57.2",
|
|
189
|
-
"svelte-check": "^4.7.
|
|
189
|
+
"svelte-check": "^4.7.7",
|
|
190
190
|
"typescript": "^5.7.3",
|
|
191
191
|
"vitest": "^5.0.5"
|
|
192
192
|
},
|