@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.
- package/dist/components/editor/active-layers.svelte +24 -9
- package/dist/components/editor/active-layers.svelte.d.ts +6 -1
- package/dist/components/editor/editor.svelte +15 -17
- package/dist/components/editor/editor.svelte.js +10 -8
- package/dist/components/editor/header.svelte +25 -21
- package/dist/components/editor/hotkeys.svelte +7 -0
- package/dist/components/editor/layers/active-layer-border.svelte +1 -1
- package/dist/components/editor/layers/buttons/border-button/border-button-colors.svelte +1 -1
- package/dist/components/editor/layers/buttons/opacity-button/opacity-button.svelte +1 -6
- package/dist/components/editor/layers/controls/corner-scale-control/corner-scale-control.svelte +1 -1
- package/dist/components/editor/layers/controls/group-resize-control/group-resize-control.svelte +11 -11
- package/dist/components/editor/layers/controls/rotate-control/rotate-control.svelte +12 -6
- package/dist/components/editor/layers/controls/rotate-control/rotate-control.svelte.d.ts +4 -1
- package/dist/components/editor/layers/controls/side-resize-control/side-resize-control.svelte +13 -13
- package/dist/components/editor/layers/controls/side-scale-control/side-scale-control.svelte +1 -1
- package/dist/components/editor/layers/index.d.ts +2 -2
- package/dist/components/editor/layers/index.js +2 -2
- package/dist/components/editor/layers/layer-button.svelte +1 -1
- package/dist/components/editor/layers/types/background/background-content-image.svelte +15 -20
- package/dist/components/editor/layers/types/background/background-layer-buttons.svelte +1 -1
- package/dist/components/editor/layers/types/background/background-layer.svelte +2 -2
- package/dist/components/editor/layers/types/image/controls/image-rotate-control/image-rotate-control.svelte +120 -0
- package/dist/components/editor/layers/types/image/controls/image-rotate-control/image-rotate-control.svelte.d.ts +8 -0
- package/dist/components/editor/layers/types/image/controls/image-rotate-control/index.d.ts +2 -0
- package/dist/components/editor/layers/types/image/controls/image-rotate-control/index.js +4 -0
- package/dist/components/editor/layers/types/image/controls/image-scale-control/image-scale-control.svelte +154 -0
- package/dist/components/editor/layers/types/image/controls/image-scale-control/image-scale-control.svelte.d.ts +91 -0
- package/dist/components/editor/layers/types/image/controls/image-scale-control/index.d.ts +2 -0
- package/dist/components/editor/layers/types/image/controls/image-scale-control/index.js +4 -0
- package/dist/components/editor/layers/types/image/image-layer-content.svelte +3 -3
- package/dist/components/editor/layers/types/image/image-layer-crop.svelte +182 -0
- package/dist/components/editor/layers/types/image/image-layer-crop.svelte.d.ts +10 -0
- package/dist/components/editor/layers/types/image/image-layer.svelte +16 -0
- package/dist/components/editor/layers/types/image/index.d.ts +2 -1
- package/dist/components/editor/layers/types/image/index.js +2 -1
- package/dist/components/editor/layers/types/text/extensions/list-item/list-item.js +0 -2
- package/dist/components/editor/layers/utils.d.ts +24 -9
- package/dist/components/editor/layers/utils.js +107 -54
- package/dist/components/editor/page-editor.svelte +6 -2
- package/dist/components/editor/sidebar/color-sidebar/color-sidebar-color.svelte +2 -2
- package/dist/components/editor/sidebar/color-sidebar/color-sidebar-gradient-picker.svelte +5 -5
- package/dist/components/editor/sidebar/color-sidebar/color-sidebar.svelte +4 -4
- package/dist/components/editor/sidebar/font-sidebar/font-sidebar.svelte +1 -1
- package/dist/components/editor/sidebar/image-crop-sidebar.svelte +112 -0
- package/dist/components/editor/sidebar/image-crop-sidebar.svelte.d.ts +7 -0
- package/dist/components/editor/sidebar/position-sidebar.svelte +0 -2
- package/dist/components/editor/sidebar/sidebar-uploads-tab.svelte +3 -3
- package/dist/components/editor/sidebar/sidebar.svelte +7 -4
- package/dist/components/editor/snapping-guides.svelte +3 -3
- package/dist/components/editor/types.d.ts +2 -1
- package/dist/components/editor/utils.js +1 -0
- package/dist/components/ui/color-picker/color-picker-alpha-grid.svelte +2 -1
- package/dist/components/ui/color-picker/color-picker.svelte +6 -6
- package/dist/components/ui/context-menu/context-menu-checkbox-item.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-content.svelte +3 -1
- package/dist/components/ui/context-menu/context-menu-group-heading.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-item.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-radio-item.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-separator.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-shortcut.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-sub-content.svelte +1 -1
- package/dist/components/ui/context-menu/context-menu-sub-trigger.svelte +1 -1
- package/dist/components/ui/dialog/dialog-content.svelte +4 -2
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +2 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
- package/dist/components/ui/input/input.svelte +1 -1
- package/dist/components/ui/slider/slider.svelte +3 -3
- package/dist/components/ui/tabs/index.d.ts +4 -4
- package/dist/components/ui/tabs/index.js +4 -4
- package/dist/components/ui/tabs/tabs-content.svelte +4 -4
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +1 -1
- package/dist/components/ui/tabs/tabs-list.svelte +5 -9
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +1 -1
- package/dist/components/ui/tabs/tabs-trigger.svelte +4 -4
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +1 -1
- 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,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;
|
|
@@ -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(${
|
|
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
|
-
|
|
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
|
-
|
|
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,
|