@peteai/presentation-editor 0.0.7 → 0.0.9

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.
Files changed (80) hide show
  1. package/dist/components/editor/active-layers.svelte +24 -9
  2. package/dist/components/editor/active-layers.svelte.d.ts +6 -1
  3. package/dist/components/editor/editor.svelte +15 -17
  4. package/dist/components/editor/editor.svelte.js +10 -8
  5. package/dist/components/editor/header.svelte +25 -21
  6. package/dist/components/editor/hotkeys.svelte +7 -0
  7. package/dist/components/editor/layers/active-layer-border.svelte +1 -1
  8. package/dist/components/editor/layers/buttons/border-button/border-button-colors.svelte +1 -1
  9. package/dist/components/editor/layers/buttons/opacity-button/opacity-button.svelte +1 -6
  10. package/dist/components/editor/layers/controls/corner-scale-control/corner-scale-control.svelte +1 -1
  11. package/dist/components/editor/layers/controls/group-resize-control/group-resize-control.svelte +11 -11
  12. package/dist/components/editor/layers/controls/rotate-control/rotate-control.svelte +12 -6
  13. package/dist/components/editor/layers/controls/rotate-control/rotate-control.svelte.d.ts +4 -1
  14. package/dist/components/editor/layers/controls/side-resize-control/side-resize-control.svelte +13 -13
  15. package/dist/components/editor/layers/controls/side-scale-control/side-scale-control.svelte +1 -1
  16. package/dist/components/editor/layers/index.d.ts +2 -2
  17. package/dist/components/editor/layers/index.js +2 -2
  18. package/dist/components/editor/layers/layer-button.svelte +1 -1
  19. package/dist/components/editor/layers/types/background/background-content-image.svelte +15 -20
  20. package/dist/components/editor/layers/types/background/background-layer-buttons.svelte +1 -1
  21. package/dist/components/editor/layers/types/background/background-layer.svelte +2 -2
  22. package/dist/components/editor/layers/types/image/controls/image-rotate-control/image-rotate-control.svelte +120 -0
  23. package/dist/components/editor/layers/types/image/controls/image-rotate-control/image-rotate-control.svelte.d.ts +8 -0
  24. package/dist/components/editor/layers/types/image/controls/image-rotate-control/index.d.ts +2 -0
  25. package/dist/components/editor/layers/types/image/controls/image-rotate-control/index.js +4 -0
  26. package/dist/components/editor/layers/types/image/controls/image-scale-control/image-scale-control.svelte +154 -0
  27. package/dist/components/editor/layers/types/image/controls/image-scale-control/image-scale-control.svelte.d.ts +91 -0
  28. package/dist/components/editor/layers/types/image/controls/image-scale-control/index.d.ts +2 -0
  29. package/dist/components/editor/layers/types/image/controls/image-scale-control/index.js +4 -0
  30. package/dist/components/editor/layers/types/image/image-layer-content.svelte +3 -3
  31. package/dist/components/editor/layers/types/image/image-layer-crop.svelte +182 -0
  32. package/dist/components/editor/layers/types/image/image-layer-crop.svelte.d.ts +10 -0
  33. package/dist/components/editor/layers/types/image/image-layer.svelte +16 -0
  34. package/dist/components/editor/layers/types/image/index.d.ts +2 -1
  35. package/dist/components/editor/layers/types/image/index.js +2 -1
  36. package/dist/components/editor/layers/types/text/extensions/list-item/list-item.js +0 -2
  37. package/dist/components/editor/layers/utils.d.ts +24 -9
  38. package/dist/components/editor/layers/utils.js +107 -54
  39. package/dist/components/editor/page-editor.svelte +6 -2
  40. package/dist/components/editor/sidebar/color-sidebar/color-sidebar-color.svelte +2 -2
  41. package/dist/components/editor/sidebar/color-sidebar/color-sidebar-gradient-picker.svelte +5 -5
  42. package/dist/components/editor/sidebar/color-sidebar/color-sidebar.svelte +4 -4
  43. package/dist/components/editor/sidebar/font-sidebar/font-sidebar.svelte +1 -1
  44. package/dist/components/editor/sidebar/image-crop-sidebar.svelte +112 -0
  45. package/dist/components/editor/sidebar/image-crop-sidebar.svelte.d.ts +7 -0
  46. package/dist/components/editor/sidebar/position-sidebar.svelte +0 -2
  47. package/dist/components/editor/sidebar/sidebar-uploads-tab.svelte +3 -3
  48. package/dist/components/editor/sidebar/sidebar.svelte +7 -4
  49. package/dist/components/editor/snapping-guides.svelte +3 -3
  50. package/dist/components/editor/types.d.ts +2 -1
  51. package/dist/components/editor/utils.js +1 -0
  52. package/dist/components/ui/color-picker/color-picker-alpha-grid.svelte +2 -1
  53. package/dist/components/ui/color-picker/color-picker.svelte +6 -6
  54. package/dist/components/ui/context-menu/context-menu-checkbox-item.svelte +1 -1
  55. package/dist/components/ui/context-menu/context-menu-content.svelte +3 -1
  56. package/dist/components/ui/context-menu/context-menu-group-heading.svelte +1 -1
  57. package/dist/components/ui/context-menu/context-menu-item.svelte +1 -1
  58. package/dist/components/ui/context-menu/context-menu-radio-item.svelte +1 -1
  59. package/dist/components/ui/context-menu/context-menu-separator.svelte +1 -1
  60. package/dist/components/ui/context-menu/context-menu-shortcut.svelte +1 -1
  61. package/dist/components/ui/context-menu/context-menu-sub-content.svelte +1 -1
  62. package/dist/components/ui/context-menu/context-menu-sub-trigger.svelte +1 -1
  63. package/dist/components/ui/dialog/dialog-content.svelte +4 -2
  64. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +1 -1
  65. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +2 -0
  66. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +1 -1
  67. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +1 -1
  68. package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
  69. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
  70. package/dist/components/ui/input/input.svelte +1 -1
  71. package/dist/components/ui/slider/slider.svelte +3 -3
  72. package/dist/components/ui/tabs/index.d.ts +4 -4
  73. package/dist/components/ui/tabs/index.js +4 -4
  74. package/dist/components/ui/tabs/tabs-content.svelte +4 -4
  75. package/dist/components/ui/tabs/tabs-content.svelte.d.ts +1 -1
  76. package/dist/components/ui/tabs/tabs-list.svelte +5 -9
  77. package/dist/components/ui/tabs/tabs-list.svelte.d.ts +1 -1
  78. package/dist/components/ui/tabs/tabs-trigger.svelte +4 -4
  79. package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +1 -1
  80. package/package.json +19 -19
