Now, now garden and profile pages

This commit is contained in:
Gordon Pedersen 2024-06-11 10:36:48 +10:00
parent 0e1f734f9f
commit 3f1531a934
17 changed files with 320 additions and 31 deletions

View file

@ -0,0 +1,33 @@
@inject IJSRuntime JS
@if(Html != null) {
<iframe id="@id" frameborder="0" scrolling="no" srcdoc="@Html" onload="() => iframeResize({ license: 'GPLv3' })"></iframe>
}
@code {
[Parameter]
public string Url { get; set; }
[Parameter]
public string id { get; set; }
public MarkupString? Html { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender) {
if(firstRender){
await Reload();
}
}
public async Task Reload() {
if (Html == null){
RestService api = new RestService();
Html = await api.GetHtml(Url);
string? HtmlString = Html?.ToString();
HtmlString = HtmlString?.Replace("</head>", "<base target='_blank'></head>");
HtmlString = HtmlString?.Replace("</body>", "<script src='https://cdn.jsdelivr.net/npm/@iframe-resizer/child'></script></body>");
Html = (MarkupString)HtmlString;
}
await InvokeAsync(StateHasChanged);
}
}

View file

@ -55,6 +55,10 @@
<i class="fa-light fa-comment-dots"></i> <i class="fa-light fa-comment-dots"></i>
<div>Eph.emer.al</div> <div>Eph.emer.al</div>
</NavLink> </NavLink>
<NavLink class="row nav-link" href="/now">
<i class="fa-duotone fa-seedling"></i>
<div>Now.garden</div>
</NavLink>
</nav> </nav>
<nav class="left m"> <nav class="left m">
@ -110,6 +114,10 @@
<i class="fa-light fa-comment-dots"></i> <i class="fa-light fa-comment-dots"></i>
<small>Eph.emer.al</small> <small>Eph.emer.al</small>
</NavLink> </NavLink>
<NavLink class="nav-link" href="/now">
<i class="fa-duotone fa-seedling"></i>
<small>Now.garden</small>
</NavLink>
</nav> </nav>
<nav class="bottom s"> <nav class="bottom s">
@ -125,6 +133,10 @@
<i class="fa-light fa-comment-dots"></i> <i class="fa-light fa-comment-dots"></i>
<small>Eph.emer.al</small> <small>Eph.emer.al</small>
</NavLink> </NavLink>
<NavLink class="nav-link" href="/now">
<i class="fa-duotone fa-seedling"></i>
<small>Now.garden</small>
</NavLink>
<AuthorizeView> <AuthorizeView>
<Authorized> <Authorized>

View file

@ -2,7 +2,7 @@
<div class="row center-align"> <div class="row center-align">
<h3> <h3>
Ephemeral <i class="fa-light fa-comment-dots"></i> Ephemeral
</h3> </h3>
</div> </div>
<div class="row center-align"> <div class="row center-align">

View file

@ -0,0 +1,34 @@
@page "/now"
@inject State State
<div class="row center-align">
<h3>
<i class="fa-duotone fa-seedling"></i> Now.garden
</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">
<Virtualize ItemsProvider="State.VirtualNowGarden" Context="now" ItemSize="180">
<ItemContent>
<article class="now">
<nav>
<a class="author" href="/person/@now.Address#now">
<i class="fa-duotone fa-seedling"></i> @now.Address
</a>
</nav>
<nav>
<small>@now.UpdatedRelative</small>
</nav>
</article>
</ItemContent>
<Placeholder>
<StatusCardSkeleton></StatusCardSkeleton>
</Placeholder>
</Virtualize>
</div>
@code {
}

View file

