+4
-2
src/app/components/cards/notification-card/notification-card.component.html
+4
-2
src/app/components/cards/notification-card/notification-card.component.html
···
173
173
<div
174
174
class="flex gap-2"
175
175
>
176
-
@for (image of attachedPost.embed.images; track image.thumb) {
176
+
@for (image of attachedPost.embed.images; track $index) {
177
177
<img
178
178
[src]="image.thumb"
179
179
[alt]="image.alt"
180
+
(click)="openImage($event, attachedPost.embed.images, $index)"
180
181
class="h-16 w-16 object-cover"
181
182
/>
182
183
}
···
196
197
<div
197
198
class="flex gap-2"
198
199
>
199
-
@for (image of attachedPost.embed.media.images; track image.thumb) {
200
+
@for (image of attachedPost.embed.media.images; track $index) {
200
201
<img
201
202
[src]="image.thumb"
202
203
[alt]="image.alt"
204
+
(click)="openImage($event, attachedPost.embed.media.images, $index)"
203
205
class="h-16 w-16 object-cover"
204
206
/>
205
207
}
+8
-1
src/app/components/cards/notification-card/notification-card.component.ts
+8
-1
src/app/components/cards/notification-card/notification-card.component.ts
···
15
15
import {IsStarterPackNotificationPipe} from '@shared/pipes/type-guards/notifications/is-starterpack-notification.pipe';
16
16
import {NgTemplateOutlet, SlicePipe} from '@angular/common';
17
17
import {DisplayNamePipe} from '@shared/pipes/display-name.pipe';
18
-
import {AppBskyFeedDefs} from '@atproto/api';
18
+
import {AppBskyEmbedImages, AppBskyFeedDefs} from '@atproto/api';
19
19
import {IsEmbedImagesViewPipe} from '@shared/pipes/type-guards/is-embed-images-view.pipe';
20
20
import {IsEmbedVideoViewPipe} from '@shared/pipes/type-guards/is-embed-video-view.pipe';
21
21
import {IsEmbedRecordWithMediaViewPipe} from '@shared/pipes/type-guards/is-embed-recordwithmedia-view.pipe';
22
+
import {DialogService} from '@services/dialog.service';
22
23
23
24
@Component({
24
25
selector: 'notification-card',
···
44
45
post: WritableSignal<AppBskyFeedDefs.PostView>;
45
46
46
47
constructor(
48
+
private dialogService: DialogService,
47
49
private cdRef: ChangeDetectorRef
48
50
) {}
49
51
···
54
56
55
57
openAuthor(event: Event, did: string) {
56
58
//TODO: OpenAuthor
59
+
}
60
+
61
+
openImage(event: Event, images: AppBskyEmbedImages.ViewImage[], index: number) {
62
+
event.stopPropagation();
63
+
this.dialogService.openImage(images, index);
57
64
}
58
65
}
+4
src/app/components/cards/post-card/post-card.component.html
+4
src/app/components/cards/post-card/post-card.component.html
···
133
133
@if (embed | isEmbedRecordView) {
134
134
<record-embed
135
135
[record]="embed.record"
136
+
(onImgClick)="openImage($event.images, $event.index)"
136
137
class="mt-2 p-2 hover:bg-primary/2"
137
138
/>
138
139
}
···
140
141
@if (embed | isEmbedImagesView) {
141
142
<images-embed
142
143
[images]="embed.images"
144
+
(onClick)="openImage(embed.images, $event)"
143
145
class="mb-1"
144
146
[class]="$any(post().record).text.length ? 'mt-2' : 'mt-3'"
145
147
/>
···
165
167
[images]="embed.media.images"
166
168
class="mb-1"
167
169
[class]="$any(post().record).text.length ? 'mt-2' : 'mt-3'"
170
+
(onClick)="openImage(embed.media.images, $event)"
168
171
/>
169
172
}
170
173
···
184
187
185
188
<record-embed
186
189
[record]="embed.record.record"
190
+
(onImgClick)="openImage($event.images, $event.index)"
187
191
class="mt-2 p-2 hover:bg-primary/2"
188
192
/>
189
193
}
+7
-1
src/app/components/cards/post-card/post-card.component.ts
+7
-1
src/app/components/cards/post-card/post-card.component.ts
···
9
9
OnDestroy,
10
10
OnInit
11
11
} from '@angular/core';
12
-
import {AppBskyFeedDefs} from '@atproto/api';
12
+
import {AppBskyEmbedImages, AppBskyFeedDefs} from '@atproto/api';
13
13
import {AvatarComponent} from '@components/shared/avatar/avatar.component';
14
14
import {DisplayNamePipe} from '@shared/pipes/display-name.pipe';
15
15
import {IsFeedPostRecordPipe} from '@shared/pipes/type-guards/is-feed-post-record';
···
32
32
import {ExternalEmbedComponent} from '@components/embeds/external-embed/external-embed.component';
33
33
import {IsEmbedExternalViewPipe} from '@shared/pipes/type-guards/is-embed-external-view.pipe';
34
34
import {MessageService} from '@services/message.service';
35
+
import {DialogService} from '@services/dialog.service';
35
36
36
37
@Component({
37
38
selector: 'post-card',
···
74
75
constructor(
75
76
private postService: PostService,
76
77
private messageService: MessageService,
78
+
private dialogService: DialogService,
77
79
private cdRef: ChangeDetectorRef
78
80
) {
79
81
effect(() => {
···
152
154
quotePost() {
153
155
this.postService.quotePost(this.post().uri);
154
156
this.rtMenuVisible = false;
157
+
}
158
+
159
+
openImage(images: AppBskyEmbedImages.ViewImage[], index: number) {
160
+
this.dialogService.openImage(images, index);
155
161
}
156
162
}
+48
src/app/components/dialogs/gallery/gallery.component.html
+48
src/app/components/dialogs/gallery/gallery.component.html
···
1
+
<div
2
+
class="flex flex-col gap-4 items-center justify-center absolute top-[1rem] left-[5rem] h-[calc(100%_-_4rem)] w-[calc(100%_-_10rem)] pointer-events-none"
3
+
[class]="{'h-[calc(100%_-_4rem)]': images.length > 1, 'h-[calc(100%_-_2rem)]': images.length == 1}"
4
+
>
5
+
<img
6
+
[src]="images[index].fullsize"
7
+
class="flex-1 min-h-0 min-w-0 w-fit pointer-events-auto"
8
+
/>
9
+
10
+
@if (images[index].alt) {
11
+
<span
12
+
class="bg-primary text-bg py-2 px-3 pointer-events-auto"
13
+
>{{ images[index].alt }}</span>
14
+
}
15
+
</div>
16
+
17
+
@if (images.length > 1) {
18
+
<button
19
+
class="flex items-center justify-center absolute h-12 w-12 left-[1rem] top-[50%] -translate-y-[50%] btn-primary"
20
+
(click)="prevImage()"
21
+
>
22
+
<span
23
+
class="material-icons !text-4xl"
24
+
>chevron_left</span>
25
+
</button>
26
+
27
+
<button
28
+
class="flex items-center justify-center absolute h-12 w-12 right-[1rem] top-[50%] -translate-y-[50%] btn-primary"
29
+
(click)="nextImage()"
30
+
>
31
+
<span
32
+
class="material-icons !text-4xl"
33
+
>chevron_right</span>
34
+
</button>
35
+
36
+
<div
37
+
class="absolute bottom-[1rem] left-[50%] -translate-x-[50%] flex gap-2"
38
+
>
39
+
@for (image of images; track $index) {
40
+
<button
41
+
class="h-4 w-4 p-0 bg-bg border border-primary"
42
+
[class.bg-primary]="$index == index"
43
+
[class.cursor-pointer]="$index !== index"
44
+
(click)="index = $index"
45
+
></button>
46
+
}
47
+
</div>
48
+
}
+39
src/app/components/dialogs/gallery/gallery.component.ts
+39
src/app/components/dialogs/gallery/gallery.component.ts
···
1
+
import {ChangeDetectionStrategy, Component, HostListener, inject} from '@angular/core';
2
+
import {AppBskyEmbedImages} from '@atproto/api';
3
+
import {DIALOG_DATA, DialogRef} from '@angular/cdk/dialog';
4
+
5
+
@Component({
6
+
selector: 'gallery',
7
+
templateUrl: './gallery.component.html',
8
+
changeDetection: ChangeDetectionStrategy.OnPush
9
+
})
10
+
export class GalleryComponent {
11
+
images: AppBskyEmbedImages.ViewImage[];
12
+
index: number;
13
+
14
+
dialogRef = inject<DialogRef<string>>(DialogRef<string>);
15
+
data = inject(DIALOG_DATA);
16
+
17
+
constructor() {
18
+
this.images = this.data.images;
19
+
this.index = this.data.index;
20
+
}
21
+
22
+
@HostListener('keydown.arrowLeft', ['$event'])
23
+
prevImage() {
24
+
if (this.index == 0) {
25
+
this.index = this.images.length - 1;
26
+
} else {
27
+
this.index = this.index - 1;
28
+
}
29
+
}
30
+
31
+
@HostListener('keydown.arrowRight', ['$event'])
32
+
nextImage() {
33
+
if (this.index + 1 == this.images.length) {
34
+
this.index = 0;
35
+
} else {
36
+
this.index = this.index + 1;
37
+
}
38
+
}
39
+
}
+2
-1
src/app/components/embeds/images-embed/images-embed.component.ts
+2
-1
src/app/components/embeds/images-embed/images-embed.component.ts
+3
src/app/components/embeds/record-embed/record-embed.component.html
+3
src/app/components/embeds/record-embed/record-embed.component.html
···
2
2
3
3
<div
4
4
class="flex"
5
+
(click)="recordClick($event)"
5
6
>
6
7
<div
7
8
class="overflow-hidden shrink-0 h-5 w-9 flex items-center justify-center"
···
104
105
<images-embed
105
106
[images]="media.images"
106
107
[class]="margin"
108
+
(onClick)="onImgClick.emit({images: media.images, index: $event})"
107
109
/>
108
110
}
109
111
···
126
128
<images-embed
127
129
[images]="media.media.images"
128
130
[class]="margin"
131
+
(onClick)="onImgClick.emit({images: media.media.images, index: $event})"
129
132
/>
130
133
}
131
134
+17
-2
src/app/components/embeds/record-embed/record-embed.component.ts
+17
-2
src/app/components/embeds/record-embed/record-embed.component.ts
···
1
-
import {ChangeDetectionStrategy, Component, input} from '@angular/core';
2
-
import {$Typed, AppBskyEmbedRecord, AppBskyFeedDefs, AppBskyGraphDefs, AppBskyLabelerDefs} from '@atproto/api';
1
+
import {ChangeDetectionStrategy, Component, input, output} from '@angular/core';
2
+
import {
3
+
$Typed,
4
+
AppBskyEmbedImages,
5
+
AppBskyEmbedRecord,
6
+
AppBskyFeedDefs,
7
+
AppBskyGraphDefs,
8
+
AppBskyLabelerDefs
9
+
} from '@atproto/api';
3
10
import {DisplayNamePipe} from '@shared/pipes/display-name.pipe';
4
11
import {IsEmbedRecordViewRecordPipe} from '@shared/pipes/type-guards/is-embed-record-viewrecord.pipe';
5
12
import {NgTemplateOutlet} from '@angular/common';
···
60
67
>();
61
68
protected readonly AppBskyFeedDefs = AppBskyFeedDefs;
62
69
protected readonly AppBskyGraphDefs = AppBskyGraphDefs;
70
+
71
+
onClick = output();
72
+
onImgClick = output<{images: AppBskyEmbedImages.ViewImage[], index: number}>();
73
+
74
+
recordClick(event: Event) {
75
+
event.stopPropagation();
76
+
this.onClick.emit();
77
+
}
63
78
}
+21
src/app/services/dialog.service.ts
+21
src/app/services/dialog.service.ts
···
1
+
import {Injectable} from '@angular/core';
2
+
import {Dialog} from '@angular/cdk/dialog';
3
+
import {GalleryComponent} from '@components/dialogs/gallery/gallery.component';
4
+
import {AppBskyEmbedImages} from '@atproto/api';
5
+
6
+
@Injectable({
7
+
providedIn: 'root'
8
+
})
9
+
export class DialogService {
10
+
11
+
constructor(
12
+
private dialog: Dialog
13
+
) {}
14
+
15
+
openImage(images: AppBskyEmbedImages.ViewImage[], index: number) {
16
+
const dialogRef = this.dialog.open(GalleryComponent, {
17
+
data: {images: images, index: index},
18
+
hasBackdrop: true
19
+
});
20
+
}
21
+
}
-3
src/app/views/dashboard/dashboard.component.ts
-3
src/app/views/dashboard/dashboard.component.ts
···
4
4
import {PostComposerComponent} from '@components/navigation/post-composer/post-composer.component';
5
5
import {PostService} from '@services/post.service';
6
6
import {AuxbarComponent} from '@components/navigation/auxbar/auxbar.component';
7
-
// import {MskyDialogService} from '@services/msky-dialog.service';
8
-
// import {PostService} from '@services/post.service';
9
7
10
8
@Component({
11
9
selector: 'app-dashboard',
···
20
18
})
21
19
export class DashboardComponent {
22
20
constructor(
23
-
// protected dialogService: MskyDialogService,
24
21
protected postService: PostService
25
22
) {}
26
23
}