Simplify and standardise a lot of the loading

I was overcomplicating everything trying to reduce the render lag.
Just simplify it a bit. It works.
This commit is contained in:
Gordon Pedersen 2024-06-13 14:46:24 +10:00
parent 08788db7fa
commit 0c3836b0c8
16 changed files with 205 additions and 261 deletions

View file

@ -1,11 +1,9 @@
@inject IJSRuntime JS @inject IJSRuntime JS
@inject State State
@if(Html != null) { @if(Html != null) {
<iframe id="@id" frameborder="0" scrolling="no" srcdoc="@Html" onload="() => iframeResize({ license: 'GPLv3' })"></iframe> <iframe id="@id" frameborder="0" scrolling="no" srcdoc="@Html" onload="() => iframeResize({ license: 'GPLv3' })"></iframe>
} }
@code { @code {
[Parameter] [Parameter]
public string Url { get; set; } public string Url { get; set; }
@ -13,6 +11,11 @@
public string id { get; set; } public string id { get; set; }
public MarkupString? Html { get; set; } public MarkupString? Html { get; set; }
protected override void OnInitialized() {
base.OnInitialized();
State.CurrentPage = Page.Other;
}
protected override async Task OnAfterRenderAsync(bool firstRender) { protected override async Task OnAfterRenderAsync(bool firstRender) {
if(firstRender){ if(firstRender){
await Reload(); await Reload();

View file

@ -0,0 +1,16 @@
<article id="@id" class="@(string.IsNullOrEmpty(@class)?"":@class) middle-align center-align">
<div>
<i class="extra square @icon"></i>
<div class="space"></div>
<progress class="circle"></progress>
</div>
</article>
@code {
[Parameter]
public string id { get; set; }
[Parameter]
public string? @class { get; set; }
[Parameter]
public string icon { get; set; }
}

View file

@ -0,0 +1,18 @@
<div class="row center-align">
<h3>
<i class="@icon extra"></i> @title
</h3>
</div>
<div class="row center-align">
<p>@Description</p>
</div>
<div class="space"></div>
@code {
[Parameter]
public string icon { get; set; }
[Parameter]
public string title { get; set; }
[Parameter]
public RenderFragment Description { get; set; }
}

View file

@ -1,27 +1,30 @@
@page "/ephemeral" @page "/ephemeral"
@inject IJSRuntime JS
@inject State State
<PageHeading title="Eph.emer.al" icon="fa-light fa-comment-dots">
<Description><a href="https://eph.emer.al">Eph.emer.al</a> is a place for fleeting thoughts. Everything on this page will disappear after a while.</Description>
</PageHeading>
<div class="row center-align"> <div id="ephemeral" class="responsive">
<h3>
<i class="fa-light fa-comment-dots"></i> Ephemeral
</h3>
</div>
<div class="row center-align">
<p><a href="https://eph.emer.al">Ephemeral</a> is a place for fleeting thoughts. Everything on this page will disappear after a while.</p>
</div>
@if (messages != null) {
<Virtualize Items="messages" Context="message"> foreach (MarkupString message in messages) {
<article class="ephemeral center"> <article class="ephemeral center">
@message @message
</article> </article>
</Virtualize> }
}
<LoadingCard id="ephemeral-loading" icon="fa-light fa-comment-dots"></LoadingCard>
</div>
@code { @code {
private List<MarkupString> messages = new List<MarkupString>(); private List<MarkupString>? messages;
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
//await Shell.Current.GoToAsync(nameof(EphemeralWebPage)); await base.OnInitializedAsync();
RestService api = new RestService(); if (messages == null || messages.Count == 0) messages = await State.GetEphemeralMessages();
messages = await api.Ephemeral(); await InvokeAsync(StateHasChanged);
await JS.InvokeVoidAsync("removeElementById", "ephemeral-loading");
} }
} }

View file

@ -1,17 +1,14 @@
@page "/now" @page "/now"
@inject IJSRuntime JS
@inject State State @inject State State
<div class="row center-align"> <PageHeading title="Now.garden" icon="fa-duotone fa-seedling">
<h3> <Description>Feel free to stroll through the <a href="now.garden">now.garden</a> and take a look at what people are up to.</Description>
<i class="fa-duotone fa-seedling"></i> Now.garden </PageHeading>
</h3>
</div>
<div class="row center-align">
<p>Feel free to stroll through the <a href="now.garden">now.garden</a> and take a look at what people are up to.</p>
</div>
<div id="now-garden" class="responsive card-grid"> <div id="now-garden" class="responsive card-grid">
<Virtualize ItemsProvider="State.VirtualNowGarden" Context="now" ItemSize="180">
<ItemContent> @if (garden != null) {
foreach (NowData now in garden) {
<article class="now"> <article class="now">
<nav> <nav>
<a class="author" href="/person/@now.Address#now"> <a class="author" href="/person/@now.Address#now">
@ -22,13 +19,19 @@
<small>@now.UpdatedRelative</small> <small>@now.UpdatedRelative</small>
</nav> </nav>
</article> </article>
</ItemContent> }
<Placeholder> }
<StatusCardSkeleton></StatusCardSkeleton> <LoadingCard id="now-loading" icon="fa-duotone fa-seedling" class="now"></LoadingCard>
</Placeholder>
</Virtualize>
</div> </div>
@code { @code {
private List<NowData>? garden;
protected override async Task OnInitializedAsync() {
await base.OnInitializedAsync();
if (garden == null || garden.Count == 0) garden = await State.GetNowGarden();
await InvokeAsync(StateHasChanged);
await JS.InvokeVoidAsync("removeElementById", "now-loading");
}
} }

View file

@ -1,6 +1,7 @@
@page "/person/{Address}" @page "/person/{Address}"
@inject State State @inject State State
@inject IJSRuntime JS @inject IJSRuntime JS
@inject NavigationManager Nav
<div class="row center-align"> <div class="row center-align">
@ -10,16 +11,6 @@
<img class="profile avatar" src="https://profiles.cache.lol/@Address/picture" alt="@Address" /> <img class="profile avatar" src="https://profiles.cache.lol/@Address/picture" alt="@Address" />
</div> </div>
<div id="bio" class="center-align max">
@if (bio == null)
{
<p><em>Getting Bio...</em></p>
}
else {
@bio
}
</div>
<div class="responsive"> <div class="responsive">
<div class="tabs"> <div class="tabs">
<a data-ui="#profile" @onclick="ReloadProfile"> <a data-ui="#profile" @onclick="ReloadProfile">
@ -42,6 +33,7 @@
} }
</div> </div>
</div> </div>
<div class="responsive page-container"> <div class="responsive page-container">
<div id="profile" class="page no-padding"> <div id="profile" class="page no-padding">
<a href="@ProfileUrl" target="_blank" class="hover absolute top right chip fill large-elevate">Open in browser <i class="fa-solid fa-arrow-up-right-from-square tiny"></i></a> <a href="@ProfileUrl" target="_blank" class="hover absolute top right chip fill large-elevate">Open in browser <i class="fa-solid fa-arrow-up-right-from-square tiny"></i></a>
@ -49,14 +41,21 @@
</div> </div>
<div id="statuses" class="page padding active"> <div id="statuses" class="page padding active">
<StatusList StatusFunc="@State.VirtualStatusesFunc(Address)"></StatusList> <div id="info" class="box basis transparent">
<article id="bio" class="container shadowed blue-2-bg gray-9-fg">
@if (bio == null) {
<p><progress class="circle small"></progress></p>
}
else {
@bio
}
</article>
</div>
<StatusList StatusFunc="@(async() => await State.GetStatuses(Address))"></StatusList>
</div> </div>
<div id="pics" class="page padding"> <div id="pics" class="page padding">
@if(Editable){ <PicList PicsFunc="@(async() => await State.GetPics(Address))" Editable="@Editable"></PicList>
<EditPicDialog @ref="editPicDialog" id="EditPicModal"></EditPicDialog>
}
<PicList PicsFunc="@(async() => await State.GetPics(Address))" Editable="@Editable" Dialog="@editPicDialog"></PicList>
</div> </div>
@if(now != null){ @if(now != null){
<div id="now" class="page no-padding"> <div id="now" class="page no-padding">
@ -73,7 +72,6 @@
get => $"https://{Address}.omg.lol/"; get => $"https://{Address}.omg.lol/";
} }
private EditPicDialog? editPicDialog { get; set; }
public ExternalPageComponent? NowPage { get; set; } public ExternalPageComponent? NowPage { get; set; }
public ExternalPageComponent? ProfilePage { get; set; } public ExternalPageComponent? ProfilePage { get; set; }
@ -86,9 +84,14 @@
private NowData? now; private NowData? now;
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
List<NowData>? garden = await State.GetNowGarden();
now = garden?.FirstOrDefault(n => n.Address == Address);
await InvokeAsync(StateHasChanged);
string fragment = new Uri(Nav.Uri).Fragment;
await JS.InvokeVoidAsync("ui", fragment);
if (fragment.EndsWith("now")) await ReloadNow();
else if (fragment.EndsWith("profile")) await ReloadProfile();
bio = await State.GetBio(Address); bio = await State.GetBio(Address);
List<NowData> garden = await State.GetNowGarden();
now = garden.FirstOrDefault(n => n.Address == Address);
} }
private async Task ReloadNow() { private async Task ReloadNow() {

View file

@ -1,16 +1,9 @@
@page "/pics" @page "/pics"
@inject State State @inject State State
@inject IJSRuntime JS
<PageHeading title="Some.pics" icon="fa-solid fa-images">
<div class="row center-align"> <Description>Sit back, relax, and look at <a href="https://some.pics/">some.pics</a></Description>
<h3> </PageHeading>
<i class="fa-solid fa-images"></i> Some.pics
</h3>
</div>
<div class="row center-align">
<p>Sit back, relax, and look at <a href="https://some.pics/">some.pics</a></p>
</div>
<AuthorizeView> <AuthorizeView>
<Authorized> <Authorized>

View file

@ -1,14 +1,9 @@
@page "/statuslog/latest" @page "/statuslog/latest"
@inject State State @inject State State
<div class="row center-align"> <PageHeading title="Status.lol" icon="fa-solid fa-message-smile">
<h3> <Description>The latest posts from everyone at <a href="https://status.lol">status.lol</a></Description>
<i class="fa-solid fa-message-smile"></i> Statuslog </PageHeading>
</h3>
</div>
<div class="row center-align">
<p>The latest posts from everyone at <a href="https://status.lol">status.lol</a></p>
</div>
<AuthorizeView> <AuthorizeView>
<Authorized> <Authorized>
@ -20,10 +15,9 @@
</AuthorizeView> </AuthorizeView>
<div id="statuses" class="responsive"> <div id="statuses" class="responsive">
<StatusList StatusFunc="@State.VirtualStatusesFunc()"></StatusList> <StatusList StatusFunc="@(async() => await State.GetStatuses())"></StatusList>
</div> </div>
@code { @code {
} }

View file

@ -12,7 +12,7 @@
<i class="fa fa-clock"></i> @Pic.RelativeTime <i class="fa fa-clock"></i> @Pic.RelativeTime
</a> </a>
</nav> </nav>
<p>@Pic.Description</p> <p>@((MarkupString)Pic.DescriptionHtml)</p>
<nav> <nav>
<div class="max"></div> <div class="max"></div>
@if(Editable) { @if(Editable) {

View file

@ -2,112 +2,30 @@
@inject State State @inject State State
@if (Editable) { @if (Editable) {
<PicCardEditableTemplate></PicCardEditableTemplate> <EditPicDialog @ref="Dialog" id="EditPicModal"></EditPicDialog>
}
else {
<PicCardTemplate></PicCardTemplate>
} }
<article id="pics-loading" class="middle-align center-align"> @if (pics != null) foreach (Pic pic in pics) {
<div> <PicCard Pic="pic" Editable="Editable" Dialog="Dialog"></PicCard>
<i class="extra fa-solid fa-images"></i> }
<div class="space"></div>
<progress class="circle"></progress>
</div>
</article>
<LoadingCard id="pics-loading" icon="fa-solid fa-images"></LoadingCard>
<script>
async function renderPics(somePics) {
const template = document.getElementById("PicCard")
if(template) for (const pic of somePics) {
const clone = template.content.cloneNode(true)
let html = clone.children[0].innerHTML
html = html.replaceAll("{{Pic.Id}}", pic.id)
html = html.replaceAll("{{Pic.Url}}", pic.url ?? `https://cdn.some.pics/${pic.address}/${pic.id}.${JSON.parse(pic.exif)["File Type Extension"]}`) //TODO: temporary fix
html = html.replaceAll("{{Pic.Address}}", pic.address)
html = html.replaceAll("{{Pic.RelativeTime}}", pic.relativeTime)
html = html.replaceAll("{{Pic.Description}}", pic.description)
html = html.replaceAll("{{Pic.DescriptionHtml}}", pic.descriptionHtml)
clone.children[0].innerHTML = html
clone.querySelector('.share-button').addEventListener('click', shareClick)
clone.querySelector('.edit-button')?.addEventListener('click', editClick)
document.getElementById("pics").insertBefore(clone, document.getElementById("pics-loading"))
}
}
async function clearLoading() {
document.getElementById("pics-loading").remove()
}
async function shareClick(e) {
const url = this.querySelector('input[name="url"]')?.value
const desc = this.querySelector('input[name="description"]')?.value
if (CSHARP) CSHARP.invokeMethodAsync('ShareClick', url, desc)
}
async function editClick(e) {
const id = this.querySelector('input[name="id"]')?.value
if (CSHARP) CSHARP.invokeMethodAsync('EditClick', id)
}
</script>
@code { @code {
[Parameter] [Parameter]
public Func<Task<List<Pic>?>> PicsFunc { get; set; } public Func<Task<List<Pic>?>> PicsFunc { get; set; }
[Parameter] [Parameter]
public bool Editable { get; set; } = false; public bool Editable { get; set; } = false;
[Parameter]
public EditPicDialog? Dialog { get; set; } public EditPicDialog? Dialog { get; set; }
private List<Pic> pics; private List<Pic>? pics;
private DotNetObjectReference<PicList>? objRef;
protected override void OnInitialized() { // TODO: There is a noticable rendering delay between the pics loading and the page rendering
base.OnInitialized(); protected override async Task OnInitializedAsync() {
objRef = DotNetObjectReference.Create(this); await base.OnInitializedAsync();
State.CurrentPage = Page.Pics;
}
protected override async Task OnAfterRenderAsync(bool firstRender) {
await base.OnAfterRenderAsync(firstRender);
if (firstRender) {
await JS.InvokeVoidAsync("injectCSharp", objRef);
if (State.CurrentPage != Page.Pics) return;
if (pics == null || pics.Count == 0) pics = await PicsFunc(); if (pics == null || pics.Count == 0) pics = await PicsFunc();
if (State.CurrentPage != Page.Pics) return; await InvokeAsync(StateHasChanged);
await JS.InvokeVoidAsync("removeElementById", "pics-loading");
int page_size = 1;
for (int i = 0; i < pics.Count; i += page_size) {
if (State.CurrentPage != Page.Pics) return;
await JS.InvokeVoidAsync("renderPics", pics.Skip(i * page_size).Take(page_size));
if (State.CurrentPage != Page.Pics) return;
}
if (State.CurrentPage != Page.Pics) return;
await JS.InvokeVoidAsync("clearLoading");
}
}
[JSInvokable]
public async Task EditClick(string? id) {
if (Editable && Dialog != null)
{
Pic? pic = pics.FirstOrDefault(p => p.Id == id);
Dialog.Pic = pic;
// await InvokeAsync(StateHasChanged);
await JS.InvokeVoidAsync("ui", "#" + Dialog?.id);
}
}
[JSInvokable]
public async Task ShareClick(string? url, string? description) {
await Share.Default.RequestAsync(new ShareTextRequest {
Uri = url,
Text = description,
Title = "I saw this on some.pics",
Subject = "I saw this on some.pics"
});
} }
} }

View file

@ -1,14 +1,12 @@
<article class="status"> <article class="status">
<div class="row"> <div class="row">
<div class="large emoji" data-emoji="@status.EmojiOrDefault">@status.EmojiOrDefault</div> <div class="emoji" data-emoji="@status.EmojiOrDefault">@status.EmojiOrDefault</div>
<div class="max"> <div class="max">
<a class="author" href="/person/@status.Address"> <a class="author" href="/person/@status.Address">
<i class="fa-solid fa-fw fa-at"></i>@status.Address <i class="fa-solid fa-fw fa-at"></i>@status.Address
</a> </a>
@status.HtmlContent @status.HtmlContent
</div> <nav class="no-margin">
</div>
<nav>
<a class="chip transparent-border"> <a class="chip transparent-border">
<i class="fa fa-clock"></i> @status.RelativeTime <i class="fa fa-clock"></i> @status.RelativeTime
</a> </a>
@ -20,6 +18,8 @@
<i class="fa-solid fa-share-nodes"></i> <i class="fa-solid fa-share-nodes"></i>
</button> </button>
</nav> </nav>
</div>
</div>
</article> </article>
@code { @code {

View file

@ -1,23 +1,24 @@
@inject State State @inject IJSRuntime JS
<Virtualize ItemsProvider="GetStatuses" Context="status" ItemSize="180"> @inject State State
<ItemContent>
@if(statuses != null) foreach(Status status in statuses) {
<StatusCard status="@status"></StatusCard> <StatusCard status="@status"></StatusCard>
</ItemContent> }
<Placeholder>
<StatusCardSkeleton></StatusCardSkeleton> <LoadingCard id="statusLoading" icon="fa-solid fa-message-smile"></LoadingCard>
</Placeholder>
</Virtualize>
@code { @code {
[Parameter] [Parameter]
public Func<ItemsProviderRequest, ValueTask<ItemsProviderResult<Status>>> StatusFunc { get; set; } public Func<Task<List<Status>?>> StatusFunc { get; set; }
[Parameter]
public bool Editable { get; set; } = false;
private async ValueTask<ItemsProviderResult<Status>> GetStatuses(ItemsProviderRequest request) { private List<Status>? statuses;
return await this.StatusFunc(request);
}
protected override void OnInitialized() { protected override async Task OnInitializedAsync() {
base.OnInitialized(); await base.OnInitializedAsync();
State.CurrentPage = Page.Status; if (statuses == null || statuses.Count == 0) statuses = await StatusFunc();
await InvokeAsync(StateHasChanged);
await JS.InvokeVoidAsync("removeElementById", "statusLoading");
} }
} }

View file

@ -17,7 +17,7 @@ namespace Neighbourhood.omg.lol.Models {
public long Size { get; set; } public long Size { get; set; }
public string Mime { get; set; } public string Mime { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string DescriptionHtml { get => Markdown.ToHtml(Description); } public string DescriptionHtml { get => Description == null ? string.Empty : Markdown.ToHtml(Description); }
[JsonPropertyName("exif")] [JsonPropertyName("exif")]
public JsonElement ExifJson { get; set; } public JsonElement ExifJson { get; set; }

View file

@ -25,6 +25,7 @@ namespace Neighbourhood.omg.lol.Models {
public List<Status>? Statuses { get; set; } public List<Status>? Statuses { get; set; }
public List<Pic>? Pics { get; set; } public List<Pic>? Pics { get; set; }
public List<NowData>? NowGarden { get; set; } public List<NowData>? NowGarden { get; set; }
public List<MarkupString>? EphemeralMessages { get; set; }
public List<Status>? CachedAddressStatuses { get; set; } public List<Status>? CachedAddressStatuses { get; set; }
public List<Pic>? CachedAddressPics { get; set; } public List<Pic>? CachedAddressPics { get; set; }
@ -88,6 +89,14 @@ namespace Neighbourhood.omg.lol.Models {
return CachedAddressBio; return CachedAddressBio;
} }
public async Task<List<MarkupString>?> GetEphemeralMessages(bool forceRefresh = false) {
RestService api = new RestService();
if(forceRefresh || this.EphemeralMessages == null || this.EphemeralMessages.Count == 0) {
this.EphemeralMessages = await api.Ephemeral();
}
return this.EphemeralMessages;
}
public async Task<List<Status>?> GetStatuses(bool forceRefresh = false) { public async Task<List<Status>?> GetStatuses(bool forceRefresh = false) {
RestService api = new RestService(); RestService api = new RestService();
if (forceRefresh || this.Statuses == null || this.Statuses.Count == 0) { if (forceRefresh || this.Statuses == null || this.Statuses.Count == 0) {
@ -96,27 +105,13 @@ namespace Neighbourhood.omg.lol.Models {
return this.Statuses; return this.Statuses;
} }
public async ValueTask<ItemsProviderResult<Status>> VirtualStatuses(ItemsProviderRequest request) { public async Task<List<Status>?> GetStatuses(string address, bool forceRefresh = false) {
// TODO: request.cancellationToken this.CachedAddress = address;
var statuses = (await this.GetStatuses()) ?? new List<Status>();
var numStatuses = Math.Min(request.Count, statuses.Count - request.StartIndex);
return new ItemsProviderResult<Status>(statuses.Skip(request.StartIndex).Take(numStatuses), statuses.Count);
}
public async ValueTask<ItemsProviderResult<Status>> VirtualStatuses(ItemsProviderRequest request, string address) {
// TODO: request.cancellationToken
RestService api = new RestService(); RestService api = new RestService();
CachedAddressStatuses = (await api.Statuslog(address)) ?? new List<Status>(); if (forceRefresh || this.CachedAddressStatuses == null || this.CachedAddressStatuses.Count == 0) {
var numStatuses = Math.Min(request.Count, CachedAddressStatuses.Count - request.StartIndex); this.CachedAddressStatuses = await api.Statuslog(address);
return new ItemsProviderResult<Status>(CachedAddressStatuses.Skip(request.StartIndex).Take(numStatuses), CachedAddressStatuses.Count);
}
public Func<ItemsProviderRequest, ValueTask<ItemsProviderResult<Status>>> VirtualStatusesFunc(string? address = null) {
if (address == null) return VirtualStatuses;
else {
CachedAddress = address;
return async (ItemsProviderRequest request) => await VirtualStatuses(request, CachedAddress);
} }
return this.CachedAddressStatuses;
} }
public async Task<List<NowData>?> GetNowGarden(bool forceRefresh = false) { public async Task<List<NowData>?> GetNowGarden(bool forceRefresh = false) {
@ -126,16 +121,6 @@ namespace Neighbourhood.omg.lol.Models {
} }
return this.NowGarden; return this.NowGarden;
} }
public async ValueTask<ItemsProviderResult<NowData>> VirtualNowGarden(ItemsProviderRequest request) {
// TODO: request.cancellationToken
var garden = (await this.GetNowGarden()) ?? new List<NowData>();
var numSeedlings = Math.Min(request.Count, garden.Count - request.StartIndex);
return new ItemsProviderResult<NowData>(garden.Skip(request.StartIndex).Take(numSeedlings), garden.Count);
}
public Func<ItemsProviderRequest, ValueTask<ItemsProviderResult<NowData>>> VirtualNowGardenFunc() {
return VirtualNowGarden;
}
public async Task<List<Pic>?> GetPics(bool forceRefresh = false) { public async Task<List<Pic>?> GetPics(bool forceRefresh = false) {
if(forceRefresh || this.Pics == null || this.Pics.Count == 0) { if(forceRefresh || this.Pics == null || this.Pics.Count == 0) {
@ -147,36 +132,13 @@ namespace Neighbourhood.omg.lol.Models {
public async Task<List<Pic>?> GetPics(string address, bool forceRefresh = false) { public async Task<List<Pic>?> GetPics(string address, bool forceRefresh = false) {
CachedAddress = address; CachedAddress = address;
if (forceRefresh || this.Pics == null || this.Pics.Count == 0) { if (forceRefresh || this.CachedAddressPics == null || this.CachedAddressPics.Count == 0) {
RestService api = new RestService(); RestService api = new RestService();
CachedAddressPics = (await api.SomePics(address)) ?? new List<Pic>(); CachedAddressPics = (await api.SomePics(address)) ?? new List<Pic>();
} }
return CachedAddressPics; return CachedAddressPics;
} }
public async ValueTask<ItemsProviderResult<Pic>> VirtualPics(ItemsProviderRequest request) {
// TODO: request.cancellationToken
var pics = (await this.GetPics()) ?? new List<Pic>();
var numPics = Math.Min(request.Count, pics.Count - request.StartIndex);
return new ItemsProviderResult<Pic>(pics.Skip(request.StartIndex).Take(numPics), pics.Count);
}
public async ValueTask<ItemsProviderResult<Pic>> VirtualPics(ItemsProviderRequest request, string address) {
// TODO: request.cancellationToken
RestService api = new RestService();
CachedAddressPics = (await api.SomePics(address)) ?? new List<Pic>();
var numPics = Math.Min(request.Count, CachedAddressPics.Count - request.StartIndex);
return new ItemsProviderResult<Pic>(CachedAddressPics.Skip(request.StartIndex).Take(numPics), CachedAddressPics.Count);
}
public Func<ItemsProviderRequest, ValueTask<ItemsProviderResult<Pic>>> VirtualPicsFunc(string? address = null) {
if (address == null) return VirtualPics;
else {
CachedAddress = address;
return async (ItemsProviderRequest request) => await VirtualPics(request, CachedAddress);
}
}
public async Task<long> FileSize(FileResult file) { public async Task<long> FileSize(FileResult file) {
using var fileStream = await file.OpenReadAsync(); using var fileStream = await file.OpenReadAsync();
return fileStream.Length; return fileStream.Length;
@ -213,7 +175,7 @@ namespace Neighbourhood.omg.lol.Models {
Status, Status,
Pics, Pics,
Ephemeral, Ephemeral,
Person, NowGarden,
Other Other
} }
} }

View file

@ -10,6 +10,13 @@
--emoji-font: SegoeUIEmoji, 'Segoe UI Emoji', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Symbol'; --emoji-font: SegoeUIEmoji, 'Segoe UI Emoji', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Symbol';
--font: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif, var(--emoji-font); --font: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif, var(--emoji-font);
--prami-svg: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><path fill="%23FF6BAE" stroke="none" d="M250 450C211.612 450 173.225 435.354 143.934 406.066L43.9346 306.066C-14.6446 247.487 -14.6446 152.513 43.9346 93.9341C100.533 37.3361 191.104 35.421 250 88.1907C308.898 35.4229 399.47 37.3379 456.066 93.9341C514.645 152.513 514.645 247.487 456.066 306.066L356.066 406.066C326.778 435.354 288.389 450 250 450" /><path fill="none" stroke="%23471036" stroke-width="19" stroke-linecap="round" d="M208.749 223.817C227.625 254.619 272.376 254.619 291.251 223.817" /><circle fill="%23471036" cx="291.3" cy="176.6" r="17.75" /><circle fill="%23471036" cx="208.6" cy="176.6" r="17.75" /><circle fill="%23E24097" cx="120.3" cy="212" r="59.2" /><circle fill="%23E24097" cx="379.7" cy="212" r="59.2" /></svg>'); --prami-svg: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><path fill="%23FF6BAE" stroke="none" d="M250 450C211.612 450 173.225 435.354 143.934 406.066L43.9346 306.066C-14.6446 247.487 -14.6446 152.513 43.9346 93.9341C100.533 37.3361 191.104 35.421 250 88.1907C308.898 35.4229 399.47 37.3379 456.066 93.9341C514.645 152.513 514.645 247.487 456.066 306.066L356.066 406.066C326.778 435.354 288.389 450 250 450" /><path fill="none" stroke="%23471036" stroke-width="19" stroke-linecap="round" d="M208.749 223.817C227.625 254.619 272.376 254.619 291.251 223.817" /><circle fill="%23471036" cx="291.3" cy="176.6" r="17.75" /><circle fill="%23471036" cx="208.6" cy="176.6" r="17.75" /><circle fill="%23E24097" cx="120.3" cy="212" r="59.2" /><circle fill="%23E24097" cx="379.7" cy="212" r="59.2" /></svg>');
--spacing: 1.5rem;
--radius: .75em;
--small-radius: .13em;
/* --color: var(--white);
--background: var(--gray-8);
--shadow: var(--black);
--button-shadow: var(--gray-7);*/
} }
html, body { html, body {
font-family: var(--font); font-family: var(--font);
@ -53,13 +60,17 @@ img {
.status .emoji, #status-emoji { .status .emoji, #status-emoji {
margin-bottom: auto; margin-bottom: auto;
inline-size: 3.5rem; inline-size: 5.5rem;
block-size: 3.5rem; block-size: 5.5rem;
font-size: 3.1rem; font-size: 5rem;
line-height: 3.5rem; line-height: 5.5rem;
text-indent: -6px; text-indent: -6px;
} }
.status nav .chip, .status nav {
color: var(--gray-7);
}
.profile.avatar { .profile.avatar {
border-radius: .75rem; border-radius: .75rem;
max-block-size: 10rem; max-block-size: 10rem;
@ -269,3 +280,11 @@ main {
.hover { .hover {
z-index: 1; z-index: 1;
} }
#info :is(ul, ol) {
margin-left: var(--spacing);
}
#info :is(p, ul, ol) {
margin-bottom: var(--spacing);
}

View file

@ -1,3 +1,14 @@
window.injectCSharp = async function (helper) { window.injectCSharp = async function (helper) {
window.CSHARP = helper window.CSHARP = helper
} }
async function delay(t) {
return new Promise((resolve) => {
setTimeout(resolve, t);
});
}
async function removeElementById(id) {
document.getElementById(id)?.remove()
}