@propbinder/mobile-design 0.2.47 → 0.2.50
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/ng-package.json +24 -0
- package/package.json +3 -39
- package/src/animations/page-transitions.ts +165 -0
- package/src/assets/fonts/brockmann-mediumitalic-webfont.woff2 +0 -0
- package/src/assets/fonts/brockmann-regularitalic-webfont.woff2 +0 -0
- package/src/assets/fonts/brockmann-semibolditalic-webfont.woff2 +0 -0
- package/src/components/action-list-item/ds-mobile-action-list-item.ts +102 -0
- package/src/components/action-list-item/index.ts +2 -0
- package/src/components/app-icon/ds-app-icon.ts +133 -0
- package/src/components/app-icon/index.ts +2 -0
- package/src/components/attachment-preview/ds-mobile-attachment-preview.css +139 -0
- package/src/components/attachment-preview/ds-mobile-attachment-preview.ts +164 -0
- package/src/components/attachment-preview/index.ts +1 -0
- package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +142 -0
- package/src/components/avatar-with-badge/index.ts +2 -0
- package/src/components/booking-modal/ds-mobile-booking-confirmation-wrapper.ts +71 -0
- package/src/components/booking-modal/ds-mobile-booking-modal.service.ts +121 -0
- package/src/components/booking-modal/ds-mobile-booking-modal.ts +598 -0
- package/src/components/booking-modal/ds-mobile-booking-summary.ts +161 -0
- package/src/components/booking-modal/index.ts +4 -0
- package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +266 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet-header.ts +146 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet-wrapper.ts +156 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +101 -0
- package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +169 -0
- package/src/components/bottom-sheet/ds-mobile-confirmation-sheet.ts +211 -0
- package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +578 -0
- package/src/components/bottom-sheet/ds-mobile-profile-actions-sheet.ts +614 -0
- package/src/components/bottom-sheet/index.ts +8 -0
- package/src/components/bottom-sheet/modal-shadow-fix.ts +42 -0
- package/src/components/card-inline/ds-mobile-card-inline.ts +301 -0
- package/src/components/card-inline/index.ts +2 -0
- package/src/components/card-inline-banner/ds-mobile-card-inline-banner.ts +118 -0
- package/src/components/card-inline-banner/index.ts +1 -0
- package/src/components/card-inline-contact/ds-mobile-card-inline-contact.ts +120 -0
- package/src/components/card-inline-contact/index.ts +1 -0
- package/src/components/card-inline-file/ds-mobile-card-inline-file.ts +141 -0
- package/src/components/card-inline-file/index.ts +1 -0
- package/src/components/chat-modal/ds-mobile-chat-modal.css +159 -0
- package/src/components/chat-modal/ds-mobile-chat-modal.service.ts +105 -0
- package/src/components/chat-modal/ds-mobile-chat-modal.ts +918 -0
- package/src/components/chat-modal/index.ts +8 -0
- package/src/components/comment/ds-mobile-comment.ts +568 -0
- package/src/components/comment/index.ts +2 -0
- package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +182 -0
- package/src/components/contact-list-item/index.ts +2 -0
- package/src/components/content/ds-mobile-content.ts +139 -0
- package/src/components/content/index.ts +2 -0
- package/src/components/dropdown/ds-mobile-dropdown.css +199 -0
- package/src/components/dropdown/ds-mobile-dropdown.ts +340 -0
- package/src/components/dropdown/index.ts +2 -0
- package/src/components/ds-mobile-tabs.css +407 -0
- package/src/components/ds-mobile-tabs.ts +216 -0
- package/src/components/empty-state/ds-mobile-empty-state.ts +120 -0
- package/src/components/empty-state/index.ts +2 -0
- package/src/components/fab/ds-mobile-fab.ts +315 -0
- package/src/components/fab/index.ts +1 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-confirmation-wrapper.ts +121 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.css +189 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.service.ts +135 -0
- package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.ts +656 -0
- package/src/components/facility-creation-modal/index.ts +9 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-access-sheet.ts +105 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-price-sheet.ts +188 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-when-can-book-sheet.ts +460 -0
- package/src/components/facility-creation-modal/sheets/ds-mobile-who-can-book-sheet.ts +134 -0
- package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.service.ts +69 -0
- package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.ts +379 -0
- package/src/components/facility-detail-modal/index.ts +2 -0
- package/src/components/file-attachment/ds-mobile-file-attachment.ts +164 -0
- package/src/components/file-attachment/index.ts +2 -0
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.css +214 -0
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +84 -0
- package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +424 -0
- package/src/components/handbook-detail-modal/index.ts +3 -0
- package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +175 -0
- package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +533 -0
- package/src/components/handbook-folder/index.ts +4 -0
- package/src/components/header-content/ds-mobile-header-content.ts +222 -0
- package/src/components/header-content/index.ts +2 -0
- package/src/components/illustration/ds-mobile-illustration.ts +124 -0
- package/src/components/illustration/index.ts +2 -0
- package/src/components/index.ts +124 -0
- package/src/components/inline-photo/ds-mobile-inline-photo.ts +361 -0
- package/src/components/inline-photo/index.ts +1 -0
- package/src/components/inline-tabs/ds-mobile-inline-tabs.ts +132 -0
- package/src/components/inline-tabs/index.ts +2 -0
- package/src/components/interactive-list-item-booking/ds-mobile-interactive-list-item-booking.ts +350 -0
- package/src/components/interactive-list-item-booking/index.ts +1 -0
- package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +321 -0
- package/src/components/interactive-list-item-inquiry/index.ts +2 -0
- package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +237 -0
- package/src/components/interactive-list-item-message/index.ts +2 -0
- package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +549 -0
- package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +124 -0
- package/src/components/interactive-list-item-post/index.ts +13 -0
- package/src/components/lightbox/ds-mobile-lightbox-footer.ts +315 -0
- package/src/components/lightbox/ds-mobile-lightbox-header.ts +202 -0
- package/src/components/lightbox/ds-mobile-lightbox-image.ts +484 -0
- package/src/components/lightbox/ds-mobile-lightbox-pdf.css +377 -0
- package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +374 -0
- package/src/components/lightbox/ds-mobile-lightbox.css +587 -0
- package/src/components/lightbox/ds-mobile-lightbox.service.ts +296 -0
- package/src/components/lightbox/ds-mobile-lightbox.ts +529 -0
- package/src/components/lightbox/index.ts +22 -0
- package/src/components/list-item/ds-mobile-list-item.ts +603 -0
- package/src/components/list-item/index.ts +2 -0
- package/src/components/list-item-static/ds-mobile-list-item-static.ts +133 -0
- package/src/components/list-item-static/index.ts +2 -0
- package/src/components/loader-overlay/ds-mobile-loader-overlay.css +49 -0
- package/src/components/loader-overlay/ds-mobile-loader-overlay.ts +77 -0
- package/src/components/loader-overlay/index.ts +1 -0
- package/src/components/logo/ds-logo.ts +95 -0
- package/src/components/logo/index.ts +2 -0
- package/src/components/message-bubble/ds-mobile-message-bubble.ts +633 -0
- package/src/components/message-bubble/index.ts +7 -0
- package/src/components/message-composer/ds-mobile-message-composer.ts +1146 -0
- package/src/components/message-composer/index.ts +7 -0
- package/src/components/modal/ds-mobile-modal.css +163 -0
- package/src/components/modal/ds-mobile-modal.service.ts +329 -0
- package/src/components/modal/index.ts +8 -0
- package/src/components/modal-base/ds-mobile-modal-base.css +378 -0
- package/src/components/modal-base/ds-mobile-modal-base.ts +261 -0
- package/src/components/modal-base/index.ts +2 -0
- package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.css +112 -0
- package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.service.ts +93 -0
- package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.ts +442 -0
- package/src/components/new-inquiry-modal/index.ts +4 -0
- package/src/components/offline-banner/ds-mobile-offline-banner.ts +135 -0
- package/src/components/offline-banner/index.ts +1 -0
- package/src/components/page-details/ds-mobile-page-details.css +83 -0
- package/src/components/page-details/ds-mobile-page-details.ts +282 -0
- package/src/components/page-details/index.ts +2 -0
- package/src/components/page-main/ds-mobile-page-main.css +68 -0
- package/src/components/page-main/ds-mobile-page-main.ts +421 -0
- package/src/components/page-main/index.ts +2 -0
- package/src/components/post-composer/ds-mobile-post-composer.ts +140 -0
- package/src/components/post-composer/index.ts +2 -0
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.css +390 -0
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +108 -0
- package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +722 -0
- package/src/components/post-detail-modal/index.ts +9 -0
- package/src/components/property-banner/ds-mobile-property-banner.ts +95 -0
- package/src/components/property-banner/index.ts +2 -0
- package/src/components/section/ds-mobile-section.ts +263 -0
- package/src/components/section/index.ts +2 -0
- package/src/components/shared/directives/index.ts +2 -0
- package/src/components/shared/directives/long-press.directive.ts +212 -0
- package/src/components/shared/index.ts +3 -0
- package/src/components/shared/mobile-modal-base.ts +457 -0
- package/src/components/shared/mobile-page-base.ts +204 -0
- package/src/components/swiper/ds-mobile-swiper-with-nav.ts +160 -0
- package/src/components/swiper/ds-mobile-swiper.ts +327 -0
- package/src/components/swiper/index.ts +3 -0
- package/src/components/system-message-banner/ds-mobile-system-message-banner.ts +129 -0
- package/src/components/system-message-banner/index.ts +2 -0
- package/src/components/tab-bar/ds-mobile-tab-bar.css +533 -0
- package/src/components/tab-bar/ds-mobile-tab-bar.ts +735 -0
- package/src/components/tab-bar/index.ts +2 -0
- package/src/components/tabs/ds-mobile-tabs.css +25 -0
- package/src/components/tabs/ds-mobile-tabs.ts +89 -0
- package/src/components/tabs/index.ts +2 -0
- package/src/components/text-input/ds-text-input.ts +287 -0
- package/src/components/text-input/index.ts +2 -0
- package/src/examples/booking.page.ts +434 -0
- package/src/examples/community.page.ts +776 -0
- package/src/examples/handbook.page.ts +324 -0
- package/src/examples/home.page.ts +347 -0
- package/src/examples/index.ts +12 -0
- package/src/examples/inquiries.example.ts +273 -0
- package/src/examples/inquiry-detail.example.css +189 -0
- package/src/examples/inquiry-detail.example.ts +415 -0
- package/src/examples/mobile-tabs-example.component.ts +208 -0
- package/src/examples/post-create.page.ts +311 -0
- package/src/examples/post-detail.page.ts +296 -0
- package/src/examples/sign-in.page.ts +291 -0
- package/src/examples/whitelabel-demo-modal.component.ts +1094 -0
- package/src/examples/whitelabel-demo-modal.service.ts +77 -0
- package/src/models/index.ts +7 -0
- package/src/models/post.model.ts +41 -0
- package/src/pages/community.page.ts +769 -0
- package/src/pages/handbook.page.ts +388 -0
- package/src/pages/home.page.ts +303 -0
- package/src/pages/index.ts +11 -0
- package/src/pages/inquiries.example.ts +273 -0
- package/src/pages/inquiry-detail.example.css +189 -0
- package/src/pages/inquiry-detail.example.ts +415 -0
- package/src/pages/mobile-tabs-example.component.ts +179 -0
- package/src/pages/post-create.page.ts +311 -0
- package/src/pages/post-detail.page.ts +296 -0
- package/src/pages/sign-in.page.ts +291 -0
- package/src/pages/whitelabel-demo-modal.component.ts +1094 -0
- package/src/pages/whitelabel-demo-modal.service.ts +77 -0
- package/src/public-api.ts +6 -0
- package/src/services/base-modal.service.ts +101 -0
- package/src/services/index.ts +11 -0
- package/src/services/posts.service.ts +542 -0
- package/src/services/tracking-permission.service.ts +88 -0
- package/src/services/user.service.ts +60 -0
- package/src/services/whitelabel.service.ts +675 -0
- package/{styles → src/styles}/ionic.css +25 -0
- package/tsconfig.lib.json +17 -0
- package/tsconfig.lib.prod.json +9 -0
- package/tsconfig.spec.json +13 -0
- package/fesm2022/propbinder-mobile-design.mjs +0 -26136
- package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
- package/index.d.ts +0 -8154
- /package/{assets → src/assets}/fonts/Brockmann-Bold.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-BoldItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-Medium.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-MediumItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-Regular.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-RegularItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-SemiBold.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann-SemiBoldItalic.otf +0 -0
- /package/{assets → src/assets}/fonts/Brockmann_desktop_license.pdf +0 -0
- /package/{assets → src/assets}/fonts/brockmann-medium-webfont.woff2 +0 -0
- /package/{assets → src/assets}/fonts/brockmann-regular-webfont.woff2 +0 -0
- /package/{assets → src/assets}/fonts/brockmann-semibold-webfont.woff2 +0 -0
- /package/{styles → src/components/shared}/mobile-common.css +0 -0
- /package/{styles → src/components/shared}/mobile-page-base.css +0 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
signal,
|
|
4
|
+
computed,
|
|
5
|
+
ViewChild,
|
|
6
|
+
ElementRef,
|
|
7
|
+
AfterViewInit,
|
|
8
|
+
OnDestroy,
|
|
9
|
+
OnInit,
|
|
10
|
+
CUSTOM_ELEMENTS_SCHEMA
|
|
11
|
+
} from '@angular/core';
|
|
12
|
+
import { CommonModule } from '@angular/common';
|
|
13
|
+
import {
|
|
14
|
+
IonContent,
|
|
15
|
+
IonSpinner,
|
|
16
|
+
ModalController,
|
|
17
|
+
GestureController,
|
|
18
|
+
Gesture
|
|
19
|
+
} from '@ionic/angular/standalone';
|
|
20
|
+
import { Share } from '@capacitor/share';
|
|
21
|
+
import { DsIconButtonComponent } from '@propbinder/design-system';
|
|
22
|
+
import { DsAvatarComponent } from '@propbinder/design-system';
|
|
23
|
+
import type { LightboxImage, LightboxAuthor } from './ds-mobile-lightbox.service';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* DsMobileLightboxComponent
|
|
27
|
+
*
|
|
28
|
+
* Full-screen image lightbox component with native mobile gestures.
|
|
29
|
+
* Supports swipe navigation, pinch-to-zoom, and double-tap zoom.
|
|
30
|
+
*
|
|
31
|
+
* This component is typically not used directly - use DsMobileLightboxService instead.
|
|
32
|
+
*
|
|
33
|
+
* Features:
|
|
34
|
+
* - Swipe left/right to navigate between images
|
|
35
|
+
* - Pinch to zoom in/out
|
|
36
|
+
* - Double-tap to toggle zoom
|
|
37
|
+
* - Swipe down to close (when not zoomed)
|
|
38
|
+
* - Image counter and navigation controls
|
|
39
|
+
* - Optional title and description display
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* // Don't instantiate directly - use the service:
|
|
44
|
+
* constructor(private lightbox: DsMobileLightboxService) {}
|
|
45
|
+
*
|
|
46
|
+
* openImage() {
|
|
47
|
+
* this.lightbox.open({
|
|
48
|
+
* images: [{ src: 'image.jpg', title: 'My Image' }]
|
|
49
|
+
* });
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
@Component({
|
|
54
|
+
selector: 'ds-mobile-lightbox',
|
|
55
|
+
standalone: true,
|
|
56
|
+
imports: [
|
|
57
|
+
CommonModule,
|
|
58
|
+
IonContent,
|
|
59
|
+
IonSpinner,
|
|
60
|
+
DsIconButtonComponent,
|
|
61
|
+
DsAvatarComponent
|
|
62
|
+
],
|
|
63
|
+
styleUrls: ['../shared/mobile-common.css', './ds-mobile-lightbox.css'],
|
|
64
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
65
|
+
template: `
|
|
66
|
+
<ion-content
|
|
67
|
+
[fullscreen]="true"
|
|
68
|
+
[scrollY]="false"
|
|
69
|
+
[scrollX]="false"
|
|
70
|
+
class="lightbox-content"
|
|
71
|
+
[class.zoomed]="isZoomed()">
|
|
72
|
+
|
|
73
|
+
<div class="lightbox-wrapper">
|
|
74
|
+
<!-- Header with action buttons -->
|
|
75
|
+
<div class="lightbox-header">
|
|
76
|
+
<!-- Action buttons row -->
|
|
77
|
+
<div class="header-actions">
|
|
78
|
+
<ds-icon-button
|
|
79
|
+
icon="remixCloseLine"
|
|
80
|
+
variant="ghost"
|
|
81
|
+
size="md"
|
|
82
|
+
(click)="close()"
|
|
83
|
+
class="close-button"
|
|
84
|
+
aria-label="Luk lyskasse">
|
|
85
|
+
</ds-icon-button>
|
|
86
|
+
|
|
87
|
+
<ds-icon-button
|
|
88
|
+
icon="remixShare2Line"
|
|
89
|
+
variant="ghost"
|
|
90
|
+
size="md"
|
|
91
|
+
(click)="onShare()"
|
|
92
|
+
class="share-button"
|
|
93
|
+
aria-label="Del billede">
|
|
94
|
+
</ds-icon-button>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- Image container with gesture support -->
|
|
99
|
+
<div class="lightbox-container" #container>
|
|
100
|
+
<div
|
|
101
|
+
class="image-wrapper"
|
|
102
|
+
#imageWrapper
|
|
103
|
+
[style.transform]="transform()">
|
|
104
|
+
<img
|
|
105
|
+
#image
|
|
106
|
+
[src]="currentImage().src"
|
|
107
|
+
[alt]="currentImage().alt || 'Lightbox image'"
|
|
108
|
+
class="lightbox-image"
|
|
109
|
+
(load)="onImageLoad()"
|
|
110
|
+
(error)="onImageError()">
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<!-- Loading indicator -->
|
|
114
|
+
@if (isLoading()) {
|
|
115
|
+
<div class="loading-spinner">
|
|
116
|
+
<ion-spinner name="crescent"></ion-spinner>
|
|
117
|
+
</div>
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
<!-- Error message -->
|
|
121
|
+
@if (hasError()) {
|
|
122
|
+
<div class="error-message">
|
|
123
|
+
<p>Failed to load image</p>
|
|
124
|
+
</div>
|
|
125
|
+
}
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<!-- Navigation controls -->
|
|
129
|
+
@if (showControls && images.length > 1) {
|
|
130
|
+
<div class="lightbox-controls">
|
|
131
|
+
<button
|
|
132
|
+
class="nav-button prev"
|
|
133
|
+
(click)="previousImage()"
|
|
134
|
+
[disabled]="currentIndex() === 0"
|
|
135
|
+
aria-label="Forrige billede">
|
|
136
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
137
|
+
<path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
138
|
+
</svg>
|
|
139
|
+
</button>
|
|
140
|
+
|
|
141
|
+
<div class="counter">
|
|
142
|
+
{{ currentIndex() + 1 }} / {{ images.length }}
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<button
|
|
146
|
+
class="nav-button next"
|
|
147
|
+
(click)="nextImage()"
|
|
148
|
+
[disabled]="currentIndex() === images.length - 1"
|
|
149
|
+
aria-label="Næste billede">
|
|
150
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
151
|
+
<path d="M9 18L15 12L9 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
152
|
+
</svg>
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
<!-- Caption at bottom -->
|
|
158
|
+
@if (showInfo && author) {
|
|
159
|
+
<div class="lightbox-caption" [class.with-controls]="showControls && images.length > 1">
|
|
160
|
+
<!-- Author meta -->
|
|
161
|
+
<div class="author-details">
|
|
162
|
+
<ds-avatar
|
|
163
|
+
[initials]="author.avatarInitials ?? ''"
|
|
164
|
+
[type]="author.avatarType ?? 'initials'"
|
|
165
|
+
[src]="author.avatarSrc ?? ''"
|
|
166
|
+
size="md"
|
|
167
|
+
/>
|
|
168
|
+
<div class="author-details">
|
|
169
|
+
<div class="author-name">{{ author.name }}</div>
|
|
170
|
+
@if (author.role || author.timestamp) {
|
|
171
|
+
<div class="author-meta">
|
|
172
|
+
@if (author.role) {
|
|
173
|
+
<span>{{ author.role }}</span>
|
|
174
|
+
}
|
|
175
|
+
@if (author.role && author.timestamp) {
|
|
176
|
+
<span class="separator">·</span>
|
|
177
|
+
}
|
|
178
|
+
@if (author.timestamp) {
|
|
179
|
+
<span>{{ author.timestamp }}</span>
|
|
180
|
+
}
|
|
181
|
+
</div>
|
|
182
|
+
}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
}
|
|
187
|
+
</div>
|
|
188
|
+
</ion-content>
|
|
189
|
+
`
|
|
190
|
+
})
|
|
191
|
+
export class DsMobileLightboxComponent implements OnInit, AfterViewInit, OnDestroy {
|
|
192
|
+
// Inputs (passed from service as regular properties, not signals)
|
|
193
|
+
images!: LightboxImage[];
|
|
194
|
+
author?: LightboxAuthor;
|
|
195
|
+
initialIndex: number = 0;
|
|
196
|
+
enableZoom: boolean = true;
|
|
197
|
+
showControls: boolean = true;
|
|
198
|
+
enableSwipe: boolean = true;
|
|
199
|
+
showInfo: boolean = true;
|
|
200
|
+
animation: 'fade' | 'zoom' | 'slide' = 'fade';
|
|
201
|
+
|
|
202
|
+
// View children
|
|
203
|
+
@ViewChild('container', { read: ElementRef }) containerRef!: ElementRef<HTMLDivElement>;
|
|
204
|
+
@ViewChild('imageWrapper', { read: ElementRef }) imageWrapperRef!: ElementRef<HTMLDivElement>;
|
|
205
|
+
@ViewChild('image', { read: ElementRef }) imageRef!: ElementRef<HTMLImageElement>;
|
|
206
|
+
|
|
207
|
+
// State
|
|
208
|
+
currentIndex = signal(0);
|
|
209
|
+
scale = signal(1);
|
|
210
|
+
translateX = signal(0);
|
|
211
|
+
translateY = signal(0);
|
|
212
|
+
isZoomed = signal(false);
|
|
213
|
+
isLoading = signal(true);
|
|
214
|
+
hasError = signal(false);
|
|
215
|
+
|
|
216
|
+
// Action states
|
|
217
|
+
isLiked = signal(false);
|
|
218
|
+
likeCount = signal(0);
|
|
219
|
+
commentCount = signal(0);
|
|
220
|
+
|
|
221
|
+
// Computed
|
|
222
|
+
currentImage = computed(() => this.images[this.currentIndex()]);
|
|
223
|
+
transform = computed(() => {
|
|
224
|
+
const s = this.scale();
|
|
225
|
+
const x = this.translateX();
|
|
226
|
+
const y = this.translateY();
|
|
227
|
+
return `scale(${s}) translate(${x}px, ${y}px)`;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Gesture tracking
|
|
231
|
+
private gesture?: Gesture;
|
|
232
|
+
private swipeGesture?: Gesture;
|
|
233
|
+
private pinchGesture?: Gesture;
|
|
234
|
+
private initialScale = 1;
|
|
235
|
+
private initialTranslateX = 0;
|
|
236
|
+
private initialTranslateY = 0;
|
|
237
|
+
private lastPinchScale = 1;
|
|
238
|
+
|
|
239
|
+
constructor(
|
|
240
|
+
private modalController: ModalController,
|
|
241
|
+
private gestureCtrl: GestureController
|
|
242
|
+
) {}
|
|
243
|
+
|
|
244
|
+
ngOnInit(): void {
|
|
245
|
+
// Set initial index from the passed property
|
|
246
|
+
if (this.initialIndex !== undefined) {
|
|
247
|
+
this.currentIndex.set(this.initialIndex);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Initialize action states from current image
|
|
251
|
+
const currentImg = this.images[this.currentIndex()];
|
|
252
|
+
if (currentImg) {
|
|
253
|
+
this.isLiked.set(currentImg.isLiked ?? false);
|
|
254
|
+
this.likeCount.set(currentImg.likeCount ?? 0);
|
|
255
|
+
this.commentCount.set(currentImg.commentCount ?? 0);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
ngAfterViewInit(): void {
|
|
260
|
+
console.log('[Lightbox] ngAfterViewInit called');
|
|
261
|
+
console.log('[Lightbox] Current image:', this.currentImage());
|
|
262
|
+
console.log('[Lightbox] Images array:', this.images);
|
|
263
|
+
console.log('[Lightbox] Container ref:', this.containerRef?.nativeElement);
|
|
264
|
+
console.log('[Lightbox] Image wrapper ref:', this.imageWrapperRef?.nativeElement);
|
|
265
|
+
console.log('[Lightbox] Image ref:', this.imageRef?.nativeElement);
|
|
266
|
+
|
|
267
|
+
// Initialize gestures after view is ready
|
|
268
|
+
if (this.enableZoom) {
|
|
269
|
+
this.initializeZoomGesture();
|
|
270
|
+
this.initializePinchZoom();
|
|
271
|
+
}
|
|
272
|
+
if (this.enableSwipe) {
|
|
273
|
+
this.initializeSwipeGesture();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
ngOnDestroy(): void {
|
|
278
|
+
// Clean up gestures
|
|
279
|
+
this.gesture?.destroy();
|
|
280
|
+
this.swipeGesture?.destroy();
|
|
281
|
+
this.pinchGesture?.destroy();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Initialize double-tap zoom gesture
|
|
286
|
+
*/
|
|
287
|
+
private initializeZoomGesture(): void {
|
|
288
|
+
if (!this.imageWrapperRef) return;
|
|
289
|
+
|
|
290
|
+
let lastTap = 0;
|
|
291
|
+
const element = this.imageWrapperRef.nativeElement;
|
|
292
|
+
|
|
293
|
+
element.addEventListener('click', (event: MouseEvent) => {
|
|
294
|
+
const now = Date.now();
|
|
295
|
+
const timeSinceLastTap = now - lastTap;
|
|
296
|
+
|
|
297
|
+
if (timeSinceLastTap < 300 && timeSinceLastTap > 0) {
|
|
298
|
+
// Double tap detected
|
|
299
|
+
event.preventDefault();
|
|
300
|
+
this.onDoubleTap();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
lastTap = now;
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Initialize pinch-to-zoom gesture
|
|
309
|
+
*/
|
|
310
|
+
private initializePinchZoom(): void {
|
|
311
|
+
if (!this.imageWrapperRef) return;
|
|
312
|
+
|
|
313
|
+
const element = this.imageWrapperRef.nativeElement;
|
|
314
|
+
let initialDistance = 0;
|
|
315
|
+
let initialCenterX = 0;
|
|
316
|
+
let initialCenterY = 0;
|
|
317
|
+
|
|
318
|
+
const getTouchDistance = (touches: TouchList) => {
|
|
319
|
+
const dx = touches[0].clientX - touches[1].clientX;
|
|
320
|
+
const dy = touches[0].clientY - touches[1].clientY;
|
|
321
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const getTouchCenter = (touches: TouchList) => {
|
|
325
|
+
return {
|
|
326
|
+
x: (touches[0].clientX + touches[1].clientX) / 2,
|
|
327
|
+
y: (touches[0].clientY + touches[1].clientY) / 2
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
element.addEventListener('touchstart', (event: TouchEvent) => {
|
|
332
|
+
if (event.touches.length === 2) {
|
|
333
|
+
event.preventDefault();
|
|
334
|
+
initialDistance = getTouchDistance(event.touches);
|
|
335
|
+
const center = getTouchCenter(event.touches);
|
|
336
|
+
initialCenterX = center.x;
|
|
337
|
+
initialCenterY = center.y;
|
|
338
|
+
this.initialScale = this.scale();
|
|
339
|
+
this.lastPinchScale = 1;
|
|
340
|
+
}
|
|
341
|
+
}, { passive: false });
|
|
342
|
+
|
|
343
|
+
element.addEventListener('touchmove', (event: TouchEvent) => {
|
|
344
|
+
if (event.touches.length === 2) {
|
|
345
|
+
event.preventDefault();
|
|
346
|
+
|
|
347
|
+
const currentDistance = getTouchDistance(event.touches);
|
|
348
|
+
const pinchScale = currentDistance / initialDistance;
|
|
349
|
+
|
|
350
|
+
// Calculate new scale
|
|
351
|
+
let newScale = this.initialScale * pinchScale;
|
|
352
|
+
newScale = Math.max(1, Math.min(newScale, 4)); // Clamp between 1x and 4x
|
|
353
|
+
|
|
354
|
+
this.scale.set(newScale);
|
|
355
|
+
|
|
356
|
+
if (newScale > 1) {
|
|
357
|
+
this.isZoomed.set(true);
|
|
358
|
+
} else {
|
|
359
|
+
this.isZoomed.set(false);
|
|
360
|
+
this.translateX.set(0);
|
|
361
|
+
this.translateY.set(0);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
this.lastPinchScale = pinchScale;
|
|
365
|
+
}
|
|
366
|
+
}, { passive: false });
|
|
367
|
+
|
|
368
|
+
element.addEventListener('touchend', (event: TouchEvent) => {
|
|
369
|
+
if (event.touches.length < 2) {
|
|
370
|
+
// Reset to 1x if we're close to it
|
|
371
|
+
if (this.scale() < 1.1) {
|
|
372
|
+
this.resetZoom();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Initialize swipe gesture for navigation
|
|
380
|
+
*/
|
|
381
|
+
private initializeSwipeGesture(): void {
|
|
382
|
+
if (!this.containerRef) return;
|
|
383
|
+
|
|
384
|
+
this.swipeGesture = this.gestureCtrl.create({
|
|
385
|
+
el: this.containerRef.nativeElement,
|
|
386
|
+
gestureName: 'swipe-navigate',
|
|
387
|
+
direction: 'x',
|
|
388
|
+
threshold: 50,
|
|
389
|
+
onStart: () => {
|
|
390
|
+
this.initialTranslateX = this.translateX();
|
|
391
|
+
},
|
|
392
|
+
onMove: (detail) => {
|
|
393
|
+
// Only allow swipe if not zoomed
|
|
394
|
+
if (!this.isZoomed() && detail.deltaX !== undefined) {
|
|
395
|
+
this.translateX.set(this.initialTranslateX + detail.deltaX / 2);
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
onEnd: (detail) => {
|
|
399
|
+
if (!this.isZoomed() && detail.deltaX !== undefined) {
|
|
400
|
+
const threshold = 100;
|
|
401
|
+
|
|
402
|
+
if (detail.deltaX > threshold) {
|
|
403
|
+
// Swipe right - previous image
|
|
404
|
+
this.previousImage();
|
|
405
|
+
} else if (detail.deltaX < -threshold) {
|
|
406
|
+
// Swipe left - next image
|
|
407
|
+
this.nextImage();
|
|
408
|
+
} else {
|
|
409
|
+
// Reset position
|
|
410
|
+
this.translateX.set(0);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}, true);
|
|
415
|
+
|
|
416
|
+
this.swipeGesture.enable();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Close the lightbox
|
|
421
|
+
*/
|
|
422
|
+
close(): void {
|
|
423
|
+
this.modalController.dismiss();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async onShare(): Promise<void> {
|
|
427
|
+
console.log('[Lightbox] Share button clicked');
|
|
428
|
+
const currentImg = this.currentImage();
|
|
429
|
+
|
|
430
|
+
if (!currentImg?.src) return;
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
// Use Capacitor Share API for native share sheet
|
|
434
|
+
await Share.share({
|
|
435
|
+
title: currentImg.title || 'Image',
|
|
436
|
+
text: currentImg.description || '',
|
|
437
|
+
url: currentImg.src,
|
|
438
|
+
dialogTitle: 'Share Image'
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
console.log('[Lightbox] Image shared successfully');
|
|
442
|
+
} catch (error) {
|
|
443
|
+
console.error('[Lightbox] Error sharing image:', error);
|
|
444
|
+
// If share fails or is cancelled, do nothing
|
|
445
|
+
// Note: User canceling the share dialog will throw an error, which is expected
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
onLike(event: { active: boolean; count: number }): void {
|
|
450
|
+
console.log('[Lightbox] Like toggled:', event);
|
|
451
|
+
this.isLiked.set(event.active);
|
|
452
|
+
this.likeCount.set(event.count);
|
|
453
|
+
// You can emit an event or call a service here to persist the like
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
onReply(): void {
|
|
457
|
+
console.log('[Lightbox] Reply button clicked');
|
|
458
|
+
// Close the lightbox and navigate to reply/comment interface
|
|
459
|
+
// Or open a bottom sheet for quick reply
|
|
460
|
+
this.modalController.dismiss({ action: 'reply' });
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Navigate to the next image
|
|
465
|
+
*/
|
|
466
|
+
nextImage(): void {
|
|
467
|
+
if (this.currentIndex() < this.images.length - 1) {
|
|
468
|
+
this.resetZoom();
|
|
469
|
+
this.currentIndex.update(i => i + 1);
|
|
470
|
+
this.isLoading.set(true);
|
|
471
|
+
this.hasError.set(false);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Navigate to the previous image
|
|
477
|
+
*/
|
|
478
|
+
previousImage(): void {
|
|
479
|
+
if (this.currentIndex() > 0) {
|
|
480
|
+
this.resetZoom();
|
|
481
|
+
this.currentIndex.update(i => i - 1);
|
|
482
|
+
this.isLoading.set(true);
|
|
483
|
+
this.hasError.set(false);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Handle double-tap to toggle zoom
|
|
489
|
+
*/
|
|
490
|
+
onDoubleTap(): void {
|
|
491
|
+
if (!this.enableZoom) return;
|
|
492
|
+
|
|
493
|
+
if (this.isZoomed()) {
|
|
494
|
+
this.resetZoom();
|
|
495
|
+
} else {
|
|
496
|
+
this.scale.set(2);
|
|
497
|
+
this.isZoomed.set(true);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Reset zoom and pan to default state
|
|
503
|
+
*/
|
|
504
|
+
resetZoom(): void {
|
|
505
|
+
this.scale.set(1);
|
|
506
|
+
this.translateX.set(0);
|
|
507
|
+
this.translateY.set(0);
|
|
508
|
+
this.isZoomed.set(false);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Handle image load success
|
|
513
|
+
*/
|
|
514
|
+
onImageLoad(): void {
|
|
515
|
+
console.log('[Lightbox] Image loaded successfully');
|
|
516
|
+
this.isLoading.set(false);
|
|
517
|
+
this.hasError.set(false);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Handle image load error
|
|
522
|
+
*/
|
|
523
|
+
onImageError(): void {
|
|
524
|
+
console.error('[Lightbox] Image failed to load');
|
|
525
|
+
this.isLoading.set(false);
|
|
526
|
+
this.hasError.set(true);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
export { DsMobileLightboxImageComponent } from './ds-mobile-lightbox-image';
|
|
3
|
+
export { DsMobileLightboxPdfComponent } from './ds-mobile-lightbox-pdf';
|
|
4
|
+
export { DsMobileLightboxHeaderComponent } from './ds-mobile-lightbox-header';
|
|
5
|
+
export { DsMobileLightboxFooterComponent } from './ds-mobile-lightbox-footer';
|
|
6
|
+
|
|
7
|
+
// Service and Types
|
|
8
|
+
export {
|
|
9
|
+
DsMobileLightboxService,
|
|
10
|
+
type LightboxImage,
|
|
11
|
+
type LightboxPdf,
|
|
12
|
+
type LightboxMediaFile,
|
|
13
|
+
type LightboxMediaType,
|
|
14
|
+
type LightboxAuthor,
|
|
15
|
+
type LightboxOptions,
|
|
16
|
+
type LightboxImageOptions,
|
|
17
|
+
type LightboxPdfOptions
|
|
18
|
+
} from './ds-mobile-lightbox.service';
|
|
19
|
+
|
|
20
|
+
// Legacy export for backward compatibility
|
|
21
|
+
export { DsMobileLightboxImageComponent as DsMobileLightboxComponent } from './ds-mobile-lightbox-image';
|
|
22
|
+
|