@ -1,5 +1,7 @@
@page "/person/{Address}" @page "/person/{Address}"
@inject State State @inject State State
@inject IJSRuntime JS
<div class="row center-align"> <div class="row center-align">
<h3><i class="fa-solid fa-fw fa-at"></i>@Address</h3> <h3><i class="fa-solid fa-fw fa-at"></i>@Address</h3>
@ -20,6 +22,10 @@
<div class="responsive"> <div class="responsive">
<div class="tabs"> <div class="tabs">
<a data-ui="#profile" @onclick="ReloadProfile">
<i class="fa-solid fa-id-card"></i>
<span>@(Address).omg.lol</span>
</a>
<a data-ui="#statuses" class="active"> <a data-ui="#statuses" class="active">
<i class="fa-solid fa-message-smile"></i> <i class="fa-solid fa-message-smile"></i>
<span>Status.lol</span> <span>Status.lol</span>
@ -28,7 +34,20 @@
<i class="fa-solid fa-images"></i> <i class="fa-solid fa-images"></i>
<span>Some.pics</span> <span>Some.pics</span>
</a> </a>
@if(now != null){
<a data-ui="#now" @onclick="ReloadNow">
<i class="fa-duotone fa-seedling"></i>
<span>/Now</span>
</a>
}
</div> </div>
</div>
<div class="responsive page-container">
<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>
<ExternalPageComponent id="profile_page" @ref="ProfilePage" Url="@ProfileUrl"></ExternalPageComponent>
</div>
<div id="statuses" class="page padding active"> <div id="statuses" class="page padding active">
<StatusList StatusFunc="@State.VirtualStatusesFunc(Address)"></StatusList> <StatusList StatusFunc="@State.VirtualStatusesFunc(Address)"></StatusList>
</div> </div>
@ -39,13 +58,24 @@
} }
<PicList PicsFunc="@State.VirtualPicsFunc(Address)" Editable="@Editable" Dialog="@editPicDialog"></PicList> <PicList PicsFunc="@State.VirtualPicsFunc(Address)" Editable="@Editable" Dialog="@editPicDialog"></PicList>
</div> </div>
@if(now != null){
<div id="now" class="page no-padding">
<a href="@now.Url" 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>
<ExternalPageComponent id="now_page" @ref="NowPage" Url="@now.Url"></ExternalPageComponent>
</div>
}
</div> </div>
@code { @code {
[Parameter] [Parameter]
public string Address { get; set; } public string Address { get; set; }
public string ProfileUrl {
get => $"https://{Address}.omg.lol/";
}
private EditPicDialog? editPicDialog { get; set; } private EditPicDialog? editPicDialog { get; set; }
public ExternalPageComponent? NowPage { get; set; }
public ExternalPageComponent? ProfilePage { get; set; }
private bool Editable { private bool Editable {
get => Address == State.SelectedAddressName; get => Address == State.SelectedAddressName;
@ -53,7 +83,29 @@
private MarkupString? bio; private MarkupString? bio;
private NowData? now;
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
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() {
if(NowPage != null) {
await NowPage.Reload();
await ResizeIframes();
}
}
private async Task ReloadProfile() {
if (ProfilePage != null) {
await ProfilePage.Reload();
await ResizeIframes();
}
}
private async Task ResizeIframes() {
await JS.InvokeVoidAsync("iframeResize", new { license = "GPLv3" });
} }
} }

View file

@ -3,7 +3,7 @@
<div class="row center-align"> <div class="row center-align">
<h3> <h3>
Some.pics <i class="fa-solid fa-images"></i> Some.pics
</h3> </h3>
</div> </div>
<div class="row center-align"> <div class="row center-align">

View file

@ -3,8 +3,7 @@
<div class="row center-align"> <div class="row center-align">
<h3> <h3>
<i><svg style="width: 1em; height: 1em; margin-right: 1em;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><path fill="#4dabf7" d="M250 450c-38.388 0-76.775-14.646-106.066-43.934l-100-100c-58.579-58.58-58.579-153.553 0-212.132C100.534 37.336 191.105 35.421 250 88.191c58.898-52.768 149.47-50.853 206.066 5.743 58.58 58.58 58.58 153.553 0 212.132l-100 100C326.778 435.354 288.39 450 250 450" /><path fill="#228be6" d="M220.52 176.634a11.792 11.792 0 1 1-23.586 0 11.792 11.792 0 0 1 23.585 0" /><path fill="#1864ab" stroke="#1864ab" stroke-miterlimit="10" stroke-width="11.32074" d="M220.52 176.634a11.792 11.792 0 1 1-23.586 0 11.792 11.792 0 0 1 23.585 0Z" /><path fill="#228be6" d="M303.066 176.634a11.792 11.792 0 1 1-23.585 0 11.792 11.792 0 0 1 23.585 0" /><path fill="#1864ab" stroke="#1864ab" stroke-miterlimit="10" stroke-width="11.32074" d="M303.066 176.634a11.792 11.792 0 1 1-23.585 0 11.792 11.792 0 0 1 23.585 0Z" /><path fill="#228be6" stroke="#461036" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="18.8679" d="M208.75 223.817c18.875 30.802 63.626 30.802 82.501 0" /><path fill="#1971c2" d="M438.68 212.011c0-32.564-26.399-58.962-58.963-58.962s-58.962 26.398-58.962 58.962 26.398 58.963 58.962 58.963 58.962-26.399 58.962-58.963m-259.433 0c0-32.564-26.398-58.962-58.962-58.962S61.32 179.447 61.32 212.011s26.398 58.963 58.963 58.963 58.962-26.399 58.962-58.963" /><path fill="#4dabf7" stroke="#1864ab" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="18.8679" d="M208.75 223.817c18.875 30.802 63.626 30.802 82.501 0" /></svg></i> <i class="fa-solid fa-message-smile"></i> Statuslog
Statuslog
</h3> </h3>
</div> </div>
<div class="row center-align"> <div class="row center-align">
@ -27,20 +26,5 @@
@code { @code {
private List<Status> statuses;
private string statusContent = string.Empty;
private string? statusEmoji = null;
protected override async Task OnInitializedAsync()
{
}
private async ValueTask<ItemsProviderResult<Status>> GetStatuses(ItemsProviderRequest request)
{
// TODO: request.cancellationToken
statuses = (await State.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);
}
} }

View file

@ -12,13 +12,6 @@ namespace Neighbourhood.omg.lol.Models {
public ExpirationData Expiration { get; set; } public ExpirationData Expiration { get; set; }
public PreferenceData? Preferences { get; set; } public PreferenceData? Preferences { get; set; }
public class TimeData {
public long? UnixEpochTime { get; set; }
public string? Iso8601Time { get; set; }
public string? Rfc2822Time { get; set; }
public string? RelativeTime { get; set; }
}
public class RegistrationData : TimeData { public class RegistrationData : TimeData {
public string? Message { get; set; } public string? Message { get; set; }
} }

17
Models/NowData.cs Normal file
View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Neighbourhood.omg.lol.Models {
public class NowData {
public string Address { get; set; }
public string Url { get; set; }
public TimeData Updated { get; set; }
public string UpdatedRelative {
get => State.RelativeTimeFromUnix(Convert.ToInt64(Updated.UnixEpochTime));
}
}
}

13
Models/NowResponseData.cs Normal file
View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Neighbourhood.omg.lol.Models {
public class NowResponseData : IOmgLolResponseData {
public string Message { get; set; }
public long Count { get; set; }
public List<NowData> Garden { get; set; }
}
}

View file

@ -22,6 +22,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<Status>? CachedAddressStatuses { get; set; } public List<Status>? CachedAddressStatuses { get; set; }
public List<Pic>? CachedAddressPics { get; set; } public List<Pic>? CachedAddressPics { get; set; }
@ -116,6 +117,24 @@ namespace Neighbourhood.omg.lol.Models {
} }
} }
public async Task<List<NowData>?> GetNowGarden(bool forceRefresh = false) {
RestService api = new RestService();
if (forceRefresh || this.NowGarden == null || this.NowGarden.Count == 0) {
this.NowGarden = await api.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) {
RestService api = new RestService(); RestService api = new RestService();
@ -162,5 +181,19 @@ namespace Neighbourhood.omg.lol.Models {
public async Task RefreshStatuses() => await GetStatuses(forceRefresh: true); public async Task RefreshStatuses() => await GetStatuses(forceRefresh: true);
public async Task RefreshPics() => await GetPics(forceRefresh: true); public async Task RefreshPics() => await GetPics(forceRefresh: true);
public async Task RefreshNow() => await GetNowGarden(forceRefresh: true);
public static string RelativeTimeFromUnix(long unix) {
DateTimeOffset createdTime = DateTimeOffset.UnixEpoch.AddSeconds(unix);
TimeSpan offset = DateTimeOffset.UtcNow - createdTime;
var offsetString = string.Empty;
if (offset.TotalDays >= 1) offsetString = $"{Math.Floor(offset.TotalDays)} days ago";
else if (offset.TotalHours >= 1) offsetString = $"{Math.Floor(offset.TotalHours)} hours, {offset.Minutes} minutes ago";
else if (offset.TotalMinutes >= 1) offsetString = $"{Math.Floor(offset.TotalMinutes)} minutes ago";
else offsetString = $"{Math.Floor(offset.TotalSeconds)} seconds ago";
return offsetString;
}
} }
} }