@@ -0,0 +1,120 @@
1
+ <script lang="ts">
2
+ import RotateIcon from '@lucide/svelte/icons/refresh-cw';
3
+ import { getEditorContext } from '../../../../../editor.svelte.js';
4
+ import {
5
+ calculateImageLayerPropsForImageRotate,
6
+ degToRad,
7
+ rotatePoint,
8
+ } from '../../../../utils.js';
9
+ import type { ImageLayer } from '../../../../../types.js';
10
+
11
+ interface Props {
12
+ pageRef: HTMLDivElement;
13
+ layer: ImageLayer;
14
+ }
15
+
16
+ const { pageRef, layer }: Props = $props();
17
+
18
+ const type = 'rotate';
19
+
20
+ const editor = getEditorContext();
21
+
22
+ let initial: {
23
+ clientX: number;
24
+ clientY: number;
25
+ layer: ImageLayer;
26
+ } | null = null;
27
+
28
+ /**
29
+ * Normalize the rotation angle to be between -180 and 180 degrees.
30
+ *
31
+ * @param rotate - The rotation angle in degrees.
32
+ * @returns The normalized rotation angle.
33
+ */
34
+ const normalizeRotation = (rotate: number) => {
35
+ // Adjust the rotation to be within the range of 0 to 360 degrees
36
+ const adjustedRotate = (rotate + 360) % 360;
37
+ // If the adjusted rotation is greater than or equal to 180, subtract 360 to bring it within the range of -180 to 180
38
+ return adjustedRotate >= 180 ? adjustedRotate - 360 : adjustedRotate;
39
+ };
40
+
41
+ const onMouseDown = (e: MouseEvent) => {
42
+ if (e.button !== 0) return;
43
+ e.preventDefault();
44
+ e.stopPropagation();
45
+ console.log(type, 'mousedown', e);
46
+
47
+ initial = {
48
+ clientX: e.clientX,
49
+ clientY: e.clientY,
50
+ layer: $state.snapshot(layer),
51
+ };
52
+ addEventListener('mousemove', onMouseMove);
53
+ addEventListener('mouseup', onMouseUp);
54
+ };
55
+
56
+ const onMouseUp = (e: MouseEvent) => {
57
+ console.log(type, 'mouseup', e);
58
+ removeEventListener('mousemove', onMouseMove);
59
+ removeEventListener('mouseup', onMouseUp);
60
+ initial = null;
61
+ };
62
+
63
+ const onMouseMove = (e: MouseEvent) => {
64
+ if (!initial) return;
65
+
66
+ const pageRect = pageRef.getBoundingClientRect();
67
+ if (!pageRect) return;
68
+
69
+ const { x: pageX, y: pageY } = pageRect;
70
+
71
+ const { x, y, width, height, scale, rotate, image, offsetX, offsetY } = initial.layer;
72
+
73
+ const relativeImageCenter = rotatePoint(degToRad(rotate), {
74
+ x: image.width / 2 + offsetX - width / 2,
75
+ y: image.height / 2 + offsetY - height / 2,
76
+ });
77
+
78
+ // Calculate the center of the layer image
79
+ const clientX = pageX + (x + (width / 2 + relativeImageCenter.x) * scale) * editor.zoom;
80
+ const clientY = pageY + (y + (height / 2 + relativeImageCenter.y) * scale) * editor.zoom;
81
+
82
+ // Calculate the current angle based on the current mouse position relative to the center of the layer
83
+ const currentAngle = Math.atan2(
84
+ (e.clientY - clientY) / editor.zoom,
85
+ (e.clientX - clientX) / editor.zoom,
86
+ );
87
+
88
+ // Convert the current angle from radians to degrees and adjust to make 0 degrees the initial position
89
+ let currentRotate = (currentAngle * 180) / Math.PI - 90 - rotate;
90
+
91
+ // Add snapping
92
+ const snapThreshold = 1;
93
+ const remainder = Math.abs(currentRotate) % 45;
94
+ if (remainder < snapThreshold || remainder > 45 - snapThreshold) {
95
+ currentRotate = Math.round(currentRotate / 45) * 45;
96
+ }
97
+
98
+ currentRotate = normalizeRotation(currentRotate);
99
+
100
+ const props = calculateImageLayerPropsForImageRotate(initial.layer, currentRotate);
101
+ Object.assign(layer, props);
102
+ };
103
+ </script>
104
+
105
+ <!-- <div class="absolute top-full flex p-6"> -->
106
+ <!-- <div class="group pointer-events-none relative m-1 flex h-8 w-8 items-center justify-center"> -->
107
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
108
+ <div
109
+ class="group pointer-events-none absolute -bottom-9 left-1/2 -m-2 h-6 w-6 cursor-grab rounded-full bg-white"
110
+ onmousedown={onMouseDown}
111
+ >
112
+ <div
113
+ class="flex h-6 w-6 items-center justify-center rounded-full bg-white transition-colors after:pointer-events-auto after:absolute after:inset-0 after:rounded-full group-hover:bg-primary group-hover:text-primary-foreground group-active:bg-primary group-active:text-primary-foreground"
114
+ style:box-shadow="0 0 4px 1px rgba(57,76,96,.15), 0 0 0 1px rgba(43,59,74,.3)"
115
+ >
116
+ <RotateIcon class="h-4 w-4" />
117
+ </div>
118
+ </div>
119
+ <!-- </div> -->
120
+ <!-- </div> -->
@@ -0,0 +1,8 @@
1
+ import type { ImageLayer } from '../../../../../types.js';
2
+ interface Props {
3
+ pageRef: HTMLDivElement;
4
+ layer: ImageLayer;
5
+ }
6
+ declare const ImageRotateControl: import("svelte").Component<Props, {}, "">;
7
+ type ImageRotateControl = ReturnType<typeof ImageRotateControl>;
8
+ export default ImageRotateControl;
@@ -0,0 +1,2 @@
1
+ import Root from './image-rotate-control.svelte';
2
+ export { Root, Root as ImageRotateControl, };
@@ -0,0 +1,4 @@
1
+ import Root from './image-rotate-control.svelte';
2
+ export { Root,
3
+ //
4
+ Root as ImageRotateControl, };
@@ -0,0 +1,154 @@
1
+ <script lang="ts" module>
2
+ import { tv, type VariantProps } from 'tailwind-variants';
3
+ import type { ImageLayer } from '../../../../../types.js';
4
+
5
+ const cornerScaleControlVariants = tv({
6
+ slots: {
7
+ base: 'group absolute w-8 h-8 flex items-center justify-center pointer-events-none',
8
+ handler: 'absolute w-4 h-4 pointer-events-auto',
9
+ cursor: '',
10
+ },
11
+ variants: {
12
+ origin: {
13
+ 'bottom-right': {
14
+ base: '-top-4 -left-4',
15
+ handler: '-translate-x-0.5 -translate-y-0.5 rounded-tl-full',
16
+ cursor: 'cursor-nwse-resize',
17
+ },
18
+ 'bottom-left': {
19
+ base: '-top-4 -right-4',
20
+ handler: 'translate-x-0.5 -translate-y-0.5 rounded-tr-full',
21
+ cursor: 'cursor-nesw-resize',
22
+ },
23
+ 'top-right': {
24
+ base: '-bottom-4 -left-4',
25
+ handler: '-translate-x-0.5 translate-y-0.5 rounded-bl-full',
26
+ cursor: 'cursor-nesw-resize',
27
+ },
28
+ 'top-left': {
29
+ base: '-bottom-4 -right-4',
30
+ handler: 'translate-x-0.5 translate-y-0.5 rounded-br-full',
31
+ cursor: 'cursor-nwse-resize',
32
+ },
33
+ },
34
+ },
35
+ });
36
+
37
+ type Variants = VariantProps<typeof cornerScaleControlVariants>;
38
+
39
+ interface Props extends Omit<Variants, 'origin'>, Required<Pick<Variants, 'origin'>> {
40
+ layer: ImageLayer;
41
+ }
42
+ </script>
43
+
44
+ <script lang="ts">
45
+ import { cn } from '../../../../../../../utils.js';
46
+ import {
47
+ getImageLayerBboxRelativeToImageCenter,
48
+ calculateNewPosition,
49
+ degToRad,
50
+ rotatePoint,
51
+ } from '../../../../utils.js';
52
+ import { getEditorContext } from '../../../../../editor.svelte.js';
53
+
54
+ let { origin, layer }: Props = $props();
55
+
56
+ const type = `${origin}-origin-scale`;
57
+
58
+ const editor = getEditorContext();
59
+
60
+ let initial: {
61
+ clientX: number;
62
+ clientY: number;
63
+ layer: ImageLayer;
64
+ minScaleFactor: number;
65
+ } | null = null;
66
+
67
+ const { base, handler, cursor } = cornerScaleControlVariants({ origin });
68
+
69
+ const onMouseDown = (e: MouseEvent) => {
70
+ if (e.button !== 0) return;
71
+ e.preventDefault();
72
+ e.stopPropagation();
73
+ console.log(type, 'mousedown', e);
74
+
75
+ const { minX, minY, maxX, maxY } = getImageLayerBboxRelativeToImageCenter(layer);
76
+
77
+ const minScaleFactor = Math.max(
78
+ ~origin.indexOf('left') ? 0.5 + maxX / layer.image.width : 0.5 - minX / layer.image.width,
79
+ ~origin.indexOf('top') ? 0.5 + maxY / layer.image.height : 0.5 - minY / layer.image.height,
80
+ );
81
+
82
+ initial = {
83
+ clientX: e.clientX,
84
+ clientY: e.clientY,
85
+ layer: $state.snapshot(layer),
86
+ minScaleFactor,
87
+ };
88
+ addEventListener('mousemove', onMouseMove);
89
+ addEventListener('mouseup', onMouseUp);
90
+ };
91
+
92
+ const onMouseUp = (e: MouseEvent) => {
93
+ console.log(type, 'mouseup', e);
94
+ removeEventListener('mousemove', onMouseMove);
95
+ removeEventListener('mouseup', onMouseUp);
96
+ initial = null;
97
+ };
98
+
99
+ const onMouseMove = (e: MouseEvent) => {
100
+ if (!initial) return;
101
+
102
+ const { x, y, width, height, scale, rotate, image, imageRotate, offsetX, offsetY } =
103
+ initial.layer;
104
+ const imageX = x + offsetX * scale;
105
+ const imageY = y + offsetY * scale;
106
+ const rad = degToRad(rotate + imageRotate);
107
+ const xDiff = (e.clientX - initial.clientX) / editor.zoom;
108
+ const yDiff = (e.clientY - initial.clientY) / editor.zoom;
109
+ const adjustedXDiff = xDiff * Math.cos(rad) + yDiff * Math.sin(rad);
110
+ const adjustedYDiff = -xDiff * Math.sin(rad) + yDiff * Math.cos(rad);
111
+
112
+ // Define the new x2 and y2 coordinates based on the variant (corner being dragged)
113
+ const x2 = imageX + image.width * scale + adjustedXDiff * (~origin.indexOf('left') ? 1 : -1);
114
+ const y2 = imageY + image.height * scale + adjustedYDiff * (~origin.indexOf('top') ? 1 : -1);
115
+
116
+ // Calculate the slope of the rectangle
117
+ const slope = image.height / image.width;
118
+
119
+ // Calculate the x-coordinate of the intersection point
120
+ const xIntersect = (y2 + x2 / slope - imageY + imageX * slope) / (slope + 1 / slope);
121
+
122
+ let scaleFactor = Math.max(
123
+ (xIntersect - imageX) / (image.width * scale),
124
+ initial.minScaleFactor,
125
+ );
126
+
127
+ if (scaleFactor > 0) {
128
+ const { newX, newY } = calculateNewPosition(
129
+ origin,
130
+ { ...image, x: offsetX, y: offsetY, rotate: imageRotate, scale: 1 },
131
+ image.width * scaleFactor,
132
+ image.height * scaleFactor,
133
+ );
134
+
135
+ const newProps = {
136
+ scale: scale * scaleFactor,
137
+ width: width / scaleFactor,
138
+ height: height / scaleFactor,
139
+ offsetX: newX / scaleFactor,
140
+ offsetY: newY / scaleFactor,
141
+ };
142
+ Object.assign(layer, newProps);
143
+ }
144
+ };
145
+ </script>
146
+
147
+ <div class={base()}>
148
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
149
+ <div class={cn(handler(), cursor())} onmousedown={onMouseDown}></div>
150
+ <div
151
+ class="h-3 w-3 rounded-full bg-white transition-colors group-hover:bg-primary group-active:bg-primary"
152
+ style:box-shadow="0 0 4px 1px rgba(57,76,96,.15), 0 0 0 1px rgba(43,59,74,.3)"
153
+ ></div>
154
+ </div>
@@ -0,0 +1,91 @@
1
+ import { type VariantProps } from 'tailwind-variants';
2
+ import type { ImageLayer } from '../../../../../types.js';
3
+ declare const cornerScaleControlVariants: import("tailwind-variants").TVReturnType<{
4
+ origin: {
5
+ 'bottom-right': {
6
+ base: string;
7
+ handler: string;
8
+ cursor: string;
9
+ };
10
+ 'bottom-left': {
11
+ base: string;
12
+ handler: string;
13
+ cursor: string;
14
+ };
15
+ 'top-right': {
16
+ base: string;
17
+ handler: string;
18
+ cursor: string;
19
+ };
20
+ 'top-left': {
21
+ base: string;
22
+ handler: string;
23
+ cursor: string;
24
+ };
25
+ };
26
+ }, {
27
+ base: string;
28
+ handler: string;
29
+ cursor: string;
30
+ }, undefined, {
31
+ origin: {
32
+ 'bottom-right': {
33
+ base: string;
34
+ handler: string;
35
+ cursor: string;
36
+ };
37
+ 'bottom-left': {
38
+ base: string;
39
+ handler: string;
40
+ cursor: string;
41
+ };
42
+ 'top-right': {
43
+ base: string;
44
+ handler: string;
45
+ cursor: string;
46
+ };
47
+ 'top-left': {
48
+ base: string;
49
+ handler: string;
50
+ cursor: string;
51
+ };
52
+ };
53
+ }, {
54
+ base: string;
55
+ handler: string;
56
+ cursor: string;
57
+ }, import("tailwind-variants").TVReturnType<{
58
+ origin: {
59
+ 'bottom-right': {
60
+ base: string;
61
+ handler: string;
62
+ cursor: string;
63
+ };
64
+ 'bottom-left': {
65
+ base: string;
66
+ handler: string;
67
+ cursor: string;
68
+ };
69
+ 'top-right': {
70
+ base: string;
71
+ handler: string;
72
+ cursor: string;
73
+ };
74
+ 'top-left': {
75
+ base: string;
76
+ handler: string;
77
+ cursor: string;
78
+ };
79
+ };
80
+ }, {
81
+ base: string;
82
+ handler: string;
83
+ cursor: string;
84
+ }, undefined, unknown, unknown, undefined>>;
85
+ type Variants = VariantProps<typeof cornerScaleControlVariants>;
86
+ interface Props extends Omit<Variants, 'origin'>, Required<Pick<Variants, 'origin'>> {
87
+ layer: ImageLayer;
88
+ }
89
+ declare const ImageScaleControl: import("svelte").Component<Props, {}, "">;
90
+ type ImageScaleControl = ReturnType<typeof ImageScaleControl>;
91
+ export default ImageScaleControl;
@@ -0,0 +1,2 @@
1
+ import Root from './image-scale-control.svelte';
2
+ export { Root, Root as ImageScaleControl, };
@@ -0,0 +1,4 @@
1
+ import Root from './image-scale-control.svelte';
2
+ export { Root,
3
+ //
4
+ Root as ImageScaleControl, };
@@ -32,7 +32,7 @@
32
32
  layer.flipX || layer.flipY ? `scale(${layer.flipX ? -1 : 1}, ${layer.flipY ? -1 : 1})` : null,
