@peteai/presentation-editor 0.0.7 → 0.0.8
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 +22 -7
- 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 +24 -20
- package/dist/components/editor/hotkeys.svelte +7 -0
- package/dist/components/editor/layers/buttons/opacity-button/opacity-button.svelte +1 -6
- 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 +11 -5
- 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/index.d.ts +2 -2
- package/dist/components/editor/layers/index.js +2 -2
- package/dist/components/editor/layers/types/background/background-content-image.svelte +15 -20
- 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/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.svelte +6 -3
- package/dist/components/editor/types.d.ts +2 -1
- package/dist/components/editor/utils.js +1 -0
- package/package.json +1 -1
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
const cos = Math.cos(angleRad);
|
|
3
|
-
const sin = Math.sin(angleRad);
|
|
4
|
-
return {
|
|
5
|
-
x: point.x * cos - point.y * sin,
|
|
6
|
-
y: point.x * sin + point.y * cos,
|
|
7
|
-
};
|
|
8
|
-
};
|
|
9
|
-
const getPoints = (origin, width, height) => {
|
|
1
|
+
const getOriginRelativePoint = (origin, width, height) => {
|
|
10
2
|
const halfWidth = width / 2;
|
|
11
3
|
const halfHeight = height / 2;
|
|
12
4
|
switch (origin) {
|
|
@@ -41,36 +33,42 @@ export const calculateLayerTransform = (layer, groupScale = 1) => ({
|
|
|
41
33
|
scale: layer.scale,
|
|
42
34
|
});
|
|
43
35
|
export const calculateNewPosition = (origin, transform, newWidth, newHeight) => {
|
|
44
|
-
const { x, y, width, height, rotate, scale } = transform;
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
36
|
+
const { x, y, width: rawWidth, height: rawHeight, rotate, scale } = transform;
|
|
37
|
+
const width = rawWidth * scale;
|
|
38
|
+
const height = rawHeight * scale;
|
|
39
|
+
// Get relative points based on origin for old and new dimensions
|
|
40
|
+
const oldPoint = getOriginRelativePoint(origin, width, height);
|
|
41
|
+
const newPoint = getOriginRelativePoint(origin, newWidth, newHeight);
|
|
42
|
+
// Calculate position delta
|
|
43
|
+
const delta = {
|
|
44
|
+
x: newPoint.x - oldPoint.x,
|
|
45
|
+
y: newPoint.y - oldPoint.y,
|
|
46
|
+
};
|
|
47
|
+
// Apply rotation if needed
|
|
48
|
+
const rotatedDelta = rotate !== 0 ? rotatePoint(degToRad(rotate), delta) : delta;
|
|
49
|
+
// Calculate new position accounting for size difference and delta
|
|
56
50
|
return {
|
|
57
|
-
newX: x -
|
|
58
|
-
newY: y -
|
|
51
|
+
newX: x - rotatedDelta.x - (newWidth - width) / 2,
|
|
52
|
+
newY: y - rotatedDelta.y - (newHeight - height) / 2,
|
|
59
53
|
};
|
|
60
54
|
};
|
|
61
55
|
export function calculateBoundingBox(transform) {
|
|
62
|
-
const { x, y, width, height, rotate, scale } = transform;
|
|
56
|
+
const { x, y, width: rawWidth, height: rawHeight, rotate, scale } = transform;
|
|
57
|
+
const width = rawWidth * scale;
|
|
58
|
+
const height = rawHeight * scale;
|
|
63
59
|
if (rotate === 0) {
|
|
64
|
-
return { x, y, width
|
|
60
|
+
return { x, y, width, height };
|
|
65
61
|
}
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
62
|
+
const rad = degToRad(rotate);
|
|
63
|
+
const cos = Math.cos(rad);
|
|
64
|
+
const sin = Math.sin(rad);
|
|
65
|
+
const hw = width / 2;
|
|
66
|
+
const hh = height / 2;
|
|
67
|
+
const maxX = Math.abs(hw * cos) + Math.abs(hh * sin);
|
|
68
|
+
const maxY = Math.abs(hw * sin) + Math.abs(hh * cos);
|
|
71
69
|
return {
|
|
72
|
-
x: x +
|
|
73
|
-
y: y +
|
|
70
|
+
x: x + width / 2 - maxX,
|
|
71
|
+
y: y + height / 2 - maxY,
|
|
74
72
|
width: maxX * 2,
|
|
75
73
|
height: maxY * 2,
|
|
76
74
|
};
|
|
@@ -78,20 +76,20 @@ export function calculateBoundingBox(transform) {
|
|
|
78
76
|
export function degToRad(deg) {
|
|
79
77
|
return (deg * Math.PI) / 180;
|
|
80
78
|
}
|
|
81
|
-
export function rotatePoint(p,
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
79
|
+
export function rotatePoint(rad, p, center = { x: 0, y: 0 }) {
|
|
80
|
+
const cos = Math.cos(rad);
|
|
81
|
+
const sin = Math.sin(rad);
|
|
82
|
+
const dx = p.x - center.x;
|
|
83
|
+
const dy = p.y - center.y;
|
|
86
84
|
return {
|
|
87
|
-
x:
|
|
88
|
-
y:
|
|
85
|
+
x: center.x + dx * cos - dy * sin,
|
|
86
|
+
y: center.y + dx * sin + dy * cos,
|
|
89
87
|
};
|
|
90
88
|
}
|
|
91
89
|
export function getRotatedCorners(rect) {
|
|
90
|
+
const rad = degToRad(rect.rotate);
|
|
92
91
|
const cx = rect.x + (rect.width * rect.scale) / 2;
|
|
93
92
|
const cy = rect.y + (rect.height * rect.scale) / 2;
|
|
94
|
-
const angle = degToRad(rect.rotate);
|
|
95
93
|
const hw = (rect.width * rect.scale) / 2;
|
|
96
94
|
const hh = (rect.height * rect.scale) / 2;
|
|
97
95
|
const corners = [
|
|
@@ -100,7 +98,7 @@ export function getRotatedCorners(rect) {
|
|
|
100
98
|
{ x: cx + hw, y: cy + hh },
|
|
101
99
|
{ x: cx - hw, y: cy + hh },
|
|
102
100
|
];
|
|
103
|
-
return corners.map((p) => rotatePoint(p, { x: cx, y: cy }
|
|
101
|
+
return corners.map((p) => rotatePoint(rad, p, { x: cx, y: cy }));
|
|
104
102
|
}
|
|
105
103
|
export function isRotatedVertically(rotate) {
|
|
106
104
|
const rotationNormalized = ((rotate % 360) + 360) % 360;
|
|
@@ -166,9 +164,9 @@ export function calculateGroupRotatedBoundingBox(transforms, rotate = 0) {
|
|
|
166
164
|
if (!rotate)
|
|
167
165
|
return calculateGroupBoundingBox(transforms);
|
|
168
166
|
const allCorners = transforms.flatMap((t) => getRotatedCorners(t));
|
|
169
|
-
const
|
|
167
|
+
const rad = degToRad(rotate);
|
|
170
168
|
// Rotate all points to align with desired angle
|
|
171
|
-
const rotatedPoints = allCorners.map((p) => rotatePoint(
|
|
169
|
+
const rotatedPoints = allCorners.map((p) => rotatePoint(-rad, p));
|
|
172
170
|
// Compute AABB of rotated points
|
|
173
171
|
const xs = rotatedPoints.map((p) => p.x);
|
|
174
172
|
const ys = rotatedPoints.map((p) => p.y);
|
|
@@ -183,7 +181,7 @@ export function calculateGroupRotatedBoundingBox(transforms, rotate = 0) {
|
|
|
183
181
|
y: (minY + maxY) / 2,
|
|
184
182
|
};
|
|
185
183
|
// Rotate center back to original space
|
|
186
|
-
const center = rotatePoint(
|
|
184
|
+
const center = rotatePoint(rad, centerRotated);
|
|
187
185
|
return {
|
|
188
186
|
x: center.x - width / 2,
|
|
189
187
|
y: center.y - height / 2,
|
|
@@ -194,7 +192,7 @@ export function calculateGroupRotatedBoundingBox(transforms, rotate = 0) {
|
|
|
194
192
|
};
|
|
195
193
|
}
|
|
196
194
|
export function calculateRelativeRects(bbox, absoluteRects) {
|
|
197
|
-
const
|
|
195
|
+
const rad = degToRad(bbox.rotate); // inverse to align bbox with axes
|
|
198
196
|
const bboxCenter = {
|
|
199
197
|
x: bbox.x + (bbox.width * bbox.scale) / 2,
|
|
200
198
|
y: bbox.y + (bbox.height * bbox.scale) / 2,
|
|
@@ -206,17 +204,18 @@ export function calculateRelativeRects(bbox, absoluteRects) {
|
|
|
206
204
|
y: rect.y + (rect.height * rect.scale) / 2,
|
|
207
205
|
};
|
|
208
206
|
// Rotate center ralatively to the bbox center
|
|
209
|
-
const relativeCenter = rotatePoint(center, bboxCenter
|
|
207
|
+
const relativeCenter = rotatePoint(-rad, center, bboxCenter);
|
|
210
208
|
return {
|
|
211
209
|
...rect,
|
|
212
|
-
x: (relativeCenter.x - bbox.x) / bbox.scale - (rect.width * rect.scale) / 2,
|
|
213
|
-
y: (relativeCenter.y - bbox.y) / bbox.scale - (rect.height * rect.scale) / 2,
|
|
210
|
+
x: ((relativeCenter.x - bbox.x) / bbox.scale - (rect.width * rect.scale) / 2) / bbox.scale,
|
|
211
|
+
y: ((relativeCenter.y - bbox.y) / bbox.scale - (rect.height * rect.scale) / 2) / bbox.scale,
|
|
214
212
|
rotate: rect.rotate - bbox.rotate,
|
|
213
|
+
scale: rect.scale / bbox.scale,
|
|
215
214
|
};
|
|
216
215
|
});
|
|
217
216
|
}
|
|
218
217
|
export function calculateAbsoluteRects(bbox, rects) {
|
|
219
|
-
const
|
|
218
|
+
const rad = degToRad(bbox.rotate);
|
|
220
219
|
const bboxCenter = {
|
|
221
220
|
x: bbox.x + (bbox.width * bbox.scale) / 2,
|
|
222
221
|
y: bbox.y + (bbox.height * bbox.scale) / 2,
|
|
@@ -226,7 +225,7 @@ export function calculateAbsoluteRects(bbox, rects) {
|
|
|
226
225
|
x: bbox.x + (rect.x + (rect.width * rect.scale) / 2) * bbox.scale,
|
|
227
226
|
y: bbox.y + (rect.y + (rect.height * rect.scale) / 2) * bbox.scale,
|
|
228
227
|
};
|
|
229
|
-
const center = rotatePoint(relativeCenter, bboxCenter
|
|
228
|
+
const center = rotatePoint(rad, relativeCenter, bboxCenter);
|
|
230
229
|
return {
|
|
231
230
|
...rect,
|
|
232
231
|
x: center.x - (rect.width * rect.scale * bbox.scale) / 2,
|
|
@@ -293,12 +292,14 @@ const minScaleToContain = (rotatedRect, rect) => {
|
|
|
293
292
|
};
|
|
294
293
|
export function calculateImageCover(image, bbox) {
|
|
295
294
|
const scale = minScaleToContain(image, bbox);
|
|
295
|
+
const width = bbox.width / scale;
|
|
296
|
+
const height = bbox.height / scale;
|
|
296
297
|
return {
|
|
297
|
-
width
|
|
298
|
-
height
|
|
298
|
+
width,
|
|
299
|
+
height,
|
|
299
300
|
scale,
|
|
300
|
-
offsetX: (
|
|
301
|
-
offsetY: (
|
|
301
|
+
offsetX: (width - image.width) / 2,
|
|
302
|
+
offsetY: (height - image.height) / 2,
|
|
302
303
|
};
|
|
303
304
|
}
|
|
304
305
|
export const defaultColor = '#000000';
|
|
@@ -322,3 +323,55 @@ export function buildBackgroundCircleStyle(colors) {
|
|
|
322
323
|
.join(', ');
|
|
323
324
|
return `background-image: conic-gradient(${gradientColors})`;
|
|
324
325
|
}
|
|
326
|
+
export function getImageLayerBboxRelativeToImageCenter(layer) {
|
|
327
|
+
const { width, height, image, offsetX, offsetY, imageRotate } = layer;
|
|
328
|
+
// current image center in layer coords (rotation doesn't change it)
|
|
329
|
+
const imageCenter = { x: image.width / 2 + offsetX, y: image.height / 2 + offsetY };
|
|
330
|
+
const imageRad = degToRad(imageRotate);
|
|
331
|
+
// For each corner, compute v = corner - C, rotate by -theta, accumulate max abs X/Y
|
|
332
|
+
const corners = [
|
|
333
|
+
{ x: 0, y: 0 },
|
|
334
|
+
{ x: width, y: 0 },
|
|
335
|
+
{ x: width, y: height },
|
|
336
|
+
{ x: 0, y: height },
|
|
337
|
+
].map((p) => {
|
|
338
|
+
const v = { x: p.x - imageCenter.x, y: p.y - imageCenter.y }; // layer->center
|
|
339
|
+
return rotatePoint(-imageRad, v); // bring into image unrotated axes
|
|
340
|
+
});
|
|
341
|
+
// Compute AABB of rotated points
|
|
342
|
+
const xs = corners.map((p) => p.x);
|
|
343
|
+
const ys = corners.map((p) => p.y);
|
|
344
|
+
const minX = Math.min(...xs);
|
|
345
|
+
const maxX = Math.max(...xs);
|
|
346
|
+
const minY = Math.min(...ys);
|
|
347
|
+
const maxY = Math.max(...ys);
|
|
348
|
+
return { minX, minY, maxX, maxY };
|
|
349
|
+
}
|
|
350
|
+
export function calculateImageLayerPropsForImageRotate(layer, imageRotate) {
|
|
351
|
+
const { width, height, scale, image, offsetX, offsetY } = layer;
|
|
352
|
+
// image half-width and half-height
|
|
353
|
+
const ihw = image.width / 2;
|
|
354
|
+
const ihh = image.height / 2;
|
|
355
|
+
// current image center in layer coords (rotation doesn't change it)
|
|
356
|
+
const imageCenter = { x: ihw + offsetX, y: ihh + offsetY };
|
|
357
|
+
const bbox = getImageLayerBboxRelativeToImageCenter({ ...layer, imageRotate });
|
|
358
|
+
const maxAbsX = Math.max(Math.abs(bbox.minX), Math.abs(bbox.maxX));
|
|
359
|
+
const maxAbsY = Math.max(Math.abs(bbox.minY), Math.abs(bbox.maxY));
|
|
360
|
+
// Required scale so half-extents of (Iw*s, Ih*s) cover maxAbsX/Y:
|
|
361
|
+
// Iw*s/2 >= maxAbsX => s >= 2*maxAbsX / Iw
|
|
362
|
+
// Ih*s/2 >= maxAbsY => s >= 2*maxAbsY / Ih
|
|
363
|
+
const sReqX = (2 * maxAbsX) / image.width;
|
|
364
|
+
const sReqY = (2 * maxAbsY) / image.height;
|
|
365
|
+
const minScaleFactor = Math.max(sReqX, sReqY);
|
|
366
|
+
if (minScaleFactor < 1) {
|
|
367
|
+
return { imageRotate };
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
imageRotate,
|
|
371
|
+
scale: scale * minScaleFactor,
|
|
372
|
+
width: width / minScaleFactor,
|
|
373
|
+
height: height / minScaleFactor,
|
|
374
|
+
offsetX: imageCenter.x / minScaleFactor - ihw,
|
|
375
|
+
offsetY: imageCenter.y / minScaleFactor - ihh,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
|
|
26
26
|
const editor = getEditorContext();
|
|
27
27
|
|
|
28
|
+
let pageRef: HTMLDivElement | null = $state(null);
|
|
29
|
+
|
|
28
30
|
let activeLayerGuides: ActiveLayerGuide[] = $state([]);
|
|
29
31
|
|
|
30
32
|
let hoveredLayerId: string | null = $state(null);
|
|
@@ -38,7 +40,7 @@
|
|
|
38
40
|
<div class="flex min-h-full flex-1">
|
|
39
41
|
<div class="box-border flex w-full shrink-0 items-center justify-center p-0">
|
|
40
42
|
<div bind:this={wrapperRef} class="translate-x-0 translate-y-0 p-4 transition-transform">
|
|
41
|
-
<div class="relative flex flex-col items-center"
|
|
43
|
+
<div bind:this={pageRef} class="relative flex flex-col items-center">
|
|
42
44
|
<div
|
|
43
45
|
class="m-0 overflow-hidden shadow-xl"
|
|
44
46
|
style:width="{scaledWidth}px"
|
|
@@ -89,7 +91,9 @@
|
|
|
89
91
|
{/if}
|
|
90
92
|
{/if}
|
|
91
93
|
|
|
92
|
-
|
|
94
|
+
{#if viewportRef && pageRef}
|
|
95
|
+
<ActiveLayers {viewportRef} {wrapperRef} {pageRef} />
|
|
96
|
+
{/if}
|
|
93
97
|
|
|
94
98
|
<SnappingGuides zoom={editor.zoom} guides={activeLayerGuides} />
|
|
95
99
|
</div>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import XIcon from '@lucide/svelte/icons/x';
|
|
3
|
+
import { Button } from '../../ui/button/index.js';
|
|
4
|
+
import { Slider } from '../../ui/slider/index.js';
|
|
5
|
+
import { Input } from '../../ui/input/index.js';
|
|
6
|
+
import { getEditorContext } from '../editor.svelte.js';
|
|
7
|
+
import {
|
|
8
|
+
calculateImageLayerPropsForImageRotate,
|
|
9
|
+
calculateRelativeRects,
|
|
10
|
+
} from '../layers/utils.js';
|
|
11
|
+
import type { ImageLayer } from '../types.js';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
layer: ImageLayer;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let { layer }: Props = $props();
|
|
18
|
+
|
|
19
|
+
const editor = $derived(getEditorContext());
|
|
20
|
+
|
|
21
|
+
let value = $derived(Math.round(layer.imageRotate * 10) / 10);
|
|
22
|
+
let originalLayer = $derived(
|
|
23
|
+
editor.selectedLayersNotLocked.find((l) => l.id === layer.id),
|
|
24
|
+
) as ImageLayer;
|
|
25
|
+
|
|
26
|
+
const min = -180;
|
|
27
|
+
const max = 180;
|
|
28
|
+
const step = 0.1;
|
|
29
|
+
|
|
30
|
+
let layerCache: ImageLayer | null = $state.snapshot(null);
|
|
31
|
+
|
|
32
|
+
const setImageRotate = (value: string | number) => {
|
|
33
|
+
value = Number(value);
|
|
34
|
+
if (!layerCache) {
|
|
35
|
+
layerCache = $state.snapshot(layer);
|
|
36
|
+
}
|
|
37
|
+
const props = calculateImageLayerPropsForImageRotate(layerCache, value);
|
|
38
|
+
Object.assign(layer, props);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const applyChanges = () => {
|
|
42
|
+
if (!originalLayer) return;
|
|
43
|
+
const relativeLayer = editor.activeGroupAndChild
|
|
44
|
+
? (calculateRelativeRects(editor.activeGroupAndChild.groupLayer, [layer])[0] as ImageLayer)
|
|
45
|
+
: layer;
|
|
46
|
+
// Define keys to check for changes in image layer properties
|
|
47
|
+
const imageLayerKeys: (keyof ImageLayer)[] = [
|
|
48
|
+
'imageRotate', // Rotation angle of the image
|
|
49
|
+
'scale', // Scale/zoom level
|
|
50
|
+
'width', // Width dimension
|
|
51
|
+
'height', // Height dimension
|
|
52
|
+
'offsetX', // X offset position
|
|
53
|
+
'offsetY', // Y offset position
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// Filter keys that have different values between original and relative layers
|
|
57
|
+
const changedKeys = imageLayerKeys.filter((key) => originalLayer[key] !== relativeLayer[key]);
|
|
58
|
+
if (changedKeys.length > 0) {
|
|
59
|
+
editor.historyPush({
|
|
60
|
+
type: 'layerUpdate',
|
|
61
|
+
pageId: editor.activePage.id,
|
|
62
|
+
layer: { id: layer.id, type: layer.type },
|
|
63
|
+
undo: Object.fromEntries(changedKeys.map((key) => [key, originalLayer[key]])),
|
|
64
|
+
redo: Object.fromEntries(changedKeys.map((key) => [key, relativeLayer[key]])),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
editor.imageCropLayer = null;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
$effect(() => {
|
|
71
|
+
if (!originalLayer) {
|
|
72
|
+
editor.imageCropLayer = null;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<div class="flex h-full flex-col gap-2">
|
|
78
|
+
<div class="flex items-center justify-between">
|
|
79
|
+
<div class="text-sm font-bold">Crop</div>
|
|
80
|
+
<Button variant="ghost" size="icon" onclick={() => (editor.imageCropLayer = null)}>
|
|
81
|
+
<XIcon />
|
|
82
|
+
</Button>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="flex-1 resize-none overflow-y-auto">
|
|
85
|
+
<div class="flex items-center gap-2">
|
|
86
|
+
<div class="grow">
|
|
87
|
+
<Slider
|
|
88
|
+
type="single"
|
|
89
|
+
{value}
|
|
90
|
+
{min}
|
|
91
|
+
{max}
|
|
92
|
+
{step}
|
|
93
|
+
onValueChange={(value) => setImageRotate(value)}
|
|
94
|
+
onValueCommit={() => (layerCache = null)}
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
<Input
|
|
98
|
+
class="w-12 bg-transparent p-0 text-center font-semibold leading-none [&::-webkit-inner-spin-button]:appearance-none"
|
|
99
|
+
inputmode="decimal"
|
|
100
|
+
placeholder="--"
|
|
101
|
+
{value}
|
|
102
|
+
onchange={(e) => setImageRotate(e.currentTarget.value)}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="flex justify-end gap-2 p-2">
|
|
106
|
+
<Button class="flex-1" variant="outline" onclick={() => (editor.imageCropLayer = null)}>
|
|
107
|
+
Cancel
|
|
108
|
+
</Button>
|
|
109
|
+
<Button class="flex-1" onclick={applyChanges}>Done</Button>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import PositionSidebar from './position-sidebar.svelte';
|
|
11
11
|
import { ColorSidebar } from './color-sidebar/index.js';
|
|
12
12
|
import { FontSidebar } from './font-sidebar/index.js';
|
|
13
|
+
import ImageCropSidebar from './image-crop-sidebar.svelte';
|
|
13
14
|
|
|
14
15
|
interface Props {
|
|
15
16
|
editor: Editor;
|
|
@@ -62,13 +63,15 @@
|
|
|
62
63
|
{/each}
|
|
63
64
|
</div>
|
|
64
65
|
|
|
65
|
-
{#if editor.activeSidebarPopup || editor.activeSidebarTab}
|
|
66
|
+
{#if editor.activeSidebarPopup || editor.activeSidebarTab || editor.imageCropLayer}
|
|
66
67
|
<div class="bg-background h-full w-80 shrink-0 overflow-y-auto border-r border-gray-200 p-2">
|
|
67
|
-
{#if editor.
|
|
68
|
+
{#if editor.imageCropLayer}
|
|
69
|
+
<ImageCropSidebar layer={editor.imageCropLayer} />
|
|
70
|
+
{:else if editor.activeSidebarPopup === 'position'}
|
|
68
71
|
<PositionSidebar />
|
|
69
72
|
{:else if editor.activeSidebarPopup === 'font'}
|
|
70
73
|
<FontSidebar />
|
|
71
|
-
{:else if editor.activeSidebarPopup}
|
|
74
|
+
{:else if editor.activeSidebarPopup?.indexOf('Color')}
|
|
72
75
|
<ColorSidebar />
|
|
73
76
|
{:else if editor.activeSidebarTab === 'text'}
|
|
74
77
|
<SidebarTextTab />
|
|
@@ -46,6 +46,7 @@ export type LayerBorder = {
|
|
|
46
46
|
export interface ImageLayer extends BaseLayer, LayerBorder {
|
|
47
47
|
type: 'image';
|
|
48
48
|
image: Image;
|
|
49
|
+
imageRotate: number;
|
|
49
50
|
offsetX: number;
|
|
50
51
|
offsetY: number;
|
|
51
52
|
cornerRadius: number;
|
|
@@ -61,7 +62,7 @@ export type Gradient = {
|
|
|
61
62
|
type: 'linear90' | 'linear180' | 'linear135' | 'radialCenter' | 'radialTopLeft';
|
|
62
63
|
colors: string[];
|
|
63
64
|
};
|
|
64
|
-
export type BackgroundImage = Pick<ImageLayer, '
|
|
65
|
+
export type BackgroundImage = Pick<ImageLayer, 'opacity' | 'scale' | 'image' | 'imageRotate' | 'offsetX' | 'offsetY' | 'flipX' | 'flipY'>;
|
|
65
66
|
export type Page = {
|
|
66
67
|
id: string;
|
|
67
68
|
backgroundColor: string | null;
|