@streamscloud/kit 0.1.10 → 0.1.12-1772032209109
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/core/toastr/index.d.ts +1 -1
- package/dist/core/toastr/toastr.scss +38 -0
- package/dist/core/toastr/toastr.svelte.d.ts +1 -1
- package/dist/core/toastr/toastr.svelte.js +13 -6
- package/dist/core/toastr/types.d.ts +2 -0
- package/dist/ui/cropper/image-editor-dialog/cmp.image-editor-dialog.svelte +9 -8
- package/dist/ui/cropper/image-editor-dialog/index.d.ts +4 -4
- package/dist/ui/cropper/image-editor-dialog/index.js +3 -3
- package/dist/ui/cropper/image-editor-dialog/types.d.ts +12 -12
- package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte +28 -38
- package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte.d.ts +8 -10
- package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.d.ts +40 -0
- package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.js +175 -0
- package/dist/ui/cropper/img-cropper/img-cropper-contain-worker.svelte.d.ts +5 -38
- package/dist/ui/cropper/img-cropper/img-cropper-contain-worker.svelte.js +29 -149
- package/dist/ui/cropper/img-cropper/img-cropper-cover-worker.svelte.d.ts +5 -38
- package/dist/ui/cropper/img-cropper/img-cropper-cover-worker.svelte.js +37 -135
- package/dist/ui/cropper/img-cropper/img-cropper-utils.d.ts +11 -1
- package/dist/ui/cropper/img-cropper/img-cropper-utils.js +30 -0
- package/dist/ui/cropper/img-cropper/img-cropper-worker.svelte.d.ts +17 -14
- package/dist/ui/cropper/img-cropper/img-cropper.svelte.d.ts +13 -31
- package/dist/ui/cropper/img-cropper/img-cropper.svelte.js +29 -28
- package/dist/ui/cropper/img-cropper/index.d.ts +1 -1
- package/dist/ui/dialog/cmp.dialog-container.svelte +12 -12
- package/dist/ui/dialog/dialog-data.d.ts +2 -0
- package/dist/ui/dialog/dialog-mount.d.ts +1 -1
- package/dist/ui/dialog/dialog-mount.js +2 -2
- package/dist/ui/dialog/dialogs.svelte.d.ts +3 -0
- package/dist/ui/dialog/dialogs.svelte.js +21 -2
- package/dist/ui/dialog/index.d.ts +1 -1
- package/dist/ui/dialog/index.js +1 -1
- package/dist/ui/dialog/types.svelte.d.ts +3 -14
- package/dist/ui/dialog/types.svelte.js +3 -18
- package/dist/ui/form-group/cmp.form-group-label.svelte +25 -0
- package/dist/ui/form-group/cmp.form-group-label.svelte.d.ts +8 -0
- package/dist/ui/form-group/cmp.form-group-note.svelte +16 -0
- package/dist/ui/form-group/cmp.form-group-note.svelte.d.ts +7 -0
- package/dist/ui/form-group/cmp.form-group.svelte +16 -0
- package/dist/ui/form-group/cmp.form-group.svelte.d.ts +8 -0
- package/dist/ui/form-group/index.d.ts +3 -0
- package/dist/ui/form-group/index.js +3 -0
- package/dist/ui/html-block/cmp.html-block.svelte +112 -0
- package/dist/ui/html-block/cmp.html-block.svelte.d.ts +7 -0
- package/dist/ui/html-block/index.d.ts +1 -0
- package/dist/ui/html-block/index.js +1 -0
- package/dist/ui/media-viewer-dialog/cmp.media-viewer-dialog.svelte +50 -0
- package/dist/ui/media-viewer-dialog/cmp.media-viewer-dialog.svelte.d.ts +9 -0
- package/dist/ui/media-viewer-dialog/index.d.ts +13 -0
- package/dist/ui/media-viewer-dialog/index.js +17 -0
- package/dist/ui/media-viewer-dialog/media-viewer-item.svelte +61 -0
- package/dist/ui/media-viewer-dialog/media-viewer-item.svelte.d.ts +7 -0
- package/dist/ui/media-viewer-dialog/types.d.ts +13 -0
- package/dist/ui/media-viewer-dialog/types.js +1 -0
- package/dist/ui/player/carousel/cmp.carousel.svelte +2 -2
- package/dist/ui/player/carousel/cmp.carousel.svelte.d.ts +1 -1
- package/dist/ui/video/cmp.video.svelte +16 -7
- package/package.json +14 -1
|
@@ -1,159 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
cropBoxVisible = $state(false);
|
|
6
|
-
dragMode = $state('move');
|
|
7
|
-
naturalWidth = $state(0);
|
|
8
|
-
naturalHeight = $state(0);
|
|
9
|
-
canvasRatio = $state(null);
|
|
10
|
-
destroy;
|
|
11
|
-
_image;
|
|
12
|
-
_selection;
|
|
13
|
-
_canvas;
|
|
14
|
-
_selectHandle;
|
|
15
|
-
_originalSrc;
|
|
1
|
+
import { ImgCropperBaseWorker } from './img-cropper-base-worker.svelte';
|
|
2
|
+
import { fitInContainer } from './img-cropper-utils';
|
|
3
|
+
export class ImgCropperContainWorker extends ImgCropperBaseWorker {
|
|
4
|
+
_centerMode = 'contain';
|
|
16
5
|
constructor(params) {
|
|
17
|
-
|
|
18
|
-
this._image = params.image;
|
|
19
|
-
this._selection = params.selection;
|
|
20
|
-
this._canvas = params.canvas;
|
|
21
|
-
this._selectHandle = params.selectHandle;
|
|
22
|
-
const onSelectionChange = (e) => {
|
|
23
|
-
const { width, height } = e.detail;
|
|
24
|
-
this.cropBoxVisible = width > 0 && height > 0;
|
|
25
|
-
};
|
|
26
|
-
const onBoundaryCheck = (e) => {
|
|
27
|
-
handleSelectionBoundary({ event: e, selection: this._selection, canvas: this._canvas });
|
|
28
|
-
};
|
|
29
|
-
this._selection.addEventListener('change', onSelectionChange);
|
|
30
|
-
this._selection.addEventListener('change', onBoundaryCheck);
|
|
31
|
-
this.destroy = () => {
|
|
32
|
-
this._selection.removeEventListener('change', onSelectionChange);
|
|
33
|
-
this._selection.removeEventListener('change', onBoundaryCheck);
|
|
34
|
-
};
|
|
35
|
-
this._applyMoveMode();
|
|
6
|
+
super(params);
|
|
36
7
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
42
|
-
rotate = async () => {
|
|
43
|
-
if (this.canvasRatio !== null) {
|
|
44
|
-
this.canvasRatio = 1 / this.canvasRatio;
|
|
45
|
-
const prevW = this.naturalWidth;
|
|
46
|
-
this.naturalWidth = this.naturalHeight;
|
|
47
|
-
this.naturalHeight = prevW;
|
|
48
|
-
await tick();
|
|
8
|
+
_computeCanvasSize = () => {
|
|
9
|
+
const container = this._canvas.parentElement;
|
|
10
|
+
if (!container) {
|
|
11
|
+
return;
|
|
49
12
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
this.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
};
|
|
59
|
-
reset = async () => {
|
|
60
|
-
this._selection.$clear();
|
|
61
|
-
this._applyMoveMode();
|
|
62
|
-
this.canvasRatio = null;
|
|
63
|
-
this._image.src = this._originalSrc;
|
|
64
|
-
await this._fitImage();
|
|
65
|
-
};
|
|
66
|
-
crop = async (options) => {
|
|
67
|
-
const fillColor = options?.fillColor;
|
|
68
|
-
const selectionRatio = this._selection.width / this._selection.height;
|
|
69
|
-
const imageScale = computeImageScale(this._image.$getTransform());
|
|
70
|
-
const outputSize = computeNaturalOutputSize({ displayWidth: this._selection.width, displayHeight: this._selection.height, imageScale });
|
|
71
|
-
const canvas = await this._selection.$toCanvas({
|
|
72
|
-
width: outputSize.width,
|
|
73
|
-
height: outputSize.height,
|
|
74
|
-
beforeDraw: (ctx, cvs) => {
|
|
75
|
-
if (fillColor && !ColorHelper.isTransparent(fillColor)) {
|
|
76
|
-
ctx.fillStyle = fillColor;
|
|
77
|
-
ctx.fillRect(0, 0, cvs.width, cvs.height);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
const dataUrl = canvas.toDataURL('image/png', 0.9);
|
|
82
|
-
this._applyMoveMode();
|
|
83
|
-
if (options?.freeRatio) {
|
|
84
|
-
this.canvasRatio = selectionRatio;
|
|
13
|
+
const containerWidth = container.clientWidth;
|
|
14
|
+
const containerHeight = container.clientHeight;
|
|
15
|
+
const visualWidth = this._visualWidth;
|
|
16
|
+
const visualHeight = this._visualHeight;
|
|
17
|
+
const aspectRatio = this.canvasGeometry.aspectRatio;
|
|
18
|
+
if (containerWidth <= 0 || containerHeight <= 0 || visualWidth <= 0 || visualHeight <= 0) {
|
|
19
|
+
this.canvasGeometry = { aspectRatio, width: 0, height: 0 };
|
|
20
|
+
return;
|
|
85
21
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const fillColor = options?.fillColor;
|
|
93
|
-
const beforeDraw = (ctx, cvs) => {
|
|
94
|
-
if (fillColor && !ColorHelper.isTransparent(fillColor)) {
|
|
95
|
-
ctx.fillStyle = fillColor;
|
|
96
|
-
ctx.fillRect(0, 0, cvs.width, cvs.height);
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
const imageScale = computeImageScale(this._image.$getTransform());
|
|
100
|
-
const displayWidth = this.cropBoxVisible ? this._selection.width : this._canvas.offsetWidth;
|
|
101
|
-
const displayHeight = this.cropBoxVisible ? this._selection.height : this._canvas.offsetHeight;
|
|
102
|
-
const outputSize = computeNaturalOutputSize({ displayWidth, displayHeight, imageScale });
|
|
103
|
-
const toCanvasOptions = { width: outputSize.width, height: outputSize.height, beforeDraw };
|
|
104
|
-
const canvas = this.cropBoxVisible ? await this._selection.$toCanvas(toCanvasOptions) : await this._canvas.$toCanvas(toCanvasOptions);
|
|
105
|
-
const dataUrl = canvas.toDataURL('image/png', 0.9);
|
|
106
|
-
return { dataUrl, width: canvas.width, height: canvas.height };
|
|
107
|
-
};
|
|
108
|
-
clearSelection = () => {
|
|
109
|
-
this._selection.$clear();
|
|
110
|
-
this._applyMoveMode();
|
|
111
|
-
};
|
|
112
|
-
enableCropMode = () => {
|
|
113
|
-
this._image.translatable = false;
|
|
114
|
-
if (this._selectHandle) {
|
|
115
|
-
this._selectHandle.setAttribute('action', 'select');
|
|
116
|
-
}
|
|
117
|
-
this.dragMode = 'crop';
|
|
118
|
-
};
|
|
119
|
-
_applyMoveMode = () => {
|
|
120
|
-
this._image.translatable = true;
|
|
121
|
-
if (this._selectHandle) {
|
|
122
|
-
this._selectHandle.setAttribute('action', 'move');
|
|
123
|
-
}
|
|
124
|
-
this.dragMode = 'move';
|
|
125
|
-
this.cropBoxVisible = false;
|
|
126
|
-
};
|
|
127
|
-
_fitImage = async () => {
|
|
128
|
-
const img = await this._image.$ready();
|
|
129
|
-
this.naturalWidth = img.naturalWidth;
|
|
130
|
-
this.naturalHeight = img.naturalHeight;
|
|
131
|
-
await tick();
|
|
132
|
-
this._image.$resetTransform();
|
|
133
|
-
this._centerForCurrentRotation();
|
|
134
|
-
};
|
|
135
|
-
// Choose $center() (natural size) or $center('contain') (scale-to-fit) based on
|
|
136
|
-
// whether the image's visual bounds fit inside the canvas at the current rotation.
|
|
137
|
-
// When canvasRatio is active, naturalWidth/Height are already swapped to match
|
|
138
|
-
// the visual orientation. Otherwise, derive visual dims from the rotation angle.
|
|
139
|
-
_centerForCurrentRotation = () => {
|
|
140
|
-
let visualW = this.naturalWidth;
|
|
141
|
-
let visualH = this.naturalHeight;
|
|
142
|
-
if (this.canvasRatio === null) {
|
|
143
|
-
const matrix = this._image.$getTransform();
|
|
144
|
-
const isSwapped = Math.abs(Math.sin(Math.atan2(matrix[1], matrix[0]))) > 0.5;
|
|
145
|
-
if (isSwapped) {
|
|
146
|
-
visualW = this.naturalHeight;
|
|
147
|
-
visualH = this.naturalWidth;
|
|
148
|
-
}
|
|
22
|
+
if (aspectRatio !== null) {
|
|
23
|
+
// Fixed ratio: fit in container, cap at natural image bounds
|
|
24
|
+
const fitted = fitInContainer({ ratio: aspectRatio, containerWidth, containerHeight });
|
|
25
|
+
const maxWidth = Math.max(visualWidth, visualHeight * aspectRatio);
|
|
26
|
+
const width = Math.min(fitted.width, maxWidth);
|
|
27
|
+
this.canvasGeometry = { aspectRatio, width, height: width / aspectRatio };
|
|
149
28
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this._image.$center();
|
|
29
|
+
else if (visualWidth <= containerWidth && visualHeight <= containerHeight) {
|
|
30
|
+
// Free ratio, image fits naturally — no upscale
|
|
31
|
+
this.canvasGeometry = { aspectRatio, width: visualWidth, height: visualHeight };
|
|
154
32
|
}
|
|
155
33
|
else {
|
|
156
|
-
|
|
34
|
+
// Free ratio, image too large — scale down to fit
|
|
35
|
+
const fitted = fitInContainer({ ratio: visualWidth / visualHeight, containerWidth, containerHeight });
|
|
36
|
+
this.canvasGeometry = { aspectRatio, width: fitted.width, height: fitted.height };
|
|
157
37
|
}
|
|
158
38
|
};
|
|
159
39
|
}
|
|
@@ -1,40 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
naturalWidth: number;
|
|
6
|
-
naturalHeight: number;
|
|
7
|
-
canvasRatio: number | null;
|
|
8
|
-
destroy: () => void;
|
|
9
|
-
private _image;
|
|
10
|
-
private _selection;
|
|
11
|
-
private _canvas;
|
|
12
|
-
private _selectHandle;
|
|
13
|
-
private _originalSrc;
|
|
14
|
-
private _withCoverCheckSuspended;
|
|
1
|
+
import { ImgCropperBaseWorker } from './img-cropper-base-worker.svelte';
|
|
2
|
+
import type { ImgCropperWorkerParams } from './img-cropper-worker.svelte';
|
|
3
|
+
export declare class ImgCropperCoverWorker extends ImgCropperBaseWorker {
|
|
4
|
+
protected readonly _centerMode: "cover";
|
|
15
5
|
constructor(params: ImgCropperWorkerParams);
|
|
16
|
-
|
|
17
|
-
rotate: () => void;
|
|
18
|
-
zoomIn: () => void;
|
|
19
|
-
zoomOut: () => void;
|
|
20
|
-
reset: () => Promise<void>;
|
|
21
|
-
crop: (options?: {
|
|
22
|
-
fillColor?: string;
|
|
23
|
-
freeRatio?: boolean;
|
|
24
|
-
}) => Promise<{
|
|
25
|
-
dataUrl: string;
|
|
26
|
-
width: number;
|
|
27
|
-
height: number;
|
|
28
|
-
}>;
|
|
29
|
-
save: (options?: {
|
|
30
|
-
fillColor?: string;
|
|
31
|
-
}) => Promise<{
|
|
32
|
-
dataUrl: string;
|
|
33
|
-
width: number;
|
|
34
|
-
height: number;
|
|
35
|
-
}>;
|
|
36
|
-
clearSelection: () => void;
|
|
37
|
-
enableCropMode: () => void;
|
|
38
|
-
private _applyMoveMode;
|
|
39
|
-
private _fitImage;
|
|
6
|
+
protected _computeCanvasSize: () => void;
|
|
40
7
|
}
|
|
@@ -1,32 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
cropBoxVisible = $state(false);
|
|
6
|
-
dragMode = $state('move');
|
|
7
|
-
naturalWidth = $state(0);
|
|
8
|
-
naturalHeight = $state(0);
|
|
9
|
-
canvasRatio = $state(null);
|
|
10
|
-
destroy;
|
|
11
|
-
_image;
|
|
12
|
-
_selection;
|
|
13
|
-
_canvas;
|
|
14
|
-
_selectHandle;
|
|
15
|
-
_originalSrc;
|
|
16
|
-
_withCoverCheckSuspended;
|
|
1
|
+
import { ImgCropperBaseWorker } from './img-cropper-base-worker.svelte';
|
|
2
|
+
import { fitInContainer, handleCoverCheck } from './img-cropper-utils';
|
|
3
|
+
export class ImgCropperCoverWorker extends ImgCropperBaseWorker {
|
|
4
|
+
_centerMode = 'cover';
|
|
17
5
|
constructor(params) {
|
|
18
|
-
|
|
19
|
-
this._image = params.image;
|
|
20
|
-
this._selection = params.selection;
|
|
21
|
-
this._canvas = params.canvas;
|
|
22
|
-
this._selectHandle = params.selectHandle;
|
|
23
|
-
const onSelectionChange = (e) => {
|
|
24
|
-
const { width, height } = e.detail;
|
|
25
|
-
this.cropBoxVisible = width > 0 && height > 0;
|
|
26
|
-
};
|
|
27
|
-
const onBoundaryCheck = (e) => {
|
|
28
|
-
handleSelectionBoundary({ event: e, selection: this._selection, canvas: this._canvas });
|
|
29
|
-
};
|
|
6
|
+
super(params);
|
|
30
7
|
// Dedup: handleCoverCheck calls $setTransform(clamped) which synchronously
|
|
31
8
|
// fires another transform event with a matrix that differs by ~0.0001px
|
|
32
9
|
// due to floating point. Without this guard CropperJS re-fires the same
|
|
@@ -41,123 +18,48 @@ export class ImgCropperCoverWorker {
|
|
|
41
18
|
handleCoverCheck({ event: e, image: this._image, canvas: this._canvas });
|
|
42
19
|
prevMatrix = null;
|
|
43
20
|
};
|
|
44
|
-
this._selection.addEventListener('change', onSelectionChange);
|
|
45
|
-
this._selection.addEventListener('change', onBoundaryCheck);
|
|
46
21
|
this._image.addEventListener('transform', onCoverCheck);
|
|
47
|
-
this.
|
|
22
|
+
this._wrapTransformOp = (fn) => {
|
|
48
23
|
this._image.removeEventListener('transform', onCoverCheck);
|
|
49
|
-
|
|
50
|
-
|
|
24
|
+
try {
|
|
25
|
+
fn();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
this._image.addEventListener('transform', onCoverCheck);
|
|
29
|
+
}
|
|
51
30
|
};
|
|
31
|
+
const baseDestroy = this.destroy;
|
|
52
32
|
this.destroy = () => {
|
|
53
|
-
|
|
54
|
-
this._selection.removeEventListener('change', onBoundaryCheck);
|
|
33
|
+
baseDestroy();
|
|
55
34
|
this._image.removeEventListener('transform', onCoverCheck);
|
|
56
35
|
};
|
|
57
|
-
this._applyMoveMode();
|
|
58
36
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
};
|
|
64
|
-
rotate = () => {
|
|
65
|
-
this._withCoverCheckSuspended(() => {
|
|
66
|
-
this._image.$rotate('90deg');
|
|
67
|
-
this._image.$center('cover');
|
|
68
|
-
});
|
|
69
|
-
};
|
|
70
|
-
zoomIn = () => {
|
|
71
|
-
this._image.$zoom(0.1);
|
|
72
|
-
};
|
|
73
|
-
zoomOut = () => {
|
|
74
|
-
this._image.$zoom(-0.1);
|
|
75
|
-
};
|
|
76
|
-
reset = async () => {
|
|
77
|
-
this._selection.$clear();
|
|
78
|
-
this._applyMoveMode();
|
|
79
|
-
this._image.src = this._originalSrc;
|
|
80
|
-
await this._fitImage();
|
|
81
|
-
};
|
|
82
|
-
crop = async (options) => {
|
|
83
|
-
const fillColor = options?.fillColor;
|
|
84
|
-
const imageScale = computeImageScale(this._image.$getTransform());
|
|
85
|
-
const outputSize = computeNaturalOutputSize({ displayWidth: this._selection.width, displayHeight: this._selection.height, imageScale });
|
|
86
|
-
const canvas = await this._selection.$toCanvas({
|
|
87
|
-
width: outputSize.width,
|
|
88
|
-
height: outputSize.height,
|
|
89
|
-
beforeDraw: (ctx, cvs) => {
|
|
90
|
-
if (fillColor && !ColorHelper.isTransparent(fillColor)) {
|
|
91
|
-
ctx.fillStyle = fillColor;
|
|
92
|
-
ctx.fillRect(0, 0, cvs.width, cvs.height);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
const dataUrl = canvas.toDataURL('image/png', 0.9);
|
|
97
|
-
this._applyMoveMode();
|
|
98
|
-
this._image.src = dataUrl;
|
|
99
|
-
await this._fitImage();
|
|
100
|
-
this._selection.$clear();
|
|
101
|
-
return { dataUrl, width: canvas.width, height: canvas.height };
|
|
102
|
-
};
|
|
103
|
-
save = async (options) => {
|
|
104
|
-
const fillColor = options?.fillColor;
|
|
105
|
-
const beforeDraw = (ctx, cvs) => {
|
|
106
|
-
if (fillColor && !ColorHelper.isTransparent(fillColor)) {
|
|
107
|
-
ctx.fillStyle = fillColor;
|
|
108
|
-
ctx.fillRect(0, 0, cvs.width, cvs.height);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
// Compute natural output dimensions before any micro-zoom so the
|
|
112
|
-
// output matches the source pixel density, not the display size.
|
|
113
|
-
const imageScale = computeImageScale(this._image.$getTransform());
|
|
114
|
-
const displayWidth = this.cropBoxVisible ? this._selection.width : this._canvas.offsetWidth;
|
|
115
|
-
const displayHeight = this.cropBoxVisible ? this._selection.height : this._canvas.offsetHeight;
|
|
116
|
-
const outputSize = computeNaturalOutputSize({ displayWidth, displayHeight, imageScale });
|
|
117
|
-
const toCanvasOptions = { width: outputSize.width, height: outputSize.height, beforeDraw };
|
|
118
|
-
// CropperJS $toCanvas exports only the visible portion of the canvas.
|
|
119
|
-
// In cover mode the image exactly covers the canvas, so sub-pixel rounding
|
|
120
|
-
// can leave a 1px transparent edge. A micro-zoom nudges the image slightly
|
|
121
|
-
// past the canvas boundary so the export captures the full area; the zoom
|
|
122
|
-
// is reversed immediately after.
|
|
123
|
-
const needsCoverZoom = !this.cropBoxVisible;
|
|
124
|
-
if (needsCoverZoom) {
|
|
125
|
-
this._image.$zoom(0.003);
|
|
37
|
+
_computeCanvasSize = () => {
|
|
38
|
+
const container = this._canvas.parentElement;
|
|
39
|
+
if (!container) {
|
|
40
|
+
return;
|
|
126
41
|
}
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
42
|
+
const containerWidth = container.clientWidth;
|
|
43
|
+
const containerHeight = container.clientHeight;
|
|
44
|
+
const visualWidth = this._visualWidth;
|
|
45
|
+
const visualHeight = this._visualHeight;
|
|
46
|
+
const aspectRatio = this.canvasGeometry.aspectRatio;
|
|
47
|
+
if (containerWidth <= 0 || containerHeight <= 0) {
|
|
48
|
+
this.canvasGeometry = { aspectRatio, width: 0, height: 0 };
|
|
49
|
+
return;
|
|
130
50
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
};
|
|
138
|
-
enableCropMode = () => {
|
|
139
|
-
this._image.translatable = false;
|
|
140
|
-
if (this._selectHandle) {
|
|
141
|
-
this._selectHandle.setAttribute('action', 'select');
|
|
51
|
+
if (aspectRatio !== null) {
|
|
52
|
+
const fitted = fitInContainer({ ratio: aspectRatio, containerWidth, containerHeight });
|
|
53
|
+
// Cap so the image can cover the canvas without upscaling
|
|
54
|
+
const maxWidth = visualWidth > 0 && visualHeight > 0 ? Math.min(visualWidth, visualHeight * aspectRatio) : fitted.width;
|
|
55
|
+
const width = Math.min(fitted.width, maxWidth);
|
|
56
|
+
this.canvasGeometry = { aspectRatio, width, height: width / aspectRatio };
|
|
142
57
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this._selectHandle.setAttribute('action', 'move');
|
|
58
|
+
else {
|
|
59
|
+
// Cap each dimension at visual size to avoid upscaling small images
|
|
60
|
+
const width = visualWidth > 0 ? Math.min(containerWidth, visualWidth) : containerWidth;
|
|
61
|
+
const height = visualHeight > 0 ? Math.min(containerHeight, visualHeight) : containerHeight;
|
|
62
|
+
this.canvasGeometry = { aspectRatio, width, height };
|
|
149
63
|
}
|
|
150
|
-
this.dragMode = 'move';
|
|
151
|
-
this.cropBoxVisible = false;
|
|
152
|
-
};
|
|
153
|
-
_fitImage = async () => {
|
|
154
|
-
const img = await this._image.$ready();
|
|
155
|
-
this.naturalWidth = img.naturalWidth;
|
|
156
|
-
this.naturalHeight = img.naturalHeight;
|
|
157
|
-
await tick();
|
|
158
|
-
this._withCoverCheckSuspended(() => {
|
|
159
|
-
this._image.$resetTransform();
|
|
160
|
-
this._image.$center('cover');
|
|
161
|
-
});
|
|
162
64
|
};
|
|
163
65
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { CropperCanvas, CropperImage, CropperSelection } from 'cropperjs';
|
|
2
|
-
|
|
2
|
+
type Rect = {
|
|
3
3
|
left: number;
|
|
4
4
|
top: number;
|
|
5
5
|
right: number;
|
|
6
6
|
bottom: number;
|
|
7
7
|
};
|
|
8
|
+
export declare const detectMimeFromUrl: (url: string) => string | null;
|
|
8
9
|
export declare const computeImageScale: (transform: number[]) => number;
|
|
9
10
|
export declare const computeNaturalOutputSize: (params: {
|
|
10
11
|
displayWidth: number;
|
|
@@ -25,8 +26,17 @@ export declare const handleCoverCheck: (params: {
|
|
|
25
26
|
image: CropperImage;
|
|
26
27
|
canvas: CropperCanvas;
|
|
27
28
|
}) => void;
|
|
29
|
+
export declare const fitInContainer: (params: {
|
|
30
|
+
ratio: number;
|
|
31
|
+
containerWidth: number;
|
|
32
|
+
containerHeight: number;
|
|
33
|
+
}) => {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
};
|
|
28
37
|
export declare const handleSelectionBoundary: (params: {
|
|
29
38
|
event: Event;
|
|
30
39
|
selection: CropperSelection;
|
|
31
40
|
canvas: CropperCanvas;
|
|
32
41
|
}) => void;
|
|
42
|
+
export {};
|
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
import { Base64Helper } from '../../../core/files/base64-helper';
|
|
2
|
+
import { default as mime } from 'mime';
|
|
3
|
+
// Detect MIME type from a URL without fetching the resource.
|
|
4
|
+
// Returns a MIME string for data: URLs, extension-based URLs, or null for blob: / unknown URLs.
|
|
5
|
+
export const detectMimeFromUrl = (url) => {
|
|
6
|
+
if (url.startsWith('data:')) {
|
|
7
|
+
return Base64Helper.getMimeType(url);
|
|
8
|
+
}
|
|
9
|
+
if (url.startsWith('blob:')) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const pathname = url.includes('://') ? new URL(url).pathname : url.split('?')[0].split('#')[0];
|
|
14
|
+
const lastDot = pathname.lastIndexOf('.');
|
|
15
|
+
if (lastDot === -1) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return mime.getType(pathname.slice(lastDot + 1).toLowerCase()) ?? null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
1
24
|
// Extract the uniform scale factor from a CSS transform matrix [a, b, c, d, e, f].
|
|
2
25
|
// Works for any combination of scale + rotation: scale = sqrt(a² + b²).
|
|
3
26
|
export const computeImageScale = (transform) => {
|
|
@@ -91,6 +114,13 @@ export const handleCoverCheck = (params) => {
|
|
|
91
114
|
}
|
|
92
115
|
image.$setTransform(matrix[0], matrix[1], matrix[2], matrix[3], clampedTx, clampedTy);
|
|
93
116
|
};
|
|
117
|
+
export const fitInContainer = (params) => {
|
|
118
|
+
const { ratio, containerWidth, containerHeight } = params;
|
|
119
|
+
if (ratio > containerWidth / containerHeight) {
|
|
120
|
+
return { width: containerWidth, height: containerWidth / ratio };
|
|
121
|
+
}
|
|
122
|
+
return { width: containerHeight * ratio, height: containerHeight };
|
|
123
|
+
};
|
|
94
124
|
export const handleSelectionBoundary = (params) => {
|
|
95
125
|
const { event, selection, canvas } = params;
|
|
96
126
|
const { x, y, width, height } = event.detail;
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
import type { CropperCanvas, CropperImage, CropperSelection } from 'cropperjs';
|
|
2
2
|
export type DragMode = 'move' | 'crop';
|
|
3
|
+
export type CanvasGeometry = {
|
|
4
|
+
aspectRatio: number | null;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
};
|
|
3
8
|
export type ImgCropperWorkerParams = {
|
|
4
9
|
originalSrc: string;
|
|
10
|
+
sourceMime: string | null;
|
|
5
11
|
image: CropperImage;
|
|
6
12
|
selection: CropperSelection;
|
|
7
13
|
canvas: CropperCanvas;
|
|
8
14
|
selectHandle: HTMLElement | null;
|
|
15
|
+
aspectRatio: number | null;
|
|
16
|
+
};
|
|
17
|
+
export type ImgCropperResult = {
|
|
18
|
+
dataUrl: string;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
mimeType: string;
|
|
9
22
|
};
|
|
10
23
|
export interface ImgCropperWorker {
|
|
11
24
|
cropBoxVisible: boolean;
|
|
12
25
|
dragMode: DragMode;
|
|
13
|
-
|
|
14
|
-
naturalHeight: number;
|
|
15
|
-
canvasRatio: number | null;
|
|
26
|
+
canvasGeometry: CanvasGeometry;
|
|
16
27
|
destroy: () => void;
|
|
17
28
|
ready: () => Promise<void>;
|
|
18
29
|
rotate: () => void | Promise<void>;
|
|
@@ -21,19 +32,11 @@ export interface ImgCropperWorker {
|
|
|
21
32
|
reset: () => Promise<void>;
|
|
22
33
|
crop: (options?: {
|
|
23
34
|
fillColor?: string;
|
|
24
|
-
|
|
25
|
-
}) => Promise<{
|
|
26
|
-
dataUrl: string;
|
|
27
|
-
width: number;
|
|
28
|
-
height: number;
|
|
29
|
-
}>;
|
|
35
|
+
}) => Promise<ImgCropperResult>;
|
|
30
36
|
save: (options?: {
|
|
31
37
|
fillColor?: string;
|
|
32
|
-
}) => Promise<
|
|
33
|
-
dataUrl: string;
|
|
34
|
-
width: number;
|
|
35
|
-
height: number;
|
|
36
|
-
}>;
|
|
38
|
+
}) => Promise<ImgCropperResult>;
|
|
37
39
|
clearSelection: () => void;
|
|
38
40
|
enableCropMode: () => void;
|
|
41
|
+
refit: () => void | Promise<void>;
|
|
39
42
|
}
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import type { DragMode } from './img-cropper-worker.svelte';
|
|
1
|
+
import type { CanvasGeometry, DragMode, ImgCropperResult } from './img-cropper-worker.svelte';
|
|
2
2
|
import type { CropperCanvas, CropperImage, CropperSelection } from 'cropperjs';
|
|
3
|
+
export type { CanvasGeometry };
|
|
3
4
|
export type CropperMode = 'contain' | 'cover';
|
|
4
|
-
export type CorsMode = 'native' | 'fetch';
|
|
5
5
|
export type { DragMode };
|
|
6
6
|
export type ImgCropperRatioOption = {
|
|
7
7
|
label: string;
|
|
8
8
|
value: number | null;
|
|
9
9
|
};
|
|
10
|
-
export type
|
|
11
|
-
initial: number;
|
|
12
|
-
supported: number[];
|
|
13
|
-
};
|
|
14
|
-
export type ImgCropperContainAspectRatio = {
|
|
10
|
+
export type ImgCropperDynamicAspectRatio = {
|
|
15
11
|
initial: number;
|
|
16
12
|
supported: number[];
|
|
17
13
|
allowFreeCrop?: false;
|
|
@@ -20,40 +16,29 @@ export type ImgCropperContainAspectRatio = {
|
|
|
20
16
|
supported: number[];
|
|
21
17
|
allowFreeCrop: true;
|
|
22
18
|
};
|
|
23
|
-
export type
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
type ImgCropperOptionsBase = {
|
|
29
|
-
corsMode?: CorsMode;
|
|
19
|
+
export type { ImgCropperResult };
|
|
20
|
+
export type ImgCropperSaveResult = ImgCropperResult;
|
|
21
|
+
export type ImgCropperOptions = {
|
|
22
|
+
mode: CropperMode;
|
|
23
|
+
aspectRatio?: number | ImgCropperDynamicAspectRatio;
|
|
30
24
|
fillColor?: string;
|
|
31
25
|
showImageShadow?: boolean;
|
|
32
26
|
};
|
|
33
|
-
export type ImgCropperOptions = (ImgCropperOptionsBase & {
|
|
34
|
-
mode: 'cover';
|
|
35
|
-
aspectRatio?: number | ImgCropperCoverAspectRatio;
|
|
36
|
-
}) | (ImgCropperOptionsBase & {
|
|
37
|
-
mode: 'contain';
|
|
38
|
-
aspectRatio?: number | ImgCropperContainAspectRatio;
|
|
39
|
-
});
|
|
40
27
|
export declare class ImgCropper {
|
|
41
28
|
loading: boolean;
|
|
42
29
|
ready: boolean;
|
|
43
30
|
fillColor: string;
|
|
44
|
-
aspectRatio: number | null;
|
|
45
31
|
readonly mode: CropperMode;
|
|
46
|
-
readonly corsMode: CorsMode;
|
|
47
32
|
readonly ratioOptions: ImgCropperRatioOption[] | null;
|
|
48
33
|
readonly showImageShadow: boolean;
|
|
49
34
|
private _worker;
|
|
50
35
|
private _originalSrc;
|
|
36
|
+
private _defaultGeometry;
|
|
51
37
|
constructor(options: ImgCropperOptions);
|
|
52
38
|
get showFillColor(): boolean;
|
|
53
39
|
get isTransparentFill(): boolean;
|
|
54
|
-
get
|
|
55
|
-
get
|
|
56
|
-
get naturalHeight(): number;
|
|
40
|
+
get canvasGeometry(): CanvasGeometry;
|
|
41
|
+
get aspectRatio(): number | null;
|
|
57
42
|
get cropBoxVisible(): boolean;
|
|
58
43
|
get dragMode(): DragMode;
|
|
59
44
|
init: (params: {
|
|
@@ -69,13 +54,10 @@ export declare class ImgCropper {
|
|
|
69
54
|
zoomOut: () => void;
|
|
70
55
|
reset: () => Promise<void>;
|
|
71
56
|
changeAspectRatio: (ratio: number | null) => Promise<void>;
|
|
72
|
-
crop: () => Promise<
|
|
73
|
-
dataUrl: string;
|
|
74
|
-
width: number;
|
|
75
|
-
height: number;
|
|
76
|
-
} | null>;
|
|
57
|
+
crop: () => Promise<ImgCropperSaveResult | null>;
|
|
77
58
|
save: () => Promise<ImgCropperSaveResult | null>;
|
|
78
59
|
clearSelection: () => void;
|
|
79
60
|
enableCropMode: () => void;
|
|
80
61
|
enableMoveMode: () => void;
|
|
62
|
+
refit: () => void;
|
|
81
63
|
}
|