14
Models/TimeData.cs Normal file
View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Neighbourhood.omg.lol.Models {
public class TimeData {
public long? UnixEpochTime { get; set; }
public string? Iso8601Time { get; set; }
public string? Rfc2822Time { get; set; }
public string? RelativeTime { get; set; }
}
}

View file

@ -29,7 +29,7 @@ namespace Neighbourhood.omg.lol {
if (token != null) _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); if (token != null) _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
} }
private async Task<T?> Get<T>(string uri, CancellationToken cancellationToken = default) where T:IOmgLolResponseData { private async Task<T?> Get<T>(string uri, CancellationToken cancellationToken = default) where T : IOmgLolResponseData {
T? responseData = default(T); T? responseData = default(T);
try { try {
HttpResponseMessage response = await _client.GetAsync(uri, cancellationToken: cancellationToken); HttpResponseMessage response = await _client.GetAsync(uri, cancellationToken: cancellationToken);
@ -148,6 +148,9 @@ namespace Neighbourhood.omg.lol {
public async Task<PutPicResponseData?> PostPicDescription(string address, string id, string description) => public async Task<PutPicResponseData?> PostPicDescription(string address, string id, string description) =>
(await Post<PutPicResponseData, PostPic>($"/address/{address}/pics/{id}", new PostPic { Description = description })); (await Post<PutPicResponseData, PostPic>($"/address/{address}/pics/{id}", new PostPic { Description = description }));
public async Task<List<NowData>?> NowGarden() =>
(await Get<NowResponseData>($"/now/garden"))?.Garden ?? new List<NowData>();
public async Task<List<string>> Ephemeral() { public async Task<List<string>> Ephemeral() {
List<string> notes = new List<string>(); List<string> notes = new List<string>();
Uri Uri = new Uri($"https://eph.emer.al/"); Uri Uri = new Uri($"https://eph.emer.al/");
@ -157,7 +160,7 @@ namespace Neighbourhood.omg.lol {
string pattern = @"<p class=""post"">(.*?)<\/p>"; string pattern = @"<p class=""post"">(.*?)<\/p>";
var matches = Regex.Matches(str, pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline); var matches = Regex.Matches(str, pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
foreach (Match match in matches) { foreach (Match match in matches) {
notes.Add(match.Groups[1].Value); notes.Add(match.Groups[1].Value);
} }
} }
catch (Exception ex) { catch (Exception ex) {
@ -183,5 +186,19 @@ namespace Neighbourhood.omg.lol {
} }
return token; return token;
} }
public async Task<MarkupString?> GetHtml(string url) {
string? raw = null;
try {
HttpResponseMessage response = await _client.GetAsync(url);
if (response.IsSuccessStatusCode) {
raw = await response.Content.ReadAsStringAsync();
}
}
catch (Exception ex) {
Debug.WriteLine(@"\tERROR {0}", ex.Message);
}
return string.IsNullOrEmpty(raw) ? null : (MarkupString)raw;
}
} }
} }

View file

@ -199,11 +199,12 @@ article.ephemeral {
text-decoration:none; text-decoration:none;
} }
.card-grid{ .card-grid {
display: flex; display: flex;
flex-direction:row; flex-direction: row;
flex-wrap:wrap; flex-wrap: wrap;
gap: .5rem; gap: .5rem;
justify-content: space-between;
} }
#pics article { #pics article {
@ -230,4 +231,41 @@ article.ephemeral {
#pics article > img:first-child { #pics article > img:first-child {
text-align: center; text-align: center;
}
#now-garden > :not(.now) {
position: absolute;
}
.card-grid > * {
flex-grow: 1;
flex-shrink: 1;
}
.page-container > .page {
display:flex;
flex-direction:column;
flex-grow: 1;
}
#now.page > iframe {
width:100%;
flex-grow: 1;
border: none;
margin:0;
padding:0;
}
main, .page-container {
flex-grow:1;
display: flex;
flex-direction: column;
}
main {
overflow: hidden;
}
.hover {
z-index: 1;
} }

View file

@ -12,6 +12,7 @@
<link rel="stylesheet" href="Neighbourhood.omg.lol.styles.css" /> <link rel="stylesheet" href="Neighbourhood.omg.lol.styles.css" />
<script type="module" src="vendor/beer.min.js"></script> <script type="module" src="vendor/beer.min.js"></script>
<script type="module" src="vendor/material-dynamic-colors.min.js"></script> <script type="module" src="vendor/material-dynamic-colors.min.js"></script>
<script src="vendor/iframe-resizer/parent.js"></script>
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/png" href="favicon.png" />
</head> </head>

24
wwwroot/vendor/iframe-resizer/child.js vendored Normal file

File diff suppressed because one or more lines are too long

24
wwwroot/vendor/iframe-resizer/parent.js vendored Normal file

File diff suppressed because one or more lines are too long