ngx-kit 4.0.0 → 6.0.0
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/README.md +3 -2
- package/angle-selector/README.md +34 -0
- package/box-shadow/README.md +33 -0
- package/color-picker/README.md +52 -0
- package/data-table/README.md +120 -0
- package/date-picker/README.md +74 -0
- package/dialog/README.md +94 -169
- package/drawer-menu/README.md +76 -0
- package/dropzone/README.md +59 -0
- package/fesm2022/ngx-kit-angle-selector.mjs +2 -2
- package/fesm2022/ngx-kit-angle-selector.mjs.map +1 -1
- package/fesm2022/ngx-kit-box-shadow.mjs +28 -27
- package/fesm2022/ngx-kit-box-shadow.mjs.map +1 -1
- package/fesm2022/ngx-kit-color-picker.mjs +173 -43
- package/fesm2022/ngx-kit-color-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-data-table.mjs +446 -191
- package/fesm2022/ngx-kit-data-table.mjs.map +1 -1
- package/fesm2022/ngx-kit-date-picker.mjs +120 -86
- package/fesm2022/ngx-kit-date-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-dialog.mjs +38 -7
- package/fesm2022/ngx-kit-dialog.mjs.map +1 -1
- package/fesm2022/ngx-kit-drawer-menu.mjs +190 -0
- package/fesm2022/ngx-kit-drawer-menu.mjs.map +1 -0
- package/fesm2022/ngx-kit-dropzone.mjs +272 -0
- package/fesm2022/ngx-kit-dropzone.mjs.map +1 -0
- package/fesm2022/ngx-kit-form-field.mjs +87 -0
- package/fesm2022/ngx-kit-form-field.mjs.map +1 -0
- package/fesm2022/ngx-kit-gallery.mjs +419 -0
- package/fesm2022/ngx-kit-gallery.mjs.map +1 -0
- package/fesm2022/ngx-kit-gradient-picker.mjs +12 -4
- package/fesm2022/ngx-kit-gradient-picker.mjs.map +1 -1
- package/fesm2022/ngx-kit-image-editor.mjs +501 -0
- package/fesm2022/ngx-kit-image-editor.mjs.map +1 -0
- package/fesm2022/ngx-kit-menu.mjs +9 -4
- package/fesm2022/ngx-kit-menu.mjs.map +1 -1
- package/fesm2022/ngx-kit-message.mjs +58 -24
- package/fesm2022/ngx-kit-message.mjs.map +1 -1
- package/fesm2022/ngx-kit-notify.mjs +10 -4
- package/fesm2022/ngx-kit-notify.mjs.map +1 -1
- package/fesm2022/ngx-kit-shared.mjs +184 -88
- package/fesm2022/ngx-kit-shared.mjs.map +1 -1
- package/fesm2022/ngx-kit-time-picker.mjs +562 -0
- package/fesm2022/ngx-kit-time-picker.mjs.map +1 -0
- package/fesm2022/ngx-kit.mjs.map +1 -1
- package/gallery/README.md +71 -0
- package/gradient-picker/README.md +41 -0
- package/image-editor/README.md +44 -0
- package/menu/README.md +85 -0
- package/message/README.md +70 -0
- package/notify/README.md +56 -0
- package/package.json +25 -1
- package/shared/README.md +117 -0
- package/styles/all.css +27 -0
- package/styles/image-viewer.css +12 -0
- package/types/ngx-kit-box-shadow.d.ts +1 -2
- package/types/ngx-kit-data-table.d.ts +290 -147
- package/types/ngx-kit-date-picker.d.ts +1 -1
- package/types/ngx-kit-dialog.d.ts +1 -0
- package/types/ngx-kit-drawer-menu.d.ts +101 -0
- package/types/ngx-kit-dropzone.d.ts +84 -0
- package/types/ngx-kit-form-field.d.ts +31 -0
- package/types/ngx-kit-gallery.d.ts +168 -0
- package/types/ngx-kit-image-editor.d.ts +175 -0
- package/types/ngx-kit-menu.d.ts +1 -0
- package/types/ngx-kit-message.d.ts +14 -3
- package/types/ngx-kit-notify.d.ts +2 -1
- package/types/ngx-kit-shared.d.ts +70 -15
- package/types/ngx-kit-time-picker.d.ts +150 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { output, HostListener, Directive, input, signal, computed, effect, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { DecimalPipe } from '@angular/common';
|
|
4
|
+
import * as i1 from '@angular/forms';
|
|
5
|
+
import { FormsModule } from '@angular/forms';
|
|
6
|
+
import { SliderComponent } from 'ngx-kit/shared';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* درگ عمومی روی پایهی Pointer Events + setPointerCapture (دقیقاً همون
|
|
10
|
+
* تکنیکی که در ngx-kit/data-table برای ریسایز ستون استفاده شده) — نیازی به
|
|
11
|
+
* listener سطح document نداره چون بعد از setPointerCapture، رویدادهای
|
|
12
|
+
* pointermove/pointerup حتی بیرون از محدودهی المنت هم به خودش میرسن.
|
|
13
|
+
*
|
|
14
|
+
* dx/dy تجمعی از لحظهی pointerdown هستن (نه دلتای بین دو move متوالی)، چون
|
|
15
|
+
* معمولاً مصرفکننده میخواد «موقعیت شروع + دلتای کل» رو حساب کنه، نه جمع
|
|
16
|
+
* تدریجی گرد شوندهی دلتاهای ریز.
|
|
17
|
+
*/
|
|
18
|
+
class NgxPointerDragDirective {
|
|
19
|
+
dragStart = output();
|
|
20
|
+
dragMove = output();
|
|
21
|
+
dragEnd = output();
|
|
22
|
+
dragging = false;
|
|
23
|
+
startX = 0;
|
|
24
|
+
startY = 0;
|
|
25
|
+
onPointerDown(ev) {
|
|
26
|
+
ev.preventDefault();
|
|
27
|
+
ev.stopPropagation();
|
|
28
|
+
this.dragging = true;
|
|
29
|
+
this.startX = ev.clientX;
|
|
30
|
+
this.startY = ev.clientY;
|
|
31
|
+
ev.target.setPointerCapture(ev.pointerId);
|
|
32
|
+
this.dragStart.emit();
|
|
33
|
+
}
|
|
34
|
+
onPointerMove(ev) {
|
|
35
|
+
if (!this.dragging)
|
|
36
|
+
return;
|
|
37
|
+
this.dragMove.emit({ dx: ev.clientX - this.startX, dy: ev.clientY - this.startY });
|
|
38
|
+
}
|
|
39
|
+
onPointerUp() {
|
|
40
|
+
if (!this.dragging)
|
|
41
|
+
return;
|
|
42
|
+
this.dragging = false;
|
|
43
|
+
this.dragEnd.emit();
|
|
44
|
+
}
|
|
45
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxPointerDragDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
46
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.4", type: NgxPointerDragDirective, isStandalone: true, selector: "[ngxPointerDrag]", outputs: { dragStart: "dragStart", dragMove: "dragMove", dragEnd: "dragEnd" }, host: { listeners: { "pointerdown": "onPointerDown($event)", "pointermove": "onPointerMove($event)", "pointerup": "onPointerUp()", "pointercancel": "onPointerUp()" } }, ngImport: i0 });
|
|
47
|
+
}
|
|
48
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxPointerDragDirective, decorators: [{
|
|
49
|
+
type: Directive,
|
|
50
|
+
args: [{
|
|
51
|
+
selector: '[ngxPointerDrag]',
|
|
52
|
+
standalone: true,
|
|
53
|
+
}]
|
|
54
|
+
}], propDecorators: { dragStart: [{ type: i0.Output, args: ["dragStart"] }], dragMove: [{ type: i0.Output, args: ["dragMove"] }], dragEnd: [{ type: i0.Output, args: ["dragEnd"] }], onPointerDown: [{
|
|
55
|
+
type: HostListener,
|
|
56
|
+
args: ['pointerdown', ['$event']]
|
|
57
|
+
}], onPointerMove: [{
|
|
58
|
+
type: HostListener,
|
|
59
|
+
args: ['pointermove', ['$event']]
|
|
60
|
+
}], onPointerUp: [{
|
|
61
|
+
type: HostListener,
|
|
62
|
+
args: ['pointerup']
|
|
63
|
+
}, {
|
|
64
|
+
type: HostListener,
|
|
65
|
+
args: ['pointercancel']
|
|
66
|
+
}] } });
|
|
67
|
+
|
|
68
|
+
/** یه File/Blob/data-URL/URL رو به یک HTMLImageElement بارگذاریشده تبدیل میکنه */
|
|
69
|
+
function loadImage(source) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const img = new Image();
|
|
72
|
+
// برای URLهای cross-origin لازم میشه، وگرنه canvas بعداً «tainted» میشه
|
|
73
|
+
// و toBlob/toDataURL با SecurityError شکست میخوره.
|
|
74
|
+
img.crossOrigin = 'anonymous';
|
|
75
|
+
const objectUrl = typeof source === 'string' ? null : URL.createObjectURL(source);
|
|
76
|
+
const url = objectUrl ?? source;
|
|
77
|
+
img.onload = () => {
|
|
78
|
+
if (objectUrl)
|
|
79
|
+
URL.revokeObjectURL(objectUrl);
|
|
80
|
+
resolve(img);
|
|
81
|
+
};
|
|
82
|
+
img.onerror = () => {
|
|
83
|
+
if (objectUrl)
|
|
84
|
+
URL.revokeObjectURL(objectUrl);
|
|
85
|
+
reject(new Error('بارگذاری تصویر شکست خورد'));
|
|
86
|
+
};
|
|
87
|
+
img.src = url;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/** canvas.toBlob بهصورت Promise، بههمراه fallback برای مرورگرهای قدیمیتر */
|
|
91
|
+
function canvasToBlob(canvas, type = 'image/jpeg', quality = 0.92) {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
canvas.toBlob((blob) => {
|
|
94
|
+
if (blob)
|
|
95
|
+
resolve(blob);
|
|
96
|
+
else
|
|
97
|
+
reject(new Error('تبدیل canvas به blob شکست خورد'));
|
|
98
|
+
}, type, quality);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* یه کپیِ کوچیکشده از تصویر اصلی میسازه که کل UI ویرایشگر (چرخش، کراپ،
|
|
103
|
+
* پیشنمایش فیلترها/کارتونی) روی همین کار میکنه، نه روی تصویر اصلی که
|
|
104
|
+
* ممکنه چند مگاپیکسل باشه. این باعث میشه درگکردنِ اسلایدرها یا لبههای
|
|
105
|
+
* کراپ روون بمونه؛ خروجی نهایی (save) همیشه از روی تصویر اصلیِ کامل رندر
|
|
106
|
+
* میشه، نه از روی این کپی.
|
|
107
|
+
*/
|
|
108
|
+
function createWorkingCopy(img, maxSize = 1200) {
|
|
109
|
+
const { width, height } = fitWithin(img.naturalWidth, img.naturalHeight, maxSize, maxSize);
|
|
110
|
+
const canvas = document.createElement('canvas');
|
|
111
|
+
canvas.width = width;
|
|
112
|
+
canvas.height = height;
|
|
113
|
+
const ctx = canvas.getContext('2d');
|
|
114
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
115
|
+
return canvas;
|
|
116
|
+
}
|
|
117
|
+
/** ابعاد جدید رو طوری حساب میکنه که از maxWidth/maxHeight بیشتر نشه، با حفظ نسبت تصویر */
|
|
118
|
+
function fitWithin(width, height, maxWidth, maxHeight) {
|
|
119
|
+
if (!maxWidth && !maxHeight)
|
|
120
|
+
return { width, height };
|
|
121
|
+
let ratio = 1;
|
|
122
|
+
if (maxWidth)
|
|
123
|
+
ratio = Math.min(ratio, maxWidth / width);
|
|
124
|
+
if (maxHeight)
|
|
125
|
+
ratio = Math.min(ratio, maxHeight / height);
|
|
126
|
+
if (ratio >= 1)
|
|
127
|
+
return { width, height };
|
|
128
|
+
return { width: Math.round(width * ratio), height: Math.round(height * ratio) };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* افکت «کارتونی»: چون ctx.filter استاندارد چیزی شبیه cartoon نداره (فقط
|
|
133
|
+
* brightness/contrast/saturate/grayscale/sepia/invert/blur/hue-rotate)، این
|
|
134
|
+
* یکی رو واقعاً با پردازش پیکسلی پیاده کردیم؛ دو مرحله:
|
|
135
|
+
*
|
|
136
|
+
* ۱) Posterize: هر کانال رنگ به تعداد محدودی سطح گرد میشه (پهنههای رنگیِ
|
|
137
|
+
* صاف و یکدست، حسِ کارتونی رنگ میده).
|
|
138
|
+
* ۲) لبهیابی Sobel روی نسخهی خاکستریِ تصویر: هرجا شدت لبه از حد آستانه
|
|
139
|
+
* بیشتر باشه، پیکسل تیره میشه (حسِ خط دور کارتونی).
|
|
140
|
+
*
|
|
141
|
+
* روی خودِ ImageData کار میکنه، پس هزینهش با اندازهی تصویر خطیست؛ برای
|
|
142
|
+
* تصاویر خیلی بزرگ (>~3000px) بهتره قبلش resize بشه (که در کامپوننت اصلی
|
|
143
|
+
* با fitWithin قبل از اعمال فیلتر انجام میشه).
|
|
144
|
+
*/
|
|
145
|
+
function applyCartoonEffect(ctx, width, height, options = {}) {
|
|
146
|
+
const levels = options.levels ?? 5;
|
|
147
|
+
const edgeThreshold = options.edgeThreshold ?? 80;
|
|
148
|
+
const imageData = ctx.getImageData(0, 0, width, height);
|
|
149
|
+
const src = imageData.data;
|
|
150
|
+
// ۱) خاکستری برای Sobel
|
|
151
|
+
const gray = new Uint8ClampedArray(width * height);
|
|
152
|
+
for (let i = 0, p = 0; i < src.length; i += 4, p++) {
|
|
153
|
+
gray[p] = (src[i] * 0.299 + src[i + 1] * 0.587 + src[i + 2] * 0.114) | 0;
|
|
154
|
+
}
|
|
155
|
+
// ۲) Posterize رنگها
|
|
156
|
+
const step = 255 / (levels - 1);
|
|
157
|
+
for (let i = 0; i < src.length; i += 4) {
|
|
158
|
+
src[i] = Math.round(Math.round(src[i] / step) * step);
|
|
159
|
+
src[i + 1] = Math.round(Math.round(src[i + 1] / step) * step);
|
|
160
|
+
src[i + 2] = Math.round(Math.round(src[i + 2] / step) * step);
|
|
161
|
+
}
|
|
162
|
+
// ۳) Sobel روی gray، خط دور تیره روی src
|
|
163
|
+
const gxKernel = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
|
|
164
|
+
const gyKernel = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
|
|
165
|
+
for (let y = 1; y < height - 1; y++) {
|
|
166
|
+
for (let x = 1; x < width - 1; x++) {
|
|
167
|
+
let gx = 0;
|
|
168
|
+
let gy = 0;
|
|
169
|
+
let k = 0;
|
|
170
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
171
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
172
|
+
const v = gray[(y + dy) * width + (x + dx)];
|
|
173
|
+
gx += v * gxKernel[k];
|
|
174
|
+
gy += v * gyKernel[k];
|
|
175
|
+
k++;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const magnitude = Math.sqrt(gx * gx + gy * gy);
|
|
179
|
+
if (magnitude > edgeThreshold) {
|
|
180
|
+
const idx = (y * width + x) * 4;
|
|
181
|
+
src[idx] = 20;
|
|
182
|
+
src[idx + 1] = 20;
|
|
183
|
+
src[idx + 2] = 20;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
ctx.putImageData(imageData, 0, 0);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** تصویر مبدأ رو با زاویهی دادهشده روی یه canvas جدید (به اندازهی bounding-box نتیجه) میچرخونه */
|
|
191
|
+
function renderRotated(source, width, height, degrees) {
|
|
192
|
+
const rad = (degrees * Math.PI) / 180;
|
|
193
|
+
const sin = Math.abs(Math.sin(rad));
|
|
194
|
+
const cos = Math.abs(Math.cos(rad));
|
|
195
|
+
const newW = Math.max(1, Math.round(width * cos + height * sin));
|
|
196
|
+
const newH = Math.max(1, Math.round(width * sin + height * cos));
|
|
197
|
+
const canvas = document.createElement('canvas');
|
|
198
|
+
canvas.width = newW;
|
|
199
|
+
canvas.height = newH;
|
|
200
|
+
const ctx = canvas.getContext('2d');
|
|
201
|
+
ctx.translate(newW / 2, newH / 2);
|
|
202
|
+
ctx.rotate(rad);
|
|
203
|
+
ctx.drawImage(source, -width / 2, -height / 2, width, height);
|
|
204
|
+
return canvas;
|
|
205
|
+
}
|
|
206
|
+
function buildCssFilter(adjustments) {
|
|
207
|
+
const parts = [
|
|
208
|
+
`brightness(${adjustments.brightness}%)`,
|
|
209
|
+
`contrast(${adjustments.contrast}%)`,
|
|
210
|
+
`saturate(${adjustments.saturation}%)`,
|
|
211
|
+
];
|
|
212
|
+
if (adjustments.filter === 'grayscale')
|
|
213
|
+
parts.push('grayscale(1)');
|
|
214
|
+
if (adjustments.filter === 'sepia')
|
|
215
|
+
parts.push('sepia(1)');
|
|
216
|
+
if (adjustments.filter === 'invert')
|
|
217
|
+
parts.push('invert(1)');
|
|
218
|
+
return parts.join(' ');
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* کراپ + فیلترها رو روی یه canvas خروجی میسازه. brightness/contrast/
|
|
222
|
+
* saturation/grayscale/sepia/invert همه با ctx.filter بومیِ Canvas 2D انجام
|
|
223
|
+
* میشن (شتابدهیشده توسط مرورگر، بدون حلقهی پیکسلی دستی)؛ فقط cartoon
|
|
224
|
+
* (که معادل بومی نداره) با پردازش پیکسلی جدا انجام میشه.
|
|
225
|
+
*
|
|
226
|
+
* maxWidth/maxHeight برای دو حالت استفاده میشه: پیشنمایش زنده (عدد کوچیک،
|
|
227
|
+
* برای واکنشگرا موندن حین کشیدن اسلایدرها) و خروجی نهایی (بدون محدودیت یا
|
|
228
|
+
* محدودیت دلخواه کاربر).
|
|
229
|
+
*/
|
|
230
|
+
function renderComposite(source, sourceWidth, sourceHeight, crop, adjustments, maxWidth, maxHeight) {
|
|
231
|
+
const { width: outW, height: outH } = fitWithin(crop.width, crop.height, maxWidth, maxHeight);
|
|
232
|
+
const canvas = document.createElement('canvas');
|
|
233
|
+
canvas.width = Math.max(1, outW);
|
|
234
|
+
canvas.height = Math.max(1, outH);
|
|
235
|
+
const ctx = canvas.getContext('2d');
|
|
236
|
+
ctx.filter = buildCssFilter(adjustments);
|
|
237
|
+
ctx.drawImage(source, crop.x, crop.y, crop.width, crop.height, 0, 0, canvas.width, canvas.height);
|
|
238
|
+
ctx.filter = 'none';
|
|
239
|
+
if (adjustments.filter === 'cartoon') {
|
|
240
|
+
applyCartoonEffect(ctx, canvas.width, canvas.height);
|
|
241
|
+
}
|
|
242
|
+
return canvas;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* ویرایشگر تصویر: کراپ + چرخش + روشنایی/کنتراست/اشباع + فیلترهای آماده
|
|
247
|
+
* (سیاهوسفید، سپیا، معکوس، کارتونی) + فشردهسازی خروجی. کاملاً روی Canvas
|
|
248
|
+
* خام پیاده شده، بدون هیچ وابستگی خارجیای.
|
|
249
|
+
*
|
|
250
|
+
* نکتهی مهمِ معماری: تمام تعامل زنده (چرخوندن، جابهجا/ریسایز کراپباکس،
|
|
251
|
+
* پیشنمایش فیلترها حتی کارتونی) روی یه کپیِ کوچیکشده از تصویر انجام میشه
|
|
252
|
+
* (createWorkingCopy)، نه روی تصویر اصلی که ممکنه چند مگاپیکسل باشه — وگرنه
|
|
253
|
+
* درگکردن اسلایدرها/گوشههای کراپ رو کاربر حس تاخیر میکرد. فقط لحظهی
|
|
254
|
+
* save()، رندر نهایی از روی تصویر اصلیِ کامل انجام میشه.
|
|
255
|
+
*/
|
|
256
|
+
class NgxImageEditorComponent {
|
|
257
|
+
source = input(null, /* @ts-ignore */
|
|
258
|
+
...(ngDevMode ? [{ debugName: "source" }] : /* istanbul ignore next */ []));
|
|
259
|
+
/** قفل نسبتِ کراپ (مثلاً ۱ برای آواتار مربعی)؛ undefined یعنی آزاد */
|
|
260
|
+
aspectRatio = input(undefined, /* @ts-ignore */
|
|
261
|
+
...(ngDevMode ? [{ debugName: "aspectRatio" }] : /* istanbul ignore next */ []));
|
|
262
|
+
outputMaxWidth = input(undefined, /* @ts-ignore */
|
|
263
|
+
...(ngDevMode ? [{ debugName: "outputMaxWidth" }] : /* istanbul ignore next */ []));
|
|
264
|
+
outputMaxHeight = input(undefined, /* @ts-ignore */
|
|
265
|
+
...(ngDevMode ? [{ debugName: "outputMaxHeight" }] : /* istanbul ignore next */ []));
|
|
266
|
+
outputType = input('image/jpeg', /* @ts-ignore */
|
|
267
|
+
...(ngDevMode ? [{ debugName: "outputType" }] : /* istanbul ignore next */ []));
|
|
268
|
+
/** حداکثر عرض نمایشِ ناحیهی کراپ روی صفحه (پیکسل) */
|
|
269
|
+
displayMaxWidth = input(420, /* @ts-ignore */
|
|
270
|
+
...(ngDevMode ? [{ debugName: "displayMaxWidth" }] : /* istanbul ignore next */ []));
|
|
271
|
+
saved = output();
|
|
272
|
+
cancelled = output();
|
|
273
|
+
loading = signal(false, /* @ts-ignore */
|
|
274
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
275
|
+
saving = signal(false, /* @ts-ignore */
|
|
276
|
+
...(ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []));
|
|
277
|
+
loadError = signal(null, /* @ts-ignore */
|
|
278
|
+
...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
|
|
279
|
+
originalImage = signal(null, /* @ts-ignore */
|
|
280
|
+
...(ngDevMode ? [{ debugName: "originalImage" }] : /* istanbul ignore next */ []));
|
|
281
|
+
workingCanvas = signal(null, /* @ts-ignore */
|
|
282
|
+
...(ngDevMode ? [{ debugName: "workingCanvas" }] : /* istanbul ignore next */ []));
|
|
283
|
+
rotation = signal(0, /* @ts-ignore */
|
|
284
|
+
...(ngDevMode ? [{ debugName: "rotation" }] : /* istanbul ignore next */ []));
|
|
285
|
+
brightness = signal(100, /* @ts-ignore */
|
|
286
|
+
...(ngDevMode ? [{ debugName: "brightness" }] : /* istanbul ignore next */ []));
|
|
287
|
+
contrast = signal(100, /* @ts-ignore */
|
|
288
|
+
...(ngDevMode ? [{ debugName: "contrast" }] : /* istanbul ignore next */ []));
|
|
289
|
+
saturation = signal(100, /* @ts-ignore */
|
|
290
|
+
...(ngDevMode ? [{ debugName: "saturation" }] : /* istanbul ignore next */ []));
|
|
291
|
+
filterPreset = signal('none', /* @ts-ignore */
|
|
292
|
+
...(ngDevMode ? [{ debugName: "filterPreset" }] : /* istanbul ignore next */ []));
|
|
293
|
+
quality = signal(0.92, /* @ts-ignore */
|
|
294
|
+
...(ngDevMode ? [{ debugName: "quality" }] : /* istanbul ignore next */ []));
|
|
295
|
+
crop = signal({ x: 0, y: 0, width: 0, height: 0 }, /* @ts-ignore */
|
|
296
|
+
...(ngDevMode ? [{ debugName: "crop" }] : /* istanbul ignore next */ []));
|
|
297
|
+
adjustments = computed(() => ({
|
|
298
|
+
brightness: this.brightness(),
|
|
299
|
+
contrast: this.contrast(),
|
|
300
|
+
saturation: this.saturation(),
|
|
301
|
+
rotation: this.rotation(),
|
|
302
|
+
filter: this.filterPreset(),
|
|
303
|
+
}), /* @ts-ignore */
|
|
304
|
+
...(ngDevMode ? [{ debugName: "adjustments" }] : /* istanbul ignore next */ []));
|
|
305
|
+
/** تصویرِ (کوچیکشدهیِ) چرخیده — پایهی نمایش صحنهی کراپ */
|
|
306
|
+
rotatedWorkingCanvas = computed(() => {
|
|
307
|
+
const wc = this.workingCanvas();
|
|
308
|
+
if (!wc)
|
|
309
|
+
return null;
|
|
310
|
+
return renderRotated(wc, wc.width, wc.height, this.rotation());
|
|
311
|
+
}, /* @ts-ignore */
|
|
312
|
+
...(ngDevMode ? [{ debugName: "rotatedWorkingCanvas" }] : /* istanbul ignore next */ []));
|
|
313
|
+
displayScale = computed(() => {
|
|
314
|
+
const rc = this.rotatedWorkingCanvas();
|
|
315
|
+
if (!rc)
|
|
316
|
+
return 1;
|
|
317
|
+
return Math.min(1, this.displayMaxWidth() / rc.width);
|
|
318
|
+
}, /* @ts-ignore */
|
|
319
|
+
...(ngDevMode ? [{ debugName: "displayScale" }] : /* istanbul ignore next */ []));
|
|
320
|
+
stageImageUrl = computed(() => {
|
|
321
|
+
const rc = this.rotatedWorkingCanvas();
|
|
322
|
+
return rc ? rc.toDataURL('image/png') : null;
|
|
323
|
+
}, /* @ts-ignore */
|
|
324
|
+
...(ngDevMode ? [{ debugName: "stageImageUrl" }] : /* istanbul ignore next */ []));
|
|
325
|
+
stageSize = computed(() => {
|
|
326
|
+
const rc = this.rotatedWorkingCanvas();
|
|
327
|
+
const scale = this.displayScale();
|
|
328
|
+
return rc ? { width: rc.width * scale, height: rc.height * scale } : { width: 0, height: 0 };
|
|
329
|
+
}, /* @ts-ignore */
|
|
330
|
+
...(ngDevMode ? [{ debugName: "stageSize" }] : /* istanbul ignore next */ []));
|
|
331
|
+
cropOverlayStyle = computed(() => {
|
|
332
|
+
const scale = this.displayScale();
|
|
333
|
+
const c = this.crop();
|
|
334
|
+
return {
|
|
335
|
+
left: `${c.x * scale}px`,
|
|
336
|
+
top: `${c.y * scale}px`,
|
|
337
|
+
width: `${c.width * scale}px`,
|
|
338
|
+
height: `${c.height * scale}px`,
|
|
339
|
+
};
|
|
340
|
+
}, /* @ts-ignore */
|
|
341
|
+
...(ngDevMode ? [{ debugName: "cropOverlayStyle" }] : /* istanbul ignore next */ []));
|
|
342
|
+
/** پیشنمایش نهایی (کوچیک، حداکثر ۴۸۰ پیکسل) — شامل همهچیز، حتی افکت کارتونی */
|
|
343
|
+
previewUrl = computed(() => {
|
|
344
|
+
const rc = this.rotatedWorkingCanvas();
|
|
345
|
+
const c = this.crop();
|
|
346
|
+
if (!rc || c.width <= 0 || c.height <= 0)
|
|
347
|
+
return null;
|
|
348
|
+
const canvas = renderComposite(rc, rc.width, rc.height, c, this.adjustments(), 480, 480);
|
|
349
|
+
return canvas.toDataURL('image/png');
|
|
350
|
+
}, /* @ts-ignore */
|
|
351
|
+
...(ngDevMode ? [{ debugName: "previewUrl" }] : /* istanbul ignore next */ []));
|
|
352
|
+
moveSnapshot = null;
|
|
353
|
+
resizeSnapshot = null;
|
|
354
|
+
constructor() {
|
|
355
|
+
// بارگذاری تصویر هروقت source عوض بشه
|
|
356
|
+
effect(() => {
|
|
357
|
+
const src = this.source();
|
|
358
|
+
if (!src) {
|
|
359
|
+
this.originalImage.set(null);
|
|
360
|
+
this.workingCanvas.set(null);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
this.loading.set(true);
|
|
364
|
+
this.loadError.set(null);
|
|
365
|
+
loadImage(src)
|
|
366
|
+
.then((img) => {
|
|
367
|
+
this.originalImage.set(img);
|
|
368
|
+
this.workingCanvas.set(createWorkingCopy(img));
|
|
369
|
+
this.resetAdjustments();
|
|
370
|
+
})
|
|
371
|
+
.catch((err) => this.loadError.set(err?.message ?? 'خطا در بارگذاری تصویر'))
|
|
372
|
+
.finally(() => this.loading.set(false));
|
|
373
|
+
});
|
|
374
|
+
// هر وقت چرخیدهشده عوض بشه (بارگذاریِ تصویر جدید یا چرخش)، کراپ روی کل قاب ریست بشه
|
|
375
|
+
effect(() => {
|
|
376
|
+
const rc = this.rotatedWorkingCanvas();
|
|
377
|
+
if (rc) {
|
|
378
|
+
this.crop.set({ x: 0, y: 0, width: rc.width, height: rc.height });
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
resetAdjustments() {
|
|
383
|
+
this.rotation.set(0);
|
|
384
|
+
this.brightness.set(100);
|
|
385
|
+
this.contrast.set(100);
|
|
386
|
+
this.saturation.set(100);
|
|
387
|
+
this.filterPreset.set('none');
|
|
388
|
+
}
|
|
389
|
+
rotateBy(delta) {
|
|
390
|
+
this.rotation.update((r) => {
|
|
391
|
+
let next = r + delta;
|
|
392
|
+
if (next > 180)
|
|
393
|
+
next -= 360;
|
|
394
|
+
if (next < -180)
|
|
395
|
+
next += 360;
|
|
396
|
+
return next;
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
setFilter(preset) {
|
|
400
|
+
this.filterPreset.set(preset);
|
|
401
|
+
}
|
|
402
|
+
// ---------- درگ جابهجاییِ کل کراپباکس ----------
|
|
403
|
+
onMoveDragStart() {
|
|
404
|
+
this.moveSnapshot = { ...this.crop() };
|
|
405
|
+
}
|
|
406
|
+
onMoveDragMove(delta) {
|
|
407
|
+
const rc = this.rotatedWorkingCanvas();
|
|
408
|
+
const snap = this.moveSnapshot;
|
|
409
|
+
if (!rc || !snap)
|
|
410
|
+
return;
|
|
411
|
+
const scale = this.displayScale();
|
|
412
|
+
const dxSrc = delta.dx / scale;
|
|
413
|
+
const dySrc = delta.dy / scale;
|
|
414
|
+
const maxX = Math.max(0, rc.width - snap.width);
|
|
415
|
+
const maxY = Math.max(0, rc.height - snap.height);
|
|
416
|
+
const x = Math.min(Math.max(0, snap.x + dxSrc), maxX);
|
|
417
|
+
const y = Math.min(Math.max(0, snap.y + dySrc), maxY);
|
|
418
|
+
this.crop.set({ ...snap, x, y });
|
|
419
|
+
}
|
|
420
|
+
// ---------- درگ ریسایز از گوشهها ----------
|
|
421
|
+
onResizeDragStart() {
|
|
422
|
+
this.resizeSnapshot = { ...this.crop() };
|
|
423
|
+
}
|
|
424
|
+
onResizeDragMove(corner, delta) {
|
|
425
|
+
const rc = this.rotatedWorkingCanvas();
|
|
426
|
+
const snap = this.resizeSnapshot;
|
|
427
|
+
if (!rc || !snap)
|
|
428
|
+
return;
|
|
429
|
+
const scale = this.displayScale();
|
|
430
|
+
const dxSrc = delta.dx / scale;
|
|
431
|
+
const dySrc = delta.dy / scale;
|
|
432
|
+
const minSize = 24;
|
|
433
|
+
let { x, y, width, height } = snap;
|
|
434
|
+
if (corner === 'nw' || corner === 'sw') {
|
|
435
|
+
const newX = Math.min(snap.x + dxSrc, snap.x + snap.width - minSize);
|
|
436
|
+
x = Math.max(0, newX);
|
|
437
|
+
width = snap.x + snap.width - x;
|
|
438
|
+
}
|
|
439
|
+
if (corner === 'ne' || corner === 'se') {
|
|
440
|
+
width = Math.min(Math.max(minSize, snap.width + dxSrc), rc.width - snap.x);
|
|
441
|
+
}
|
|
442
|
+
if (corner === 'nw' || corner === 'ne') {
|
|
443
|
+
const newY = Math.min(snap.y + dySrc, snap.y + snap.height - minSize);
|
|
444
|
+
y = Math.max(0, newY);
|
|
445
|
+
height = snap.y + snap.height - y;
|
|
446
|
+
}
|
|
447
|
+
if (corner === 'sw' || corner === 'se') {
|
|
448
|
+
height = Math.min(Math.max(minSize, snap.height + dySrc), rc.height - snap.y);
|
|
449
|
+
}
|
|
450
|
+
const ratio = this.aspectRatio();
|
|
451
|
+
if (ratio) {
|
|
452
|
+
height = width / ratio;
|
|
453
|
+
}
|
|
454
|
+
this.crop.set({ x, y, width, height });
|
|
455
|
+
}
|
|
456
|
+
cancel() {
|
|
457
|
+
this.cancelled.emit();
|
|
458
|
+
}
|
|
459
|
+
async save() {
|
|
460
|
+
const original = this.originalImage();
|
|
461
|
+
const working = this.workingCanvas();
|
|
462
|
+
if (!original || !working)
|
|
463
|
+
return;
|
|
464
|
+
this.saving.set(true);
|
|
465
|
+
try {
|
|
466
|
+
// نسبت مقیاس بین کپیِ کوچیکشده (که کراپ/چرخش روش تعریف شده) و تصویر
|
|
467
|
+
// اصلیِ کامل. چون createWorkingCopy مقیاس یکنواخت (همجهت) اعمال
|
|
468
|
+
// میکنه، همین یه ضریب برای هر دو بعد و بعد از چرخش هم معتبر میمونه.
|
|
469
|
+
const scaleToOriginal = original.naturalWidth / working.width;
|
|
470
|
+
const rotatedFull = renderRotated(original, original.naturalWidth, original.naturalHeight, this.rotation());
|
|
471
|
+
const c = this.crop();
|
|
472
|
+
const cropInFullRes = {
|
|
473
|
+
x: c.x * scaleToOriginal,
|
|
474
|
+
y: c.y * scaleToOriginal,
|
|
475
|
+
width: c.width * scaleToOriginal,
|
|
476
|
+
height: c.height * scaleToOriginal,
|
|
477
|
+
};
|
|
478
|
+
const finalCanvas = renderComposite(rotatedFull, rotatedFull.width, rotatedFull.height, cropInFullRes, this.adjustments(), this.outputMaxWidth(), this.outputMaxHeight());
|
|
479
|
+
const type = this.outputType();
|
|
480
|
+
const blob = await canvasToBlob(finalCanvas, type, this.quality());
|
|
481
|
+
const dataUrl = finalCanvas.toDataURL(type, this.quality());
|
|
482
|
+
this.saved.emit({ blob, dataUrl, width: finalCanvas.width, height: finalCanvas.height });
|
|
483
|
+
}
|
|
484
|
+
finally {
|
|
485
|
+
this.saving.set(false);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxImageEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
489
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: NgxImageEditorComponent, isStandalone: true, selector: "ngx-image-editor", inputs: { source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, outputMaxWidth: { classPropertyName: "outputMaxWidth", publicName: "outputMaxWidth", isSignal: true, isRequired: false, transformFunction: null }, outputMaxHeight: { classPropertyName: "outputMaxHeight", publicName: "outputMaxHeight", isSignal: true, isRequired: false, transformFunction: null }, outputType: { classPropertyName: "outputType", publicName: "outputType", isSignal: true, isRequired: false, transformFunction: null }, displayMaxWidth: { classPropertyName: "displayMaxWidth", publicName: "displayMaxWidth", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", cancelled: "cancelled" }, host: { classAttribute: "ngx-image-editor-host" }, ngImport: i0, template: "@if (loading()) {\n <div class=\"ngx-ie-state\">\u062F\u0631 \u062D\u0627\u0644 \u0628\u0627\u0631\u06AF\u0630\u0627\u0631\u06CC \u062A\u0635\u0648\u06CC\u0631\u2026</div>\n} @else if (loadError()) {\n <div class=\"ngx-ie-state ngx-ie-state--error\">{{ loadError() }}</div>\n} @else if (stageImageUrl()) {\n <div class=\"ngx-ie-layout\">\n <div class=\"ngx-ie-stage-wrap\">\n <div\n class=\"ngx-ie-stage\"\n [style.width.px]=\"stageSize().width\"\n [style.height.px]=\"stageSize().height\">\n <img class=\"ngx-ie-stage-img\" [src]=\"stageImageUrl()\" alt=\"\" draggable=\"false\" />\n\n <div\n class=\"ngx-ie-crop-box\"\n [style.left]=\"cropOverlayStyle().left\"\n [style.top]=\"cropOverlayStyle().top\"\n [style.width]=\"cropOverlayStyle().width\"\n [style.height]=\"cropOverlayStyle().height\"\n ngxPointerDrag\n (dragStart)=\"onMoveDragStart()\"\n (dragMove)=\"onMoveDragMove($event)\">\n <span\n class=\"ngx-ie-handle ngx-ie-handle--nw\"\n ngxPointerDrag\n (dragStart)=\"onResizeDragStart()\"\n (dragMove)=\"onResizeDragMove('nw', $event)\"></span>\n <span\n class=\"ngx-ie-handle ngx-ie-handle--ne\"\n ngxPointerDrag\n (dragStart)=\"onResizeDragStart()\"\n (dragMove)=\"onResizeDragMove('ne', $event)\"></span>\n <span\n class=\"ngx-ie-handle ngx-ie-handle--sw\"\n ngxPointerDrag\n (dragStart)=\"onResizeDragStart()\"\n (dragMove)=\"onResizeDragMove('sw', $event)\"></span>\n <span\n class=\"ngx-ie-handle ngx-ie-handle--se\"\n ngxPointerDrag\n (dragStart)=\"onResizeDragStart()\"\n (dragMove)=\"onResizeDragMove('se', $event)\"></span>\n </div>\n </div>\n </div>\n\n <div class=\"ngx-ie-panel\">\n <div class=\"ngx-ie-preview\">\n @if (previewUrl(); as url) {\n <img [src]=\"url\" alt=\"\u067E\u06CC\u0634\u200C\u0646\u0645\u0627\u06CC\u0634\" />\n }\n </div>\n\n <div class=\"ngx-ie-row ngx-ie-rotate-row\">\n <button type=\"button\" (click)=\"rotateBy(-90)\" title=\"\u0686\u0631\u062E\u0634 \u06F9\u06F0 \u062F\u0631\u062C\u0647 \u0628\u0647 \u0686\u067E\">\u27F2 90</button>\n <button type=\"button\" (click)=\"rotateBy(90)\" title=\"\u0686\u0631\u062E\u0634 \u06F9\u06F0 \u062F\u0631\u062C\u0647 \u0628\u0647 \u0631\u0627\u0633\u062A\">\u27F3 90</button>\n </div>\n\n <label class=\"ngx-ie-field\">\n <span>\u0686\u0631\u062E\u0634 \u062F\u0642\u06CC\u0642 ({{ rotation() }}\u00B0)</span>\n <slider\n [ngModel]=\"rotation()\"\n (ngModelChange)=\"rotation.set($event)\"\n [min]=\"-180\"\n [max]=\"180\"></slider>\n </label>\n\n <label class=\"ngx-ie-field\">\n <span>\u0631\u0648\u0634\u0646\u0627\u06CC\u06CC ({{ brightness() }}%)</span>\n <slider\n [ngModel]=\"brightness()\"\n (ngModelChange)=\"brightness.set($event)\"\n [min]=\"0\"\n [max]=\"200\"></slider>\n </label>\n\n <label class=\"ngx-ie-field\">\n <span>\u06A9\u0646\u062A\u0631\u0627\u0633\u062A ({{ contrast() }}%)</span>\n <slider\n [ngModel]=\"contrast()\"\n (ngModelChange)=\"contrast.set($event)\"\n [min]=\"0\"\n [max]=\"200\"></slider>\n </label>\n\n <label class=\"ngx-ie-field\">\n <span>\u0627\u0634\u0628\u0627\u0639 \u0631\u0646\u06AF ({{ saturation() }}%)</span>\n <slider\n [ngModel]=\"saturation()\"\n (ngModelChange)=\"saturation.set($event)\"\n [min]=\"0\"\n [max]=\"200\"></slider>\n </label>\n\n <div class=\"ngx-ie-field\">\n <span>\u0627\u0641\u06A9\u062A</span>\n <div class=\"ngx-ie-filter-row\">\n <button\n type=\"button\"\n [class.active]=\"filterPreset() === 'none'\"\n (click)=\"setFilter('none')\">\n \u0647\u06CC\u0686\u200C\u06A9\u062F\u0627\u0645\n </button>\n <button\n type=\"button\"\n [class.active]=\"filterPreset() === 'grayscale'\"\n (click)=\"setFilter('grayscale')\">\n \u0633\u06CC\u0627\u0647\u200C\u0648\u0633\u0641\u06CC\u062F\n </button>\n <button\n type=\"button\"\n [class.active]=\"filterPreset() === 'sepia'\"\n (click)=\"setFilter('sepia')\">\n \u0633\u067E\u06CC\u0627\n </button>\n <button\n type=\"button\"\n [class.active]=\"filterPreset() === 'invert'\"\n (click)=\"setFilter('invert')\">\n \u0645\u0639\u06A9\u0648\u0633\n </button>\n <button\n type=\"button\"\n [class.active]=\"filterPreset() === 'cartoon'\"\n (click)=\"setFilter('cartoon')\">\n \u06A9\u0627\u0631\u062A\u0648\u0646\u06CC\n </button>\n </div>\n </div>\n\n <label class=\"ngx-ie-field\">\n <span>\u06A9\u06CC\u0641\u06CC\u062A \u062E\u0631\u0648\u062C\u06CC ({{ quality() * 100 | number: '1.0-0' }}\u066A)</span>\n <slider\n [ngModel]=\"quality() * 100\"\n (ngModelChange)=\"quality.set($event / 100)\"\n [min]=\"10\"\n [max]=\"100\"></slider>\n </label>\n\n <div class=\"ngx-ie-row ngx-ie-actions\">\n <button type=\"button\" class=\"ngx-ie-btn-secondary\" (click)=\"cancel()\">\u0627\u0646\u0635\u0631\u0627\u0641</button>\n <button type=\"button\" class=\"ngx-ie-btn-primary\" [disabled]=\"saving()\" (click)=\"save()\">\n {{ saving() ? '\u062F\u0631 \u062D\u0627\u0644 \u0630\u062E\u06CC\u0631\u0647\u2026' : '\u0630\u062E\u06CC\u0631\u0647' }}\n </button>\n </div>\n </div>\n </div>\n}\n", styles: [":host{display:block}.ngx-ie-state{padding:2rem;text-align:center;color:light-dark(#6b7280,#9ca3af)}.ngx-ie-state--error{color:light-dark(#b91c1c,#f87171)}.ngx-ie-layout{display:flex;flex-wrap:wrap;gap:1.25rem}.ngx-ie-stage-wrap{display:flex;align-items:flex-start;justify-content:center;padding:.5rem;background:light-dark(#f3f4f6,#18181b);border-radius:10px}.ngx-ie-stage{position:relative;touch-action:none;-webkit-user-select:none;user-select:none}.ngx-ie-stage-img{display:block;inline-size:100%;block-size:100%;border-radius:4px;pointer-events:none}.ngx-ie-crop-box{position:absolute;box-sizing:border-box;border:2px solid #2563eb;box-shadow:0 0 0 9999px #00000073;cursor:move;touch-action:none}.ngx-ie-handle{position:absolute;inline-size:14px;block-size:14px;background:#fff;border:2px solid #2563eb;border-radius:50%;touch-action:none}.ngx-ie-handle--nw{inset-inline-start:-7px;inset-block-start:-7px;cursor:nwse-resize}.ngx-ie-handle--ne{inset-inline-end:-7px;inset-block-start:-7px;cursor:nesw-resize}.ngx-ie-handle--sw{inset-inline-start:-7px;inset-block-end:-7px;cursor:nesw-resize}.ngx-ie-handle--se{inset-inline-end:-7px;inset-block-end:-7px;cursor:nwse-resize}.ngx-ie-panel{flex:1;min-inline-size:240px;display:flex;flex-direction:column;gap:.75rem}.ngx-ie-preview{display:flex;align-items:center;justify-content:center;min-block-size:100px;background:light-dark(#f3f4f6,#18181b);border-radius:8px;overflow:hidden}.ngx-ie-preview img{max-inline-size:100%;max-block-size:160px;display:block}.ngx-ie-field{display:flex;flex-direction:column;gap:.3rem;font-size:.8rem;color:light-dark(#374151,#d4d4d8)}.ngx-ie-row{display:flex;gap:.5rem}.ngx-ie-rotate-row button,.ngx-ie-filter-row button{appearance:none;border:1px solid light-dark(#e5e7eb,#33333a);background:light-dark(#ffffff,#232327);color:light-dark(#374151,#d4d4d8);border-radius:999px;padding:.4rem .85rem;font-size:.75rem;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,transform .15s ease,color .15s ease}.ngx-ie-rotate-row button:hover,.ngx-ie-filter-row button:hover{background:light-dark(#f3f4f6,#2c2c31);transform:translateY(-1px)}.ngx-ie-rotate-row button:active,.ngx-ie-filter-row button:active{transform:translateY(0)}.ngx-ie-rotate-row button.active,.ngx-ie-filter-row button.active{background:light-dark(#2563eb,#3b82f6);border-color:light-dark(#2563eb,#3b82f6);color:#fff;box-shadow:0 2px 10px light-dark(rgba(37,99,235,.3),rgba(59,130,246,.3))}.ngx-ie-filter-row{display:flex;flex-wrap:wrap;gap:.4rem}.ngx-ie-actions{margin-block-start:auto;justify-content:flex-end}.ngx-ie-btn-primary,.ngx-ie-btn-secondary{appearance:none;border:none;border-radius:8px;padding:.55rem 1.1rem;font-size:.85rem;cursor:pointer;font-weight:500;transition:transform .15s ease,box-shadow .15s ease,opacity .15s ease}.ngx-ie-btn-primary:hover,.ngx-ie-btn-secondary:hover{transform:translateY(-1px)}.ngx-ie-btn-primary:active,.ngx-ie-btn-secondary:active{transform:translateY(0)}.ngx-ie-btn-primary{background:light-dark(#2563eb,#3b82f6);color:#fff;box-shadow:0 2px 10px light-dark(rgba(37,99,235,.25),rgba(59,130,246,.25))}.ngx-ie-btn-primary:disabled{opacity:.6;cursor:not-allowed;transform:none}.ngx-ie-btn-secondary{background:light-dark(#e5e7eb,#3f3f46);color:light-dark(#374151,#e4e4e7)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SliderComponent, selector: "slider", inputs: ["step", "min", "max", "background", "isBgTransparent"], outputs: ["change"] }, { kind: "directive", type: NgxPointerDragDirective, selector: "[ngxPointerDrag]", outputs: ["dragStart", "dragMove", "dragEnd"] }, { kind: "pipe", type: DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
490
|
+
}
|
|
491
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxImageEditorComponent, decorators: [{
|
|
492
|
+
type: Component,
|
|
493
|
+
args: [{ selector: 'ngx-image-editor', standalone: true, imports: [FormsModule, DecimalPipe, SliderComponent, NgxPointerDragDirective], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'ngx-image-editor-host' }, template: "@if (loading()) {\n <div class=\"ngx-ie-state\">\u062F\u0631 \u062D\u0627\u0644 \u0628\u0627\u0631\u06AF\u0630\u0627\u0631\u06CC \u062A\u0635\u0648\u06CC\u0631\u2026</div>\n} @else if (loadError()) {\n <div class=\"ngx-ie-state ngx-ie-state--error\">{{ loadError() }}</div>\n} @else if (stageImageUrl()) {\n <div class=\"ngx-ie-layout\">\n <div class=\"ngx-ie-stage-wrap\">\n <div\n class=\"ngx-ie-stage\"\n [style.width.px]=\"stageSize().width\"\n [style.height.px]=\"stageSize().height\">\n <img class=\"ngx-ie-stage-img\" [src]=\"stageImageUrl()\" alt=\"\" draggable=\"false\" />\n\n <div\n class=\"ngx-ie-crop-box\"\n [style.left]=\"cropOverlayStyle().left\"\n [style.top]=\"cropOverlayStyle().top\"\n [style.width]=\"cropOverlayStyle().width\"\n [style.height]=\"cropOverlayStyle().height\"\n ngxPointerDrag\n (dragStart)=\"onMoveDragStart()\"\n (dragMove)=\"onMoveDragMove($event)\">\n <span\n class=\"ngx-ie-handle ngx-ie-handle--nw\"\n ngxPointerDrag\n (dragStart)=\"onResizeDragStart()\"\n (dragMove)=\"onResizeDragMove('nw', $event)\"></span>\n <span\n class=\"ngx-ie-handle ngx-ie-handle--ne\"\n ngxPointerDrag\n (dragStart)=\"onResizeDragStart()\"\n (dragMove)=\"onResizeDragMove('ne', $event)\"></span>\n <span\n class=\"ngx-ie-handle ngx-ie-handle--sw\"\n ngxPointerDrag\n (dragStart)=\"onResizeDragStart()\"\n (dragMove)=\"onResizeDragMove('sw', $event)\"></span>\n <span\n class=\"ngx-ie-handle ngx-ie-handle--se\"\n ngxPointerDrag\n (dragStart)=\"onResizeDragStart()\"\n (dragMove)=\"onResizeDragMove('se', $event)\"></span>\n </div>\n </div>\n </div>\n\n <div class=\"ngx-ie-panel\">\n <div class=\"ngx-ie-preview\">\n @if (previewUrl(); as url) {\n <img [src]=\"url\" alt=\"\u067E\u06CC\u0634\u200C\u0646\u0645\u0627\u06CC\u0634\" />\n }\n </div>\n\n <div class=\"ngx-ie-row ngx-ie-rotate-row\">\n <button type=\"button\" (click)=\"rotateBy(-90)\" title=\"\u0686\u0631\u062E\u0634 \u06F9\u06F0 \u062F\u0631\u062C\u0647 \u0628\u0647 \u0686\u067E\">\u27F2 90</button>\n <button type=\"button\" (click)=\"rotateBy(90)\" title=\"\u0686\u0631\u062E\u0634 \u06F9\u06F0 \u062F\u0631\u062C\u0647 \u0628\u0647 \u0631\u0627\u0633\u062A\">\u27F3 90</button>\n </div>\n\n <label class=\"ngx-ie-field\">\n <span>\u0686\u0631\u062E\u0634 \u062F\u0642\u06CC\u0642 ({{ rotation() }}\u00B0)</span>\n <slider\n [ngModel]=\"rotation()\"\n (ngModelChange)=\"rotation.set($event)\"\n [min]=\"-180\"\n [max]=\"180\"></slider>\n </label>\n\n <label class=\"ngx-ie-field\">\n <span>\u0631\u0648\u0634\u0646\u0627\u06CC\u06CC ({{ brightness() }}%)</span>\n <slider\n [ngModel]=\"brightness()\"\n (ngModelChange)=\"brightness.set($event)\"\n [min]=\"0\"\n [max]=\"200\"></slider>\n </label>\n\n <label class=\"ngx-ie-field\">\n <span>\u06A9\u0646\u062A\u0631\u0627\u0633\u062A ({{ contrast() }}%)</span>\n <slider\n [ngModel]=\"contrast()\"\n (ngModelChange)=\"contrast.set($event)\"\n [min]=\"0\"\n [max]=\"200\"></slider>\n </label>\n\n <label class=\"ngx-ie-field\">\n <span>\u0627\u0634\u0628\u0627\u0639 \u0631\u0646\u06AF ({{ saturation() }}%)</span>\n <slider\n [ngModel]=\"saturation()\"\n (ngModelChange)=\"saturation.set($event)\"\n [min]=\"0\"\n [max]=\"200\"></slider>\n </label>\n\n <div class=\"ngx-ie-field\">\n <span>\u0627\u0641\u06A9\u062A</span>\n <div class=\"ngx-ie-filter-row\">\n <button\n type=\"button\"\n [class.active]=\"filterPreset() === 'none'\"\n (click)=\"setFilter('none')\">\n \u0647\u06CC\u0686\u200C\u06A9\u062F\u0627\u0645\n </button>\n <button\n type=\"button\"\n [class.active]=\"filterPreset() === 'grayscale'\"\n (click)=\"setFilter('grayscale')\">\n \u0633\u06CC\u0627\u0647\u200C\u0648\u0633\u0641\u06CC\u062F\n </button>\n <button\n type=\"button\"\n [class.active]=\"filterPreset() === 'sepia'\"\n (click)=\"setFilter('sepia')\">\n \u0633\u067E\u06CC\u0627\n </button>\n <button\n type=\"button\"\n [class.active]=\"filterPreset() === 'invert'\"\n (click)=\"setFilter('invert')\">\n \u0645\u0639\u06A9\u0648\u0633\n </button>\n <button\n type=\"button\"\n [class.active]=\"filterPreset() === 'cartoon'\"\n (click)=\"setFilter('cartoon')\">\n \u06A9\u0627\u0631\u062A\u0648\u0646\u06CC\n </button>\n </div>\n </div>\n\n <label class=\"ngx-ie-field\">\n <span>\u06A9\u06CC\u0641\u06CC\u062A \u062E\u0631\u0648\u062C\u06CC ({{ quality() * 100 | number: '1.0-0' }}\u066A)</span>\n <slider\n [ngModel]=\"quality() * 100\"\n (ngModelChange)=\"quality.set($event / 100)\"\n [min]=\"10\"\n [max]=\"100\"></slider>\n </label>\n\n <div class=\"ngx-ie-row ngx-ie-actions\">\n <button type=\"button\" class=\"ngx-ie-btn-secondary\" (click)=\"cancel()\">\u0627\u0646\u0635\u0631\u0627\u0641</button>\n <button type=\"button\" class=\"ngx-ie-btn-primary\" [disabled]=\"saving()\" (click)=\"save()\">\n {{ saving() ? '\u062F\u0631 \u062D\u0627\u0644 \u0630\u062E\u06CC\u0631\u0647\u2026' : '\u0630\u062E\u06CC\u0631\u0647' }}\n </button>\n </div>\n </div>\n </div>\n}\n", styles: [":host{display:block}.ngx-ie-state{padding:2rem;text-align:center;color:light-dark(#6b7280,#9ca3af)}.ngx-ie-state--error{color:light-dark(#b91c1c,#f87171)}.ngx-ie-layout{display:flex;flex-wrap:wrap;gap:1.25rem}.ngx-ie-stage-wrap{display:flex;align-items:flex-start;justify-content:center;padding:.5rem;background:light-dark(#f3f4f6,#18181b);border-radius:10px}.ngx-ie-stage{position:relative;touch-action:none;-webkit-user-select:none;user-select:none}.ngx-ie-stage-img{display:block;inline-size:100%;block-size:100%;border-radius:4px;pointer-events:none}.ngx-ie-crop-box{position:absolute;box-sizing:border-box;border:2px solid #2563eb;box-shadow:0 0 0 9999px #00000073;cursor:move;touch-action:none}.ngx-ie-handle{position:absolute;inline-size:14px;block-size:14px;background:#fff;border:2px solid #2563eb;border-radius:50%;touch-action:none}.ngx-ie-handle--nw{inset-inline-start:-7px;inset-block-start:-7px;cursor:nwse-resize}.ngx-ie-handle--ne{inset-inline-end:-7px;inset-block-start:-7px;cursor:nesw-resize}.ngx-ie-handle--sw{inset-inline-start:-7px;inset-block-end:-7px;cursor:nesw-resize}.ngx-ie-handle--se{inset-inline-end:-7px;inset-block-end:-7px;cursor:nwse-resize}.ngx-ie-panel{flex:1;min-inline-size:240px;display:flex;flex-direction:column;gap:.75rem}.ngx-ie-preview{display:flex;align-items:center;justify-content:center;min-block-size:100px;background:light-dark(#f3f4f6,#18181b);border-radius:8px;overflow:hidden}.ngx-ie-preview img{max-inline-size:100%;max-block-size:160px;display:block}.ngx-ie-field{display:flex;flex-direction:column;gap:.3rem;font-size:.8rem;color:light-dark(#374151,#d4d4d8)}.ngx-ie-row{display:flex;gap:.5rem}.ngx-ie-rotate-row button,.ngx-ie-filter-row button{appearance:none;border:1px solid light-dark(#e5e7eb,#33333a);background:light-dark(#ffffff,#232327);color:light-dark(#374151,#d4d4d8);border-radius:999px;padding:.4rem .85rem;font-size:.75rem;cursor:pointer;transition:background-color .15s ease,border-color .15s ease,transform .15s ease,color .15s ease}.ngx-ie-rotate-row button:hover,.ngx-ie-filter-row button:hover{background:light-dark(#f3f4f6,#2c2c31);transform:translateY(-1px)}.ngx-ie-rotate-row button:active,.ngx-ie-filter-row button:active{transform:translateY(0)}.ngx-ie-rotate-row button.active,.ngx-ie-filter-row button.active{background:light-dark(#2563eb,#3b82f6);border-color:light-dark(#2563eb,#3b82f6);color:#fff;box-shadow:0 2px 10px light-dark(rgba(37,99,235,.3),rgba(59,130,246,.3))}.ngx-ie-filter-row{display:flex;flex-wrap:wrap;gap:.4rem}.ngx-ie-actions{margin-block-start:auto;justify-content:flex-end}.ngx-ie-btn-primary,.ngx-ie-btn-secondary{appearance:none;border:none;border-radius:8px;padding:.55rem 1.1rem;font-size:.85rem;cursor:pointer;font-weight:500;transition:transform .15s ease,box-shadow .15s ease,opacity .15s ease}.ngx-ie-btn-primary:hover,.ngx-ie-btn-secondary:hover{transform:translateY(-1px)}.ngx-ie-btn-primary:active,.ngx-ie-btn-secondary:active{transform:translateY(0)}.ngx-ie-btn-primary{background:light-dark(#2563eb,#3b82f6);color:#fff;box-shadow:0 2px 10px light-dark(rgba(37,99,235,.25),rgba(59,130,246,.25))}.ngx-ie-btn-primary:disabled{opacity:.6;cursor:not-allowed;transform:none}.ngx-ie-btn-secondary{background:light-dark(#e5e7eb,#3f3f46);color:light-dark(#374151,#e4e4e7)}\n"] }]
|
|
494
|
+
}], ctorParameters: () => [], propDecorators: { source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], outputMaxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "outputMaxWidth", required: false }] }], outputMaxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "outputMaxHeight", required: false }] }], outputType: [{ type: i0.Input, args: [{ isSignal: true, alias: "outputType", required: false }] }], displayMaxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayMaxWidth", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Generated bundle index. Do not edit.
|
|
498
|
+
*/
|
|
499
|
+
|
|
500
|
+
export { NgxImageEditorComponent, NgxPointerDragDirective, applyCartoonEffect, canvasToBlob, createWorkingCopy, fitWithin, loadImage, renderComposite, renderRotated };
|
|
501
|
+
//# sourceMappingURL=ngx-kit-image-editor.mjs.map
|