33
33
  );
34
34
 
35
- let cornderRadius = $derived(layer.cornerRadius * thumbScale);
35
+ let cornderRadius = $derived(Math.min(layer.cornerRadius * thumbScale, width / 2, height / 2));
36
36
 
37
37
  const bezier = 0.447715;
38
38
  let roundedClipPath = $derived(
@@ -51,7 +51,7 @@
51
51
  class="relative"
52
52
  style:width="{layer.image.width * scale}px"
53
53
  style:height="{layer.image.height * scale}px"
54
- style:transform={`translate(${-layer.offsetX * scale}px, ${-layer.offsetY * scale}px)`}
54
+ style:transform={`translate(${layer.offsetX * scale}px, ${layer.offsetY * scale}px) rotate(${layer.imageRotate}deg)`}
55
55
  >
56
56
  {#if imageLoaded}
57
57
  <img
@@ -85,7 +85,7 @@
85
85
  <ColorIndicatorGradientDef id={gradientId} color={borderColor} {width} {height} />
86
86
  {/if}
87
87
  <clipPath id={layerBorderId}>
88
- <path d={path}></path>
88
+ <path d={roundedClipPath || path}></path>
89
89
  </clipPath>
90
90
  </defs>
91
91
  <path
@@ -0,0 +1,182 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import type { ImageLayer } from '../../../types.js';
4
+ import { getEditorContext } from '../../../editor.svelte.js';
5
+ import ImageLayerContent from './image-layer-content.svelte';
6
+ import { ImageScaleControl } from './controls/image-scale-control/index.js';
7
+ import { ImageRotateControl } from './controls/image-rotate-control/index.js';
8
+ import { getImageLayerBboxRelativeToImageCenter, degToRad, rotatePoint } from '../../utils.js';
9
+
10
+ interface Props {
11
+ layer: ImageLayer;
12
+ viewportRef: HTMLDivElement;
13
+ wrapperRef: HTMLDivElement;
14
+ pageRef: HTMLDivElement;
15
+ }
16
+
17
+ const { layer, viewportRef, wrapperRef, pageRef }: Props = $props();
18
+
19
+ const editor = getEditorContext();
20
+
21
+ let overlayRef: HTMLDivElement | null = $state(null);
22
+
23
+ let imageTransform = $derived.by(() => {
24
+ const rad = degToRad(layer.rotate);
25
+
26
+ const layerCenter = {
27
+ x: layer.x + (layer.width * layer.scale) / 2,
28
+ y: layer.y + (layer.height * layer.scale) / 2,
29
+ };
30
+
31
+ const relativeCenter = {
32
+ x: layer.x + (layer.offsetX + layer.image.width / 2) * layer.scale,
33
+ y: layer.y + (layer.offsetY + layer.image.height / 2) * layer.scale,
34
+ };
35
+
36
+ const center = rotatePoint(rad, relativeCenter, layerCenter);
37
+
38
+ return {
39
+ x: center.x - (layer.image.width * layer.scale) / 2,
40
+ y: center.y - (layer.image.height * layer.scale) / 2,
41
+ width: layer.image.width,
42
+ height: layer.image.height,
43
+ rotate: layer.imageRotate + layer.rotate,
44
+ scale: layer.scale,
45
+ };
46
+ });
47
+
48
+ const updateOverlay = () => {
49
+ if (overlayRef) {
50
+ const viewportRect = viewportRef.getBoundingClientRect();
51
+ const wrapperRect = wrapperRef.getBoundingClientRect();
52
+ const pageRect = pageRef.getBoundingClientRect();
53
+
54
+ overlayRef.style.left = `${viewportRect.left - viewportRef.scrollLeft - pageRect.left}px`;
55
+ overlayRef.style.top = `${viewportRect.top - viewportRef.scrollTop - pageRect.top}px`;
56
+ overlayRef.style.width = `${wrapperRect.width > viewportRect.width ? wrapperRect.width : viewportRect.width}px`;
57
+ overlayRef.style.height = `${wrapperRect.height > viewportRect.height ? wrapperRect.height : viewportRect.height}px`;
58
+ }
59
+ };
60
+
61
+ $effect(() => {
62
+ editor.zoom;
63
+ updateOverlay();
64
+ });
65
+
66
+ onMount(() => {
67
+ const observer = new ResizeObserver(updateOverlay);
68
+
69
+ if (viewportRef) observer.observe(viewportRef);
70
+
71
+ return () => observer.disconnect();
72
+ });
73
+
74
+ let initial: {
75
+ clientX: number;
76
+ clientY: number;
77
+ layer: ImageLayer;
78
+ minImageXDiff: number;
79
+ minImageYDiff: number;
80
+ maxImageXDiff: number;
81
+ maxImageYDiff: number;
82
+ } | null = null;
83
+
84
+ const onMouseDown = (e: MouseEvent) => {
85
+ e.preventDefault();
86
+ e.stopPropagation();
87
+
88
+ const { minX, minY, maxX, maxY } = getImageLayerBboxRelativeToImageCenter(layer);
89
+
90
+ initial = {
91
+ clientX: e.clientX,
92
+ clientY: e.clientY,
93
+ layer: $state.snapshot(layer),
94
+ minImageXDiff: maxX - layer.image.width / 2,
95
+ minImageYDiff: maxY - layer.image.height / 2,
96
+ maxImageXDiff: layer.image.width / 2 + minX,
97
+ maxImageYDiff: layer.image.height / 2 + minY,
98
+ };
99
+ addEventListener('mousemove', onMouseMove);
100
+ addEventListener('mouseup', onMouseUp);
101
+ };
102
+
103
+ const onMouseUp = (e: MouseEvent) => {
104
+ removeEventListener('mousemove', onMouseMove);
105
+ removeEventListener('mouseup', onMouseUp);
106
+ initial = null;
107
+ };
108
+
109
+ const onMouseMove = (e: MouseEvent) => {
110
+ if (!initial) return;
111
+ const { scale, rotate, imageRotate, offsetX, offsetY } = initial.layer;
112
+
113
+ const xDiff = (e.clientX - initial.clientX) / scale / editor.zoom;
114
+ const yDiff = (e.clientY - initial.clientY) / scale / editor.zoom;
115
+
116
+ const imageRad = degToRad(rotate + imageRotate);
117
+
118
+ const imageXDiff2 = Math.min(
119
+ Math.max(xDiff * Math.cos(imageRad) + yDiff * Math.sin(imageRad), initial.minImageXDiff),
120
+ initial.maxImageXDiff,
121
+ );
122
+ const imageYDiff2 = Math.min(
123
+ Math.max(-xDiff * Math.sin(imageRad) + yDiff * Math.cos(imageRad), initial.minImageYDiff),
124
+ initial.maxImageYDiff,
125
+ );
126
+
127
+ const rad = degToRad(-imageRotate);
128
+
129
+ const adjustedXDiff = imageXDiff2 * Math.cos(rad) + imageYDiff2 * Math.sin(rad);
130
+ const adjustedYDiff = -imageXDiff2 * Math.sin(rad) + imageYDiff2 * Math.cos(rad);
131
+
132
+ Object.assign(layer, {
133
+ offsetX: offsetX + adjustedXDiff,
134
+ offsetY: offsetY + adjustedYDiff,
135
+ });
136
+ };
137
+ </script>
138
+
139
+ <div class="select-none">
140
+ <div bind:this={overlayRef} class="absolute bg-slate-500/50"></div>
141
+ <div
142
+ class="absolute opacity-50"
143
+ style:width={`${layer.image.width * layer.scale * editor.zoom}px`}
144
+ style:height={`${layer.image.height * layer.scale * editor.zoom}px`}
145
+ style:transform={`translate(${imageTransform.x * editor.zoom}px, ${imageTransform.y * editor.zoom}px) rotate(${imageTransform.rotate}deg)`}
146
+ >
147
+ <img class="pointer-events-none h-full w-full" src={layer.image.src} alt="" />
148
+ </div>
149
+ <div
150
+ class="absolute overflow-hidden"
151
+ style:width={`${layer.width * layer.scale * editor.zoom}px`}
152
+ style:height={`${layer.height * layer.scale * editor.zoom}px`}
153
+ style:transform={`translate(${layer.x * editor.zoom}px, ${layer.y * editor.zoom}px) rotate(${layer.rotate}deg)`}
154
+ >
155
+ <ImageLayerContent {layer} thumbScale={editor.zoom} />
156
+ </div>
157
+ <div class="pointer-events-auto">
158
+ <div
159
+ class="pointer-events-none absolute shadow-inner-1 outline outline-2 outline-primary/50"
160
+ style:width={`${layer.image.width * layer.scale * editor.zoom}px`}
161
+ style:height={`${layer.image.height * layer.scale * editor.zoom}px`}
162
+ style:transform={`translate(${imageTransform.x * editor.zoom}px, ${imageTransform.y * editor.zoom}px) rotate(${imageTransform.rotate}deg)`}
163
+ >
164
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
165
+ <div
166
+ class="pointer-events-auto absolute -inset-3 cursor-move"
167
+ onmousedown={onMouseDown}
168
+ ></div>
169
+ <ImageScaleControl origin="bottom-right" {layer} />
170
+ <ImageScaleControl origin="bottom-left" {layer} />
171
+ <ImageScaleControl origin="top-right" {layer} />
172
+ <ImageScaleControl origin="top-left" {layer} />
173
+ <ImageRotateControl {pageRef} {layer} />
174
+ </div>
175
+ <div
176
+ class="pointer-events-none absolute outline outline-2 outline-primary"
177
+ style:width={`${layer.width * layer.scale * editor.zoom}px`}
178
+ style:height={`${layer.height * layer.scale * editor.zoom}px`}
179
+ style:transform={`translate(${layer.x * editor.zoom}px, ${layer.y * editor.zoom}px) rotate(${layer.rotate}deg)`}
180
+ ></div>
181
+ </div>
182
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { ImageLayer } from '../../../types.js';
2
+ interface Props {
3
+ layer: ImageLayer;
4
+ viewportRef: HTMLDivElement;
5
+ wrapperRef: HTMLDivElement;
6
+ pageRef: HTMLDivElement;
7
+ }
8
+ declare const ImageLayerCrop: import("svelte").Component<Props, {}, "">;
9
+ type ImageLayerCrop = ReturnType<typeof ImageLayerCrop>;
10
+ export default ImageLayerCrop;
@@ -12,6 +12,8 @@
12
12
 
13
13
  const editor = getEditorContext();
14
14
 
15
+ let isCropping = $derived(editor.imageCropLayer?.id === layer.id);
16
+
15
17
  let layerSubstitute: ImageLayer | null = $state(null);
16
18
 
17
19
  let ref: HTMLDivElement;
@@ -19,6 +21,7 @@
19
21
  const buildImageDropChanges = (image: Image) => {
20
22
  return {
21
23
  image,
24
+ imageRotate: 0,
22
25
  ...calculateImageCover(image, {
23
26
  width: layer.width * layer.scale,
24
27
  height: layer.height * layer.scale,
@@ -89,12 +92,25 @@
89
92
  }
90
93
  layerSubstitute = null;
91
94
  };
95
+
96
+ const onDoubleClick = () => {
97
+ if (editor.activeLayers.length > 1) return;
98
+ editor.imageCropLayer = $state.snapshot(
99
+ editor.activeGroupAndChild
100
+ ? (editor.getAbsoluteGroupLayers(editor.activeGroupAndChild.groupLayer, [
101
+ layer,
102
+ ])[0] as ImageLayer)
103
+ : layer,
104
+ );
105
+ };
92
106
  </script>
93
107
 
94
108
  <!-- svelte-ignore a11y_no_static_element_interactions -->
95
109
  <div
96
110
  bind:this={ref}
97
111
  class="h-full w-full"
112
+ style:opacity={isCropping ? 0 : null}
113
+ ondblclick={onDoubleClick}
98
114
  ondragenter={onDragEnter}
99
115
  ondragover={onDragOver}
100
116
  ondragleave={onDragLeave}
@@ -1,3 +1,4 @@
1
1
  import Root from './image-layer.svelte';
2
2
  import ImageLayerContent from './image-layer-content.svelte';
3
- export { Root, Root as ImageLayer, ImageLayerContent };
3
+ import ImageLayerCrop from './image-layer-crop.svelte';
4
+ export { Root, Root as ImageLayer, ImageLayerContent, ImageLayerCrop };
@@ -1,3 +1,4 @@
1
1
  import Root from './image-layer.svelte';
2
2
  import ImageLayerContent from './image-layer-content.svelte';
3
- export { Root, Root as ImageLayer, ImageLayerContent };
3
+ import ImageLayerCrop from './image-layer-crop.svelte';
4
+ export { Root, Root as ImageLayer, ImageLayerContent, ImageLayerCrop };
@@ -28,7 +28,6 @@ export const ListItem = TiptapNode.create({
28
28
  addCommands() {
29
29
  return {
30
30
  splitListItem: (typeOrName, overrideAttrs) => ({ editor, tr, state, dispatch }) => {
31
- console.log('splitListItem overwritten');
32
31
  const type = getNodeType(typeOrName, state.schema);
33
32
  const { $from, $to } = state.selection;
34
33
  // @ts-ignore
@@ -160,7 +159,6 @@ export const ListItem = TiptapNode.create({
160
159
  // Check if selection is in first list item
161
160
  const firstListItem = findParentNodeClosestToPos(tr.selection.$from, (node) => node.type.name === this.name);
162
161
  if (firstListItem && newParentList.node.firstChild === firstListItem.node) {
163
- console.log('firstListItem', firstListItem);
164
162
  const attrs = {
165
163
  ...newParentList.node.attrs,
166
164
  ...oldParentList.node.attrs,