1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
@page "/"
@page "/Gallery"
@inject ITagService tagService
@inject IFeedService feedService
@inject IUserService userService
@inject IJSRuntime jsRuntime
@implements IDisposable
@attribute [Authorize]
<PageTitle>@Title</PageTitle>
@if(Ingest && !userService.UserSessionState.ShowNsfw) {
<div id="feed-error">
<p><center>Ingest feed is not available unless NSFW mode is enabled!</center></p>
<p><center><i>You must enable NSFW mode to continue...</i></center></p>
</div>
} else if(TagId is not null && Query is not null) {
<div id="feed-error">
<p><center>Invalid query parameters! Both a search query and</center></p>
<p><center>a tag ID have been specified!</center></p>
</div>
} else {
<div style="margin:0 auto 0 auto;padding:var(--size-default-gap);">
<ButtonContainer>
<Button
OnClick=@(async () => await SetViewSize(GalleryViewSize.Small))
IconUrl="/images/grid-small.svg"/>
<Button
OnClick=@(async () => await SetViewSize(GalleryViewSize.Medium))
IconUrl="/images/grid-medium.svg"/>
<Button
OnClick=@(async () => await SetViewSize(GalleryViewSize.Large))
IconUrl="/images/grid-large.svg"/>
</ButtonContainer>
@foreach(var media in displayMedia) {
// Precalculate thumbnail size to help the browser
// lay out the images during initial page load
int width = (int) media.CurrentUploadedFile!.Width! * 200 / (int) media.CurrentUploadedFile.Height!;
<a href="/ViewMedia?m=@(media.Guid)">
@switch(viewSize) {
case GalleryViewSize.Small:
<img src="/media/thumb/@(media.Guid)?w=100&h=100" width="100" height="100"/>
break;
case GalleryViewSize.Medium:
<img src="/media/thumb/@(media.Guid)?w=200&h=200" width="200" height="200"/>
break;
case GalleryViewSize.Large:
<img src="/media/thumb/@(media.Guid)?h=200" width=@width height="200"/>
break;
}
</a>
}
<div id="canary" style="height:1px;"></div>
</div>
}
<script suppress-error="BL9992">
function registerScrollObserver(dotNetObject) {
var scrollObserver = new IntersectionObserver(
async (e) => {
if(e[0].isIntersecting) {
await dotNetObject.invokeMethodAsync('LoadMedia', false);
}
},
{
root: document.getElementById('content'),
threshold: 0
});
scrollObserver.observe(document.getElementById("canary"));
}
</script>
@code {
[Parameter]
[SupplyParameterFromQuery(Name = "t")]
public Guid? TagId { get; set; }
[Parameter]
[SupplyParameterFromQuery(Name = "q")]
public string? Query { get; set; }
[Parameter]
[SupplyParameterFromQuery(Name = "s")]
public string? SortOrder { get; set; }
[Parameter]
[SupplyParameterFromQuery]
public bool Ingest { get; set; } = false;
public const int PageSize = 50;
private string Title {
get {
if(Query is null)
return Ingest ? "Ingest Feed" : "Gallery";
else
return "Search Results";
}
}
private GalleryViewSize viewSize = GalleryViewSize.Large;
private List<Media> displayMedia;
protected override void OnInitialized() =>
userService.UserSessionState.OnStateChange += ShowNsfwChanged;
protected override void OnParametersSet() => LoadMedia(true);
protected override void OnAfterRender(bool firstRender) {
if(firstRender)
jsRuntime.InvokeVoidAsync(
"registerScrollObserver",
DotNetObjectReference.Create(this));
}
[JSInvokable("LoadMedia")]
public void LoadMedia(bool initial = false) {
Media? key = displayMedia?.Any() ?? false && !initial ? displayMedia.Last() : null;
if(initial)
displayMedia = new();
ApiModels.SortOrder? sortOrder = null;
if(Enum.TryParse<ApiModels.SortOrder>(SortOrder, true, out var so))
sortOrder = so;
if(TagId is not null && Query is null) {
displayMedia!.AddRange(feedService.LoadChunk(
selectIngest: Ingest,
includeNsfw: userService.UserSessionState.ShowNsfw,
tagId: (Guid) TagId!,
key: key,
count: PageSize,
sortOrder: sortOrder ?? default));
} else if(Query is not null && TagId is null) {
displayMedia!.AddRange(feedService.LoadChunk(
selectIngest: Ingest,
includeNsfw: userService.UserSessionState.ShowNsfw,
query: string.IsNullOrWhiteSpace(Query) ? null : Query,
key: key,
count: PageSize,
sortOrder: sortOrder ?? default));
} else {
displayMedia!.AddRange(feedService.LoadChunk(
selectIngest: Ingest,
includeNsfw: userService.UserSessionState.ShowNsfw,
key: key,
count: PageSize,
sortOrder: sortOrder ?? default));
}
StateHasChanged();
}
private async void ShowNsfwChanged(UserSessionState userSessionState) {
await InvokeAsync(() => {
LoadMedia(true);
});
}
private async Task SetViewSize(GalleryViewSize viewSize) {
this.viewSize = viewSize;
await InvokeAsync(() => StateHasChanged());
}
public void Dispose() =>
userService.UserSessionState.OnStateChange -= ShowNsfwChanged;
public enum GalleryViewSize {
Small,
Medium,
Large
}
}
|