Now, now garden and profile pages
This commit is contained in:
parent
0e1f734f9f
commit
3f1531a934
17 changed files with 320 additions and 31 deletions
33
Components/ExternalPageComponent.razor
Normal file
33
Components/ExternalPageComponent.razor
Normal 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);
|
||||
}
|
||||
}
|
|
@ -55,6 +55,10 @@
|
|||
<i class="fa-light fa-comment-dots"></i>
|
||||
<div>Eph.emer.al</div>
|
||||
</NavLink>
|
||||
<NavLink class="row nav-link" href="/now">
|
||||
<i class="fa-duotone fa-seedling"></i>
|
||||
<div>Now.garden</div>
|
||||
</NavLink>
|
||||
</nav>
|
||||
|
||||
<nav class="left m">
|
||||
|
@ -110,6 +114,10 @@
|
|||
<i class="fa-light fa-comment-dots"></i>
|
||||
<small>Eph.emer.al</small>
|
||||
</NavLink>
|
||||
<NavLink class="nav-link" href="/now">
|
||||
<i class="fa-duotone fa-seedling"></i>
|
||||
<small>Now.garden</small>
|
||||
</NavLink>
|
||||
</nav>
|
||||
|
||||
<nav class="bottom s">
|
||||
|
@ -125,6 +133,10 @@
|
|||
<i class="fa-light fa-comment-dots"></i>
|
||||
<small>Eph.emer.al</small>
|
||||
</NavLink>
|
||||
<NavLink class="nav-link" href="/now">
|
||||
<i class="fa-duotone fa-seedling"></i>
|
||||
<small>Now.garden</small>
|
||||
</NavLink>
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<div class="row center-align">
|
||||
<h3>
|
||||
Ephemeral
|
||||
<i class="fa-light fa-comment-dots"></i> Ephemeral
|
||||
</h3>
|
||||
</div>
|
||||
<div class="row center-align">
|
||||
|
|
34
Components/Pages/Now.razor
Normal file
34
Components/Pages/Now.razor
Normal 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 {
|
||||
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
@page "/person/{Address}"
|
||||
@inject State State
|
||||
@inject IJSRuntime JS
|
||||
|
||||
|
||||
<div class="row center-align">
|
||||
<h3><i class="fa-solid fa-fw fa-at"></i>@Address</h3>
|
||||
|
@ -20,6 +22,10 @@
|
|||
|
||||
<div class="responsive">
|
||||
<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">
|
||||
<i class="fa-solid fa-message-smile"></i>
|
||||
<span>Status.lol</span>
|
||||
|
@ -28,7 +34,20 @@
|
|||
<i class="fa-solid fa-images"></i>
|
||||
<span>Some.pics</span>
|
||||
</a>
|
||||
@if(now != null){
|
||||
<a data-ui="#now" @onclick="ReloadNow">
|
||||
<i class="fa-duotone fa-seedling"></i>
|
||||
<span>/Now</span>
|
||||
</a>
|
||||
}
|
||||
</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">
|
||||
<StatusList StatusFunc="@State.VirtualStatusesFunc(Address)"></StatusList>
|
||||
</div>
|
||||
|
@ -39,13 +58,24 @@
|
|||
}
|
||||
<PicList PicsFunc="@State.VirtualPicsFunc(Address)" Editable="@Editable" Dialog="@editPicDialog"></PicList>
|
||||
</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>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string Address { get; set; }
|
||||
public string ProfileUrl {
|
||||
get => $"https://{Address}.omg.lol/";
|
||||
}
|
||||
|
||||
private EditPicDialog? editPicDialog { get; set; }
|
||||
public ExternalPageComponent? NowPage { get; set; }
|
||||
public ExternalPageComponent? ProfilePage { get; set; }
|
||||
|
||||
private bool Editable {
|
||||
get => Address == State.SelectedAddressName;
|
||||
|
@ -53,7 +83,29 @@
|
|||
|
||||
private MarkupString? bio;
|
||||
|
||||
private NowData? now;
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
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" });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<div class="row center-align">
|
||||
<h3>
|
||||
Some.pics
|
||||
<i class="fa-solid fa-images"></i> Some.pics
|
||||
</h3>
|
||||
</div>
|
||||
<div class="row center-align">
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
|
||||
<div class="row center-align">
|
||||
<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>
|
||||
Statuslog
|
||||
<i class="fa-solid fa-message-smile"></i> Statuslog
|
||||
</h3>
|
||||
</div>
|
||||
<div class="row center-align">
|
||||
|
@ -27,20 +26,5 @@
|
|||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,13 +12,6 @@ namespace Neighbourhood.omg.lol.Models {
|
|||
public ExpirationData Expiration { 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 string? Message { get; set; }
|
||||
}
|
||||
|
|
17
Models/NowData.cs
Normal file
17
Models/NowData.cs
Normal 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
13
Models/NowResponseData.cs
Normal 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; }
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ namespace Neighbourhood.omg.lol.Models {
|
|||
|
||||
public List<Status>? Statuses { get; set; }
|
||||
public List<Pic>? Pics { get; set; }
|
||||
public List<NowData>? NowGarden { get; set; }
|
||||
|
||||
public List<Status>? CachedAddressStatuses { 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) {
|
||||
if(forceRefresh || this.Pics == null || this.Pics.Count == 0) {
|
||||
RestService api = new RestService();
|
||||
|
@ -162,5 +181,19 @@ namespace Neighbourhood.omg.lol.Models {
|
|||
|
||||
public async Task RefreshStatuses() => await GetStatuses(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
14
Models/TimeData.cs
Normal 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; }
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ namespace Neighbourhood.omg.lol {
|
|||
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);
|
||||
try {
|
||||
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) =>
|
||||
(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() {
|
||||
List<string> notes = new List<string>();
|
||||
Uri Uri = new Uri($"https://eph.emer.al/");
|
||||
|
@ -183,5 +186,19 @@ namespace Neighbourhood.omg.lol {
|
|||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -199,11 +199,12 @@ article.ephemeral {
|
|||
text-decoration:none;
|
||||
}
|
||||
|
||||
.card-grid{
|
||||
.card-grid {
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
flex-wrap:wrap;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: .5rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#pics article {
|
||||
|
@ -231,3 +232,40 @@ article.ephemeral {
|
|||
#pics article > img:first-child {
|
||||
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;
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
<link rel="stylesheet" href="Neighbourhood.omg.lol.styles.css" />
|
||||
<script type="module" src="vendor/beer.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" />
|
||||
</head>
|
||||
|
||||
|
|
24
wwwroot/vendor/iframe-resizer/child.js
vendored
Normal file
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
24
wwwroot/vendor/iframe-resizer/parent.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue