@streamscloud/kit 0.1.11 → 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 +8 -7
- package/dist/ui/cropper/image-editor-dialog/index.d.ts +2 -2
- package/dist/ui/cropper/image-editor-dialog/index.js +1 -1
- package/dist/ui/cropper/image-editor-dialog/types.d.ts +11 -3
- package/dist/ui/cropper/img-cropper/cmp.img-cropper.svelte +1 -2
- package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.d.ts +8 -11
- package/dist/ui/cropper/img-cropper/img-cropper-base-worker.svelte.js +19 -7
- package/dist/ui/cropper/img-cropper/img-cropper-utils.d.ts +1 -0
- package/dist/ui/cropper/img-cropper/img-cropper-utils.js +23 -0
- package/dist/ui/cropper/img-cropper/img-cropper-worker.svelte.d.ts +9 -10
- package/dist/ui/cropper/img-cropper/img-cropper.svelte.d.ts +7 -19
- package/dist/ui/cropper/img-cropper/img-cropper.svelte.js +10 -5
- 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
|
@@ -26,3 +26,41 @@
|
|
|
26
26
|
color: var(--info-bg);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
// Theme classes — override svelte-sonner color variables per-toast.
|
|
31
|
+
// Applied via the `class` option when an explicit theme is passed to Toastr methods.
|
|
32
|
+
.sc-toast--light {
|
|
33
|
+
--normal-bg: #fff;
|
|
34
|
+
--normal-border: hsl(0, 0%, 93%);
|
|
35
|
+
--normal-text: hsl(0, 0%, 9%);
|
|
36
|
+
--success-bg: hsl(143, 85%, 96%);
|
|
37
|
+
--success-border: hsl(145, 92%, 87%);
|
|
38
|
+
--success-text: hsl(140, 100%, 27%);
|
|
39
|
+
--info-bg: hsl(208, 100%, 97%);
|
|
40
|
+
--info-border: hsl(221, 91%, 93%);
|
|
41
|
+
--info-text: hsl(210, 92%, 45%);
|
|
42
|
+
--warning-bg: hsl(49, 100%, 97%);
|
|
43
|
+
--warning-border: hsl(49, 91%, 84%);
|
|
44
|
+
--warning-text: hsl(31, 92%, 45%);
|
|
45
|
+
--error-bg: hsl(359, 100%, 97%);
|
|
46
|
+
--error-border: hsl(359, 100%, 94%);
|
|
47
|
+
--error-text: hsl(360, 100%, 45%);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.sc-toast--dark {
|
|
51
|
+
--normal-bg: #000;
|
|
52
|
+
--normal-border: hsl(0, 0%, 20%);
|
|
53
|
+
--normal-text: hsl(0, 0%, 99%);
|
|
54
|
+
--success-bg: hsl(150, 100%, 6%);
|
|
55
|
+
--success-border: hsl(147, 100%, 12%);
|
|
56
|
+
--success-text: hsl(150, 86%, 65%);
|
|
57
|
+
--info-bg: hsl(215, 100%, 6%);
|
|
58
|
+
--info-border: hsl(223, 43%, 17%);
|
|
59
|
+
--info-text: hsl(216, 87%, 65%);
|
|
60
|
+
--warning-bg: hsl(64, 100%, 6%);
|
|
61
|
+
--warning-border: hsl(60, 100%, 9%);
|
|
62
|
+
--warning-text: hsl(46, 87%, 65%);
|
|
63
|
+
--error-bg: hsl(358, 76%, 10%);
|
|
64
|
+
--error-border: hsl(357, 89%, 16%);
|
|
65
|
+
--error-text: hsl(358, 100%, 81%);
|
|
66
|
+
}
|
|
@@ -4,5 +4,5 @@ export declare class Toastr {
|
|
|
4
4
|
static success(message: string, options?: ToastrOptions): Promise<void>;
|
|
5
5
|
static warning(message: string, options?: ToastrOptions): Promise<void>;
|
|
6
6
|
static info(message: string, options?: ToastrOptions): Promise<void>;
|
|
7
|
-
static promise<T>(promise: Promise<T> | (() => Promise<T>), messages: ToastrPromiseMessages<T
|
|
7
|
+
static promise<T>(promise: Promise<T> | (() => Promise<T>), messages: ToastrPromiseMessages<T>, options?: ToastrOptions): Promise<void>;
|
|
8
8
|
}
|
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
import { ToasterHost } from './toaster-host.svelte';
|
|
2
|
+
const toExternalToast = (options) => {
|
|
3
|
+
if (!options) {
|
|
4
|
+
return {};
|
|
5
|
+
}
|
|
6
|
+
const { theme, ...rest } = options;
|
|
7
|
+
return { ...rest, class: theme ? `sc-toast--${theme}` : undefined };
|
|
8
|
+
};
|
|
2
9
|
export class Toastr {
|
|
3
10
|
static async error(message, options) {
|
|
4
11
|
const toast = await ToasterHost.ensure();
|
|
5
|
-
toast.error(message, options);
|
|
12
|
+
toast.error(message, toExternalToast(options));
|
|
6
13
|
}
|
|
7
14
|
static async success(message, options) {
|
|
8
15
|
const toast = await ToasterHost.ensure();
|
|
9
|
-
toast.success(message, options);
|
|
16
|
+
toast.success(message, toExternalToast(options));
|
|
10
17
|
}
|
|
11
18
|
static async warning(message, options) {
|
|
12
19
|
const toast = await ToasterHost.ensure();
|
|
13
|
-
toast.warning(message, options);
|
|
20
|
+
toast.warning(message, toExternalToast(options));
|
|
14
21
|
}
|
|
15
22
|
static async info(message, options) {
|
|
16
23
|
const toast = await ToasterHost.ensure();
|
|
17
|
-
toast.info(message, options);
|
|
24
|
+
toast.info(message, toExternalToast(options));
|
|
18
25
|
}
|
|
19
|
-
static async promise(promise, messages) {
|
|
26
|
+
static async promise(promise, messages, options) {
|
|
20
27
|
const toast = await ToasterHost.ensure();
|
|
21
|
-
toast.promise(promise, messages);
|
|
28
|
+
toast.promise(promise, { ...messages, ...toExternalToast(options) });
|
|
22
29
|
}
|
|
23
30
|
}
|
|
@@ -2,10 +2,12 @@ export interface ToastrAction {
|
|
|
2
2
|
label: string;
|
|
3
3
|
onClick: () => void;
|
|
4
4
|
}
|
|
5
|
+
export type ToastrTheme = 'light' | 'dark';
|
|
5
6
|
export interface ToastrOptions {
|
|
6
7
|
action?: ToastrAction;
|
|
7
8
|
description?: string;
|
|
8
9
|
duration?: number;
|
|
10
|
+
theme?: ToastrTheme;
|
|
9
11
|
}
|
|
10
12
|
export interface ToastrPromiseMessages<T> {
|
|
11
13
|
loading: string;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
<script lang="ts">import { FileWithBlobDataHelper } from '../../../core/files';
|
|
2
2
|
import { Toastr } from '../../../core/toastr';
|
|
3
|
-
import { Dialog, DialogButton, DialogCancelButton
|
|
3
|
+
import { Dialog, DialogButton, DialogCancelButton } from '../../dialog';
|
|
4
4
|
import { ImgCropper, ImgCropperControls, ImgCropperToolbar, ImgCropperView } from '../img-cropper';
|
|
5
5
|
import { ImageEditorDialogLocalization } from './image-editor-dialog-localization';
|
|
6
6
|
import { untrack } from 'svelte';
|
|
7
7
|
const { controller, data } = $props();
|
|
8
8
|
const loc = new ImageEditorDialogLocalization();
|
|
9
9
|
const cropper = untrack(() => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
return new ImgCropper({
|
|
11
|
+
mode: data.mode ?? 'cover',
|
|
12
|
+
aspectRatio: data.aspectRatio,
|
|
13
|
+
fillColor: data.fillColor,
|
|
14
|
+
showImageShadow: data.showImageShadow ?? true
|
|
15
|
+
});
|
|
15
16
|
});
|
|
16
17
|
const url = $derived(data.url);
|
|
17
18
|
const save = async () => {
|
|
@@ -36,7 +37,7 @@ const cancel = () => {
|
|
|
36
37
|
};
|
|
37
38
|
$effect(() => untrack(() => {
|
|
38
39
|
controller.updateSettings({ closeOnClickOutside: false, closeOnEsc: true });
|
|
39
|
-
controller.updateContainerSettings({ size:
|
|
40
|
+
controller.updateContainerSettings({ size: 'fullhd' });
|
|
40
41
|
}));
|
|
41
42
|
</script>
|
|
42
43
|
|
|
@@ -9,7 +9,7 @@ import type { ImageEditorDialogOptions, ImageEditorDialogResult } from './types'
|
|
|
9
9
|
*
|
|
10
10
|
* ### Aspect ratio
|
|
11
11
|
* - `number` — fixed ratio, no dropdown.
|
|
12
|
-
* - `
|
|
12
|
+
* - `ImageEditorDynamicAspectRatio` — dropdown with supported ratios. `allowFreeCrop: true`
|
|
13
13
|
* adds a free-ratio option; `initial` is optional when free crop is enabled.
|
|
14
14
|
* - Omitted — free crop (no ratio constraint on the selection).
|
|
15
15
|
*
|
|
@@ -18,4 +18,4 @@ import type { ImageEditorDialogOptions, ImageEditorDialogResult } from './types'
|
|
|
18
18
|
* - `selectedRatio` — active aspect ratio at save time (`null` for free crop).
|
|
19
19
|
*/
|
|
20
20
|
export declare const openImageEditorDialog: (options: ImageEditorDialogOptions) => Promise<DialogResult<ImageEditorDialogResult>>;
|
|
21
|
-
export type { CroppedFile, ImageEditorDialogOptions, ImageEditorDialogResult } from './types';
|
|
21
|
+
export type { CroppedFile, ImageEditorDialogOptions, ImageEditorDialogResult, ImageEditorDynamicAspectRatio } from './types';
|
|
@@ -9,7 +9,7 @@ import { default as ImageEditorDialog } from './cmp.image-editor-dialog.svelte';
|
|
|
9
9
|
*
|
|
10
10
|
* ### Aspect ratio
|
|
11
11
|
* - `number` — fixed ratio, no dropdown.
|
|
12
|
-
* - `
|
|
12
|
+
* - `ImageEditorDynamicAspectRatio` — dropdown with supported ratios. `allowFreeCrop: true`
|
|
13
13
|
* adds a free-ratio option; `initial` is optional when free crop is enabled.
|
|
14
14
|
* - Omitted — free crop (no ratio constraint on the selection).
|
|
15
15
|
*
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import type { FileWithBlobUrl } from '../../../core/files';
|
|
2
|
-
|
|
2
|
+
export type ImageEditorDynamicAspectRatio = {
|
|
3
|
+
initial: number;
|
|
4
|
+
supported: number[];
|
|
5
|
+
allowFreeCrop?: false;
|
|
6
|
+
} | {
|
|
7
|
+
initial?: number;
|
|
8
|
+
supported: number[];
|
|
9
|
+
allowFreeCrop: true;
|
|
10
|
+
};
|
|
3
11
|
export type ImageEditorDialogOptions = {
|
|
4
12
|
url: string;
|
|
5
13
|
fillColor?: string;
|
|
6
14
|
showImageShadow?: boolean;
|
|
7
|
-
mode?:
|
|
8
|
-
aspectRatio?: number |
|
|
15
|
+
mode?: 'contain' | 'cover';
|
|
16
|
+
aspectRatio?: number | ImageEditorDynamicAspectRatio;
|
|
9
17
|
};
|
|
10
18
|
export type CroppedFile = FileWithBlobUrl & {
|
|
11
19
|
width: number;
|
|
@@ -69,8 +69,7 @@ const observeResize = (el) => {
|
|
|
69
69
|
style:height={cropper.canvasGeometry.height ? `${cropper.canvasGeometry.height}px` : undefined}
|
|
70
70
|
use:initCanvas
|
|
71
71
|
background={(cropper.showFillColor && cropper.isTransparentFill) || undefined}>
|
|
72
|
-
<cropper-image
|
|
73
|
-
</cropper-image>
|
|
72
|
+
<cropper-image initial-center-size={cropper.mode} scalable translatable rotatable> </cropper-image>
|
|
74
73
|
<cropper-shade hidden></cropper-shade>
|
|
75
74
|
<cropper-handle action="select" plain></cropper-handle>
|
|
76
75
|
<cropper-selection movable resizable aspect-ratio={cropper.aspectRatio ?? undefined}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CanvasGeometry, DragMode, ImgCropperWorker, ImgCropperWorkerParams } from './img-cropper-worker.svelte';
|
|
1
|
+
import type { CanvasGeometry, DragMode, ImgCropperResult, ImgCropperWorker, ImgCropperWorkerParams } from './img-cropper-worker.svelte';
|
|
2
2
|
import type { CropperCanvas, CropperImage, CropperSelection } from 'cropperjs';
|
|
3
3
|
export declare abstract class ImgCropperBaseWorker implements ImgCropperWorker {
|
|
4
4
|
cropBoxVisible: boolean;
|
|
@@ -10,6 +10,7 @@ export declare abstract class ImgCropperBaseWorker implements ImgCropperWorker {
|
|
|
10
10
|
protected _canvas: CropperCanvas;
|
|
11
11
|
protected _selectHandle: HTMLElement | null;
|
|
12
12
|
protected _originalSrc: string;
|
|
13
|
+
protected _sourceMime: string | null;
|
|
13
14
|
protected _visualWidth: number;
|
|
14
15
|
protected _visualHeight: number;
|
|
15
16
|
protected abstract readonly _centerMode: 'contain' | 'cover';
|
|
@@ -21,23 +22,19 @@ export declare abstract class ImgCropperBaseWorker implements ImgCropperWorker {
|
|
|
21
22
|
reset: () => Promise<void>;
|
|
22
23
|
crop: (options?: {
|
|
23
24
|
fillColor?: string;
|
|
24
|
-
}) => Promise<
|
|
25
|
-
dataUrl: string;
|
|
26
|
-
width: number;
|
|
27
|
-
height: number;
|
|
28
|
-
}>;
|
|
25
|
+
}) => Promise<ImgCropperResult>;
|
|
29
26
|
save: (options?: {
|
|
30
27
|
fillColor?: string;
|
|
31
|
-
}) => Promise<
|
|
32
|
-
dataUrl: string;
|
|
33
|
-
width: number;
|
|
34
|
-
height: number;
|
|
35
|
-
}>;
|
|
28
|
+
}) => Promise<ImgCropperResult>;
|
|
36
29
|
clearSelection: () => void;
|
|
37
30
|
refit: () => Promise<void>;
|
|
38
31
|
enableCropMode: () => void;
|
|
39
32
|
protected _wrapTransformOp: (fn: () => void) => void;
|
|
40
33
|
protected _applyMoveMode: () => void;
|
|
34
|
+
protected _resolveOutputFormat: (fillColor: string | undefined) => {
|
|
35
|
+
mime: string;
|
|
36
|
+
quality: number;
|
|
37
|
+
};
|
|
41
38
|
protected _fitImage: () => Promise<void>;
|
|
42
39
|
protected abstract _computeCanvasSize(): void;
|
|
43
40
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ColorHelper } from '../../../core/utils';
|
|
2
2
|
import { computeImageScale, computeNaturalOutputSize, handleSelectionBoundary } from './img-cropper-utils';
|
|
3
3
|
import { tick } from 'svelte';
|
|
4
|
+
const OPAQUE_MIME_TYPES = new Set(['image/jpeg', 'image/bmp']);
|
|
4
5
|
export class ImgCropperBaseWorker {
|
|
5
6
|
cropBoxVisible = $state(false);
|
|
6
7
|
dragMode = $state('move');
|
|
@@ -11,10 +12,12 @@ export class ImgCropperBaseWorker {
|
|
|
11
12
|
_canvas;
|
|
12
13
|
_selectHandle;
|
|
13
14
|
_originalSrc;
|
|
15
|
+
_sourceMime;
|
|
14
16
|
_visualWidth = 0;
|
|
15
17
|
_visualHeight = 0;
|
|
16
18
|
constructor(params) {
|
|
17
19
|
this._originalSrc = params.originalSrc;
|
|
20
|
+
this._sourceMime = params.sourceMime;
|
|
18
21
|
this._image = params.image;
|
|
19
22
|
this._selection = params.selection;
|
|
20
23
|
this._canvas = params.canvas;
|
|
@@ -41,9 +44,7 @@ export class ImgCropperBaseWorker {
|
|
|
41
44
|
this._selection.$clear();
|
|
42
45
|
};
|
|
43
46
|
rotate = async () => {
|
|
44
|
-
|
|
45
|
-
this._visualWidth = this._visualHeight;
|
|
46
|
-
this._visualHeight = prevW;
|
|
47
|
+
[this._visualWidth, this._visualHeight] = [this._visualHeight, this._visualWidth];
|
|
47
48
|
this._computeCanvasSize();
|
|
48
49
|
await tick();
|
|
49
50
|
this._wrapTransformOp(() => {
|
|
@@ -77,12 +78,13 @@ export class ImgCropperBaseWorker {
|
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
});
|
|
80
|
-
const
|
|
81
|
+
const format = this._resolveOutputFormat(fillColor);
|
|
82
|
+
const dataUrl = canvas.toDataURL(format.mime, format.quality);
|
|
81
83
|
this._applyMoveMode();
|
|
82
84
|
this._image.src = dataUrl;
|
|
83
85
|
await this._fitImage();
|
|
84
86
|
this._selection.$clear();
|
|
85
|
-
return { dataUrl, width: canvas.width, height: canvas.height };
|
|
87
|
+
return { dataUrl, width: canvas.width, height: canvas.height, mimeType: format.mime };
|
|
86
88
|
};
|
|
87
89
|
save = async (options) => {
|
|
88
90
|
const fillColor = options?.fillColor;
|
|
@@ -114,8 +116,9 @@ export class ImgCropperBaseWorker {
|
|
|
114
116
|
// Reverse zoom shrinks image — must suspend cover check to avoid clamping
|
|
115
117
|
this._wrapTransformOp(() => this._image.$zoom(-0.003));
|
|
116
118
|
}
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
+
const format = this._resolveOutputFormat(fillColor);
|
|
120
|
+
const dataUrl = canvas.toDataURL(format.mime, format.quality);
|
|
121
|
+
return { dataUrl, width: canvas.width, height: canvas.height, mimeType: format.mime };
|
|
119
122
|
};
|
|
120
123
|
clearSelection = () => {
|
|
121
124
|
this._selection.$clear();
|
|
@@ -149,6 +152,15 @@ export class ImgCropperBaseWorker {
|
|
|
149
152
|
this.dragMode = 'move';
|
|
150
153
|
this.cropBoxVisible = false;
|
|
151
154
|
};
|
|
155
|
+
_resolveOutputFormat = (fillColor) => {
|
|
156
|
+
if (this._sourceMime && OPAQUE_MIME_TYPES.has(this._sourceMime)) {
|
|
157
|
+
return { mime: 'image/jpeg', quality: 0.92 };
|
|
158
|
+
}
|
|
159
|
+
if (fillColor && !ColorHelper.isTransparent(fillColor)) {
|
|
160
|
+
return { mime: 'image/jpeg', quality: 0.92 };
|
|
161
|
+
}
|
|
162
|
+
return { mime: 'image/png', quality: 1 };
|
|
163
|
+
};
|
|
152
164
|
_fitImage = async () => {
|
|
153
165
|
const img = await this._image.$ready();
|
|
154
166
|
this._visualWidth = img.naturalWidth;
|
|
@@ -5,6 +5,7 @@ type Rect = {
|
|
|
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;
|
|
@@ -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) => {
|
|
@@ -7,12 +7,19 @@ export type CanvasGeometry = {
|
|
|
7
7
|
};
|
|
8
8
|
export type ImgCropperWorkerParams = {
|
|
9
9
|
originalSrc: string;
|
|
10
|
+
sourceMime: string | null;
|
|
10
11
|
image: CropperImage;
|
|
11
12
|
selection: CropperSelection;
|
|
12
13
|
canvas: CropperCanvas;
|
|
13
14
|
selectHandle: HTMLElement | null;
|
|
14
15
|
aspectRatio: number | null;
|
|
15
16
|
};
|
|
17
|
+
export type ImgCropperResult = {
|
|
18
|
+
dataUrl: string;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
mimeType: string;
|
|
22
|
+
};
|
|
16
23
|
export interface ImgCropperWorker {
|
|
17
24
|
cropBoxVisible: boolean;
|
|
18
25
|
dragMode: DragMode;
|
|
@@ -25,18 +32,10 @@ export interface ImgCropperWorker {
|
|
|
25
32
|
reset: () => Promise<void>;
|
|
26
33
|
crop: (options?: {
|
|
27
34
|
fillColor?: string;
|
|
28
|
-
}) => Promise<
|
|
29
|
-
dataUrl: string;
|
|
30
|
-
width: number;
|
|
31
|
-
height: number;
|
|
32
|
-
}>;
|
|
35
|
+
}) => Promise<ImgCropperResult>;
|
|
33
36
|
save: (options?: {
|
|
34
37
|
fillColor?: string;
|
|
35
|
-
}) => Promise<
|
|
36
|
-
dataUrl: string;
|
|
37
|
-
width: number;
|
|
38
|
-
height: number;
|
|
39
|
-
}>;
|
|
38
|
+
}) => Promise<ImgCropperResult>;
|
|
40
39
|
clearSelection: () => void;
|
|
41
40
|
enableCropMode: () => void;
|
|
42
41
|
refit: () => void | Promise<void>;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type { CanvasGeometry, 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
3
|
export type { CanvasGeometry };
|
|
4
4
|
export type CropperMode = 'contain' | 'cover';
|
|
5
|
-
export type CorsMode = 'native' | 'fetch';
|
|
6
5
|
export type { DragMode };
|
|
7
6
|
export type ImgCropperRatioOption = {
|
|
8
7
|
label: string;
|
|
@@ -17,26 +16,19 @@ export type ImgCropperDynamicAspectRatio = {
|
|
|
17
16
|
supported: number[];
|
|
18
17
|
allowFreeCrop: true;
|
|
19
18
|
};
|
|
20
|
-
export type
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
height: number;
|
|
24
|
-
};
|
|
25
|
-
type ImgCropperOptionsBase = {
|
|
26
|
-
corsMode?: CorsMode;
|
|
27
|
-
fillColor?: string;
|
|
28
|
-
showImageShadow?: boolean;
|
|
29
|
-
};
|
|
30
|
-
export type ImgCropperOptions = ImgCropperOptionsBase & {
|
|
19
|
+
export type { ImgCropperResult };
|
|
20
|
+
export type ImgCropperSaveResult = ImgCropperResult;
|
|
21
|
+
export type ImgCropperOptions = {
|
|
31
22
|
mode: CropperMode;
|
|
32
23
|
aspectRatio?: number | ImgCropperDynamicAspectRatio;
|
|
24
|
+
fillColor?: string;
|
|
25
|
+
showImageShadow?: boolean;
|
|
33
26
|
};
|
|
34
27
|
export declare class ImgCropper {
|
|
35
28
|
loading: boolean;
|
|
36
29
|
ready: boolean;
|
|
37
30
|
fillColor: string;
|
|
38
31
|
readonly mode: CropperMode;
|
|
39
|
-
readonly corsMode: CorsMode;
|
|
40
32
|
readonly ratioOptions: ImgCropperRatioOption[] | null;
|
|
41
33
|
readonly showImageShadow: boolean;
|
|
42
34
|
private _worker;
|
|
@@ -62,11 +54,7 @@ export declare class ImgCropper {
|
|
|
62
54
|
zoomOut: () => void;
|
|
63
55
|
reset: () => Promise<void>;
|
|
64
56
|
changeAspectRatio: (ratio: number | null) => Promise<void>;
|
|
65
|
-
crop: () => Promise<
|
|
66
|
-
dataUrl: string;
|
|
67
|
-
width: number;
|
|
68
|
-
height: number;
|
|
69
|
-
} | null>;
|
|
57
|
+
crop: () => Promise<ImgCropperSaveResult | null>;
|
|
70
58
|
save: () => Promise<ImgCropperSaveResult | null>;
|
|
71
59
|
clearSelection: () => void;
|
|
72
60
|
enableCropMode: () => void;
|
|
@@ -2,12 +2,12 @@ import { FileWithBlobDataHelper } from '../../../core/files';
|
|
|
2
2
|
import { ColorHelper, Utils } from '../../../core/utils';
|
|
3
3
|
import { ImgCropperContainWorker } from './img-cropper-contain-worker.svelte';
|
|
4
4
|
import { ImgCropperCoverWorker } from './img-cropper-cover-worker.svelte';
|
|
5
|
+
import { detectMimeFromUrl } from './img-cropper-utils';
|
|
5
6
|
export class ImgCropper {
|
|
6
7
|
loading = $state(false);
|
|
7
8
|
ready = $state(false);
|
|
8
9
|
fillColor = $state('');
|
|
9
10
|
mode;
|
|
10
|
-
corsMode;
|
|
11
11
|
ratioOptions;
|
|
12
12
|
showImageShadow;
|
|
13
13
|
_worker = $state.raw(null);
|
|
@@ -15,7 +15,6 @@ export class ImgCropper {
|
|
|
15
15
|
_defaultGeometry;
|
|
16
16
|
constructor(options) {
|
|
17
17
|
this.mode = options.mode;
|
|
18
|
-
this.corsMode = options.corsMode ?? 'native';
|
|
19
18
|
this.fillColor = options.fillColor ?? '';
|
|
20
19
|
this.showImageShadow = options.showImageShadow ?? true;
|
|
21
20
|
const toOption = (r) => {
|
|
@@ -68,13 +67,19 @@ export class ImgCropper {
|
|
|
68
67
|
this.loading = true;
|
|
69
68
|
try {
|
|
70
69
|
let resolvedSrc = params.src;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
let sourceMime = null;
|
|
71
|
+
const file = await FileWithBlobDataHelper.fromUrl(params.src);
|
|
72
|
+
if (file) {
|
|
73
|
+
resolvedSrc = file.blobUrl;
|
|
74
|
+
sourceMime = file.type || null;
|
|
74
75
|
this._originalSrc = resolvedSrc;
|
|
75
76
|
}
|
|
77
|
+
if (!sourceMime) {
|
|
78
|
+
sourceMime = detectMimeFromUrl(params.src);
|
|
79
|
+
}
|
|
76
80
|
const workerParams = {
|
|
77
81
|
originalSrc: resolvedSrc,
|
|
82
|
+
sourceMime,
|
|
78
83
|
image: params.cropperImage,
|
|
79
84
|
selection: params.cropperSelection,
|
|
80
85
|
canvas: params.cropperCanvas,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { default as ImgCropperControls } from './cmp.img-cropper-controls.svelte';
|
|
2
2
|
export { default as ImgCropperToolbar } from './cmp.img-cropper-toolbar.svelte';
|
|
3
3
|
export { default as ImgCropperView } from './cmp.img-cropper.svelte';
|
|
4
|
-
export { type CanvasGeometry, type
|
|
4
|
+
export { type CanvasGeometry, type CropperMode, type DragMode, ImgCropper, type ImgCropperDynamicAspectRatio, type ImgCropperOptions, type ImgCropperRatioOption, type ImgCropperSaveResult } from './img-cropper.svelte';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts" generics="TResult = void, TCancelResult = void, TData = void">import { Dialogs } from './dialogs.svelte';
|
|
2
|
-
import {
|
|
2
|
+
import { DIALOG_SIZES } from './types.svelte';
|
|
3
3
|
import { untrack } from 'svelte';
|
|
4
4
|
let { dialog } = $props();
|
|
5
5
|
let dialogElement = $state();
|
|
@@ -57,7 +57,7 @@ $effect(() => {
|
|
|
57
57
|
});
|
|
58
58
|
const size = $derived(dialog.controller.containerSettings.size);
|
|
59
59
|
const position = $derived(dialog.controller.containerSettings.position);
|
|
60
|
-
const isCustomWidth = $derived(!
|
|
60
|
+
const isCustomWidth = $derived(!DIALOG_SIZES.includes(size));
|
|
61
61
|
const customWidth = $derived(isCustomWidth ? size : null);
|
|
62
62
|
</script>
|
|
63
63
|
|
|
@@ -65,16 +65,16 @@ const customWidth = $derived(isCustomWidth ? size : null);
|
|
|
65
65
|
bind:this={dialogElement}
|
|
66
66
|
class="dialog-container"
|
|
67
67
|
class:dialog-container--inactive={!isActive}
|
|
68
|
-
class:dialog-container--size-small={size ===
|
|
69
|
-
class:dialog-container--size-medium={size ===
|
|
70
|
-
class:dialog-container--size-default={size ===
|
|
71
|
-
class:dialog-container--size-large={size ===
|
|
72
|
-
class:dialog-container--size-fullhd={size ===
|
|
73
|
-
class:dialog-container--size-auto={size ===
|
|
74
|
-
class:dialog-container--position-center={position ===
|
|
75
|
-
class:dialog-container--position-center-top={position ===
|
|
76
|
-
class:dialog-container--position-full-screen={position ===
|
|
77
|
-
class:dialog-container--position-full-height={position ===
|
|
68
|
+
class:dialog-container--size-small={size === 'small'}
|
|
69
|
+
class:dialog-container--size-medium={size === 'medium'}
|
|
70
|
+
class:dialog-container--size-default={size === 'default'}
|
|
71
|
+
class:dialog-container--size-large={size === 'large'}
|
|
72
|
+
class:dialog-container--size-fullhd={size === 'fullhd'}
|
|
73
|
+
class:dialog-container--size-auto={size === 'auto'}
|
|
74
|
+
class:dialog-container--position-center={position === 'center'}
|
|
75
|
+
class:dialog-container--position-center-top={position === 'center-top'}
|
|
76
|
+
class:dialog-container--position-full-screen={position === 'full-screen'}
|
|
77
|
+
class:dialog-container--position-full-height={position === 'full-height'}
|
|
78
78
|
onkeydown={handleKeydown}
|
|
79
79
|
oncancel={handleCancel}
|
|
80
80
|
onclick={handleBackdropClick}
|
|
@@ -23,5 +23,7 @@ export type DialogInit<TData = void, TResult = void, TCancelResult = void> = {
|
|
|
23
23
|
data?: TData;
|
|
24
24
|
explicitSettings?: Partial<DialogSettings>;
|
|
25
25
|
containerSettings?: Partial<DialogContainerSettings>;
|
|
26
|
+
/** Mount target for the dialog element. Useful for shadow DOM isolation. Defaults to `document.body`. */
|
|
27
|
+
host?: ParentNode;
|
|
26
28
|
};
|
|
27
29
|
export {};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { AnyDialogData } from './dialog-data';
|
|
2
|
-
export declare const mountDialog: (dialog: AnyDialogData) => void;
|
|
2
|
+
export declare const mountDialog: (dialog: AnyDialogData, host?: ParentNode) => void;
|
|
3
3
|
export declare const unmountDialog: (dialog: AnyDialogData) => void;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { default as DialogContainer } from './cmp.dialog-container.svelte';
|
|
2
2
|
import { mount, unmount } from 'svelte';
|
|
3
|
-
export const mountDialog = (dialog) => {
|
|
3
|
+
export const mountDialog = (dialog, host) => {
|
|
4
4
|
const hostElement = document.createElement('div');
|
|
5
5
|
hostElement.setAttribute('data-dialog-host', String(dialog.id));
|
|
6
|
-
document.body.appendChild(hostElement);
|
|
6
|
+
(host ?? document.body).appendChild(hostElement);
|
|
7
7
|
const component = mount(DialogContainer, {
|
|
8
8
|
target: hostElement,
|
|
9
9
|
props: { dialog }
|
|
@@ -3,11 +3,14 @@ import { type DialogResult } from './types.svelte';
|
|
|
3
3
|
export declare class DialogsContainer {
|
|
4
4
|
private _dialogs;
|
|
5
5
|
private _mountModule;
|
|
6
|
+
private _savedOverflow;
|
|
6
7
|
get all(): ReadonlyArray<AnyDialogData>;
|
|
7
8
|
get active(): AnyDialogData | null;
|
|
8
9
|
get count(): number;
|
|
9
10
|
open: <TData = void, TResult = void, TCancelResult = void>(init: DialogInit<TData, TResult, TCancelResult>, id?: number) => Promise<DialogResult<TResult, TCancelResult>>;
|
|
10
11
|
close: (id: number) => void;
|
|
11
12
|
closeAll: () => void;
|
|
13
|
+
private lockBodyScroll;
|
|
14
|
+
private unlockBodyScroll;
|
|
12
15
|
}
|
|
13
16
|
export declare const Dialogs: DialogsContainer;
|
|
@@ -5,6 +5,7 @@ import {} from './types.svelte';
|
|
|
5
5
|
export class DialogsContainer {
|
|
6
6
|
_dialogs = $state.raw([]);
|
|
7
7
|
_mountModule = null;
|
|
8
|
+
_savedOverflow = null;
|
|
8
9
|
get all() {
|
|
9
10
|
return this._dialogs;
|
|
10
11
|
}
|
|
@@ -18,7 +19,7 @@ export class DialogsContainer {
|
|
|
18
19
|
if (!this._mountModule) {
|
|
19
20
|
this._mountModule = await import('./dialog-mount');
|
|
20
21
|
}
|
|
21
|
-
const { view, data, explicitSettings, containerSettings } = init;
|
|
22
|
+
const { view, data, explicitSettings, containerSettings, host } = init;
|
|
22
23
|
const controller = new DialogController({
|
|
23
24
|
id,
|
|
24
25
|
onClose: () => {
|
|
@@ -26,6 +27,9 @@ export class DialogsContainer {
|
|
|
26
27
|
if (dialog) {
|
|
27
28
|
this._mountModule?.unmountDialog(dialog);
|
|
28
29
|
this._dialogs = this._dialogs.filter((d) => d.id !== id);
|
|
30
|
+
if (this._dialogs.length === 0) {
|
|
31
|
+
this.unlockBodyScroll();
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
34
|
},
|
|
31
35
|
settings: explicitSettings,
|
|
@@ -38,7 +42,8 @@ export class DialogsContainer {
|
|
|
38
42
|
controller
|
|
39
43
|
};
|
|
40
44
|
this._dialogs = [...this._dialogs, newDialog];
|
|
41
|
-
this.
|
|
45
|
+
this.lockBodyScroll();
|
|
46
|
+
this._mountModule.mountDialog(newDialog, host);
|
|
42
47
|
return controller.openedPromise;
|
|
43
48
|
};
|
|
44
49
|
close = (id) => {
|
|
@@ -53,6 +58,20 @@ export class DialogsContainer {
|
|
|
53
58
|
dialog.controller.cancel();
|
|
54
59
|
}
|
|
55
60
|
};
|
|
61
|
+
lockBodyScroll = () => {
|
|
62
|
+
if (this._savedOverflow !== null) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this._savedOverflow = document.body.style.overflow;
|
|
66
|
+
document.body.style.overflow = 'hidden';
|
|
67
|
+
};
|
|
68
|
+
unlockBodyScroll = () => {
|
|
69
|
+
if (this._savedOverflow === null) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
document.body.style.overflow = this._savedOverflow;
|
|
73
|
+
this._savedOverflow = null;
|
|
74
|
+
};
|
|
56
75
|
}
|
|
57
76
|
const createDialogsSingleton = () => {
|
|
58
77
|
if (!isBrowser()) {
|