@sumaris-net/ngx-components 18.25.5 → 18.25.7
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/doc/changelog.md +3 -1
- package/esm2022/public_api.mjs +3 -1
- package/esm2022/src/app/shared/image/gallery/image-gallery.component.mjs +82 -82
- package/esm2022/src/app/shared/image/gallery/image-gallery.model.mjs +2 -0
- package/esm2022/src/app/shared/image/gallery/image-gallery.module.mjs +8 -3
- package/esm2022/src/app/shared/image/gallery/slideshow/image-gallery-slideshow.component.mjs +685 -0
- package/esm2022/src/app/shared/image/gallery/testing/gallery.testing.mjs +16 -9
- package/fesm2022/sumaris-net.ngx-components.mjs +767 -86
- package/fesm2022/sumaris-net.ngx-components.mjs.map +1 -1
- package/package.json +1 -1
- package/public_api.d.ts +2 -0
- package/src/app/shared/image/gallery/image-gallery.component.d.ts +42 -21
- package/src/app/shared/image/gallery/image-gallery.model.d.ts +11 -0
- package/src/app/shared/image/gallery/image-gallery.module.d.ts +10 -9
- package/src/app/shared/image/gallery/slideshow/image-gallery-slideshow.component.d.ts +149 -0
- package/src/app/shared/image/gallery/testing/gallery.testing.d.ts +6 -1
- package/src/assets/i18n/en-US.json +6 -0
- package/src/assets/i18n/en.json +6 -0
- package/src/assets/i18n/fr.json +6 -0
- package/src/assets/manifest.json +1 -1
|
@@ -28334,6 +28334,676 @@ ProgressInterceptor = __decorate([
|
|
|
28334
28334
|
__param(0, Inject(APP_PROGRESS_BAR_SERVICE))
|
|
28335
28335
|
], ProgressInterceptor);
|
|
28336
28336
|
|
|
28337
|
+
/**
|
|
28338
|
+
* The zoom/pan/rotate/crop slideshow shown when clicking an image in `AppImageGalleryComponent`.
|
|
28339
|
+
* Extracted into its own component so the exact same slideshow can be presented either inside an
|
|
28340
|
+
* `ion-modal` (default), or inline as an overlay directly within the gallery, without any duplication.
|
|
28341
|
+
*/
|
|
28342
|
+
class AppImageGallerySlideshowComponent {
|
|
28343
|
+
alterCtrl;
|
|
28344
|
+
translate;
|
|
28345
|
+
cd;
|
|
28346
|
+
activeSlideIndex = 0;
|
|
28347
|
+
swiperModules = [IonicSlides];
|
|
28348
|
+
_swiper;
|
|
28349
|
+
// Mouse wheel zoom & rotation state
|
|
28350
|
+
_zoomState = { scale: 1, tx: 0, ty: 0 };
|
|
28351
|
+
_rotation = 0; // degrees: 0, 90, 180, 270
|
|
28352
|
+
_dragState = null;
|
|
28353
|
+
_wheelListener = null;
|
|
28354
|
+
_wheelEl = null;
|
|
28355
|
+
// Crop & rotate state. Rotation is always available; the crop grid/handles only show once the
|
|
28356
|
+
// crop tool has been explicitly activated (fab button). Validate/cancel show up once something changed.
|
|
28357
|
+
_cropToolActive = false;
|
|
28358
|
+
_cropRect = { x: 0, y: 0, width: 1, height: 1 };
|
|
28359
|
+
_cropOverlayRect = null;
|
|
28360
|
+
_cropDragState = null;
|
|
28361
|
+
_cropHandles = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
|
|
28362
|
+
rows = [];
|
|
28363
|
+
selectedIndex = 0;
|
|
28364
|
+
mobile = false;
|
|
28365
|
+
readOnly = false;
|
|
28366
|
+
disabled = false;
|
|
28367
|
+
debug = false;
|
|
28368
|
+
mouseZoomEnabled = false;
|
|
28369
|
+
wheelZoomStep = 0.3;
|
|
28370
|
+
maxZoomRatio = 3;
|
|
28371
|
+
rotationEnabled = true;
|
|
28372
|
+
cropEnabled = true;
|
|
28373
|
+
imageExportFormat = 'png';
|
|
28374
|
+
imageExportQuality = 0.92;
|
|
28375
|
+
imageSizeQueryParam = 'size';
|
|
28376
|
+
modalImageSize;
|
|
28377
|
+
/** Requests deletion of the given row. The caller (gallery) owns the confirmation/deletion logic and resolves the promise with whether it actually happened. */
|
|
28378
|
+
deleteRequested = createPromiseEventEmitter();
|
|
28379
|
+
closeRequested = new EventEmitter();
|
|
28380
|
+
afterEditImage = new EventEmitter();
|
|
28381
|
+
afterEditRow = new EventEmitter();
|
|
28382
|
+
swiperRef;
|
|
28383
|
+
showTooltip = inject(APP_SHOW_TOOLTIP, { optional: true });
|
|
28384
|
+
get hasPendingEdit() {
|
|
28385
|
+
const r = this._cropRect;
|
|
28386
|
+
return this._rotation !== 0 || r.x !== 0 || r.y !== 0 || r.width !== 1 || r.height !== 1;
|
|
28387
|
+
}
|
|
28388
|
+
constructor(alterCtrl, translate, cd) {
|
|
28389
|
+
this.alterCtrl = alterCtrl;
|
|
28390
|
+
this.translate = translate;
|
|
28391
|
+
this.cd = cd;
|
|
28392
|
+
}
|
|
28393
|
+
ngOnDestroy() {
|
|
28394
|
+
this._cleanupWheelZoom();
|
|
28395
|
+
}
|
|
28396
|
+
getSwiper(swiperElement) {
|
|
28397
|
+
this._swiper = (swiperElement || this.swiperRef?.nativeElement)?.swiper || this._swiper;
|
|
28398
|
+
return this._swiper;
|
|
28399
|
+
}
|
|
28400
|
+
onSlideChange(swiperElement) {
|
|
28401
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28402
|
+
this.activeSlideIndex = swiper.activeIndex;
|
|
28403
|
+
this._resetZoom(swiperElement);
|
|
28404
|
+
this._updateSwiperTouchMove(swiperElement);
|
|
28405
|
+
this.markForCheck();
|
|
28406
|
+
}
|
|
28407
|
+
onSwiperInit(swiperElement) {
|
|
28408
|
+
this._resetZoom();
|
|
28409
|
+
if (this.selectedIndex > 0) {
|
|
28410
|
+
if (this.debug)
|
|
28411
|
+
console.debug(`[image-gallery-slideshow] Initializing slideshow, selectedIndex=${this.selectedIndex}`);
|
|
28412
|
+
// Use a setTimeout otherwise the swiper cannot slide
|
|
28413
|
+
setTimeout(() => {
|
|
28414
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28415
|
+
swiper.slideTo(this.selectedIndex, 0);
|
|
28416
|
+
});
|
|
28417
|
+
}
|
|
28418
|
+
this._setupWheelZoom(swiperElement);
|
|
28419
|
+
this._updateSwiperTouchMove(swiperElement);
|
|
28420
|
+
setTimeout(() => {
|
|
28421
|
+
this._updateEditImageSize(swiperElement);
|
|
28422
|
+
this._updateCropOverlayPosition(swiperElement);
|
|
28423
|
+
});
|
|
28424
|
+
}
|
|
28425
|
+
/**
|
|
28426
|
+
* Swiper's own touch/mouse-drag-to-swipe gesture is captured very early (before our crop overlay's
|
|
28427
|
+
* own mousedown handler can stop it), so it must be disabled upfront whenever the crop tool is active
|
|
28428
|
+
* -- otherwise dragging inside the crop selection ends up sliding to the next/previous image instead.
|
|
28429
|
+
* Prev/next arrow buttons keep working, since they call the Swiper API directly.
|
|
28430
|
+
*/
|
|
28431
|
+
_updateSwiperTouchMove(swiperElement) {
|
|
28432
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28433
|
+
if (!swiper)
|
|
28434
|
+
return;
|
|
28435
|
+
swiper.allowTouchMove = !this._cropToolActive;
|
|
28436
|
+
}
|
|
28437
|
+
async zoom(swiperElement, zoomIn) {
|
|
28438
|
+
if (this.debug)
|
|
28439
|
+
console.debug('[image-gallery-slideshow] zoom ' + (zoomIn ? 'in' : 'out'), swiperElement);
|
|
28440
|
+
if (this.mouseZoomEnabled) {
|
|
28441
|
+
const delta = zoomIn ? this.wheelZoomStep : -this.wheelZoomStep;
|
|
28442
|
+
const newScale = Math.max(1, Math.min(this._zoomState.scale + delta, this.maxZoomRatio));
|
|
28443
|
+
if (newScale === this._zoomState.scale)
|
|
28444
|
+
return;
|
|
28445
|
+
if (newScale === 1) {
|
|
28446
|
+
this._zoomState = { scale: 1, tx: 0, ty: 0 };
|
|
28447
|
+
}
|
|
28448
|
+
else {
|
|
28449
|
+
this._zoomState.scale = newScale;
|
|
28450
|
+
this._clampTranslations(swiperElement);
|
|
28451
|
+
}
|
|
28452
|
+
this._applyZoom(swiperElement);
|
|
28453
|
+
}
|
|
28454
|
+
else {
|
|
28455
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28456
|
+
if (zoomIn)
|
|
28457
|
+
swiper.zoom.in();
|
|
28458
|
+
else
|
|
28459
|
+
swiper.zoom.out();
|
|
28460
|
+
}
|
|
28461
|
+
}
|
|
28462
|
+
async requestDelete(swiperElement) {
|
|
28463
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28464
|
+
const currentRow = this.rows.find((row, index) => index === swiper.activeIndex);
|
|
28465
|
+
if (!currentRow)
|
|
28466
|
+
return false; // Row not found
|
|
28467
|
+
let deleted = false;
|
|
28468
|
+
try {
|
|
28469
|
+
deleted = await emitPromiseEvent(this.deleteRequested, 'delete', { detail: { row: currentRow } });
|
|
28470
|
+
}
|
|
28471
|
+
catch (err) {
|
|
28472
|
+
if (err === 'CANCELLED')
|
|
28473
|
+
return false; // User cancel
|
|
28474
|
+
throw err;
|
|
28475
|
+
}
|
|
28476
|
+
if (deleted) {
|
|
28477
|
+
if (!this.rows?.length) {
|
|
28478
|
+
this.closeRequested.emit();
|
|
28479
|
+
}
|
|
28480
|
+
else {
|
|
28481
|
+
this._resetZoom(swiperElement);
|
|
28482
|
+
swiper.update();
|
|
28483
|
+
this.markForCheck();
|
|
28484
|
+
}
|
|
28485
|
+
}
|
|
28486
|
+
return deleted;
|
|
28487
|
+
}
|
|
28488
|
+
async slidePrev(swiperElement) {
|
|
28489
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28490
|
+
const activeIndex = swiper.activeIndex;
|
|
28491
|
+
if (activeIndex === 0) {
|
|
28492
|
+
// Loop to last
|
|
28493
|
+
await swiper.slideTo(this.rows.length - 1, 0 /*no transition, to avoid a "revert" effect*/);
|
|
28494
|
+
}
|
|
28495
|
+
else {
|
|
28496
|
+
await swiper.slidePrev();
|
|
28497
|
+
}
|
|
28498
|
+
}
|
|
28499
|
+
async slideNext(swiperElement) {
|
|
28500
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28501
|
+
const activeIndex = swiper.activeIndex;
|
|
28502
|
+
if (activeIndex === this.rows.length - 1) {
|
|
28503
|
+
// Loop to first
|
|
28504
|
+
await swiper.slideTo(0, 0 /*no transition, to avoid a "revert" effect*/);
|
|
28505
|
+
}
|
|
28506
|
+
else {
|
|
28507
|
+
await swiper.slideNext();
|
|
28508
|
+
}
|
|
28509
|
+
}
|
|
28510
|
+
getImageSizeQueryParams() {
|
|
28511
|
+
if (!this.imageSizeQueryParam || isNilOrBlank(this.modalImageSize))
|
|
28512
|
+
return undefined;
|
|
28513
|
+
return {
|
|
28514
|
+
[this.imageSizeQueryParam]: this.modalImageSize,
|
|
28515
|
+
};
|
|
28516
|
+
}
|
|
28517
|
+
// --- Crop & rotate tools: always available (no "enter edit mode" step) ---
|
|
28518
|
+
// Validate/Cancel only appear once the user actually changed something (see `hasPendingEdit`).
|
|
28519
|
+
discardEdit(swiperElement) {
|
|
28520
|
+
this._resetEditState(swiperElement);
|
|
28521
|
+
}
|
|
28522
|
+
resetCrop(_swiperElement) {
|
|
28523
|
+
this._cropRect = { x: 0, y: 0, width: 1, height: 1 };
|
|
28524
|
+
this.markForCheck();
|
|
28525
|
+
}
|
|
28526
|
+
toggleCropTool(swiperElement) {
|
|
28527
|
+
if (!this.cropEnabled || this.readOnly)
|
|
28528
|
+
return;
|
|
28529
|
+
this._cropToolActive = !this._cropToolActive;
|
|
28530
|
+
// Discard any pending crop selection when hiding the tool
|
|
28531
|
+
if (!this._cropToolActive) {
|
|
28532
|
+
this._cropRect = { x: 0, y: 0, width: 1, height: 1 };
|
|
28533
|
+
this._cropDragState = null;
|
|
28534
|
+
}
|
|
28535
|
+
this._updateSwiperTouchMove(swiperElement);
|
|
28536
|
+
this.markForCheck();
|
|
28537
|
+
if (this._cropToolActive) {
|
|
28538
|
+
// Wait for the crop overlay to be rendered, then compute its position
|
|
28539
|
+
setTimeout(() => this._updateCropOverlayPosition(swiperElement));
|
|
28540
|
+
}
|
|
28541
|
+
}
|
|
28542
|
+
async validateEdit(swiperElement) {
|
|
28543
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28544
|
+
const currentRow = this.rows.find((row, index) => index === swiper.activeIndex);
|
|
28545
|
+
if (!currentRow)
|
|
28546
|
+
return;
|
|
28547
|
+
const image = currentRow.currentData;
|
|
28548
|
+
const format = this.imageExportFormat;
|
|
28549
|
+
const mimeType = format === 'jpeg' ? 'image/jpeg' : 'image/png';
|
|
28550
|
+
const quality = format === 'jpeg' ? this.imageExportQuality : undefined;
|
|
28551
|
+
try {
|
|
28552
|
+
const canvas = await this._renderEditedImage(image, this._rotation, this._cropRect);
|
|
28553
|
+
const dataUrl = canvas.toDataURL(mimeType, quality);
|
|
28554
|
+
const blob = await new Promise((resolve, reject) => {
|
|
28555
|
+
canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('canvas.toBlob() failed'))), mimeType, quality);
|
|
28556
|
+
});
|
|
28557
|
+
// Update the row, so the slideshow and thumbnail reflect the change immediately
|
|
28558
|
+
image.dataUrl = dataUrl;
|
|
28559
|
+
image.url = null;
|
|
28560
|
+
this.afterEditImage.emit({ row: currentRow, image, dataUrl, blob, format });
|
|
28561
|
+
this.afterEditRow.emit(currentRow);
|
|
28562
|
+
this._resetEditState(swiperElement);
|
|
28563
|
+
}
|
|
28564
|
+
catch (err) {
|
|
28565
|
+
console.error('[image-gallery-slideshow] Failed to export the edited image', err);
|
|
28566
|
+
await Alerts.showError('IMAGE.GALLERY.ERROR_EDIT_IMAGE', this.alterCtrl, this.translate);
|
|
28567
|
+
}
|
|
28568
|
+
}
|
|
28569
|
+
_resetEditState(swiperElement) {
|
|
28570
|
+
this._rotation = 0;
|
|
28571
|
+
this._cropRect = { x: 0, y: 0, width: 1, height: 1 };
|
|
28572
|
+
this._cropDragState = null;
|
|
28573
|
+
this._cropToolActive = false;
|
|
28574
|
+
this._applyZoom(swiperElement);
|
|
28575
|
+
this._updateEditImageSize(swiperElement);
|
|
28576
|
+
this._updateSwiperTouchMove(swiperElement);
|
|
28577
|
+
this.markForCheck();
|
|
28578
|
+
}
|
|
28579
|
+
onWindowResize() {
|
|
28580
|
+
const swiperElement = this.swiperRef?.nativeElement;
|
|
28581
|
+
if (!swiperElement)
|
|
28582
|
+
return;
|
|
28583
|
+
this._updateEditImageSize(swiperElement);
|
|
28584
|
+
this._updateCropOverlayPosition(swiperElement);
|
|
28585
|
+
}
|
|
28586
|
+
rotate(swiperElement, direction) {
|
|
28587
|
+
if (!this.rotationEnabled)
|
|
28588
|
+
return;
|
|
28589
|
+
const delta = direction === 'right' ? 90 : -90;
|
|
28590
|
+
this._rotation = (((this._rotation + delta) % 360) + 360) % 360;
|
|
28591
|
+
this._applyZoom(swiperElement);
|
|
28592
|
+
this._updateEditImageSize(swiperElement);
|
|
28593
|
+
this._updateCropOverlayPosition(swiperElement);
|
|
28594
|
+
}
|
|
28595
|
+
/**
|
|
28596
|
+
* Constrains the displayed image so that, even once rotated 90°/270° via CSS transform,
|
|
28597
|
+
* its visual bounding box always fits inside the slide container (no overflow).
|
|
28598
|
+
*/
|
|
28599
|
+
_updateEditImageSize(swiperElement, retriesLeft = 60) {
|
|
28600
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28601
|
+
const activeSlide = swiper?.slides?.[swiper.activeIndex];
|
|
28602
|
+
if (!activeSlide)
|
|
28603
|
+
return;
|
|
28604
|
+
const containerEl = activeSlide.querySelector('.swiper-zoom-container');
|
|
28605
|
+
const imgEl = activeSlide.querySelector('.swiper-zoom-target, .swiper-zoom-container > img');
|
|
28606
|
+
if (!containerEl || !imgEl)
|
|
28607
|
+
return;
|
|
28608
|
+
if (!this.cropEnabled && !this.rotationEnabled) {
|
|
28609
|
+
imgEl.style.width = '';
|
|
28610
|
+
imgEl.style.height = '';
|
|
28611
|
+
return;
|
|
28612
|
+
}
|
|
28613
|
+
const containerRect = containerEl.getBoundingClientRect();
|
|
28614
|
+
const naturalSize = this._getImageNaturalSize(imgEl);
|
|
28615
|
+
// The container/image may not be laid out or loaded yet: skip and retry on the next frame
|
|
28616
|
+
if (!containerRect.width || !containerRect.height || !naturalSize) {
|
|
28617
|
+
if (retriesLeft > 0) {
|
|
28618
|
+
requestAnimationFrame(() => this._updateEditImageSize(swiperElement, retriesLeft - 1));
|
|
28619
|
+
}
|
|
28620
|
+
return;
|
|
28621
|
+
}
|
|
28622
|
+
// Compute the exact box (preserving the image's aspect ratio) that fits the available space,
|
|
28623
|
+
// swapping width/height when rotated 90°/270° so the rotated visual box never overflows.
|
|
28624
|
+
const swapped = this._rotation % 180 !== 0;
|
|
28625
|
+
const availWidth = swapped ? containerRect.height : containerRect.width;
|
|
28626
|
+
const availHeight = swapped ? containerRect.width : containerRect.height;
|
|
28627
|
+
const ratio = Math.min(availWidth / naturalSize.width, availHeight / naturalSize.height);
|
|
28628
|
+
imgEl.style.width = `${naturalSize.width * ratio}px`;
|
|
28629
|
+
imgEl.style.height = `${naturalSize.height * ratio}px`;
|
|
28630
|
+
}
|
|
28631
|
+
_getImageNaturalSize(imgEl) {
|
|
28632
|
+
const inner = (imgEl.tagName === 'ION-IMG' ? imgEl.shadowRoot?.querySelector('img') : imgEl);
|
|
28633
|
+
if (!inner?.naturalWidth || !inner?.naturalHeight)
|
|
28634
|
+
return null;
|
|
28635
|
+
return { width: inner.naturalWidth, height: inner.naturalHeight };
|
|
28636
|
+
}
|
|
28637
|
+
// --- Crop overlay: drag to move/resize the crop rectangle ---
|
|
28638
|
+
onCropPointerDown(event, swiperElement, handle) {
|
|
28639
|
+
event.preventDefault();
|
|
28640
|
+
event.stopPropagation();
|
|
28641
|
+
const pt = this._getEventPoint(event);
|
|
28642
|
+
this._cropOverlayRect = this._cropOverlayRect ?? this._updateCropOverlayPosition(swiperElement);
|
|
28643
|
+
this._cropDragState = {
|
|
28644
|
+
handle,
|
|
28645
|
+
startX: pt.x,
|
|
28646
|
+
startY: pt.y,
|
|
28647
|
+
startRect: { ...this._cropRect },
|
|
28648
|
+
};
|
|
28649
|
+
// Also disable programmatic slide navigation while dragging (allowTouchMove is already
|
|
28650
|
+
// permanently off whenever crop is enabled, see `_updateSwiperTouchMove`)
|
|
28651
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28652
|
+
if (swiper) {
|
|
28653
|
+
swiper.allowSlideNext = false;
|
|
28654
|
+
swiper.allowSlidePrev = false;
|
|
28655
|
+
}
|
|
28656
|
+
}
|
|
28657
|
+
onCropPointerMove(event, _swiperElement) {
|
|
28658
|
+
if (!this._cropDragState)
|
|
28659
|
+
return;
|
|
28660
|
+
event.preventDefault();
|
|
28661
|
+
const rect = this._cropOverlayRect;
|
|
28662
|
+
if (!rect?.width || !rect?.height)
|
|
28663
|
+
return;
|
|
28664
|
+
const pt = this._getEventPoint(event);
|
|
28665
|
+
const dx = (pt.x - this._cropDragState.startX) / rect.width;
|
|
28666
|
+
const dy = (pt.y - this._cropDragState.startY) / rect.height;
|
|
28667
|
+
this._cropRect = this._computeCropRect(this._cropDragState.handle, this._cropDragState.startRect, dx, dy);
|
|
28668
|
+
this.markForCheck();
|
|
28669
|
+
}
|
|
28670
|
+
onCropPointerUp(_event, swiperElement) {
|
|
28671
|
+
if (!this._cropDragState)
|
|
28672
|
+
return;
|
|
28673
|
+
this._cropDragState = null;
|
|
28674
|
+
const swiper = swiperElement ? this.getSwiper(swiperElement) : null;
|
|
28675
|
+
if (swiper) {
|
|
28676
|
+
swiper.allowSlideNext = true;
|
|
28677
|
+
swiper.allowSlidePrev = true;
|
|
28678
|
+
}
|
|
28679
|
+
if (swiperElement)
|
|
28680
|
+
this._updateSwiperTouchMove(swiperElement);
|
|
28681
|
+
}
|
|
28682
|
+
_computeCropRect(handle, start, dx, dy) {
|
|
28683
|
+
const MIN_SIZE = 0.08;
|
|
28684
|
+
const clamp01 = (v) => Math.max(0, Math.min(1, v));
|
|
28685
|
+
if (handle === 'move') {
|
|
28686
|
+
let x = clamp01(start.x + dx);
|
|
28687
|
+
let y = clamp01(start.y + dy);
|
|
28688
|
+
x = Math.min(x, 1 - start.width);
|
|
28689
|
+
y = Math.min(y, 1 - start.height);
|
|
28690
|
+
return { x, y, width: start.width, height: start.height };
|
|
28691
|
+
}
|
|
28692
|
+
let left = start.x;
|
|
28693
|
+
let top = start.y;
|
|
28694
|
+
let right = start.x + start.width;
|
|
28695
|
+
let bottom = start.y + start.height;
|
|
28696
|
+
if (handle.includes('w'))
|
|
28697
|
+
left = clamp01(start.x + dx);
|
|
28698
|
+
if (handle.includes('e'))
|
|
28699
|
+
right = clamp01(start.x + start.width + dx);
|
|
28700
|
+
if (handle.includes('n'))
|
|
28701
|
+
top = clamp01(start.y + dy);
|
|
28702
|
+
if (handle.includes('s'))
|
|
28703
|
+
bottom = clamp01(start.y + start.height + dy);
|
|
28704
|
+
if (right - left < MIN_SIZE) {
|
|
28705
|
+
if (handle.includes('w'))
|
|
28706
|
+
left = right - MIN_SIZE;
|
|
28707
|
+
else
|
|
28708
|
+
right = left + MIN_SIZE;
|
|
28709
|
+
}
|
|
28710
|
+
if (bottom - top < MIN_SIZE) {
|
|
28711
|
+
if (handle.includes('n'))
|
|
28712
|
+
top = bottom - MIN_SIZE;
|
|
28713
|
+
else
|
|
28714
|
+
bottom = top + MIN_SIZE;
|
|
28715
|
+
}
|
|
28716
|
+
left = clamp01(left);
|
|
28717
|
+
right = clamp01(right);
|
|
28718
|
+
top = clamp01(top);
|
|
28719
|
+
bottom = clamp01(bottom);
|
|
28720
|
+
return { x: left, y: top, width: right - left, height: bottom - top };
|
|
28721
|
+
}
|
|
28722
|
+
_getEventPoint(event) {
|
|
28723
|
+
if ('touches' in event && event.touches.length) {
|
|
28724
|
+
return { x: event.touches[0].clientX, y: event.touches[0].clientY };
|
|
28725
|
+
}
|
|
28726
|
+
if ('changedTouches' in event && event.changedTouches.length) {
|
|
28727
|
+
return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
|
|
28728
|
+
}
|
|
28729
|
+
const mouseEvent = event;
|
|
28730
|
+
return { x: mouseEvent.clientX, y: mouseEvent.clientY };
|
|
28731
|
+
}
|
|
28732
|
+
_updateCropOverlayPosition(swiperElement, retriesLeft = 60) {
|
|
28733
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28734
|
+
const activeSlide = swiper?.slides?.[swiper.activeIndex];
|
|
28735
|
+
if (!activeSlide)
|
|
28736
|
+
return null;
|
|
28737
|
+
const containerEl = activeSlide.querySelector('.swiper-zoom-container');
|
|
28738
|
+
const imgEl = activeSlide.querySelector('.swiper-zoom-target, .swiper-zoom-container > img');
|
|
28739
|
+
const overlayEl = activeSlide.querySelector('.crop-overlay');
|
|
28740
|
+
if (!containerEl || !imgEl || !overlayEl)
|
|
28741
|
+
return null;
|
|
28742
|
+
const containerRect = containerEl.getBoundingClientRect();
|
|
28743
|
+
const imgRect = imgEl.getBoundingClientRect();
|
|
28744
|
+
// The image may not be laid out yet: keep any previous (non-zero) value and retry on the next frame
|
|
28745
|
+
if (!imgRect.width || !imgRect.height) {
|
|
28746
|
+
if (retriesLeft > 0) {
|
|
28747
|
+
requestAnimationFrame(() => this._updateCropOverlayPosition(swiperElement, retriesLeft - 1));
|
|
28748
|
+
}
|
|
28749
|
+
return this._cropOverlayRect;
|
|
28750
|
+
}
|
|
28751
|
+
overlayEl.style.left = `${imgRect.left - containerRect.left}px`;
|
|
28752
|
+
overlayEl.style.top = `${imgRect.top - containerRect.top}px`;
|
|
28753
|
+
overlayEl.style.width = `${imgRect.width}px`;
|
|
28754
|
+
overlayEl.style.height = `${imgRect.height}px`;
|
|
28755
|
+
this._cropOverlayRect = { width: imgRect.width, height: imgRect.height };
|
|
28756
|
+
return this._cropOverlayRect;
|
|
28757
|
+
}
|
|
28758
|
+
// --- Canvas rendering, used to export the cropped/rotated image ---
|
|
28759
|
+
async _renderEditedImage(image, rotation, cropRect) {
|
|
28760
|
+
const src = image.dataUrl || image.url;
|
|
28761
|
+
const img = await this._loadImageElement(src);
|
|
28762
|
+
const sw = img.naturalWidth;
|
|
28763
|
+
const sh = img.naturalHeight;
|
|
28764
|
+
const swapped = rotation % 180 !== 0;
|
|
28765
|
+
const rw = swapped ? sh : sw;
|
|
28766
|
+
const rh = swapped ? sw : sh;
|
|
28767
|
+
// 1) Draw the full (uncropped) image, rotated
|
|
28768
|
+
const rotCanvas = document.createElement('canvas');
|
|
28769
|
+
rotCanvas.width = rw;
|
|
28770
|
+
rotCanvas.height = rh;
|
|
28771
|
+
const rctx = rotCanvas.getContext('2d');
|
|
28772
|
+
rctx.translate(rw / 2, rh / 2);
|
|
28773
|
+
rctx.rotate((rotation * Math.PI) / 180);
|
|
28774
|
+
rctx.drawImage(img, -sw / 2, -sh / 2);
|
|
28775
|
+
// 2) Crop the rotated image
|
|
28776
|
+
const cropX = Math.round(cropRect.x * rw);
|
|
28777
|
+
const cropY = Math.round(cropRect.y * rh);
|
|
28778
|
+
const cropW = Math.max(1, Math.round(cropRect.width * rw));
|
|
28779
|
+
const cropH = Math.max(1, Math.round(cropRect.height * rh));
|
|
28780
|
+
const outCanvas = document.createElement('canvas');
|
|
28781
|
+
outCanvas.width = cropW;
|
|
28782
|
+
outCanvas.height = cropH;
|
|
28783
|
+
outCanvas.getContext('2d').drawImage(rotCanvas, cropX, cropY, cropW, cropH, 0, 0, cropW, cropH);
|
|
28784
|
+
return outCanvas;
|
|
28785
|
+
}
|
|
28786
|
+
_loadImageElement(src) {
|
|
28787
|
+
return new Promise((resolve, reject) => {
|
|
28788
|
+
const img = new Image();
|
|
28789
|
+
if (!src.startsWith('data:'))
|
|
28790
|
+
img.crossOrigin = 'anonymous';
|
|
28791
|
+
img.onload = () => resolve(img);
|
|
28792
|
+
img.onerror = () => reject(new Error(`[image-gallery-slideshow] Cannot load image: ${src}`));
|
|
28793
|
+
img.src = src;
|
|
28794
|
+
});
|
|
28795
|
+
}
|
|
28796
|
+
// --- Mouse-wheel zoom & mouse-drag pan ---
|
|
28797
|
+
onPointerDown(event, swiperElement) {
|
|
28798
|
+
if (!this.mouseZoomEnabled || this._zoomState.scale <= 1)
|
|
28799
|
+
return;
|
|
28800
|
+
// Don't intercept clicks on buttons/controls
|
|
28801
|
+
if (event.target.closest('ion-fab-button, ion-button, button'))
|
|
28802
|
+
return;
|
|
28803
|
+
event.preventDefault();
|
|
28804
|
+
this._dragState = {
|
|
28805
|
+
startX: event.clientX,
|
|
28806
|
+
startY: event.clientY,
|
|
28807
|
+
startTx: this._zoomState.tx,
|
|
28808
|
+
startTy: this._zoomState.ty,
|
|
28809
|
+
};
|
|
28810
|
+
const wrapEl = this._getActiveZoomWrapEl(swiperElement);
|
|
28811
|
+
if (wrapEl)
|
|
28812
|
+
wrapEl.style.cursor = 'grabbing';
|
|
28813
|
+
// Disable Swiper slide-change while dragging
|
|
28814
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28815
|
+
if (swiper) {
|
|
28816
|
+
swiper.allowSlideNext = false;
|
|
28817
|
+
swiper.allowSlidePrev = false;
|
|
28818
|
+
}
|
|
28819
|
+
}
|
|
28820
|
+
onPointerMove(event, swiperElement) {
|
|
28821
|
+
if (this._cropDragState) {
|
|
28822
|
+
this.onCropPointerMove(event, swiperElement);
|
|
28823
|
+
return;
|
|
28824
|
+
}
|
|
28825
|
+
if (!this._dragState)
|
|
28826
|
+
return;
|
|
28827
|
+
event.preventDefault();
|
|
28828
|
+
this._zoomState.tx = this._dragState.startTx + event.clientX - this._dragState.startX;
|
|
28829
|
+
this._zoomState.ty = this._dragState.startTy + event.clientY - this._dragState.startY;
|
|
28830
|
+
this._clampTranslations(swiperElement);
|
|
28831
|
+
this._applyZoom(swiperElement);
|
|
28832
|
+
}
|
|
28833
|
+
onPointerUp(event, swiperElement) {
|
|
28834
|
+
if (this._cropDragState) {
|
|
28835
|
+
this.onCropPointerUp(event, swiperElement);
|
|
28836
|
+
return;
|
|
28837
|
+
}
|
|
28838
|
+
if (!this._dragState)
|
|
28839
|
+
return;
|
|
28840
|
+
this._dragState = null;
|
|
28841
|
+
const wrapEl = this._getActiveZoomWrapEl(swiperElement);
|
|
28842
|
+
if (wrapEl)
|
|
28843
|
+
wrapEl.style.cursor = this._zoomState.scale > 1 ? 'grab' : '';
|
|
28844
|
+
// Re-enable Swiper navigation
|
|
28845
|
+
const swiper = swiperElement ? this.getSwiper(swiperElement) : null;
|
|
28846
|
+
if (swiper) {
|
|
28847
|
+
swiper.allowSlideNext = true;
|
|
28848
|
+
swiper.allowSlidePrev = true;
|
|
28849
|
+
}
|
|
28850
|
+
}
|
|
28851
|
+
_resetZoom(swiperElement) {
|
|
28852
|
+
this._zoomState = { scale: 1, tx: 0, ty: 0 };
|
|
28853
|
+
this._dragState = null;
|
|
28854
|
+
this._rotation = 0;
|
|
28855
|
+
this._cropRect = { x: 0, y: 0, width: 1, height: 1 };
|
|
28856
|
+
this._cropDragState = null;
|
|
28857
|
+
this._cropToolActive = false;
|
|
28858
|
+
if (swiperElement) {
|
|
28859
|
+
this._applyZoom(swiperElement);
|
|
28860
|
+
this._updateEditImageSize(swiperElement);
|
|
28861
|
+
}
|
|
28862
|
+
}
|
|
28863
|
+
_setupWheelZoom(swiperElement) {
|
|
28864
|
+
this._cleanupWheelZoom();
|
|
28865
|
+
if (!this.mouseZoomEnabled)
|
|
28866
|
+
return;
|
|
28867
|
+
const el = swiperElement?.nativeElement ?? swiperElement;
|
|
28868
|
+
if (!el)
|
|
28869
|
+
return;
|
|
28870
|
+
this._wheelListener = (event) => {
|
|
28871
|
+
event.preventDefault();
|
|
28872
|
+
const step = this.wheelZoomStep;
|
|
28873
|
+
const oldScale = this._zoomState.scale;
|
|
28874
|
+
const newScale = Math.max(1, Math.min(oldScale + (event.deltaY < 0 ? step : -step), this.maxZoomRatio));
|
|
28875
|
+
if (newScale === oldScale)
|
|
28876
|
+
return;
|
|
28877
|
+
if (newScale === 1) {
|
|
28878
|
+
this._zoomState = { scale: 1, tx: 0, ty: 0 };
|
|
28879
|
+
}
|
|
28880
|
+
else {
|
|
28881
|
+
// Zoom towards cursor position
|
|
28882
|
+
const swiper = this.getSwiper(el);
|
|
28883
|
+
const activeSlide = swiper?.slides?.[swiper.activeIndex];
|
|
28884
|
+
if (activeSlide) {
|
|
28885
|
+
const rect = activeSlide.getBoundingClientRect();
|
|
28886
|
+
const cx = event.clientX - rect.left - rect.width / 2;
|
|
28887
|
+
const cy = event.clientY - rect.top - rect.height / 2;
|
|
28888
|
+
this._zoomState.tx = cx - ((cx - this._zoomState.tx) * newScale) / oldScale;
|
|
28889
|
+
this._zoomState.ty = cy - ((cy - this._zoomState.ty) * newScale) / oldScale;
|
|
28890
|
+
}
|
|
28891
|
+
this._zoomState.scale = newScale;
|
|
28892
|
+
this._clampTranslations(el);
|
|
28893
|
+
}
|
|
28894
|
+
this._applyZoom(el);
|
|
28895
|
+
};
|
|
28896
|
+
el.addEventListener('wheel', this._wheelListener, { passive: false });
|
|
28897
|
+
this._wheelEl = el;
|
|
28898
|
+
}
|
|
28899
|
+
_cleanupWheelZoom() {
|
|
28900
|
+
if (this._wheelEl && this._wheelListener) {
|
|
28901
|
+
this._wheelEl.removeEventListener('wheel', this._wheelListener);
|
|
28902
|
+
}
|
|
28903
|
+
this._wheelEl = null;
|
|
28904
|
+
this._wheelListener = null;
|
|
28905
|
+
}
|
|
28906
|
+
_applyZoom(swiperElement) {
|
|
28907
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28908
|
+
const activeSlide = swiper?.slides?.[swiper.activeIndex];
|
|
28909
|
+
if (!activeSlide)
|
|
28910
|
+
return;
|
|
28911
|
+
const wrapEl = activeSlide.querySelector('.swiper-zoom-container');
|
|
28912
|
+
const imgEl = activeSlide.querySelector('.swiper-zoom-target, .swiper-zoom-container > img');
|
|
28913
|
+
if (!wrapEl || !imgEl)
|
|
28914
|
+
return;
|
|
28915
|
+
const { scale, tx, ty } = this._zoomState;
|
|
28916
|
+
wrapEl.style.transitionDuration = '0ms';
|
|
28917
|
+
wrapEl.style.transform = `translate3d(${tx}px, ${ty}px, 0)`;
|
|
28918
|
+
imgEl.style.transitionDuration = '0ms';
|
|
28919
|
+
imgEl.style.transform = `translate3d(0, 0, 0) scale(${scale}) rotate(${this._rotation}deg)`;
|
|
28920
|
+
wrapEl.style.cursor = scale > 1 ? 'grab' : '';
|
|
28921
|
+
}
|
|
28922
|
+
_clampTranslations(swiperElement) {
|
|
28923
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28924
|
+
const activeSlide = swiper?.slides?.[swiper.activeIndex];
|
|
28925
|
+
if (!activeSlide)
|
|
28926
|
+
return;
|
|
28927
|
+
const imgEl = activeSlide.querySelector('.swiper-zoom-target, .swiper-zoom-container > img');
|
|
28928
|
+
if (!imgEl)
|
|
28929
|
+
return;
|
|
28930
|
+
const imgW = imgEl.offsetWidth || imgEl.clientWidth;
|
|
28931
|
+
const imgH = imgEl.offsetHeight || imgEl.clientHeight;
|
|
28932
|
+
if (!imgW || !imgH)
|
|
28933
|
+
return;
|
|
28934
|
+
const { scale } = this._zoomState;
|
|
28935
|
+
const maxTx = Math.max(0, (imgW * scale - activeSlide.offsetWidth) / 2);
|
|
28936
|
+
const maxTy = Math.max(0, (imgH * scale - activeSlide.offsetHeight) / 2);
|
|
28937
|
+
this._zoomState.tx = Math.max(-maxTx, Math.min(maxTx, this._zoomState.tx));
|
|
28938
|
+
this._zoomState.ty = Math.max(-maxTy, Math.min(maxTy, this._zoomState.ty));
|
|
28939
|
+
}
|
|
28940
|
+
_getActiveZoomWrapEl(swiperElement) {
|
|
28941
|
+
const swiper = this.getSwiper(swiperElement);
|
|
28942
|
+
const activeSlide = swiper?.slides?.[swiper.activeIndex];
|
|
28943
|
+
return activeSlide?.querySelector('.swiper-zoom-container') ?? null;
|
|
28944
|
+
}
|
|
28945
|
+
markForCheck() {
|
|
28946
|
+
this.cd.markForCheck();
|
|
28947
|
+
}
|
|
28948
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppImageGallerySlideshowComponent, deps: [{ token: i2$1.AlertController }, { token: i1$1.TranslateService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
28949
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppImageGallerySlideshowComponent, selector: "app-image-gallery-slideshow", inputs: { rows: "rows", selectedIndex: "selectedIndex", mobile: ["mobile", "mobile", booleanAttribute], readOnly: ["readOnly", "readOnly", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], debug: ["debug", "debug", booleanAttribute], mouseZoomEnabled: ["mouseZoomEnabled", "mouseZoomEnabled", booleanAttribute], wheelZoomStep: "wheelZoomStep", maxZoomRatio: "maxZoomRatio", rotationEnabled: ["rotationEnabled", "rotationEnabled", booleanAttribute], cropEnabled: ["cropEnabled", "cropEnabled", booleanAttribute], imageExportFormat: "imageExportFormat", imageExportQuality: "imageExportQuality", imageSizeQueryParam: "imageSizeQueryParam", modalImageSize: "modalImageSize" }, outputs: { deleteRequested: "deleteRequested", closeRequested: "closeRequested", afterEditImage: "afterEditImage", afterEditRow: "afterEditRow" }, host: { listeners: { "window:resize": "onWindowResize()" } }, viewQueries: [{ propertyName: "swiperRef", first: true, predicate: ["slideshowSwiper"], descendants: true }], ngImport: i0, template: "<ion-content\n class=\"ion-no-padding ion-no-margin\"\n (mousedown)=\"onPointerDown($event, slideshowSwiper)\"\n (mousemove)=\"onPointerMove($event, slideshowSwiper)\"\n (mouseup)=\"onPointerUp($event, slideshowSwiper)\"\n (mouseleave)=\"onPointerUp($event, slideshowSwiper)\"\n (touchmove)=\"onCropPointerMove($event, slideshowSwiper)\"\n (touchend)=\"onCropPointerUp($event, slideshowSwiper)\"\n (touchcancel)=\"onCropPointerUp($event, slideshowSwiper)\"\n>\n @let count = rows?.length || 0;\n <!-- Counter -->\n <div class=\"top-left\">\n <ion-label color=\"light\">{{ activeSlideIndex + 1 }} / {{ count }}</ion-label>\n </div>\n\n <!-- zoom / rotation / crop buttons: all shown together, no \"enter edit mode\" step needed -->\n <div class=\"top-right\">\n @if (!mobile) {\n <ion-fab-button size=\"small\" (click)=\"zoom(slideshowSwiper, true)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"add\"></ion-icon>\n </ion-fab-button>\n <ion-fab-button size=\"small\" (click)=\"zoom(slideshowSwiper, false)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"remove\"></ion-icon>\n </ion-fab-button>\n }\n @if (!readOnly && rotationEnabled) {\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(slideshowSwiper, 'left')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n >\n <mat-icon>rotate_left</mat-icon>\n </ion-fab-button>\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(slideshowSwiper, 'right')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n >\n <mat-icon>rotate_right</mat-icon>\n </ion-fab-button>\n }\n @if (!readOnly && cropEnabled) {\n <ion-fab-button\n size=\"small\"\n (click)=\"toggleCropTool(slideshowSwiper)\"\n [color]=\"_cropToolActive ? 'tertiary' : 'light'\"\n [title]=\"\n !showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n [matTooltip]=\"\n showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n >\n <mat-icon>crop</mat-icon>\n </ion-fab-button>\n @if (_cropToolActive) {\n <ion-fab-button\n size=\"small\"\n (click)=\"resetCrop(slideshowSwiper)\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n >\n <mat-icon>crop_free</mat-icon>\n </ion-fab-button>\n }\n }\n </div>\n\n <!-- navigation side buttons next/back -->\n <ng-container *ngIf=\"!mobile && (rows | isArrayLength: { greaterThan: 1 })\">\n <div class=\"side-buttons prev\" (click)=\"slidePrev(slideshowSwiper)\">\n <ion-icon name=\"chevron-back\"></ion-icon>\n </div>\n <div class=\"side-buttons next\" (click)=\"slideNext(slideshowSwiper)\">\n <ion-icon name=\"chevron-forward\"></ion-icon>\n </div>\n </ng-container>\n\n <!-- image slides -->\n <swiper-container\n #slideshowSwiper\n [modules]=\"swiperModules\"\n [zoom]=\"!mouseZoomEnabled\"\n (swiperslidechange)=\"onSlideChange(slideshowSwiper)\"\n (swiperinit)=\"onSwiperInit(slideshowSwiper)\"\n >\n @for (row of rows; track row.id; let i = $index) {\n <swiper-slide>\n @let image = row | propertyGet: 'currentData';\n <div class=\"swiper-zoom-container\">\n @if (image.url) {\n <ion-img class=\"swiper-zoom-target\" [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <img [src]=\"image.dataUrl\" [alt]=\"image.title\" />\n }\n\n <!-- crop overlay: only rendered for the active slide -->\n @if (!readOnly && cropEnabled && _cropToolActive && i === activeSlideIndex) {\n <div\n class=\"crop-overlay\"\n (mousedown)=\"onCropPointerDown($event, slideshowSwiper, 'move')\"\n (touchstart)=\"onCropPointerDown($event, slideshowSwiper, 'move')\"\n >\n <!-- darkened mask around the selection: clipped to the image bounds -->\n <div class=\"crop-mask\">\n <div\n class=\"crop-mask-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n ></div>\n </div>\n\n <!-- interactive selection border, grid & drag handles: never clipped, so corner/edge handles\n remain fully grabbable even when the selection touches the image bounds -->\n <div\n class=\"crop-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n >\n <div class=\"crop-grid\">\n <span class=\"line v\" style=\"left: 33.333%\"></span>\n <span class=\"line v\" style=\"left: 66.666%\"></span>\n <span class=\"line h\" style=\"top: 33.333%\"></span>\n <span class=\"line h\" style=\"top: 66.666%\"></span>\n </div>\n @for (handle of _cropHandles; track handle) {\n <div\n class=\"crop-handle {{ handle }}\"\n (mousedown)=\"onCropPointerDown($event, slideshowSwiper, handle)\"\n (touchstart)=\"onCropPointerDown($event, slideshowSwiper, handle)\"\n ></div>\n }\n </div>\n </div>\n }\n </div>\n </swiper-slide>\n }\n </swiper-container>\n</ion-content>\n\n<ion-footer>\n <ion-toolbar color=\"light\">\n @if (!hasPendingEdit) {\n <ion-row>\n <ion-col size=\"4\" class=\"ion-text-start\">\n <ion-button\n *ngIf=\"!readOnly\"\n (click)=\"requestDelete(slideshowSwiper)\"\n [disabled]=\"disabled\"\n fill=\"clear\"\n color=\"danger\"\n >\n <mat-icon slot=\"start\">delete</mat-icon>\n {{ 'COMMON.BTN_DELETE' | translate }}\n </ion-button>\n </ion-col>\n <ion-col size=\"4\"></ion-col>\n <ion-col size=\"4\" class=\"ion-text-end\">\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\" fill=\"clear\" *ngIf=\"mobile; else desktop\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n <ng-template #desktop>\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\">\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ng-template>\n </ion-col>\n </ion-row>\n } @else {\n <!-- A crop or rotation change is pending: ask for an explicit validation before exporting the image -->\n <ion-row>\n <ion-col size=\"6\" class=\"ion-text-start\">\n <ion-button (click)=\"discardEdit(slideshowSwiper)\" color=\"medium\" fill=\"clear\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CANCEL' | translate }}\n </ion-button>\n </ion-col>\n <ion-col size=\"6\" class=\"ion-text-end\">\n <ion-button (click)=\"validateEdit(slideshowSwiper)\" color=\"tertiary\">\n <ion-icon slot=\"start\" name=\"checkmark\"></ion-icon>\n {{ 'COMMON.BTN_VALIDATE' | translate }}\n </ion-button>\n </ion-col>\n </ion-row>\n }\n </ion-toolbar>\n</ion-footer>\n", styles: [":host{display:flex;flex-direction:column;height:100%;-webkit-user-select:none;user-select:none}:host ion-content{flex:1 1 auto}:host .top-left{z-index:14;position:absolute;display:inline-grid;left:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .top-right{z-index:14;position:absolute;display:inline-grid;right:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .side-buttons{--side-buttons-width: 120px;--side-buttons-margin: calc(var(--side-buttons-width) / 3);z-index:13;position:absolute;top:0;bottom:0;width:var(--side-buttons-width)}:host .side-buttons ion-icon{top:50%;color:#fff;font-size:2.5rem;position:absolute;opacity:0;transition-duration:.15s;transition-timing-function:ease-in}:host .side-buttons:hover{cursor:pointer}:host .side-buttons:hover ion-icon{opacity:1}:host .side-buttons.prev{left:0}:host .side-buttons.prev ion-icon{left:var(--side-buttons-margin)}:host .side-buttons.next{right:0}:host .side-buttons.next ion-icon{right:var(--side-buttons-margin)}:host swiper-container{height:100%;background:#000}:host .swiper-zoom-container{position:relative}:host ion-fab-button .icon-secondary{top:11px;left:6px;height:14px}:host .crop-overlay{position:absolute;z-index:15;cursor:move;touch-action:none;animation:crop-overlay-fade-in .15s ease-in}:host .crop-overlay .crop-mask{position:absolute;inset:0;overflow:hidden;pointer-events:none}:host .crop-overlay .crop-mask .crop-mask-rect{position:absolute;box-shadow:0 0 0 2000px #0009}:host .crop-overlay .crop-rect{position:absolute;box-sizing:border-box;border:1px solid rgba(255,255,255,.95);cursor:move;touch-action:none}:host .crop-overlay .crop-rect .crop-grid{position:absolute;inset:0;pointer-events:none}:host .crop-overlay .crop-rect .crop-grid .line{position:absolute;background:#ffffff73}:host .crop-overlay .crop-rect .crop-grid .line.v{top:0;bottom:0;width:1px}:host .crop-overlay .crop-rect .crop-grid .line.h{left:0;right:0;height:1px}:host .crop-overlay .crop-rect .crop-handle{position:absolute;width:16px;height:16px;margin:-8px;background:#fff;border:1px solid rgba(0,0,0,.45);border-radius:50%;box-shadow:0 1px 4px #00000080;transition:transform .12s ease;touch-action:none}:host .crop-overlay .crop-rect .crop-handle:hover{transform:scale(1.3)}:host .crop-overlay .crop-rect .crop-handle.nw{left:0;top:0;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.n{left:50%;top:0;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.ne{left:100%;top:0;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.e{left:100%;top:50%;cursor:ew-resize}:host .crop-overlay .crop-rect .crop-handle.se{left:100%;top:100%;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.s{left:50%;top:100%;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.sw{left:0;top:100%;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.w{left:0;top:50%;cursor:ew-resize}@keyframes crop-overlay-fade-in{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFabButton, selector: "ion-fab-button", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: ArrayLengthPipe, name: "isArrayLength" }, { kind: "pipe", type: AppendQueryParamsPipePipe, name: "appendQueryParams" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
28950
|
+
}
|
|
28951
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppImageGallerySlideshowComponent, decorators: [{
|
|
28952
|
+
type: Component,
|
|
28953
|
+
args: [{ selector: 'app-image-gallery-slideshow', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-content\n class=\"ion-no-padding ion-no-margin\"\n (mousedown)=\"onPointerDown($event, slideshowSwiper)\"\n (mousemove)=\"onPointerMove($event, slideshowSwiper)\"\n (mouseup)=\"onPointerUp($event, slideshowSwiper)\"\n (mouseleave)=\"onPointerUp($event, slideshowSwiper)\"\n (touchmove)=\"onCropPointerMove($event, slideshowSwiper)\"\n (touchend)=\"onCropPointerUp($event, slideshowSwiper)\"\n (touchcancel)=\"onCropPointerUp($event, slideshowSwiper)\"\n>\n @let count = rows?.length || 0;\n <!-- Counter -->\n <div class=\"top-left\">\n <ion-label color=\"light\">{{ activeSlideIndex + 1 }} / {{ count }}</ion-label>\n </div>\n\n <!-- zoom / rotation / crop buttons: all shown together, no \"enter edit mode\" step needed -->\n <div class=\"top-right\">\n @if (!mobile) {\n <ion-fab-button size=\"small\" (click)=\"zoom(slideshowSwiper, true)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"add\"></ion-icon>\n </ion-fab-button>\n <ion-fab-button size=\"small\" (click)=\"zoom(slideshowSwiper, false)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"remove\"></ion-icon>\n </ion-fab-button>\n }\n @if (!readOnly && rotationEnabled) {\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(slideshowSwiper, 'left')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_LEFT' | translate) : ''\"\n >\n <mat-icon>rotate_left</mat-icon>\n </ion-fab-button>\n <ion-fab-button\n size=\"small\"\n (click)=\"rotate(slideshowSwiper, 'right')\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_ROTATE_RIGHT' | translate) : ''\"\n >\n <mat-icon>rotate_right</mat-icon>\n </ion-fab-button>\n }\n @if (!readOnly && cropEnabled) {\n <ion-fab-button\n size=\"small\"\n (click)=\"toggleCropTool(slideshowSwiper)\"\n [color]=\"_cropToolActive ? 'tertiary' : 'light'\"\n [title]=\"\n !showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n [matTooltip]=\"\n showTooltip\n ? ((_cropToolActive ? 'IMAGE.GALLERY.BTN_CROP_DEACTIVATE' : 'IMAGE.GALLERY.BTN_CROP_ACTIVATE') | translate)\n : ''\n \"\n >\n <mat-icon>crop</mat-icon>\n </ion-fab-button>\n @if (_cropToolActive) {\n <ion-fab-button\n size=\"small\"\n (click)=\"resetCrop(slideshowSwiper)\"\n color=\"light\"\n [title]=\"!showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('IMAGE.GALLERY.BTN_CROP_RESET' | translate) : ''\"\n >\n <mat-icon>crop_free</mat-icon>\n </ion-fab-button>\n }\n }\n </div>\n\n <!-- navigation side buttons next/back -->\n <ng-container *ngIf=\"!mobile && (rows | isArrayLength: { greaterThan: 1 })\">\n <div class=\"side-buttons prev\" (click)=\"slidePrev(slideshowSwiper)\">\n <ion-icon name=\"chevron-back\"></ion-icon>\n </div>\n <div class=\"side-buttons next\" (click)=\"slideNext(slideshowSwiper)\">\n <ion-icon name=\"chevron-forward\"></ion-icon>\n </div>\n </ng-container>\n\n <!-- image slides -->\n <swiper-container\n #slideshowSwiper\n [modules]=\"swiperModules\"\n [zoom]=\"!mouseZoomEnabled\"\n (swiperslidechange)=\"onSlideChange(slideshowSwiper)\"\n (swiperinit)=\"onSwiperInit(slideshowSwiper)\"\n >\n @for (row of rows; track row.id; let i = $index) {\n <swiper-slide>\n @let image = row | propertyGet: 'currentData';\n <div class=\"swiper-zoom-container\">\n @if (image.url) {\n <ion-img class=\"swiper-zoom-target\" [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <img [src]=\"image.dataUrl\" [alt]=\"image.title\" />\n }\n\n <!-- crop overlay: only rendered for the active slide -->\n @if (!readOnly && cropEnabled && _cropToolActive && i === activeSlideIndex) {\n <div\n class=\"crop-overlay\"\n (mousedown)=\"onCropPointerDown($event, slideshowSwiper, 'move')\"\n (touchstart)=\"onCropPointerDown($event, slideshowSwiper, 'move')\"\n >\n <!-- darkened mask around the selection: clipped to the image bounds -->\n <div class=\"crop-mask\">\n <div\n class=\"crop-mask-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n ></div>\n </div>\n\n <!-- interactive selection border, grid & drag handles: never clipped, so corner/edge handles\n remain fully grabbable even when the selection touches the image bounds -->\n <div\n class=\"crop-rect\"\n [style.left.%]=\"_cropRect.x * 100\"\n [style.top.%]=\"_cropRect.y * 100\"\n [style.width.%]=\"_cropRect.width * 100\"\n [style.height.%]=\"_cropRect.height * 100\"\n >\n <div class=\"crop-grid\">\n <span class=\"line v\" style=\"left: 33.333%\"></span>\n <span class=\"line v\" style=\"left: 66.666%\"></span>\n <span class=\"line h\" style=\"top: 33.333%\"></span>\n <span class=\"line h\" style=\"top: 66.666%\"></span>\n </div>\n @for (handle of _cropHandles; track handle) {\n <div\n class=\"crop-handle {{ handle }}\"\n (mousedown)=\"onCropPointerDown($event, slideshowSwiper, handle)\"\n (touchstart)=\"onCropPointerDown($event, slideshowSwiper, handle)\"\n ></div>\n }\n </div>\n </div>\n }\n </div>\n </swiper-slide>\n }\n </swiper-container>\n</ion-content>\n\n<ion-footer>\n <ion-toolbar color=\"light\">\n @if (!hasPendingEdit) {\n <ion-row>\n <ion-col size=\"4\" class=\"ion-text-start\">\n <ion-button\n *ngIf=\"!readOnly\"\n (click)=\"requestDelete(slideshowSwiper)\"\n [disabled]=\"disabled\"\n fill=\"clear\"\n color=\"danger\"\n >\n <mat-icon slot=\"start\">delete</mat-icon>\n {{ 'COMMON.BTN_DELETE' | translate }}\n </ion-button>\n </ion-col>\n <ion-col size=\"4\"></ion-col>\n <ion-col size=\"4\" class=\"ion-text-end\">\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\" fill=\"clear\" *ngIf=\"mobile; else desktop\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n <ng-template #desktop>\n <ion-button (click)=\"closeRequested.emit()\" color=\"tertiary\">\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ng-template>\n </ion-col>\n </ion-row>\n } @else {\n <!-- A crop or rotation change is pending: ask for an explicit validation before exporting the image -->\n <ion-row>\n <ion-col size=\"6\" class=\"ion-text-start\">\n <ion-button (click)=\"discardEdit(slideshowSwiper)\" color=\"medium\" fill=\"clear\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CANCEL' | translate }}\n </ion-button>\n </ion-col>\n <ion-col size=\"6\" class=\"ion-text-end\">\n <ion-button (click)=\"validateEdit(slideshowSwiper)\" color=\"tertiary\">\n <ion-icon slot=\"start\" name=\"checkmark\"></ion-icon>\n {{ 'COMMON.BTN_VALIDATE' | translate }}\n </ion-button>\n </ion-col>\n </ion-row>\n }\n </ion-toolbar>\n</ion-footer>\n", styles: [":host{display:flex;flex-direction:column;height:100%;-webkit-user-select:none;user-select:none}:host ion-content{flex:1 1 auto}:host .top-left{z-index:14;position:absolute;display:inline-grid;left:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .top-right{z-index:14;position:absolute;display:inline-grid;right:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}:host .side-buttons{--side-buttons-width: 120px;--side-buttons-margin: calc(var(--side-buttons-width) / 3);z-index:13;position:absolute;top:0;bottom:0;width:var(--side-buttons-width)}:host .side-buttons ion-icon{top:50%;color:#fff;font-size:2.5rem;position:absolute;opacity:0;transition-duration:.15s;transition-timing-function:ease-in}:host .side-buttons:hover{cursor:pointer}:host .side-buttons:hover ion-icon{opacity:1}:host .side-buttons.prev{left:0}:host .side-buttons.prev ion-icon{left:var(--side-buttons-margin)}:host .side-buttons.next{right:0}:host .side-buttons.next ion-icon{right:var(--side-buttons-margin)}:host swiper-container{height:100%;background:#000}:host .swiper-zoom-container{position:relative}:host ion-fab-button .icon-secondary{top:11px;left:6px;height:14px}:host .crop-overlay{position:absolute;z-index:15;cursor:move;touch-action:none;animation:crop-overlay-fade-in .15s ease-in}:host .crop-overlay .crop-mask{position:absolute;inset:0;overflow:hidden;pointer-events:none}:host .crop-overlay .crop-mask .crop-mask-rect{position:absolute;box-shadow:0 0 0 2000px #0009}:host .crop-overlay .crop-rect{position:absolute;box-sizing:border-box;border:1px solid rgba(255,255,255,.95);cursor:move;touch-action:none}:host .crop-overlay .crop-rect .crop-grid{position:absolute;inset:0;pointer-events:none}:host .crop-overlay .crop-rect .crop-grid .line{position:absolute;background:#ffffff73}:host .crop-overlay .crop-rect .crop-grid .line.v{top:0;bottom:0;width:1px}:host .crop-overlay .crop-rect .crop-grid .line.h{left:0;right:0;height:1px}:host .crop-overlay .crop-rect .crop-handle{position:absolute;width:16px;height:16px;margin:-8px;background:#fff;border:1px solid rgba(0,0,0,.45);border-radius:50%;box-shadow:0 1px 4px #00000080;transition:transform .12s ease;touch-action:none}:host .crop-overlay .crop-rect .crop-handle:hover{transform:scale(1.3)}:host .crop-overlay .crop-rect .crop-handle.nw{left:0;top:0;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.n{left:50%;top:0;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.ne{left:100%;top:0;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.e{left:100%;top:50%;cursor:ew-resize}:host .crop-overlay .crop-rect .crop-handle.se{left:100%;top:100%;cursor:nwse-resize}:host .crop-overlay .crop-rect .crop-handle.s{left:50%;top:100%;cursor:ns-resize}:host .crop-overlay .crop-rect .crop-handle.sw{left:0;top:100%;cursor:nesw-resize}:host .crop-overlay .crop-rect .crop-handle.w{left:0;top:50%;cursor:ew-resize}@keyframes crop-overlay-fade-in{0%{opacity:0}to{opacity:1}}\n"] }]
|
|
28954
|
+
}], ctorParameters: () => [{ type: i2$1.AlertController }, { type: i1$1.TranslateService }, { type: i0.ChangeDetectorRef }], propDecorators: { rows: [{
|
|
28955
|
+
type: Input
|
|
28956
|
+
}], selectedIndex: [{
|
|
28957
|
+
type: Input
|
|
28958
|
+
}], mobile: [{
|
|
28959
|
+
type: Input,
|
|
28960
|
+
args: [{ transform: booleanAttribute }]
|
|
28961
|
+
}], readOnly: [{
|
|
28962
|
+
type: Input,
|
|
28963
|
+
args: [{ transform: booleanAttribute }]
|
|
28964
|
+
}], disabled: [{
|
|
28965
|
+
type: Input,
|
|
28966
|
+
args: [{ transform: booleanAttribute }]
|
|
28967
|
+
}], debug: [{
|
|
28968
|
+
type: Input,
|
|
28969
|
+
args: [{ transform: booleanAttribute }]
|
|
28970
|
+
}], mouseZoomEnabled: [{
|
|
28971
|
+
type: Input,
|
|
28972
|
+
args: [{ transform: booleanAttribute }]
|
|
28973
|
+
}], wheelZoomStep: [{
|
|
28974
|
+
type: Input
|
|
28975
|
+
}], maxZoomRatio: [{
|
|
28976
|
+
type: Input
|
|
28977
|
+
}], rotationEnabled: [{
|
|
28978
|
+
type: Input,
|
|
28979
|
+
args: [{ transform: booleanAttribute }]
|
|
28980
|
+
}], cropEnabled: [{
|
|
28981
|
+
type: Input,
|
|
28982
|
+
args: [{ transform: booleanAttribute }]
|
|
28983
|
+
}], imageExportFormat: [{
|
|
28984
|
+
type: Input
|
|
28985
|
+
}], imageExportQuality: [{
|
|
28986
|
+
type: Input
|
|
28987
|
+
}], imageSizeQueryParam: [{
|
|
28988
|
+
type: Input
|
|
28989
|
+
}], modalImageSize: [{
|
|
28990
|
+
type: Input
|
|
28991
|
+
}], deleteRequested: [{
|
|
28992
|
+
type: Output
|
|
28993
|
+
}], closeRequested: [{
|
|
28994
|
+
type: Output
|
|
28995
|
+
}], afterEditImage: [{
|
|
28996
|
+
type: Output
|
|
28997
|
+
}], afterEditRow: [{
|
|
28998
|
+
type: Output
|
|
28999
|
+
}], swiperRef: [{
|
|
29000
|
+
type: ViewChild,
|
|
29001
|
+
args: ['slideshowSwiper']
|
|
29002
|
+
}], onWindowResize: [{
|
|
29003
|
+
type: HostListener,
|
|
29004
|
+
args: ['window:resize']
|
|
29005
|
+
}] } });
|
|
29006
|
+
|
|
28337
29007
|
class ImageService extends StartableService {
|
|
28338
29008
|
platform;
|
|
28339
29009
|
settings;
|
|
@@ -28459,10 +29129,13 @@ class AppImageGalleryComponent {
|
|
|
28459
29129
|
_titleForm;
|
|
28460
29130
|
_titleAutofocus = false;
|
|
28461
29131
|
_slidesSubscription;
|
|
28462
|
-
_swiper;
|
|
28463
|
-
swiperModules = [IonicSlides];
|
|
28464
29132
|
zoomActive = false;
|
|
28465
29133
|
zoomScale = 1;
|
|
29134
|
+
_mouseZoomEnabled = false;
|
|
29135
|
+
_rotationEnabled = false;
|
|
29136
|
+
_cropEnabled = false;
|
|
29137
|
+
/** Whether the inline slideshow overlay is currently shown (only relevant when zoomPresentation === 'inline'). */
|
|
29138
|
+
_inlineSlideshowActive = false;
|
|
28466
29139
|
viewChange;
|
|
28467
29140
|
cardColor;
|
|
28468
29141
|
debug = null;
|
|
@@ -28487,6 +29160,24 @@ class AppImageGalleryComponent {
|
|
|
28487
29160
|
modal: 'large',
|
|
28488
29161
|
};
|
|
28489
29162
|
imageSizeQueryParam = 'size';
|
|
29163
|
+
/** Enable mouse-wheel zoom and mouse-drag pan in the slideshow (defaults to true on desktop). */
|
|
29164
|
+
enableMouseZoom = null;
|
|
29165
|
+
/** Zoom step applied on each mouse-wheel tick (default 0.3). */
|
|
29166
|
+
wheelZoomStep = 0.3;
|
|
29167
|
+
/** Maximum zoom ratio reachable via mouse wheel (default 3). */
|
|
29168
|
+
maxZoomRatio = 3;
|
|
29169
|
+
/** Enable 90° rotation buttons in the slideshow (defaults to true). */
|
|
29170
|
+
enableRotation = null;
|
|
29171
|
+
/** Enable the crop tool in the slideshow (defaults to true). */
|
|
29172
|
+
enableCrop = null;
|
|
29173
|
+
/** Image format used when emitting the edited (cropped/rotated) image. */
|
|
29174
|
+
imageExportFormat = 'png';
|
|
29175
|
+
/** Export quality, applied only when imageExportFormat is 'jpeg' (0 to 1). */
|
|
29176
|
+
imageExportQuality = 0.92;
|
|
29177
|
+
/** Where to show the slideshow: in a full-screen modal (default), or inline, as an overlay over the gallery itself (desktop only). */
|
|
29178
|
+
zoomPresentation = 'inline';
|
|
29179
|
+
/** Height of the inline slideshow overlay (CSS value, e.g. '60vh'). Only used when zoomPresentation is 'inline'. */
|
|
29180
|
+
inlineZoomHeight = '100%';
|
|
28490
29181
|
set dataSource(dataSource) {
|
|
28491
29182
|
if (this._dataSource !== dataSource) {
|
|
28492
29183
|
this._switchDataSource(dataSource);
|
|
@@ -28504,15 +29195,12 @@ class AppImageGalleryComponent {
|
|
|
28504
29195
|
get rowCount() {
|
|
28505
29196
|
return this.rows?.length || 0;
|
|
28506
29197
|
}
|
|
28507
|
-
get activeIndex() {
|
|
28508
|
-
return this.getSwiper()?.activeIndex || 0;
|
|
28509
|
-
}
|
|
28510
29198
|
zoomModal;
|
|
28511
29199
|
titlePopover;
|
|
28512
|
-
swiperRef;
|
|
28513
29200
|
onBeforeDeleteRows = createPromiseEventEmitter();
|
|
28514
29201
|
onAfterAddRows = new EventEmitter();
|
|
28515
29202
|
onAfterEditRow = new EventEmitter();
|
|
29203
|
+
onAfterEditImage = new EventEmitter();
|
|
28516
29204
|
click = new EventEmitter();
|
|
28517
29205
|
showTooltip = inject(APP_SHOW_TOOLTIP, { optional: true });
|
|
28518
29206
|
constructor(imageService, platform, alterCtrl, translate, cd, environment) {
|
|
@@ -28534,6 +29222,13 @@ class AppImageGalleryComponent {
|
|
|
28534
29222
|
this.showAddCardButton = toBoolean(this.showAddCardButton, this.mobile);
|
|
28535
29223
|
this.showFabButton = toBoolean(this.showFabButton, this.mobile && !this.showAddCardButton);
|
|
28536
29224
|
this.showAddToolbarButton = this.showAddToolbarButton ?? (this.showToolbar && !this.readOnly);
|
|
29225
|
+
this._mouseZoomEnabled = toBoolean(this.enableMouseZoom, !this.mobile);
|
|
29226
|
+
this._rotationEnabled = toBoolean(this.enableRotation, true);
|
|
29227
|
+
this._cropEnabled = toBoolean(this.enableCrop, true);
|
|
29228
|
+
// The inline presentation is only meaningful on desktop: there is no "rest of the page" worth
|
|
29229
|
+
// preserving on a small mobile screen, and the fullscreen modal already works well there.
|
|
29230
|
+
if (this.mobile)
|
|
29231
|
+
this.zoomPresentation = 'modal';
|
|
28537
29232
|
// Get modal presenting element (iOS only)
|
|
28538
29233
|
this._presentingElement = this.mobile ? document.querySelector('.ion-page') : null;
|
|
28539
29234
|
}
|
|
@@ -28664,82 +29359,38 @@ class AppImageGalleryComponent {
|
|
|
28664
29359
|
// Delegate click action
|
|
28665
29360
|
if (this.click.observed) {
|
|
28666
29361
|
this.click.emit(row.currentData);
|
|
29362
|
+
return;
|
|
29363
|
+
}
|
|
29364
|
+
// By default, open the slideshow: either inline (overlay in the gallery itself), or in a full-screen modal
|
|
29365
|
+
this._selectedRowIndex = index;
|
|
29366
|
+
if (this.zoomPresentation === 'inline') {
|
|
29367
|
+
this._inlineSlideshowActive = true;
|
|
29368
|
+
this.markForCheck();
|
|
28667
29369
|
}
|
|
28668
|
-
// By default, open a zoom modal
|
|
28669
29370
|
else {
|
|
28670
|
-
this._selectedRowIndex = index;
|
|
28671
29371
|
await this.zoomModal.present();
|
|
28672
29372
|
await this.zoomModal.onDidDismiss();
|
|
28673
29373
|
}
|
|
28674
29374
|
}
|
|
28675
|
-
|
|
28676
|
-
|
|
28677
|
-
this._swiper = (swiperElement || this.swiperRef?.nativeElement)?.swiper || this._swiper;
|
|
28678
|
-
return this._swiper;
|
|
28679
|
-
}
|
|
28680
|
-
onSlideChange(swiperElement) {
|
|
28681
|
-
const swiper = this.getSwiper(swiperElement);
|
|
28682
|
-
this.activeSlideIndex = swiper.activeIndex;
|
|
29375
|
+
closeInlineSlideshow() {
|
|
29376
|
+
this._inlineSlideshowActive = false;
|
|
28683
29377
|
this.markForCheck();
|
|
28684
29378
|
}
|
|
28685
|
-
|
|
28686
|
-
|
|
28687
|
-
|
|
28688
|
-
|
|
28689
|
-
|
|
28690
|
-
|
|
28691
|
-
|
|
28692
|
-
|
|
28693
|
-
|
|
28694
|
-
|
|
28695
|
-
|
|
28696
|
-
|
|
28697
|
-
if (this.debug)
|
|
28698
|
-
console.debug('[image-gallery] zoom ' + (zoomIn ? 'in' : 'out'), swiperElement);
|
|
28699
|
-
const swiper = this.getSwiper(swiperElement);
|
|
28700
|
-
const zoom = swiper.zoom;
|
|
28701
|
-
if (zoomIn)
|
|
28702
|
-
zoom.in();
|
|
28703
|
-
else
|
|
28704
|
-
zoom.out();
|
|
28705
|
-
}
|
|
28706
|
-
async deleteFromModal(event, swiperElement) {
|
|
28707
|
-
const swiper = this.getSwiper(swiperElement);
|
|
28708
|
-
const currentRow = this.rows.find((row, index) => index === swiper.activeIndex);
|
|
28709
|
-
if (!currentRow)
|
|
28710
|
-
return false; // Row not found
|
|
28711
|
-
const deleted = await this.delete(event, currentRow);
|
|
28712
|
-
if (this.rows.length === 0) {
|
|
28713
|
-
await this.zoomModal.dismiss();
|
|
28714
|
-
}
|
|
28715
|
-
else {
|
|
28716
|
-
// Reset the zoom
|
|
28717
|
-
swiper.zoom.out();
|
|
28718
|
-
swiper.update();
|
|
28719
|
-
this.markForCheck();
|
|
28720
|
-
}
|
|
28721
|
-
return deleted;
|
|
28722
|
-
}
|
|
28723
|
-
async slidePrev(swiperElement) {
|
|
28724
|
-
const swiper = this.getSwiper(swiperElement);
|
|
28725
|
-
const activeIndex = swiper.activeIndex;
|
|
28726
|
-
if (activeIndex === 0) {
|
|
28727
|
-
// Loop to last
|
|
28728
|
-
await swiper.slideTo(this.rows.length - 1, 0 /*no transition, to avoid a "revert" effect*/);
|
|
28729
|
-
}
|
|
28730
|
-
else {
|
|
28731
|
-
await swiper.slidePrev();
|
|
28732
|
-
}
|
|
28733
|
-
}
|
|
28734
|
-
async slideNext(swiperElement) {
|
|
28735
|
-
const swiper = this.getSwiper(swiperElement);
|
|
28736
|
-
const activeIndex = swiper.activeIndex;
|
|
28737
|
-
if (activeIndex === this.rows.length - 1) {
|
|
28738
|
-
// Loop to first
|
|
28739
|
-
await swiper.slideTo(0, 0 /*no transition, to avoid a "revert" effect*/);
|
|
29379
|
+
/**
|
|
29380
|
+
* Handles a delete request coming from the slideshow component (either inline or in the modal):
|
|
29381
|
+
* runs the same confirmation/deletion flow as a card's delete button, then resolves the promise
|
|
29382
|
+
* so the slideshow knows whether to reset its zoom or close.
|
|
29383
|
+
*/
|
|
29384
|
+
async onSlideshowDeleteRequested(promiseEvent) {
|
|
29385
|
+
try {
|
|
29386
|
+
const deleted = await this.delete(promiseEvent, promiseEvent.detail.row);
|
|
29387
|
+
promiseEvent.detail.success(deleted);
|
|
29388
|
+
if (deleted && this.rows.length === 0 && this.zoomPresentation === 'inline') {
|
|
29389
|
+
this.closeInlineSlideshow();
|
|
29390
|
+
}
|
|
28740
29391
|
}
|
|
28741
|
-
|
|
28742
|
-
|
|
29392
|
+
catch (err) {
|
|
29393
|
+
promiseEvent.detail.error(err);
|
|
28743
29394
|
}
|
|
28744
29395
|
}
|
|
28745
29396
|
async canDeleteRows(rows, opts) {
|
|
@@ -28778,11 +29429,11 @@ class AppImageGalleryComponent {
|
|
|
28778
29429
|
this.cd.markForCheck();
|
|
28779
29430
|
}
|
|
28780
29431
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppImageGalleryComponent, deps: [{ token: ImageService }, { token: i2$1.Platform }, { token: i2$1.AlertController }, { token: i1$1.TranslateService }, { token: i0.ChangeDetectorRef }, { token: ENVIRONMENT, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
28781
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppImageGalleryComponent, selector: "app-image-gallery", inputs: { cardColor: "cardColor", debug: ["debug", "debug", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], readOnly: ["readOnly", "readOnly", booleanAttribute], mobile: ["mobile", "mobile", booleanAttribute], mode: "mode", confirmBeforeDelete: "confirmBeforeDelete", showToolbar: ["showToolbar", "showToolbar", booleanAttribute], showFabButton: ["showFabButton", "showFabButton", booleanAttribute], showTitle: ["showTitle", "showTitle", booleanAttribute], showAddToolbarButton: ["showAddToolbarButton", "showAddToolbarButton", booleanAttribute], showAddTextButton: ["showAddTextButton", "showAddTextButton", booleanAttribute], showAddCardButton: ["showAddCardButton", "showAddCardButton", booleanAttribute], showCardToolbar: ["showCardToolbar", "showCardToolbar", booleanAttribute], addButtonColor: "addButtonColor", addButtonText: "addButtonText", cardTemplate: "cardTemplate", imageSizes: "imageSizes", imageSizeQueryParam: "imageSizeQueryParam", dataSource: "dataSource" }, outputs: { onBeforeDeleteRows: "onBeforeDeleteRows", onAfterAddRows: "onAfterAddRows", onAfterEditRow: "onAfterEditRow", click: "click" }, viewQueries: [{ propertyName: "zoomModal", first: true, predicate: ["zoomModal"], descendants: true }, { propertyName: "titlePopover", first: true, predicate: ["titlePopover"], descendants: true }, { propertyName: "swiperRef", first: true, predicate: ["modalSwiper"], descendants: true }], ngImport: i0, template: "<ion-toolbar color=\"light\" *ngIf=\"showToolbar\">\n <ng-content select=\"ion-buttons[slot=start]\"></ng-content>\n\n <!-- Add -->\n @if (!readOnly && showAddToolbarButton && enabled) {\n <button\n mat-icon-button\n [title]=\"!showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n (click)=\"add()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n\n <ng-content select=\"ion-buttons[slot=end]\"></ng-content>\n\n <ion-buttons slot=\"end\">\n <!-- Toggle view mode -->\n <ion-segment (ionChange)=\"toggleViewMode($event)\" color=\"accent\" [value]=\"_colSize\">\n <ion-segment-button [value]=\"4\">\n <mat-icon>view_module</mat-icon>\n </ion-segment-button>\n <ion-segment-button [value]=\"12\">\n <mat-icon>view_list</mat-icon>\n </ion-segment-button>\n </ion-segment>\n </ion-buttons>\n</ion-toolbar>\n\n<div class=\"gallery-container ion-padding-bottom {{ this.mode }}\">\n <div *ngIf=\"mobile && zoomActive\" class=\"backdrop\" [style.opacity]=\"zoomScale\"></div>\n\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col *rxFor=\"let row of _data$; index as index; trackBy: trackByFn\" [size]=\"_colSize\">\n @let image = row | propertyGet: 'currentData';\n @if (cardTemplate) {\n <ng-container\n *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, mode: mode, image: image }\"\n ></ng-container>\n } @else {\n <ion-card\n #card\n class=\"image-card\"\n [class.zoom-hover]=\"!mobile\"\n [color]=\"cardColor\"\n (click)=\"clickRow(row, index)\"\n tappable\n @fadeInAnimation\n >\n <figure class=\"card-thumbnail\">\n @if (image.url) {\n <ion-img [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <ion-img [src]=\"image.dataUrl\" />\n }\n </figure>\n\n <!-- toolbar for title (when not hover) -->\n @if (showTitle && image.title) {\n <ion-toolbar color=\"transparent\" class=\"title\">\n <ion-label class=\"card-title\">{{ image.title }}</ion-label>\n </ion-toolbar>\n }\n\n <!-- action toolbar shown when hover image -->\n @if (!readOnly && !mobile && showCardToolbar) {\n <ion-toolbar #cardToolbar color=\"light\" class=\"buttons\">\n <!-- edit title button -->\n @if (showTitle) {\n <button\n mat-button\n slot=\"start\"\n [disabled]=\"disabled\"\n (click)=\"editTitle($event, row, cardToolbar)\"\n (focusin)=\"focusCardToolbar(cardToolbar)\"\n >\n <mat-icon>edit</mat-icon>\n <mat-label>\n <span *ngIf=\"row.currentData.title | isNotNilOrBlank; else editLabel\" translate>\n IMAGE.GALLERY.BTN_EDIT_TITLE\n </span>\n <ng-template #editLabel>\n <span translate>IMAGE.GALLERY.BTN_ADD_TITLE</span>\n </ng-template>\n </mat-label>\n </button>\n }\n\n <div class=\"flex-spacer\"></div>\n\n <!-- delete button -->\n <button\n mat-icon-button\n slot=\"end\"\n class=\"ion-float-end\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [disabled]=\"disabled\"\n (click)=\"delete($event, row)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </ion-toolbar>\n }\n </ion-card>\n }\n </ion-col>\n\n <!-- add button -->\n @if ((showAddTextButton || showAddCardButton) && !readOnly) {\n <ion-col [size]=\"_colSize\" @fadeInAnimation>\n @if (showAddTextButton) {\n <ion-button (click)=\"add($event)\" [disabled]=\"disabled\" [color]=\"addButtonColor\">\n <mat-label translate>{{ addButtonText }}</mat-label>\n <ion-icon slot=\"end\" name=\"camera\"></ion-icon>\n </ion-button>\n } @else if (showAddCardButton) {\n <ion-card\n class=\"image-card-button\"\n color=\"light\"\n (click)=\"add($event)\"\n [disabled]=\"disabled\"\n [title]=\"!showTooltip ? addButtonText : ''\"\n [matTooltip]=\"showTooltip ? addButtonText : ''\"\n tappable\n >\n <ion-card-content>\n <div class=\"icon-container\">\n <ion-icon name=\"camera\" [color]=\"addButtonColor\"></ion-icon>\n <ion-icon name=\"add\" [color]=\"addButtonColor\" class=\"icon-secondary\"></ion-icon>\n </div>\n </ion-card-content>\n </ion-card>\n }\n </ion-col>\n }\n </ion-row>\n </ion-grid>\n</div>\n\n<!-- FAB button: add -->\n<ion-fab\n slot=\"fixed\"\n vertical=\"bottom\"\n horizontal=\"end\"\n *ngIf=\"showFabButton && !readOnly\"\n [class.cdk-visually-hidden]=\"disabled\"\n @fadeInOutAnimation\n>\n <ion-fab-button color=\"tertiary\" (click)=\"add($event)\">\n <ion-icon name=\"camera\"></ion-icon>\n <ion-icon name=\"add\" class=\"icon-secondary\" style=\"left: 33px; top: 9px; font-size: 20px\"></ion-icon>\n </ion-fab-button>\n</ion-fab>\n\n<!-- Zoom modal -->\n<ion-modal #zoomModal class=\"stack-modal zoom-modal\" [class.modal-fullscreen]=\"mobile\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-content class=\"ion-no-padding ion-no-margin\">\n @let count = rows?.length || 0;\n <!-- Counter -->\n <div class=\"top-left\">\n <ion-label color=\"light\">{{ activeSlideIndex + 1 }} / {{ count }}</ion-label>\n </div>\n\n <!-- zoom button -->\n <div class=\"top-right\">\n @if (!mobile) {\n <ion-fab-button size=\"small\" (click)=\"zoom(modalSwiper, true)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"add\"></ion-icon>\n </ion-fab-button>\n <ion-fab-button size=\"small\" (click)=\"zoom(modalSwiper, false)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"remove\"></ion-icon>\n </ion-fab-button>\n }\n </div>\n\n <!-- navigation side buttons next/back -->\n <ng-container *ngIf=\"!mobile && (rows | isArrayLength: { greaterThan: 1 })\">\n <div class=\"side-buttons prev\" (click)=\"slidePrev(modalSwiper)\">\n <ion-icon name=\"chevron-back\"></ion-icon>\n </div>\n <div class=\"side-buttons next\" (click)=\"slideNext(modalSwiper)\">\n <ion-icon name=\"chevron-forward\"></ion-icon>\n </div>\n </ng-container>\n\n <!-- image slides -->\n <swiper-container\n #modalSwiper\n [modules]=\"swiperModules\"\n [zoom]=\"true\"\n (swiperslidechange)=\"onSlideChange(modalSwiper)\"\n (swiperinit)=\"initModalSlides(modalSwiper)\"\n >\n @for (row of rows; track row.id; let last = $last) {\n <swiper-slide>\n @let image = row | propertyGet: 'currentData';\n <div class=\"swiper-zoom-container\">\n @if (image.url) {\n <ion-img [src]=\"image.url | appendQueryParams: getImageSizeQueryParams('modal')\" />\n } @else {\n <img [src]=\"image.dataUrl\" [alt]=\"image.title\" />\n }\n </div>\n </swiper-slide>\n }\n </swiper-container>\n </ion-content>\n\n <ion-footer>\n <ion-toolbar color=\"light\">\n <ion-row>\n <ion-col size=\"4\" class=\"ion-text-start\">\n <ion-button\n *ngIf=\"!readOnly\"\n (click)=\"deleteFromModal($event, modalSwiper)\"\n [disabled]=\"disabled\"\n fill=\"clear\"\n color=\"danger\"\n >\n <mat-icon slot=\"start\">delete</mat-icon>\n {{ 'COMMON.BTN_DELETE' | translate }}\n </ion-button>\n </ion-col>\n <ion-col size=\"4\"></ion-col>\n <ion-col size=\"4\" class=\"ion-text-end\">\n <ion-button (click)=\"zoomModal.dismiss()\" color=\"tertiary\" fill=\"clear\" *ngIf=\"mobile; else desktop\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n <ng-template #desktop>\n <ion-button (click)=\"zoomModal.dismiss()\" color=\"tertiary\">\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ng-template>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n </ion-footer>\n </ng-template>\n</ion-modal>\n\n<ion-popover #titlePopover>\n <ng-template>\n <ion-content class=\"ion-padding\" cdkTrapFocus [cdkTrapFocusAutoCapture]=\"true\">\n <form [formGroup]=\"_titleForm\">\n <mat-form-field floatLabel=\"auto\">\n <mat-label translate>IMAGE.GALLERY.TITLE</mat-label>\n <input\n matInput\n formControlName=\"title\"\n [readonly]=\"disabled\"\n [appAutofocus]=\"_titleAutofocus\"\n (keydown.control.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n />\n </mat-form-field>\n </form>\n </ion-content>\n\n <ion-footer>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"titlePopover.dismiss(null, 'CANCEL')\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n <ion-button\n [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n (keyup.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n color=\"tertiary\"\n [disabled]=\"disabled\"\n [title]=\"!showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n >\n <ion-label translate>COMMON.BTN_VALIDATE</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n </ion-footer>\n </ng-template>\n</ion-popover>\n", styles: [":host{--image-margin-inline: 10px;--image-margin-safe-area: var(--image-margin-inline, 0px) * 2 - 3px;--segment-max-width: 150px}ion-toolbar{position:sticky;top:0}ion-toolbar ion-segment{max-width:var(--segment-max-width)}mat-toolbar,.mat-toolbar-single-row{padding:0;height:42px}ion-card{margin-left:unset;margin-right:unset;margin-inline:var(--image-margin-inline, 10px)}.image-slide{overflow:visible}.image-card{background-color:var(--ion-color-base);z-index:9;--img-scale: 1}.image-card .card-thumbnail{position:relative;overflow:hidden;display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:100%;height:100%;max-height:var(--img-max-height, calc(100% - var(--image-margin-safe-area, 0px)))}.image-card .card-thumbnail>ion-img{min-height:100%;height:100%;object-fit:cover;object-position:center;transition:scale .2s ease-in-out;scale:var(--img-scale)}.image-card .card-thumbnail>ion-img img{max-height:var(--img-max-height)}.image-card.zoom-hover:hover{--img-scale: 1.2}.image-card ion-toolbar{--top-offset: calc(-1 * var(--ion-toolbar-height));position:absolute}.image-card ion-toolbar.title{--padding-start: 16px;--padding-end: 16px;--ion-color-base: rgba(0, 0, 0, .45) !important;position:absolute;top:calc(100% + var(--top-offset));transition:opacity .2s ease-in-out}.image-card ion-toolbar.title ion-label{color:#fff}.image-card ion-toolbar.buttons{--padding-start: 4px;--padding-end: 4px;--ion-color-base: var(--ion-color-light) !important;position:absolute;top:100%;transition:transform .2s ease-in-out}.image-card:hover ion-toolbar.buttons,.image-card ion-toolbar.buttons.focused{transform:translateY(var(--top-offset))}.image-card-button{height:calc(100% - var(--image-margin-safe-area, 0px));max-height:max(min(30vh,200px),var(--img-max-height, 0px));transition:.2s ease-in-out;max-width:250px}.image-card-button ion-card-content{height:100%}.image-card-button .icon-container{height:auto;position:relative;top:calc(50% - 75px);display:flex;align-items:center;justify-content:center}.image-card-button ion-icon:first-of-type{font-size:150px;height:150px;position:relative;top:0}.image-card-button ion-icon.icon-secondary{font-size:50px;position:absolute;top:0;left:max(min(50% + 25px,100% - 50px),0px)}.image-card-button ion-button{float:right}.image-card-button:hover{--ion-color-base: var(--ion-color-shade) !important}.image-card-button:hover ion-icon{--ion-color-base: var(--ion-color-shade) !important}.backdrop{height:100vh;width:100vw;background:#000;position:absolute;z-index:10}.zoom-modal{-webkit-user-select:none;user-select:none}.zoom-modal .top-left{z-index:14;position:absolute;display:inline-grid;left:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}.zoom-modal .top-right{z-index:14;position:absolute;display:inline-grid;right:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}.zoom-modal .side-buttons{--side-buttons-width: 120px;--side-buttons-margin: calc(var(--side-buttons-width) / 3);z-index:13;position:absolute;top:0;bottom:0;width:var(--side-buttons-width)}.zoom-modal .side-buttons ion-icon{top:50%;color:#fff;font-size:2.5rem;position:absolute;opacity:0;transition-duration:.15s;transition-timing-function:ease-in}.zoom-modal .side-buttons:hover{cursor:pointer}.zoom-modal .side-buttons:hover ion-icon{opacity:1}.zoom-modal .side-buttons.prev{left:0}.zoom-modal .side-buttons.prev ion-icon{left:var(--side-buttons-margin)}.zoom-modal .side-buttons.next{right:0}.zoom-modal .side-buttons.next ion-icon{right:var(--side-buttons-margin)}.zoom-modal swiper-container{height:100%;background:#000}.zoom-modal ion-fab-button .icon-secondary{top:11px;left:6px;height:14px}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFab, selector: "ion-fab", inputs: ["activated", "edge", "horizontal", "vertical"] }, { kind: "component", type: i2$1.IonFabButton, selector: "ion-fab-button", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSegment, selector: "ion-segment", inputs: ["color", "disabled", "mode", "scrollable", "selectOnFocus", "swipeGesture", "value"] }, { kind: "component", type: i2$1.IonSegmentButton, selector: "ion-segment-button", inputs: ["disabled", "layout", "mode", "type", "value"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonModal, selector: "ion-modal" }, { kind: "component", type: i2$1.IonPopover, selector: "ion-popover" }, { kind: "directive", type: i2$1.SelectValueAccessor, selector: "ion-select, ion-radio-group, ion-segment, ion-datetime" }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: i1$6.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: AutofocusDirective, selector: "[autofocus], input[appAutofocus]", inputs: ["appAutofocus", "autofocusDelay"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i13$3.RxFor, selector: "[rxFor][rxForOf]", inputs: ["rxForOf", "rxForTemplate", "rxForStrategy", "rxForParent", "rxForPatchZone", "rxForTrackBy", "rxForRenderCallback"] }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: ArrayLengthPipe, name: "isArrayLength" }, { kind: "pipe", type: IsNotNilOrBlankPipe, name: "isNotNilOrBlank" }, { kind: "pipe", type: AppendQueryParamsPipePipe, name: "appendQueryParams" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], animations: [fadeInAnimation, fadeInOutAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
29432
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AppImageGalleryComponent, selector: "app-image-gallery", inputs: { cardColor: "cardColor", debug: ["debug", "debug", booleanAttribute], disabled: ["disabled", "disabled", booleanAttribute], readOnly: ["readOnly", "readOnly", booleanAttribute], mobile: ["mobile", "mobile", booleanAttribute], mode: "mode", confirmBeforeDelete: "confirmBeforeDelete", showToolbar: ["showToolbar", "showToolbar", booleanAttribute], showFabButton: ["showFabButton", "showFabButton", booleanAttribute], showTitle: ["showTitle", "showTitle", booleanAttribute], showAddToolbarButton: ["showAddToolbarButton", "showAddToolbarButton", booleanAttribute], showAddTextButton: ["showAddTextButton", "showAddTextButton", booleanAttribute], showAddCardButton: ["showAddCardButton", "showAddCardButton", booleanAttribute], showCardToolbar: ["showCardToolbar", "showCardToolbar", booleanAttribute], addButtonColor: "addButtonColor", addButtonText: "addButtonText", cardTemplate: "cardTemplate", imageSizes: "imageSizes", imageSizeQueryParam: "imageSizeQueryParam", enableMouseZoom: ["enableMouseZoom", "enableMouseZoom", booleanAttribute], wheelZoomStep: "wheelZoomStep", maxZoomRatio: "maxZoomRatio", enableRotation: ["enableRotation", "enableRotation", booleanAttribute], enableCrop: ["enableCrop", "enableCrop", booleanAttribute], imageExportFormat: "imageExportFormat", imageExportQuality: "imageExportQuality", zoomPresentation: "zoomPresentation", inlineZoomHeight: "inlineZoomHeight", dataSource: "dataSource" }, outputs: { onBeforeDeleteRows: "onBeforeDeleteRows", onAfterAddRows: "onAfterAddRows", onAfterEditRow: "onAfterEditRow", onAfterEditImage: "onAfterEditImage", click: "click" }, viewQueries: [{ propertyName: "zoomModal", first: true, predicate: ["zoomModal"], descendants: true }, { propertyName: "titlePopover", first: true, predicate: ["titlePopover"], descendants: true }], ngImport: i0, template: "<ion-toolbar color=\"light\" class=\"gallery-toolbar\" *ngIf=\"showToolbar && !_inlineSlideshowActive\">\n <ng-content select=\"ion-buttons[slot=start]\"></ng-content>\n\n <!-- Add -->\n @if (!readOnly && showAddToolbarButton && enabled) {\n <button\n mat-icon-button\n [title]=\"!showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n (click)=\"add()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n\n <ng-content select=\"ion-buttons[slot=end]\"></ng-content>\n\n <ion-buttons slot=\"end\">\n <!-- Toggle view mode -->\n <ion-segment (ionChange)=\"toggleViewMode($event)\" color=\"accent\" [value]=\"_colSize\">\n <ion-segment-button [value]=\"4\">\n <mat-icon>view_module</mat-icon>\n </ion-segment-button>\n <ion-segment-button [value]=\"12\">\n <mat-icon>view_list</mat-icon>\n </ion-segment-button>\n </ion-segment>\n </ion-buttons>\n</ion-toolbar>\n\n<div\n class=\"gallery-container ion-padding-bottom {{ mode }}\"\n [class.inline-zoom-active]=\"_inlineSlideshowActive\"\n [style.--inline-zoom-height]=\"inlineZoomHeight\"\n>\n <div *ngIf=\"mobile && zoomActive\" class=\"backdrop\" [style.opacity]=\"zoomScale\"></div>\n\n <ion-grid class=\"ion-no-padding\" [class.cdk-visually-hidden]=\"_inlineSlideshowActive\">\n <ion-row>\n <ion-col *rxFor=\"let row of _data$; index as index; trackBy: trackByFn\" [size]=\"_colSize\">\n @let image = row | propertyGet: 'currentData';\n @if (cardTemplate) {\n <ng-container\n *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, mode: mode, image: image }\"\n ></ng-container>\n } @else {\n <ion-card\n #card\n class=\"image-card\"\n [class.zoom-hover]=\"!mobile\"\n [color]=\"cardColor\"\n (click)=\"clickRow(row, index)\"\n tappable\n @fadeInAnimation\n >\n <figure class=\"card-thumbnail\">\n @if (image.url) {\n <ion-img [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <ion-img [src]=\"image.dataUrl\" />\n }\n </figure>\n\n <!-- toolbar for title (when not hover) -->\n @if (showTitle && image.title) {\n <ion-toolbar color=\"transparent\" class=\"title\">\n <ion-label class=\"card-title\">{{ image.title }}</ion-label>\n </ion-toolbar>\n }\n\n <!-- action toolbar shown when hover image -->\n @if (!readOnly && !mobile && showCardToolbar) {\n <ion-toolbar #cardToolbar color=\"light\" class=\"buttons\">\n <!-- edit title button -->\n @if (showTitle) {\n <button\n mat-button\n slot=\"start\"\n [disabled]=\"disabled\"\n (click)=\"editTitle($event, row, cardToolbar)\"\n (focusin)=\"focusCardToolbar(cardToolbar)\"\n >\n <mat-icon>edit</mat-icon>\n <mat-label>\n <span *ngIf=\"row.currentData.title | isNotNilOrBlank; else editLabel\" translate>\n IMAGE.GALLERY.BTN_EDIT_TITLE\n </span>\n <ng-template #editLabel>\n <span translate>IMAGE.GALLERY.BTN_ADD_TITLE</span>\n </ng-template>\n </mat-label>\n </button>\n }\n\n <div class=\"flex-spacer\"></div>\n\n <!-- delete button -->\n <button\n mat-icon-button\n slot=\"end\"\n class=\"ion-float-end\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [disabled]=\"disabled\"\n (click)=\"delete($event, row)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </ion-toolbar>\n }\n </ion-card>\n }\n </ion-col>\n\n <!-- add button -->\n @if ((showAddTextButton || showAddCardButton) && !readOnly) {\n <ion-col [size]=\"_colSize\" @fadeInAnimation>\n @if (showAddTextButton) {\n <ion-button (click)=\"add($event)\" [disabled]=\"disabled\" [color]=\"addButtonColor\">\n <mat-label translate>{{ addButtonText }}</mat-label>\n <ion-icon slot=\"end\" name=\"camera\"></ion-icon>\n </ion-button>\n } @else if (showAddCardButton) {\n <ion-card\n class=\"image-card-button\"\n color=\"light\"\n (click)=\"add($event)\"\n [disabled]=\"disabled\"\n [title]=\"!showTooltip ? addButtonText : ''\"\n [matTooltip]=\"showTooltip ? addButtonText : ''\"\n tappable\n >\n <ion-card-content>\n <div class=\"icon-container\">\n <ion-icon name=\"camera\" [color]=\"addButtonColor\"></ion-icon>\n <ion-icon name=\"add\" [color]=\"addButtonColor\" class=\"icon-secondary\"></ion-icon>\n </div>\n </ion-card-content>\n </ion-card>\n }\n </ion-col>\n }\n </ion-row>\n </ion-grid>\n\n <!-- Inline slideshow: shown as an overlay directly within the gallery, instead of a full-screen modal.\n The rest of the page (outside the gallery) stays fully visible and usable, unlike the modal's backdrop. -->\n @if (zoomPresentation === 'inline' && _inlineSlideshowActive) {\n <app-image-gallery-slideshow\n class=\"inline-overlay\"\n @fadeInOutAnimation\n [rows]=\"rows\"\n [selectedIndex]=\"_selectedRowIndex\"\n [mobile]=\"mobile\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [debug]=\"debug\"\n [mouseZoomEnabled]=\"_mouseZoomEnabled\"\n [wheelZoomStep]=\"wheelZoomStep\"\n [maxZoomRatio]=\"maxZoomRatio\"\n [rotationEnabled]=\"_rotationEnabled\"\n [cropEnabled]=\"_cropEnabled\"\n [imageExportFormat]=\"imageExportFormat\"\n [imageExportQuality]=\"imageExportQuality\"\n [imageSizeQueryParam]=\"imageSizeQueryParam\"\n [modalImageSize]=\"imageSizes?.modal\"\n (deleteRequested)=\"onSlideshowDeleteRequested($event)\"\n (closeRequested)=\"closeInlineSlideshow()\"\n (afterEditImage)=\"onAfterEditImage.emit($event)\"\n (afterEditRow)=\"onAfterEditRow.emit($event)\"\n ></app-image-gallery-slideshow>\n }\n</div>\n\n<!-- FAB button: add -->\n<ion-fab\n slot=\"fixed\"\n vertical=\"bottom\"\n horizontal=\"end\"\n *ngIf=\"showFabButton && !readOnly\"\n [class.cdk-visually-hidden]=\"disabled\"\n @fadeInOutAnimation\n>\n <ion-fab-button color=\"tertiary\" (click)=\"add($event)\">\n <ion-icon name=\"camera\"></ion-icon>\n <ion-icon name=\"add\" class=\"icon-secondary\" style=\"left: 33px; top: 9px; font-size: 20px\"></ion-icon>\n </ion-fab-button>\n</ion-fab>\n\n<!-- Zoom modal -->\n@if (zoomPresentation === 'modal') {\n <ion-modal #zoomModal class=\"stack-modal zoom-modal\" [class.modal-fullscreen]=\"mobile\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <app-image-gallery-slideshow\n [rows]=\"rows\"\n [selectedIndex]=\"_selectedRowIndex\"\n [mobile]=\"mobile\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [debug]=\"debug\"\n [mouseZoomEnabled]=\"_mouseZoomEnabled\"\n [wheelZoomStep]=\"wheelZoomStep\"\n [maxZoomRatio]=\"maxZoomRatio\"\n [rotationEnabled]=\"_rotationEnabled\"\n [cropEnabled]=\"_cropEnabled\"\n [imageExportFormat]=\"imageExportFormat\"\n [imageExportQuality]=\"imageExportQuality\"\n [imageSizeQueryParam]=\"imageSizeQueryParam\"\n [modalImageSize]=\"imageSizes?.modal\"\n (deleteRequested)=\"onSlideshowDeleteRequested($event)\"\n (closeRequested)=\"zoomModal.dismiss()\"\n (afterEditImage)=\"onAfterEditImage.emit($event)\"\n (afterEditRow)=\"onAfterEditRow.emit($event)\"\n ></app-image-gallery-slideshow>\n </ng-template>\n </ion-modal>\n}\n\n<ion-popover #titlePopover>\n <ng-template>\n <ion-content class=\"ion-padding\" cdkTrapFocus [cdkTrapFocusAutoCapture]=\"true\">\n <form [formGroup]=\"_titleForm\">\n <mat-form-field floatLabel=\"auto\">\n <mat-label translate>IMAGE.GALLERY.TITLE</mat-label>\n <input\n matInput\n formControlName=\"title\"\n [readonly]=\"disabled\"\n [appAutofocus]=\"_titleAutofocus\"\n (keydown.control.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n />\n </mat-form-field>\n </form>\n </ion-content>\n\n <ion-footer>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"titlePopover.dismiss(null, 'CANCEL')\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n <ion-button\n [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n (keyup.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n color=\"tertiary\"\n [disabled]=\"disabled\"\n [title]=\"!showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n >\n <ion-label translate>COMMON.BTN_VALIDATE</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n </ion-footer>\n </ng-template>\n</ion-popover>\n", styles: [":host{--image-margin-inline: 10px;--image-margin-safe-area: var(--image-margin-inline, 0px) * 2 - 3px;--segment-max-width: 150px}ion-toolbar{position:sticky;top:0}ion-toolbar ion-segment{max-width:var(--segment-max-width)}mat-toolbar,.mat-toolbar-single-row{padding:0;height:42px}ion-card{margin-left:unset;margin-right:unset;margin-inline:var(--image-margin-inline, 10px)}.image-slide{overflow:visible}.image-card{background-color:var(--ion-color-base);z-index:9;--img-scale: 1}.image-card .card-thumbnail{position:relative;overflow:hidden;display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:100%;height:100%;max-height:var(--img-max-height, calc(100% - var(--image-margin-safe-area, 0px)))}.image-card .card-thumbnail>ion-img{min-height:100%;height:100%;object-fit:cover;object-position:center;transition:scale .2s ease-in-out;scale:var(--img-scale)}.image-card .card-thumbnail>ion-img img{max-height:var(--img-max-height)}.image-card.zoom-hover:hover{--img-scale: 1.2}.image-card ion-toolbar{--top-offset: calc(-1 * var(--ion-toolbar-height));position:absolute}.image-card ion-toolbar.title{--padding-start: 16px;--padding-end: 16px;--ion-color-base: rgba(0, 0, 0, .45) !important;position:absolute;top:calc(100% + var(--top-offset));transition:opacity .2s ease-in-out}.image-card ion-toolbar.title ion-label{color:#fff}.image-card ion-toolbar.buttons{--padding-start: 4px;--padding-end: 4px;--ion-color-base: var(--ion-color-light) !important;position:absolute;top:100%;transition:transform .2s ease-in-out}.image-card:hover ion-toolbar.buttons,.image-card ion-toolbar.buttons.focused{transform:translateY(var(--top-offset))}.image-card-button{height:calc(100% - var(--image-margin-safe-area, 0px));max-height:max(min(30vh,200px),var(--img-max-height, 0px));transition:.2s ease-in-out;max-width:250px}.image-card-button ion-card-content{height:100%}.image-card-button .icon-container{height:auto;position:relative;top:calc(50% - 75px);display:flex;align-items:center;justify-content:center}.image-card-button ion-icon:first-of-type{font-size:150px;height:150px;position:relative;top:0}.image-card-button ion-icon.icon-secondary{font-size:50px;position:absolute;top:0;left:max(min(50% + 25px,100% - 50px),0px)}.image-card-button ion-button{float:right}.image-card-button:hover{--ion-color-base: var(--ion-color-shade) !important}.image-card-button:hover ion-icon{--ion-color-base: var(--ion-color-shade) !important}.backdrop{height:100vh;width:100vw;background:#000;position:absolute;z-index:10}.gallery-container{position:relative}.gallery-container.inline-zoom-active{min-height:var(--inline-zoom-height, 60vh)}.inline-overlay{position:absolute;inset:0;z-index:20;background:#000}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFab, selector: "ion-fab", inputs: ["activated", "edge", "horizontal", "vertical"] }, { kind: "component", type: i2$1.IonFabButton, selector: "ion-fab-button", inputs: ["activated", "closeIcon", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "show", "size", "target", "translucent", "type"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonSegment, selector: "ion-segment", inputs: ["color", "disabled", "mode", "scrollable", "selectOnFocus", "swipeGesture", "value"] }, { kind: "component", type: i2$1.IonSegmentButton, selector: "ion-segment-button", inputs: ["disabled", "layout", "mode", "type", "value"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonModal, selector: "ion-modal" }, { kind: "component", type: i2$1.IonPopover, selector: "ion-popover" }, { kind: "directive", type: i2$1.SelectValueAccessor, selector: "ion-select, ion-radio-group, ion-segment, ion-datetime" }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i11$1.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i11$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: i1$6.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: i1$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: AutofocusDirective, selector: "[autofocus], input[appAutofocus]", inputs: ["appAutofocus", "autofocusDelay"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i13$3.RxFor, selector: "[rxFor][rxForOf]", inputs: ["rxForOf", "rxForTemplate", "rxForStrategy", "rxForParent", "rxForPatchZone", "rxForTrackBy", "rxForRenderCallback"] }, { kind: "component", type: AppImageGallerySlideshowComponent, selector: "app-image-gallery-slideshow", inputs: ["rows", "selectedIndex", "mobile", "readOnly", "disabled", "debug", "mouseZoomEnabled", "wheelZoomStep", "maxZoomRatio", "rotationEnabled", "cropEnabled", "imageExportFormat", "imageExportQuality", "imageSizeQueryParam", "modalImageSize"], outputs: ["deleteRequested", "closeRequested", "afterEditImage", "afterEditRow"] }, { kind: "pipe", type: PropertyGetPipe, name: "propertyGet" }, { kind: "pipe", type: IsNotNilOrBlankPipe, name: "isNotNilOrBlank" }, { kind: "pipe", type: AppendQueryParamsPipePipe, name: "appendQueryParams" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], animations: [fadeInAnimation, fadeInOutAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
28782
29433
|
}
|
|
28783
29434
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AppImageGalleryComponent, decorators: [{
|
|
28784
29435
|
type: Component,
|
|
28785
|
-
args: [{ selector: 'app-image-gallery', animations: [fadeInAnimation, fadeInOutAnimation], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-toolbar color=\"light\" *ngIf=\"showToolbar\">\n <ng-content select=\"ion-buttons[slot=start]\"></ng-content>\n\n <!-- Add -->\n @if (!readOnly && showAddToolbarButton && enabled) {\n <button\n mat-icon-button\n [title]=\"!showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n (click)=\"add()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n\n <ng-content select=\"ion-buttons[slot=end]\"></ng-content>\n\n <ion-buttons slot=\"end\">\n <!-- Toggle view mode -->\n <ion-segment (ionChange)=\"toggleViewMode($event)\" color=\"accent\" [value]=\"_colSize\">\n <ion-segment-button [value]=\"4\">\n <mat-icon>view_module</mat-icon>\n </ion-segment-button>\n <ion-segment-button [value]=\"12\">\n <mat-icon>view_list</mat-icon>\n </ion-segment-button>\n </ion-segment>\n </ion-buttons>\n</ion-toolbar>\n\n<div class=\"gallery-container ion-padding-bottom {{ this.mode }}\">\n <div *ngIf=\"mobile && zoomActive\" class=\"backdrop\" [style.opacity]=\"zoomScale\"></div>\n\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col *rxFor=\"let row of _data$; index as index; trackBy: trackByFn\" [size]=\"_colSize\">\n @let image = row | propertyGet: 'currentData';\n @if (cardTemplate) {\n <ng-container\n *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, mode: mode, image: image }\"\n ></ng-container>\n } @else {\n <ion-card\n #card\n class=\"image-card\"\n [class.zoom-hover]=\"!mobile\"\n [color]=\"cardColor\"\n (click)=\"clickRow(row, index)\"\n tappable\n @fadeInAnimation\n >\n <figure class=\"card-thumbnail\">\n @if (image.url) {\n <ion-img [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <ion-img [src]=\"image.dataUrl\" />\n }\n </figure>\n\n <!-- toolbar for title (when not hover) -->\n @if (showTitle && image.title) {\n <ion-toolbar color=\"transparent\" class=\"title\">\n <ion-label class=\"card-title\">{{ image.title }}</ion-label>\n </ion-toolbar>\n }\n\n <!-- action toolbar shown when hover image -->\n @if (!readOnly && !mobile && showCardToolbar) {\n <ion-toolbar #cardToolbar color=\"light\" class=\"buttons\">\n <!-- edit title button -->\n @if (showTitle) {\n <button\n mat-button\n slot=\"start\"\n [disabled]=\"disabled\"\n (click)=\"editTitle($event, row, cardToolbar)\"\n (focusin)=\"focusCardToolbar(cardToolbar)\"\n >\n <mat-icon>edit</mat-icon>\n <mat-label>\n <span *ngIf=\"row.currentData.title | isNotNilOrBlank; else editLabel\" translate>\n IMAGE.GALLERY.BTN_EDIT_TITLE\n </span>\n <ng-template #editLabel>\n <span translate>IMAGE.GALLERY.BTN_ADD_TITLE</span>\n </ng-template>\n </mat-label>\n </button>\n }\n\n <div class=\"flex-spacer\"></div>\n\n <!-- delete button -->\n <button\n mat-icon-button\n slot=\"end\"\n class=\"ion-float-end\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [disabled]=\"disabled\"\n (click)=\"delete($event, row)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </ion-toolbar>\n }\n </ion-card>\n }\n </ion-col>\n\n <!-- add button -->\n @if ((showAddTextButton || showAddCardButton) && !readOnly) {\n <ion-col [size]=\"_colSize\" @fadeInAnimation>\n @if (showAddTextButton) {\n <ion-button (click)=\"add($event)\" [disabled]=\"disabled\" [color]=\"addButtonColor\">\n <mat-label translate>{{ addButtonText }}</mat-label>\n <ion-icon slot=\"end\" name=\"camera\"></ion-icon>\n </ion-button>\n } @else if (showAddCardButton) {\n <ion-card\n class=\"image-card-button\"\n color=\"light\"\n (click)=\"add($event)\"\n [disabled]=\"disabled\"\n [title]=\"!showTooltip ? addButtonText : ''\"\n [matTooltip]=\"showTooltip ? addButtonText : ''\"\n tappable\n >\n <ion-card-content>\n <div class=\"icon-container\">\n <ion-icon name=\"camera\" [color]=\"addButtonColor\"></ion-icon>\n <ion-icon name=\"add\" [color]=\"addButtonColor\" class=\"icon-secondary\"></ion-icon>\n </div>\n </ion-card-content>\n </ion-card>\n }\n </ion-col>\n }\n </ion-row>\n </ion-grid>\n</div>\n\n<!-- FAB button: add -->\n<ion-fab\n slot=\"fixed\"\n vertical=\"bottom\"\n horizontal=\"end\"\n *ngIf=\"showFabButton && !readOnly\"\n [class.cdk-visually-hidden]=\"disabled\"\n @fadeInOutAnimation\n>\n <ion-fab-button color=\"tertiary\" (click)=\"add($event)\">\n <ion-icon name=\"camera\"></ion-icon>\n <ion-icon name=\"add\" class=\"icon-secondary\" style=\"left: 33px; top: 9px; font-size: 20px\"></ion-icon>\n </ion-fab-button>\n</ion-fab>\n\n<!-- Zoom modal -->\n<ion-modal #zoomModal class=\"stack-modal zoom-modal\" [class.modal-fullscreen]=\"mobile\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-content class=\"ion-no-padding ion-no-margin\">\n @let count = rows?.length || 0;\n <!-- Counter -->\n <div class=\"top-left\">\n <ion-label color=\"light\">{{ activeSlideIndex + 1 }} / {{ count }}</ion-label>\n </div>\n\n <!-- zoom button -->\n <div class=\"top-right\">\n @if (!mobile) {\n <ion-fab-button size=\"small\" (click)=\"zoom(modalSwiper, true)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"add\"></ion-icon>\n </ion-fab-button>\n <ion-fab-button size=\"small\" (click)=\"zoom(modalSwiper, false)\" color=\"light\">\n <ion-icon name=\"search\"></ion-icon>\n <ion-icon class=\"icon-secondary\" name=\"remove\"></ion-icon>\n </ion-fab-button>\n }\n </div>\n\n <!-- navigation side buttons next/back -->\n <ng-container *ngIf=\"!mobile && (rows | isArrayLength: { greaterThan: 1 })\">\n <div class=\"side-buttons prev\" (click)=\"slidePrev(modalSwiper)\">\n <ion-icon name=\"chevron-back\"></ion-icon>\n </div>\n <div class=\"side-buttons next\" (click)=\"slideNext(modalSwiper)\">\n <ion-icon name=\"chevron-forward\"></ion-icon>\n </div>\n </ng-container>\n\n <!-- image slides -->\n <swiper-container\n #modalSwiper\n [modules]=\"swiperModules\"\n [zoom]=\"true\"\n (swiperslidechange)=\"onSlideChange(modalSwiper)\"\n (swiperinit)=\"initModalSlides(modalSwiper)\"\n >\n @for (row of rows; track row.id; let last = $last) {\n <swiper-slide>\n @let image = row | propertyGet: 'currentData';\n <div class=\"swiper-zoom-container\">\n @if (image.url) {\n <ion-img [src]=\"image.url | appendQueryParams: getImageSizeQueryParams('modal')\" />\n } @else {\n <img [src]=\"image.dataUrl\" [alt]=\"image.title\" />\n }\n </div>\n </swiper-slide>\n }\n </swiper-container>\n </ion-content>\n\n <ion-footer>\n <ion-toolbar color=\"light\">\n <ion-row>\n <ion-col size=\"4\" class=\"ion-text-start\">\n <ion-button\n *ngIf=\"!readOnly\"\n (click)=\"deleteFromModal($event, modalSwiper)\"\n [disabled]=\"disabled\"\n fill=\"clear\"\n color=\"danger\"\n >\n <mat-icon slot=\"start\">delete</mat-icon>\n {{ 'COMMON.BTN_DELETE' | translate }}\n </ion-button>\n </ion-col>\n <ion-col size=\"4\"></ion-col>\n <ion-col size=\"4\" class=\"ion-text-end\">\n <ion-button (click)=\"zoomModal.dismiss()\" color=\"tertiary\" fill=\"clear\" *ngIf=\"mobile; else desktop\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n <ng-template #desktop>\n <ion-button (click)=\"zoomModal.dismiss()\" color=\"tertiary\">\n {{ 'COMMON.BTN_CLOSE' | translate }}\n </ion-button>\n </ng-template>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n </ion-footer>\n </ng-template>\n</ion-modal>\n\n<ion-popover #titlePopover>\n <ng-template>\n <ion-content class=\"ion-padding\" cdkTrapFocus [cdkTrapFocusAutoCapture]=\"true\">\n <form [formGroup]=\"_titleForm\">\n <mat-form-field floatLabel=\"auto\">\n <mat-label translate>IMAGE.GALLERY.TITLE</mat-label>\n <input\n matInput\n formControlName=\"title\"\n [readonly]=\"disabled\"\n [appAutofocus]=\"_titleAutofocus\"\n (keydown.control.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n />\n </mat-form-field>\n </form>\n </ion-content>\n\n <ion-footer>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"titlePopover.dismiss(null, 'CANCEL')\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n <ion-button\n [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n (keyup.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n color=\"tertiary\"\n [disabled]=\"disabled\"\n [title]=\"!showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n >\n <ion-label translate>COMMON.BTN_VALIDATE</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n </ion-footer>\n </ng-template>\n</ion-popover>\n", styles: [":host{--image-margin-inline: 10px;--image-margin-safe-area: var(--image-margin-inline, 0px) * 2 - 3px;--segment-max-width: 150px}ion-toolbar{position:sticky;top:0}ion-toolbar ion-segment{max-width:var(--segment-max-width)}mat-toolbar,.mat-toolbar-single-row{padding:0;height:42px}ion-card{margin-left:unset;margin-right:unset;margin-inline:var(--image-margin-inline, 10px)}.image-slide{overflow:visible}.image-card{background-color:var(--ion-color-base);z-index:9;--img-scale: 1}.image-card .card-thumbnail{position:relative;overflow:hidden;display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:100%;height:100%;max-height:var(--img-max-height, calc(100% - var(--image-margin-safe-area, 0px)))}.image-card .card-thumbnail>ion-img{min-height:100%;height:100%;object-fit:cover;object-position:center;transition:scale .2s ease-in-out;scale:var(--img-scale)}.image-card .card-thumbnail>ion-img img{max-height:var(--img-max-height)}.image-card.zoom-hover:hover{--img-scale: 1.2}.image-card ion-toolbar{--top-offset: calc(-1 * var(--ion-toolbar-height));position:absolute}.image-card ion-toolbar.title{--padding-start: 16px;--padding-end: 16px;--ion-color-base: rgba(0, 0, 0, .45) !important;position:absolute;top:calc(100% + var(--top-offset));transition:opacity .2s ease-in-out}.image-card ion-toolbar.title ion-label{color:#fff}.image-card ion-toolbar.buttons{--padding-start: 4px;--padding-end: 4px;--ion-color-base: var(--ion-color-light) !important;position:absolute;top:100%;transition:transform .2s ease-in-out}.image-card:hover ion-toolbar.buttons,.image-card ion-toolbar.buttons.focused{transform:translateY(var(--top-offset))}.image-card-button{height:calc(100% - var(--image-margin-safe-area, 0px));max-height:max(min(30vh,200px),var(--img-max-height, 0px));transition:.2s ease-in-out;max-width:250px}.image-card-button ion-card-content{height:100%}.image-card-button .icon-container{height:auto;position:relative;top:calc(50% - 75px);display:flex;align-items:center;justify-content:center}.image-card-button ion-icon:first-of-type{font-size:150px;height:150px;position:relative;top:0}.image-card-button ion-icon.icon-secondary{font-size:50px;position:absolute;top:0;left:max(min(50% + 25px,100% - 50px),0px)}.image-card-button ion-button{float:right}.image-card-button:hover{--ion-color-base: var(--ion-color-shade) !important}.image-card-button:hover ion-icon{--ion-color-base: var(--ion-color-shade) !important}.backdrop{height:100vh;width:100vw;background:#000;position:absolute;z-index:10}.zoom-modal{-webkit-user-select:none;user-select:none}.zoom-modal .top-left{z-index:14;position:absolute;display:inline-grid;left:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}.zoom-modal .top-right{z-index:14;position:absolute;display:inline-grid;right:var(--ion-padding-start, 8px);top:var(--ion-padding-start, 8px)}.zoom-modal .side-buttons{--side-buttons-width: 120px;--side-buttons-margin: calc(var(--side-buttons-width) / 3);z-index:13;position:absolute;top:0;bottom:0;width:var(--side-buttons-width)}.zoom-modal .side-buttons ion-icon{top:50%;color:#fff;font-size:2.5rem;position:absolute;opacity:0;transition-duration:.15s;transition-timing-function:ease-in}.zoom-modal .side-buttons:hover{cursor:pointer}.zoom-modal .side-buttons:hover ion-icon{opacity:1}.zoom-modal .side-buttons.prev{left:0}.zoom-modal .side-buttons.prev ion-icon{left:var(--side-buttons-margin)}.zoom-modal .side-buttons.next{right:0}.zoom-modal .side-buttons.next ion-icon{right:var(--side-buttons-margin)}.zoom-modal swiper-container{height:100%;background:#000}.zoom-modal ion-fab-button .icon-secondary{top:11px;left:6px;height:14px}\n"] }]
|
|
29436
|
+
args: [{ selector: 'app-image-gallery', animations: [fadeInAnimation, fadeInOutAnimation], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-toolbar color=\"light\" class=\"gallery-toolbar\" *ngIf=\"showToolbar && !_inlineSlideshowActive\">\n <ng-content select=\"ion-buttons[slot=start]\"></ng-content>\n\n <!-- Add -->\n @if (!readOnly && showAddToolbarButton && enabled) {\n <button\n mat-icon-button\n [title]=\"!showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_ADD' | translate) : ''\"\n (click)=\"add()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n }\n\n <ng-content select=\"ion-buttons[slot=end]\"></ng-content>\n\n <ion-buttons slot=\"end\">\n <!-- Toggle view mode -->\n <ion-segment (ionChange)=\"toggleViewMode($event)\" color=\"accent\" [value]=\"_colSize\">\n <ion-segment-button [value]=\"4\">\n <mat-icon>view_module</mat-icon>\n </ion-segment-button>\n <ion-segment-button [value]=\"12\">\n <mat-icon>view_list</mat-icon>\n </ion-segment-button>\n </ion-segment>\n </ion-buttons>\n</ion-toolbar>\n\n<div\n class=\"gallery-container ion-padding-bottom {{ mode }}\"\n [class.inline-zoom-active]=\"_inlineSlideshowActive\"\n [style.--inline-zoom-height]=\"inlineZoomHeight\"\n>\n <div *ngIf=\"mobile && zoomActive\" class=\"backdrop\" [style.opacity]=\"zoomScale\"></div>\n\n <ion-grid class=\"ion-no-padding\" [class.cdk-visually-hidden]=\"_inlineSlideshowActive\">\n <ion-row>\n <ion-col *rxFor=\"let row of _data$; index as index; trackBy: trackByFn\" [size]=\"_colSize\">\n @let image = row | propertyGet: 'currentData';\n @if (cardTemplate) {\n <ng-container\n *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, mode: mode, image: image }\"\n ></ng-container>\n } @else {\n <ion-card\n #card\n class=\"image-card\"\n [class.zoom-hover]=\"!mobile\"\n [color]=\"cardColor\"\n (click)=\"clickRow(row, index)\"\n tappable\n @fadeInAnimation\n >\n <figure class=\"card-thumbnail\">\n @if (image.url) {\n <ion-img [src]=\"image.url | appendQueryParams: getImageSizeQueryParams()\" />\n } @else {\n <ion-img [src]=\"image.dataUrl\" />\n }\n </figure>\n\n <!-- toolbar for title (when not hover) -->\n @if (showTitle && image.title) {\n <ion-toolbar color=\"transparent\" class=\"title\">\n <ion-label class=\"card-title\">{{ image.title }}</ion-label>\n </ion-toolbar>\n }\n\n <!-- action toolbar shown when hover image -->\n @if (!readOnly && !mobile && showCardToolbar) {\n <ion-toolbar #cardToolbar color=\"light\" class=\"buttons\">\n <!-- edit title button -->\n @if (showTitle) {\n <button\n mat-button\n slot=\"start\"\n [disabled]=\"disabled\"\n (click)=\"editTitle($event, row, cardToolbar)\"\n (focusin)=\"focusCardToolbar(cardToolbar)\"\n >\n <mat-icon>edit</mat-icon>\n <mat-label>\n <span *ngIf=\"row.currentData.title | isNotNilOrBlank; else editLabel\" translate>\n IMAGE.GALLERY.BTN_EDIT_TITLE\n </span>\n <ng-template #editLabel>\n <span translate>IMAGE.GALLERY.BTN_ADD_TITLE</span>\n </ng-template>\n </mat-label>\n </button>\n }\n\n <div class=\"flex-spacer\"></div>\n\n <!-- delete button -->\n <button\n mat-icon-button\n slot=\"end\"\n class=\"ion-float-end\"\n [title]=\"!showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_DELETE' | translate) : ''\"\n [disabled]=\"disabled\"\n (click)=\"delete($event, row)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </ion-toolbar>\n }\n </ion-card>\n }\n </ion-col>\n\n <!-- add button -->\n @if ((showAddTextButton || showAddCardButton) && !readOnly) {\n <ion-col [size]=\"_colSize\" @fadeInAnimation>\n @if (showAddTextButton) {\n <ion-button (click)=\"add($event)\" [disabled]=\"disabled\" [color]=\"addButtonColor\">\n <mat-label translate>{{ addButtonText }}</mat-label>\n <ion-icon slot=\"end\" name=\"camera\"></ion-icon>\n </ion-button>\n } @else if (showAddCardButton) {\n <ion-card\n class=\"image-card-button\"\n color=\"light\"\n (click)=\"add($event)\"\n [disabled]=\"disabled\"\n [title]=\"!showTooltip ? addButtonText : ''\"\n [matTooltip]=\"showTooltip ? addButtonText : ''\"\n tappable\n >\n <ion-card-content>\n <div class=\"icon-container\">\n <ion-icon name=\"camera\" [color]=\"addButtonColor\"></ion-icon>\n <ion-icon name=\"add\" [color]=\"addButtonColor\" class=\"icon-secondary\"></ion-icon>\n </div>\n </ion-card-content>\n </ion-card>\n }\n </ion-col>\n }\n </ion-row>\n </ion-grid>\n\n <!-- Inline slideshow: shown as an overlay directly within the gallery, instead of a full-screen modal.\n The rest of the page (outside the gallery) stays fully visible and usable, unlike the modal's backdrop. -->\n @if (zoomPresentation === 'inline' && _inlineSlideshowActive) {\n <app-image-gallery-slideshow\n class=\"inline-overlay\"\n @fadeInOutAnimation\n [rows]=\"rows\"\n [selectedIndex]=\"_selectedRowIndex\"\n [mobile]=\"mobile\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [debug]=\"debug\"\n [mouseZoomEnabled]=\"_mouseZoomEnabled\"\n [wheelZoomStep]=\"wheelZoomStep\"\n [maxZoomRatio]=\"maxZoomRatio\"\n [rotationEnabled]=\"_rotationEnabled\"\n [cropEnabled]=\"_cropEnabled\"\n [imageExportFormat]=\"imageExportFormat\"\n [imageExportQuality]=\"imageExportQuality\"\n [imageSizeQueryParam]=\"imageSizeQueryParam\"\n [modalImageSize]=\"imageSizes?.modal\"\n (deleteRequested)=\"onSlideshowDeleteRequested($event)\"\n (closeRequested)=\"closeInlineSlideshow()\"\n (afterEditImage)=\"onAfterEditImage.emit($event)\"\n (afterEditRow)=\"onAfterEditRow.emit($event)\"\n ></app-image-gallery-slideshow>\n }\n</div>\n\n<!-- FAB button: add -->\n<ion-fab\n slot=\"fixed\"\n vertical=\"bottom\"\n horizontal=\"end\"\n *ngIf=\"showFabButton && !readOnly\"\n [class.cdk-visually-hidden]=\"disabled\"\n @fadeInOutAnimation\n>\n <ion-fab-button color=\"tertiary\" (click)=\"add($event)\">\n <ion-icon name=\"camera\"></ion-icon>\n <ion-icon name=\"add\" class=\"icon-secondary\" style=\"left: 33px; top: 9px; font-size: 20px\"></ion-icon>\n </ion-fab-button>\n</ion-fab>\n\n<!-- Zoom modal -->\n@if (zoomPresentation === 'modal') {\n <ion-modal #zoomModal class=\"stack-modal zoom-modal\" [class.modal-fullscreen]=\"mobile\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <app-image-gallery-slideshow\n [rows]=\"rows\"\n [selectedIndex]=\"_selectedRowIndex\"\n [mobile]=\"mobile\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [debug]=\"debug\"\n [mouseZoomEnabled]=\"_mouseZoomEnabled\"\n [wheelZoomStep]=\"wheelZoomStep\"\n [maxZoomRatio]=\"maxZoomRatio\"\n [rotationEnabled]=\"_rotationEnabled\"\n [cropEnabled]=\"_cropEnabled\"\n [imageExportFormat]=\"imageExportFormat\"\n [imageExportQuality]=\"imageExportQuality\"\n [imageSizeQueryParam]=\"imageSizeQueryParam\"\n [modalImageSize]=\"imageSizes?.modal\"\n (deleteRequested)=\"onSlideshowDeleteRequested($event)\"\n (closeRequested)=\"zoomModal.dismiss()\"\n (afterEditImage)=\"onAfterEditImage.emit($event)\"\n (afterEditRow)=\"onAfterEditRow.emit($event)\"\n ></app-image-gallery-slideshow>\n </ng-template>\n </ion-modal>\n}\n\n<ion-popover #titlePopover>\n <ng-template>\n <ion-content class=\"ion-padding\" cdkTrapFocus [cdkTrapFocusAutoCapture]=\"true\">\n <form [formGroup]=\"_titleForm\">\n <mat-form-field floatLabel=\"auto\">\n <mat-label translate>IMAGE.GALLERY.TITLE</mat-label>\n <input\n matInput\n formControlName=\"title\"\n [readonly]=\"disabled\"\n [appAutofocus]=\"_titleAutofocus\"\n (keydown.control.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n />\n </mat-form-field>\n </form>\n </ion-content>\n\n <ion-footer>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"titlePopover.dismiss(null, 'CANCEL')\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n <ion-button\n [fill]=\"disabled ? 'clear' : 'solid'\"\n (click)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n (keyup.enter)=\"titlePopover.dismiss(_titleForm.controls.title.value)\"\n color=\"tertiary\"\n [disabled]=\"disabled\"\n [title]=\"!showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n [matTooltip]=\"showTooltip ? ('COMMON.BTN_VALIDATE_WITH_SHORTCUT_HELP' | translate) : ''\"\n >\n <ion-label translate>COMMON.BTN_VALIDATE</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n </ion-footer>\n </ng-template>\n</ion-popover>\n", styles: [":host{--image-margin-inline: 10px;--image-margin-safe-area: var(--image-margin-inline, 0px) * 2 - 3px;--segment-max-width: 150px}ion-toolbar{position:sticky;top:0}ion-toolbar ion-segment{max-width:var(--segment-max-width)}mat-toolbar,.mat-toolbar-single-row{padding:0;height:42px}ion-card{margin-left:unset;margin-right:unset;margin-inline:var(--image-margin-inline, 10px)}.image-slide{overflow:visible}.image-card{background-color:var(--ion-color-base);z-index:9;--img-scale: 1}.image-card .card-thumbnail{position:relative;overflow:hidden;display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:100%;height:100%;max-height:var(--img-max-height, calc(100% - var(--image-margin-safe-area, 0px)))}.image-card .card-thumbnail>ion-img{min-height:100%;height:100%;object-fit:cover;object-position:center;transition:scale .2s ease-in-out;scale:var(--img-scale)}.image-card .card-thumbnail>ion-img img{max-height:var(--img-max-height)}.image-card.zoom-hover:hover{--img-scale: 1.2}.image-card ion-toolbar{--top-offset: calc(-1 * var(--ion-toolbar-height));position:absolute}.image-card ion-toolbar.title{--padding-start: 16px;--padding-end: 16px;--ion-color-base: rgba(0, 0, 0, .45) !important;position:absolute;top:calc(100% + var(--top-offset));transition:opacity .2s ease-in-out}.image-card ion-toolbar.title ion-label{color:#fff}.image-card ion-toolbar.buttons{--padding-start: 4px;--padding-end: 4px;--ion-color-base: var(--ion-color-light) !important;position:absolute;top:100%;transition:transform .2s ease-in-out}.image-card:hover ion-toolbar.buttons,.image-card ion-toolbar.buttons.focused{transform:translateY(var(--top-offset))}.image-card-button{height:calc(100% - var(--image-margin-safe-area, 0px));max-height:max(min(30vh,200px),var(--img-max-height, 0px));transition:.2s ease-in-out;max-width:250px}.image-card-button ion-card-content{height:100%}.image-card-button .icon-container{height:auto;position:relative;top:calc(50% - 75px);display:flex;align-items:center;justify-content:center}.image-card-button ion-icon:first-of-type{font-size:150px;height:150px;position:relative;top:0}.image-card-button ion-icon.icon-secondary{font-size:50px;position:absolute;top:0;left:max(min(50% + 25px,100% - 50px),0px)}.image-card-button ion-button{float:right}.image-card-button:hover{--ion-color-base: var(--ion-color-shade) !important}.image-card-button:hover ion-icon{--ion-color-base: var(--ion-color-shade) !important}.backdrop{height:100vh;width:100vw;background:#000;position:absolute;z-index:10}.gallery-container{position:relative}.gallery-container.inline-zoom-active{min-height:var(--inline-zoom-height, 60vh)}.inline-overlay{position:absolute;inset:0;z-index:20;background:#000}\n"] }]
|
|
28786
29437
|
}], ctorParameters: () => [{ type: ImageService }, { type: i2$1.Platform }, { type: i2$1.AlertController }, { type: i1$1.TranslateService }, { type: i0.ChangeDetectorRef }, { type: Environment, decorators: [{
|
|
28787
29438
|
type: Optional
|
|
28788
29439
|
}, {
|
|
@@ -28837,6 +29488,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
28837
29488
|
type: Input
|
|
28838
29489
|
}], imageSizeQueryParam: [{
|
|
28839
29490
|
type: Input
|
|
29491
|
+
}], enableMouseZoom: [{
|
|
29492
|
+
type: Input,
|
|
29493
|
+
args: [{ transform: booleanAttribute }]
|
|
29494
|
+
}], wheelZoomStep: [{
|
|
29495
|
+
type: Input
|
|
29496
|
+
}], maxZoomRatio: [{
|
|
29497
|
+
type: Input
|
|
29498
|
+
}], enableRotation: [{
|
|
29499
|
+
type: Input,
|
|
29500
|
+
args: [{ transform: booleanAttribute }]
|
|
29501
|
+
}], enableCrop: [{
|
|
29502
|
+
type: Input,
|
|
29503
|
+
args: [{ transform: booleanAttribute }]
|
|
29504
|
+
}], imageExportFormat: [{
|
|
29505
|
+
type: Input
|
|
29506
|
+
}], imageExportQuality: [{
|
|
29507
|
+
type: Input
|
|
29508
|
+
}], zoomPresentation: [{
|
|
29509
|
+
type: Input
|
|
29510
|
+
}], inlineZoomHeight: [{
|
|
29511
|
+
type: Input
|
|
28840
29512
|
}], dataSource: [{
|
|
28841
29513
|
type: Input
|
|
28842
29514
|
}], zoomModal: [{
|
|
@@ -28845,15 +29517,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
28845
29517
|
}], titlePopover: [{
|
|
28846
29518
|
type: ViewChild,
|
|
28847
29519
|
args: ['titlePopover']
|
|
28848
|
-
}], swiperRef: [{
|
|
28849
|
-
type: ViewChild,
|
|
28850
|
-
args: ['modalSwiper']
|
|
28851
29520
|
}], onBeforeDeleteRows: [{
|
|
28852
29521
|
type: Output
|
|
28853
29522
|
}], onAfterAddRows: [{
|
|
28854
29523
|
type: Output
|
|
28855
29524
|
}], onAfterEditRow: [{
|
|
28856
29525
|
type: Output
|
|
29526
|
+
}], onAfterEditImage: [{
|
|
29527
|
+
type: Output
|
|
28857
29528
|
}], click: [{
|
|
28858
29529
|
type: Output
|
|
28859
29530
|
}] } });
|
|
@@ -28862,7 +29533,8 @@ class ImageGalleryModule {
|
|
|
28862
29533
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageGalleryModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
28863
29534
|
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: ImageGalleryModule, declarations: [
|
|
28864
29535
|
// Components
|
|
28865
|
-
AppImageGalleryComponent
|
|
29536
|
+
AppImageGalleryComponent,
|
|
29537
|
+
AppImageGallerySlideshowComponent], imports: [CommonModule,
|
|
28866
29538
|
IonicModule,
|
|
28867
29539
|
ReactiveFormsModule,
|
|
28868
29540
|
SharedMaterialModule,
|
|
@@ -28870,7 +29542,8 @@ class ImageGalleryModule {
|
|
|
28870
29542
|
SharedDirectivesModule, i1$1.TranslateModule, RxStateModule], exports: [IonicModule,
|
|
28871
29543
|
TranslateModule,
|
|
28872
29544
|
// Components
|
|
28873
|
-
AppImageGalleryComponent
|
|
29545
|
+
AppImageGalleryComponent,
|
|
29546
|
+
AppImageGallerySlideshowComponent] });
|
|
28874
29547
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageGalleryModule, imports: [CommonModule,
|
|
28875
29548
|
IonicModule,
|
|
28876
29549
|
ReactiveFormsModule,
|
|
@@ -28898,12 +29571,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
28898
29571
|
declarations: [
|
|
28899
29572
|
// Components
|
|
28900
29573
|
AppImageGalleryComponent,
|
|
29574
|
+
AppImageGallerySlideshowComponent,
|
|
28901
29575
|
],
|
|
28902
29576
|
exports: [
|
|
28903
29577
|
IonicModule,
|
|
28904
29578
|
TranslateModule,
|
|
28905
29579
|
// Components
|
|
28906
29580
|
AppImageGalleryComponent,
|
|
29581
|
+
AppImageGallerySlideshowComponent,
|
|
28907
29582
|
],
|
|
28908
29583
|
}]
|
|
28909
29584
|
}] });
|
|
@@ -51169,16 +51844,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
51169
51844
|
|
|
51170
51845
|
class GalleryTestPage {
|
|
51171
51846
|
dataService;
|
|
51847
|
+
route;
|
|
51172
51848
|
_subscription = new Subscription();
|
|
51173
51849
|
dataSource;
|
|
51174
|
-
|
|
51850
|
+
zoomPresentation = 'modal';
|
|
51851
|
+
debug = false;
|
|
51852
|
+
constructor(dataService, route) {
|
|
51175
51853
|
this.dataService = dataService;
|
|
51854
|
+
this.route = route;
|
|
51176
51855
|
this.dataSource = new EntitiesTableDataSource(ImageAttachment, this.dataService, null, {
|
|
51177
51856
|
saveBeforeDelete: true,
|
|
51178
51857
|
saveOnlyDirtyRows: false,
|
|
51179
51858
|
});
|
|
51859
|
+
this.debug = this.route.snapshot.queryParamMap.get('debug') === 'true';
|
|
51180
51860
|
}
|
|
51181
51861
|
ngOnInit() {
|
|
51862
|
+
this.zoomPresentation = this.route.snapshot.queryParamMap.get('zoomPresentation') || 'modal';
|
|
51182
51863
|
this._subscription.add(this.dataSource.watchAll(0, 100, null, null, null).subscribe());
|
|
51183
51864
|
}
|
|
51184
51865
|
ngOnDestroy() {
|
|
@@ -51187,13 +51868,13 @@ class GalleryTestPage {
|
|
|
51187
51868
|
save() {
|
|
51188
51869
|
this.dataSource.save();
|
|
51189
51870
|
}
|
|
51190
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GalleryTestPage, deps: [{ token: ImageAttachmentService }], target: i0.ɵɵFactoryTarget.Component });
|
|
51191
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
51871
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GalleryTestPage, deps: [{ token: ImageAttachmentService }, { token: i1$7.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component });
|
|
51872
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: GalleryTestPage, selector: "app-gallery-test-page", providers: [{ provide: ImageAttachmentService, useClass: ImageAttachmentService }], ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Image gallery</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <!-- DEBUG -->\n @if (debug) {\n <app-debug title=\"Galley\">\n <ion-grid>\n <ion-row>\n <ion-col>gallery.count: {{ gallery.rows?.length }}</ion-col>\n </ion-row>\n </ion-grid>\n </app-debug>\n }\n\n <app-image-gallery\n #gallery\n [dataSource]=\"dataSource\"\n [zoomPresentation]=\"zoomPresentation\"\n [inlineZoomHeight]=\"'100%'\"\n (onAfterAddRows)=\"save()\"\n (onAfterEditRow)=\"save()\"\n [debug]=\"debug\"\n ></app-image-gallery>\n</ion-content>\n", dependencies: [{ kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonBackButton, selector: "ion-back-button" }, { kind: "component", type: AppImageGalleryComponent, selector: "app-image-gallery", inputs: ["cardColor", "debug", "disabled", "readOnly", "mobile", "mode", "confirmBeforeDelete", "showToolbar", "showFabButton", "showTitle", "showAddToolbarButton", "showAddTextButton", "showAddCardButton", "showCardToolbar", "addButtonColor", "addButtonText", "cardTemplate", "imageSizes", "imageSizeQueryParam", "enableMouseZoom", "wheelZoomStep", "maxZoomRatio", "enableRotation", "enableCrop", "imageExportFormat", "imageExportQuality", "zoomPresentation", "inlineZoomHeight", "dataSource"], outputs: ["onBeforeDeleteRows", "onAfterAddRows", "onAfterEditRow", "onAfterEditImage", "click"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }] });
|
|
51192
51873
|
}
|
|
51193
51874
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: GalleryTestPage, decorators: [{
|
|
51194
51875
|
type: Component,
|
|
51195
|
-
args: [{ selector: 'app-gallery-test-page', providers: [{ provide: ImageAttachmentService, useClass: ImageAttachmentService }], template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Image gallery</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <!-- DEBUG -->\n <app-debug title=\"Galley\">\n
|
|
51196
|
-
}], ctorParameters: () => [{ type: ImageAttachmentService }] });
|
|
51876
|
+
args: [{ selector: 'app-gallery-test-page', providers: [{ provide: ImageAttachmentService, useClass: ImageAttachmentService }], template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Image gallery</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <!-- DEBUG -->\n @if (debug) {\n <app-debug title=\"Galley\">\n <ion-grid>\n <ion-row>\n <ion-col>gallery.count: {{ gallery.rows?.length }}</ion-col>\n </ion-row>\n </ion-grid>\n </app-debug>\n }\n\n <app-image-gallery\n #gallery\n [dataSource]=\"dataSource\"\n [zoomPresentation]=\"zoomPresentation\"\n [inlineZoomHeight]=\"'100%'\"\n (onAfterAddRows)=\"save()\"\n (onAfterEditRow)=\"save()\"\n [debug]=\"debug\"\n ></app-image-gallery>\n</ion-content>\n" }]
|
|
51877
|
+
}], ctorParameters: () => [{ type: ImageAttachmentService }, { type: i1$7.ActivatedRoute }] });
|
|
51197
51878
|
|
|
51198
51879
|
const routes$8 = [
|
|
51199
51880
|
{
|
|
@@ -53931,5 +54612,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
53931
54612
|
* Generated bundle index. Do not edit.
|
|
53932
54613
|
*/
|
|
53933
54614
|
|
|
53934
|
-
export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_ACCOUNT_SERVICE, APP_ACCOUNT_SERVICE_OPTIONS, APP_CELL_SELECTION_SERVICE_CONFIG_TOKEN, APP_CELL_SELECTION_SERVICE_TOKEN, APP_CONFIG_OPTIONS, APP_DEBUG_DATA_SERVICE, APP_FEED_SERVICE, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_FRAGMENTS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_HOME_CONFIG, APP_HOME_TOOLBAR_BUTTONS, APP_HOTKEYS_CONFIG, APP_JOB_PROGRESSION_SERVICE, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_LOGGING_SERVICE, APP_MENU_ITEMS, APP_MENU_OPTIONS, APP_NAMED_FILTER_SERVICE, APP_PERSON_SERVICE, APP_PERSON_SERVICE_OPTIONS, APP_PROGRESS_BAR_SERVICE, APP_SETTINGS_MENU_ITEMS, APP_SHOW_TOOLTIP, APP_STORAGE, APP_STORAGE_EXPLORER_PROTECTED_KEYS, APP_TESTING_PAGES, APP_USER_EVENT_LIST_INFINITE_SCROLL_THRESHOLD, APP_USER_EVENT_SERVICE, APP_USER_SETTINGS_OPTIONS, APP_USER_TOKEN_SCOPES, AboutModal, AbstractNamedFilterService, AbstractPersonService, AbstractSelectionModelPipe, AbstractTableSelectionPipe, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, AccountUtils, ActionsColumnComponent, AdminModule, AdminRoutingModule, AdminUsersModule, Alerts, AndroidOsEnvironment, AppAboutModalModule, AppAccountModule, AppAsyncTable, AppAuthForm, AppAuthModal, AppAuthModule, AppChangePasswordModule, AppChangePasswordPage, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppEntityFormModule, AppForm, AppFormArray, AppFormButtonsBarModule, AppFormContainer, AppFormField, AppFormModule, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHomePageModule, AppIconComponent, AppIconModule, AppImageGalleryComponent, AppInMemoryTable, AppInstallUpgradeCard, AppInstallUpgradeCardModule, AppListForm, AppListFormModule, AppLoadingSpinner, AppMarkdownContent, AppMarkdownModal, AppMenuModule, AppNullForm, AppPropertiesForm, AppPropertiesFormModule, AppPropertiesTable, AppPropertiesUtils, AppPropertyUtils, AppRegisterModule, AppResetPasswordModal, AppRowField, AppSelectPeerModule, AppSelectUsersModal, AppSettingsPageModule, AppTabEditor, AppTabEditorOptions, AppTable, AppTableModule, AppTableUtils, AppTextFormModule, AppTextPopoverModule, AppUpdateOfflineModeCard, AppUpdateOfflineModeCardModule, AppValidatorService, AppendQueryParamsPipePipe, ArrayDistinctPipe, ArrayFilterPipe, ArrayFindByPropertyPipe, ArrayFirstPipe, ArrayFormTestPage, ArrayIncludesPipe, ArrayJoinPipe, ArrayLastPipe, ArrayLengthPipe, ArrayMapPipe, ArrayPluckPipe, ArraySlicePipe, ArraySortPipe, AsAnyPipe, AsArrayPipe, AsBooleanPipe, AsFloatLabelTypePipe, AsObservablePipe, AudioProvider, AudioTestingModule, AudioTestingPage, AuthGuardService, AutoResizeDirective, AutoTitleDirective, AutoTooltipDirective, AutocompleteTestPage, AutofocusDirective, BadgeDirective, BadgeNumberPipe, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, BooleanFormatPipe, BooleanTestPage, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CapitalizePipe, CellIdentifierDirective, CellSelectionDirective, CellSelectionService, CellValueChangeListener, ChangeCaseToUnderscorePipe, ChangePasswordForm, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigFragments, ConfigService, Configuration, CoreModule, CorePipesModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_ISO_PATTERNS, DATE_MATCH_REGEXP, DATE_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_JOIN_ARRAY_VALUES_SEPARATOR, DEFAULT_JOIN_PROPERTIES_SEPARATOR, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFormatService, DateFromNowPipe, DateFromPipe, DateShortTestPage, DateTestPage, DateTimeTestPage, DateUtils, DebugComponent, Department, DepartmentToStringPipe, DisplayWithPipe, DragAndDropDirective, DurationPipe, DurationTestPage, ED25519_SEED_LENGTH, EMPTY_PLACEHOLDER_CHAR, EMPTY_PLACEHOLDER_CHAR_REGEXP_GLOBAL, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesAsyncTableDataSource, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, EnvironmentHttpLoader, EnvironmentLoader, ErrorCodes, EvenPipe, FeedDirective, FeedModule, FeedPage, FeedService, FeedsComponent, FileResponse, FileService, FileSizePipe, FilesUtils, FirstFalsePipe, FirstPipe, FirstTruePipe, FormArrayAtControlPipe, FormArrayAtGroupPipe, FormArrayHelper, FormArrayTestModule, FormButtonsBarComponent, FormButtonsBarToken, FormErrorPipe, FormErrorTranslatePipe, FormErrorTranslator, FormFieldDefinitionUtils, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetNamePipe, FormGetPipe, FormGetValuePipe, GalleryTestPage, GeolocationUtils, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, IMAGE_DEFAULTS, IPosition, ImageAttachment, ImageAttachmentFilter, ImageAttachmentService, ImageGalleryModule, ImageGalleryTestingModule, ImageModule, ImageService, ImagesUtils, InMemoryEntitiesService, IsAllSelectedPipe, IsEmptySelectionPipe, IsLoginAccountPipe, IsMultipleSelectionPipe, IsNilOrBlankPipe, IsNilOrNaNPipe, IsNilPipe, IsNotAllSelectedPipe, IsNotEmptySelectionPipe, IsNotNilOrBlankPipe, IsNotNilOrNaNPipe, IsNotNilPipe, IsOnDeskPipe, IsOnFieldPipe, IsSelectedPipe, IsSingleSelectionPipe, IsValidDatePipe, JobModule, JobProgression, JobProgressionComponent, JobProgressionIcon, JobProgressionList, JobProgressionService, JobProgressionTestService, JobProgressionTestingPage, JobTestingModule, JobUtils, JsonFeedUtils, JsonUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_PATTERNS, LAT_LONG_PATTERN_MAX_DECIMALS, LAT_LONG_VALUE_MAX_DECIMALS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LogLevel, LogUtils, Logger, LoggingService, LoggingServiceModule, LongitudeFormatPipe, MASKS, MASK_RANGES, MAT_FORM_FIELD_DEFAULT_APPEARANCE, MAT_FORM_FIELD_DEFAULT_SUBSCRIPT_SIZING, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MOMENT_NO_TIME_PROPERTY, MapGetPipe, MapKeysPipe, MapPipe, MapToPipe, MapValuesPipe, MarkdownDirective, MarkdownService, MarkdownTestPage, MarkdownTestingModule, MarkdownUtils, MaskitoPlaceholderPipe, MaskitoTestPage, MatAutocompleteConfigHolder, MatAutocompleteField, MatAutocompleteFieldUtils, MatBadgeTestPage, MatBooleanField, MatChipsField, MatColorPipe, MatCommonTestPage, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatLatLongFieldInput, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialAutocompleteFooterDirective, MaterialAutocompleteHeaderDirective, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItem, MenuItems, MenuOptions, MenuService, MenuTestingModule, MenuTestingPage, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NETWORK_DEFAULT_CONNECTION_TIMEOUT, NamedFilter, NamedFilterFilter, NamedFilterSelector, NamedFilterSelectorTestingModule, NamedFilterSelectorTestingPage, NavActionsColumnComponent, NestedTableTestPage, NetworkService, NetworkUtils, NewTokenForm, NewTokenModal, NgInitDirective, NgVarDirective, NoHtmlPipe, NotEmptyArrayPipe, NumberFormatPipe, ObservableTestPage, OddPipe, OtherMenuTestingPage, PEER_URL_REGEXP, PLUS_PLACEHOLDER_CHAR_REGEXP_GLOBAL, PRINT_ID_QUERY_PARAM, PRINT_LOADING_STORAGE_KEY_PREFIX, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFilterAdditionalFields, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, PrintService, ProgressBarService, ProgressInterceptor, PropertiesFormTestPage, PropertiesFormTestingModule, PropertyEntity, PropertyEntityFilter, PropertyEntityValidator, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialToStringPipe, ReferentialUtils, ReferentialValidatorService, ReferentialsToStringPipe, RegExpUtils, RegisterConfirmPage, RegisterForm, RegisterModal, ResizableComponent, ResizableDirective, ResizableModule, RoundPipe, RxStateComputed, RxStateModule, RxStateOutput, RxStateProperty, RxStateRegister, RxStateSelect, SCRYPT_PARAMS, SETTINGS_COMPACT_ROWS, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_STORAGE_TESTING_PAGES, SHARED_TESTING_PAGES, SOCIAL_CONFIG_OPTIONS, SOCIAL_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SPACE_PLACEHOLDER_CHAR_REGEXP_GLOBAL, STARTUP_DATA_STORAGE_KEY, SafeHtmlPipe, SafeStylePipe, SelectPeerModal, SelectionLengthPipe, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedBadgeModule, SharedDebugModule, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMarkdownModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedNamedFilterModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedToolbarModule, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, SocialTestingModule, Software, SplitArrayInChunksPipe, StartableService, StartupService, StatusById, StatusIds, StatusList, StorageDrivers, StorageExplorerComponent, StorageExplorerModule, StorageExplorerTestingModule, StorageExplorerTestingRoutingModule, StorageService, StrIncludesPipe, StrLengthPipe, StrReplacePipe, SubMenuTabDirective, SwipeTestPage, TABLE_SETTINGS_ENUM, TOOLBAR_HEADER_ID, Table2TestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TableValidatorService, TextForm, TextFormTestingModule, TextFormTestingPage, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ThrottledClickDirective, ToStringPipe, ToastTestingModule, ToastTestingPage, Toasts, TokenScope, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, TreeItemEntityUtils, TruncHtmlPipe, TruncTextPipe, TruncateHtmlPipe, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UrlUtils, UserEventModule, UserEventNotificationIcon, UserEventNotificationList, UserEventNotificationModal, UserEventTestService, UserEventTestingModule, UserEventTestingPage, UserSettings, UserToken, UserTokenTable, UsersPage, UsersUtils, ValueFormatPipe, VersionUtils, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayResize, arraySize, asInputElement, assignSkipUndefined, base64ArrayBuffer, booleanToString, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, collectByProperty, collectByPropertyPath, compareValues, compareValuesDesc, compareVersionNumbers, composeComparators, computeDecimalDegrees, computeDecimalPart, copyEntity2Form, createAppStartupInitializer, createPromiseEvent, createPromiseEventEmitter, decorateWithTakeUntil, deepMergeSkipUndefined, departmentToString, departmentsToString, disableAndClearControl, disableAndClearControls, disableControl, disableControls, emitPromiseEvent, enableControl, enableControls, enableRxStateProdMode, entityToString, equals, equalsOrNil, escapeRegExp, expansionAnimation, fadeInAnimation, fadeInOutAnimation, fadeInSlowAnimation, filterFalse, filterFormErrors, filterFormErrorsByPath, filterFormErrorsByPrefix, filterNotNil, filterNumberInput, filterTrue, findParentWithClass, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatLong, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorLuminance, getColorShade, getColorTint, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getInputRangeFromCaretIndex, getInputSelectionRangesFromMask, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, getRandomImageWithCredit, getUserAgent, hexToRgb, hexToRgbArray, initArrayControlsFromValues, interpolateString, intersectArrays, isAndroid, isBlankString, isCapacitor, isChrome, isControlHasInput, isEdge, isEmptyArray, isEntityService, isFirefox, isFocusableElement, isIOS, isInputElement, isInstanceOf, isInt, isIpad, isLightColor, isMacOS, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilObject, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isOnFieldMode, isPrint, isProgressEvent, isPromise, isResponseEvent, isSafari, isSameVersion, isStartableService, isTouchUi, isVersionCompatible, isWindows, joinProperties, joinPropertiesPath, lastArrayValue, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, maskitoAutoSelectByMaskPattern, maskitoPrefixPlugin, matchMedia, matchUpperCase, mergeLoadResult, mergeObjectsWithoutUndefined, mixHex, moveInputCaretToSeparator, newArray, noHtml, noTrailingSlash, notNilOrDefault, nullIfNilOrBlank, nullIfUndefined, numberOrNilAttribute, numberToString, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, provideAccountService, providePersonService, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputContentFromEvent, selectInputRange, setCalculatedValue, setControlEnabled, setControlRequired, setControlsEnabled, setFormErrors, setPropertyByPath, setTabIndex, sleep, slideDownAnimation, slideInAnimation, slideInOutAnimation, slideUpDownAnimation, sort, splitArrayInChunks, splitById, splitByProperty, splitDegreesToDDArray, splitDegreesToDDMMArray, splitDegreesToDDMMSSArray, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toLoadData, toLoadResult, toNotNil, toNumber, trimEmptyToNull, truncateHtml, uncapitalizeFirstLetter, undefinedIfNull, underscoreToChangeCase, updateValueAndValidity, waitFor, waitForFalse, waitForTrue, waitIdle, waitWhilePending };
|
|
54615
|
+
export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_ACCOUNT_SERVICE, APP_ACCOUNT_SERVICE_OPTIONS, APP_CELL_SELECTION_SERVICE_CONFIG_TOKEN, APP_CELL_SELECTION_SERVICE_TOKEN, APP_CONFIG_OPTIONS, APP_DEBUG_DATA_SERVICE, APP_FEED_SERVICE, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_FRAGMENTS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_HOME_CONFIG, APP_HOME_TOOLBAR_BUTTONS, APP_HOTKEYS_CONFIG, APP_JOB_PROGRESSION_SERVICE, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_LOGGING_SERVICE, APP_MENU_ITEMS, APP_MENU_OPTIONS, APP_NAMED_FILTER_SERVICE, APP_PERSON_SERVICE, APP_PERSON_SERVICE_OPTIONS, APP_PROGRESS_BAR_SERVICE, APP_SETTINGS_MENU_ITEMS, APP_SHOW_TOOLTIP, APP_STORAGE, APP_STORAGE_EXPLORER_PROTECTED_KEYS, APP_TESTING_PAGES, APP_USER_EVENT_LIST_INFINITE_SCROLL_THRESHOLD, APP_USER_EVENT_SERVICE, APP_USER_SETTINGS_OPTIONS, APP_USER_TOKEN_SCOPES, AboutModal, AbstractNamedFilterService, AbstractPersonService, AbstractSelectionModelPipe, AbstractTableSelectionPipe, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, AccountUtils, ActionsColumnComponent, AdminModule, AdminRoutingModule, AdminUsersModule, Alerts, AndroidOsEnvironment, AppAboutModalModule, AppAccountModule, AppAsyncTable, AppAuthForm, AppAuthModal, AppAuthModule, AppChangePasswordModule, AppChangePasswordPage, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppEntityFormModule, AppForm, AppFormArray, AppFormButtonsBarModule, AppFormContainer, AppFormField, AppFormModule, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHomePageModule, AppIconComponent, AppIconModule, AppImageGalleryComponent, AppImageGallerySlideshowComponent, AppInMemoryTable, AppInstallUpgradeCard, AppInstallUpgradeCardModule, AppListForm, AppListFormModule, AppLoadingSpinner, AppMarkdownContent, AppMarkdownModal, AppMenuModule, AppNullForm, AppPropertiesForm, AppPropertiesFormModule, AppPropertiesTable, AppPropertiesUtils, AppPropertyUtils, AppRegisterModule, AppResetPasswordModal, AppRowField, AppSelectPeerModule, AppSelectUsersModal, AppSettingsPageModule, AppTabEditor, AppTabEditorOptions, AppTable, AppTableModule, AppTableUtils, AppTextFormModule, AppTextPopoverModule, AppUpdateOfflineModeCard, AppUpdateOfflineModeCardModule, AppValidatorService, AppendQueryParamsPipePipe, ArrayDistinctPipe, ArrayFilterPipe, ArrayFindByPropertyPipe, ArrayFirstPipe, ArrayFormTestPage, ArrayIncludesPipe, ArrayJoinPipe, ArrayLastPipe, ArrayLengthPipe, ArrayMapPipe, ArrayPluckPipe, ArraySlicePipe, ArraySortPipe, AsAnyPipe, AsArrayPipe, AsBooleanPipe, AsFloatLabelTypePipe, AsObservablePipe, AudioProvider, AudioTestingModule, AudioTestingPage, AuthGuardService, AutoResizeDirective, AutoTitleDirective, AutoTooltipDirective, AutocompleteTestPage, AutofocusDirective, BadgeDirective, BadgeNumberPipe, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, BooleanFormatPipe, BooleanTestPage, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CapitalizePipe, CellIdentifierDirective, CellSelectionDirective, CellSelectionService, CellValueChangeListener, ChangeCaseToUnderscorePipe, ChangePasswordForm, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigFragments, ConfigService, Configuration, CoreModule, CorePipesModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_ISO_PATTERNS, DATE_MATCH_REGEXP, DATE_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_JOIN_ARRAY_VALUES_SEPARATOR, DEFAULT_JOIN_PROPERTIES_SEPARATOR, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFormatService, DateFromNowPipe, DateFromPipe, DateShortTestPage, DateTestPage, DateTimeTestPage, DateUtils, DebugComponent, Department, DepartmentToStringPipe, DisplayWithPipe, DragAndDropDirective, DurationPipe, DurationTestPage, ED25519_SEED_LENGTH, EMPTY_PLACEHOLDER_CHAR, EMPTY_PLACEHOLDER_CHAR_REGEXP_GLOBAL, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesAsyncTableDataSource, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, EnvironmentHttpLoader, EnvironmentLoader, ErrorCodes, EvenPipe, FeedDirective, FeedModule, FeedPage, FeedService, FeedsComponent, FileResponse, FileService, FileSizePipe, FilesUtils, FirstFalsePipe, FirstPipe, FirstTruePipe, FormArrayAtControlPipe, FormArrayAtGroupPipe, FormArrayHelper, FormArrayTestModule, FormButtonsBarComponent, FormButtonsBarToken, FormErrorPipe, FormErrorTranslatePipe, FormErrorTranslator, FormFieldDefinitionUtils, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetNamePipe, FormGetPipe, FormGetValuePipe, GalleryTestPage, GeolocationUtils, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, IMAGE_DEFAULTS, IPosition, ImageAttachment, ImageAttachmentFilter, ImageAttachmentService, ImageGalleryModule, ImageGalleryTestingModule, ImageModule, ImageService, ImagesUtils, InMemoryEntitiesService, IsAllSelectedPipe, IsEmptySelectionPipe, IsLoginAccountPipe, IsMultipleSelectionPipe, IsNilOrBlankPipe, IsNilOrNaNPipe, IsNilPipe, IsNotAllSelectedPipe, IsNotEmptySelectionPipe, IsNotNilOrBlankPipe, IsNotNilOrNaNPipe, IsNotNilPipe, IsOnDeskPipe, IsOnFieldPipe, IsSelectedPipe, IsSingleSelectionPipe, IsValidDatePipe, JobModule, JobProgression, JobProgressionComponent, JobProgressionIcon, JobProgressionList, JobProgressionService, JobProgressionTestService, JobProgressionTestingPage, JobTestingModule, JobUtils, JsonFeedUtils, JsonUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_PATTERNS, LAT_LONG_PATTERN_MAX_DECIMALS, LAT_LONG_VALUE_MAX_DECIMALS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LogLevel, LogUtils, Logger, LoggingService, LoggingServiceModule, LongitudeFormatPipe, MASKS, MASK_RANGES, MAT_FORM_FIELD_DEFAULT_APPEARANCE, MAT_FORM_FIELD_DEFAULT_SUBSCRIPT_SIZING, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MOMENT_NO_TIME_PROPERTY, MapGetPipe, MapKeysPipe, MapPipe, MapToPipe, MapValuesPipe, MarkdownDirective, MarkdownService, MarkdownTestPage, MarkdownTestingModule, MarkdownUtils, MaskitoPlaceholderPipe, MaskitoTestPage, MatAutocompleteConfigHolder, MatAutocompleteField, MatAutocompleteFieldUtils, MatBadgeTestPage, MatBooleanField, MatChipsField, MatColorPipe, MatCommonTestPage, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatLatLongFieldInput, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialAutocompleteFooterDirective, MaterialAutocompleteHeaderDirective, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItem, MenuItems, MenuOptions, MenuService, MenuTestingModule, MenuTestingPage, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NETWORK_DEFAULT_CONNECTION_TIMEOUT, NamedFilter, NamedFilterFilter, NamedFilterSelector, NamedFilterSelectorTestingModule, NamedFilterSelectorTestingPage, NavActionsColumnComponent, NestedTableTestPage, NetworkService, NetworkUtils, NewTokenForm, NewTokenModal, NgInitDirective, NgVarDirective, NoHtmlPipe, NotEmptyArrayPipe, NumberFormatPipe, ObservableTestPage, OddPipe, OtherMenuTestingPage, PEER_URL_REGEXP, PLUS_PLACEHOLDER_CHAR_REGEXP_GLOBAL, PRINT_ID_QUERY_PARAM, PRINT_LOADING_STORAGE_KEY_PREFIX, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFilterAdditionalFields, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, PrintService, ProgressBarService, ProgressInterceptor, PropertiesFormTestPage, PropertiesFormTestingModule, PropertyEntity, PropertyEntityFilter, PropertyEntityValidator, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialToStringPipe, ReferentialUtils, ReferentialValidatorService, ReferentialsToStringPipe, RegExpUtils, RegisterConfirmPage, RegisterForm, RegisterModal, ResizableComponent, ResizableDirective, ResizableModule, RoundPipe, RxStateComputed, RxStateModule, RxStateOutput, RxStateProperty, RxStateRegister, RxStateSelect, SCRYPT_PARAMS, SETTINGS_COMPACT_ROWS, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_STORAGE_TESTING_PAGES, SHARED_TESTING_PAGES, SOCIAL_CONFIG_OPTIONS, SOCIAL_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SPACE_PLACEHOLDER_CHAR_REGEXP_GLOBAL, STARTUP_DATA_STORAGE_KEY, SafeHtmlPipe, SafeStylePipe, SelectPeerModal, SelectionLengthPipe, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedBadgeModule, SharedDebugModule, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMarkdownModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedNamedFilterModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedToolbarModule, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, SocialTestingModule, Software, SplitArrayInChunksPipe, StartableService, StartupService, StatusById, StatusIds, StatusList, StorageDrivers, StorageExplorerComponent, StorageExplorerModule, StorageExplorerTestingModule, StorageExplorerTestingRoutingModule, StorageService, StrIncludesPipe, StrLengthPipe, StrReplacePipe, SubMenuTabDirective, SwipeTestPage, TABLE_SETTINGS_ENUM, TOOLBAR_HEADER_ID, Table2TestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TableValidatorService, TextForm, TextFormTestingModule, TextFormTestingPage, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ThrottledClickDirective, ToStringPipe, ToastTestingModule, ToastTestingPage, Toasts, TokenScope, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, TreeItemEntityUtils, TruncHtmlPipe, TruncTextPipe, TruncateHtmlPipe, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UrlUtils, UserEventModule, UserEventNotificationIcon, UserEventNotificationList, UserEventNotificationModal, UserEventTestService, UserEventTestingModule, UserEventTestingPage, UserSettings, UserToken, UserTokenTable, UsersPage, UsersUtils, ValueFormatPipe, VersionUtils, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayResize, arraySize, asInputElement, assignSkipUndefined, base64ArrayBuffer, booleanToString, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, collectByProperty, collectByPropertyPath, compareValues, compareValuesDesc, compareVersionNumbers, composeComparators, computeDecimalDegrees, computeDecimalPart, copyEntity2Form, createAppStartupInitializer, createPromiseEvent, createPromiseEventEmitter, decorateWithTakeUntil, deepMergeSkipUndefined, departmentToString, departmentsToString, disableAndClearControl, disableAndClearControls, disableControl, disableControls, emitPromiseEvent, enableControl, enableControls, enableRxStateProdMode, entityToString, equals, equalsOrNil, escapeRegExp, expansionAnimation, fadeInAnimation, fadeInOutAnimation, fadeInSlowAnimation, filterFalse, filterFormErrors, filterFormErrorsByPath, filterFormErrorsByPrefix, filterNotNil, filterNumberInput, filterTrue, findParentWithClass, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatLong, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorLuminance, getColorShade, getColorTint, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getInputRangeFromCaretIndex, getInputSelectionRangesFromMask, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, getRandomImageWithCredit, getUserAgent, hexToRgb, hexToRgbArray, initArrayControlsFromValues, interpolateString, intersectArrays, isAndroid, isBlankString, isCapacitor, isChrome, isControlHasInput, isEdge, isEmptyArray, isEntityService, isFirefox, isFocusableElement, isIOS, isInputElement, isInstanceOf, isInt, isIpad, isLightColor, isMacOS, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilObject, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isOnFieldMode, isPrint, isProgressEvent, isPromise, isResponseEvent, isSafari, isSameVersion, isStartableService, isTouchUi, isVersionCompatible, isWindows, joinProperties, joinPropertiesPath, lastArrayValue, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, maskitoAutoSelectByMaskPattern, maskitoPrefixPlugin, matchMedia, matchUpperCase, mergeLoadResult, mergeObjectsWithoutUndefined, mixHex, moveInputCaretToSeparator, newArray, noHtml, noTrailingSlash, notNilOrDefault, nullIfNilOrBlank, nullIfUndefined, numberOrNilAttribute, numberToString, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, provideAccountService, providePersonService, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputContentFromEvent, selectInputRange, setCalculatedValue, setControlEnabled, setControlRequired, setControlsEnabled, setFormErrors, setPropertyByPath, setTabIndex, sleep, slideDownAnimation, slideInAnimation, slideInOutAnimation, slideUpDownAnimation, sort, splitArrayInChunks, splitById, splitByProperty, splitDegreesToDDArray, splitDegreesToDDMMArray, splitDegreesToDDMMSSArray, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toLoadData, toLoadResult, toNotNil, toNumber, trimEmptyToNull, truncateHtml, uncapitalizeFirstLetter, undefinedIfNull, underscoreToChangeCase, updateValueAndValidity, waitFor, waitForFalse, waitForTrue, waitIdle, waitWhilePending };
|
|
53935
54616
|
//# sourceMappingURL=sumaris-net.ngx-components.mjs.map
|