Tidying up the feed feature

This commit is contained in:
Gordon Pedersen 2024-07-02 12:55:07 +10:00
parent 9c3f578eb6
commit 93b49f2533
10 changed files with 159 additions and 95 deletions

View file

@ -1,5 +1,5 @@
namespace Neighbourhood.omg.lol { namespace Neighbourhood.omg.lol {
public static class FeatureFlags { public static class FeatureFlags {
public static bool Following { get; } = false; public static bool Following { get; } = true;
} }
} }

View file

@ -198,16 +198,18 @@ namespace Neighbourhood.omg.lol {
} }
public bool IsFollowing(string address) => Following?.Contains(address) ?? false; public bool IsFollowing(string address) => Following?.Contains(address) ?? false;
public void Follow(string address) { public async Task Follow(string address) {
if (Following == null) Following = new List<string>(); if (Following == null) Following = new List<string>();
Following.Add(address); Following.Add(address);
Preferences.Default.Set("following", JsonSerializer.Serialize(Following)); Preferences.Default.Set("following", JsonSerializer.Serialize(Following));
await GetFeed(forceRefresh: true);
} }
public void Unfollow(string address) { public async Task Unfollow(string address) {
if (Following == null) Following = new List<string>(); if (Following == null) Following = new List<string>();
Following.Remove(address); Following.Remove(address);
Preferences.Default.Set("following", JsonSerializer.Serialize(Following)); Preferences.Default.Set("following", JsonSerializer.Serialize(Following));
await GetFeed(forceRefresh: true);
} }
public async Task<MarkupString?> GetBio(string address, bool forceRefresh = false) { public async Task<MarkupString?> GetBio(string address, bool forceRefresh = false) {

View file

@ -4,9 +4,13 @@
<i class="emoji medium" data-emoji="👋">👋</i> <i class="emoji medium" data-emoji="👋">👋</i>
<span>Hey, @(State.Name ?? "there").</span> <span>Hey, @(State.Name ?? "there").</span>
</a> </a>
<a class="m s row" href="/directory"> <a class="s row" href="/now">
<i class="square fa-duotone fa-seedling"></i>
<span>Now.garden</span>
</a>
<a class="s row" href="/directory">
<i class="square fa-duotone fa-address-book"></i> <i class="square fa-duotone fa-address-book"></i>
<span>Address Directory</span> <span>Directory</span>
</a> </a>
@foreach (AddressResponseData address in State.AddressList ?? new List<AddressResponseData>()) { @foreach (AddressResponseData address in State.AddressList ?? new List<AddressResponseData>()) {
<a class="row @(address == State.SelectedAddress ? "active" : "")" @onclick="() => State.SelectedAddress = address"> <a class="row @(address == State.SelectedAddress ? "active" : "")" @onclick="() => State.SelectedAddress = address">
@ -32,13 +36,7 @@
</a> </a>
} }
} }
@if (FeatureFlags.Following && State.IsAuthorized) {
<a class="m s row" href="/feed">
<i class="square fa-solid fa-list-timeline"></i>
<span>Feed</span>
</a>
<FollowingList class="m s"></FollowingList>
}
@if (State.IsAuthorized) { @if (State.IsAuthorized) {
<a class="row" @onclick='() => AuthStateProvider.Logout()'> <a class="row" @onclick='() => AuthStateProvider.Logout()'>
<i class="fa-solid fa-door-open"></i> <i class="fa-solid fa-door-open"></i>

View file

@ -1,23 +0,0 @@
@inject State State
@if (State.Following != null) {
<details class="@(@class)">
<summary class="none">
<a class="row">
<i class="square fa-solid fa-caret-down"></i>
<span>Following</span>
</a>
</summary>
@foreach (string address in State.Following) {
<a class="transparent button horizontal-padding indent row" href="/person/@address">
<img class="tiny circle avatar" src="https://profiles.cache.lol/@address/picture" />
<span class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@address</span>
</a>
}
</details>
}
@code {
[Parameter]
public string? @class { get; set; }
}

View file

@ -11,24 +11,26 @@
<i class="square fa-light fa-comment-dots"></i> <i class="square fa-light fa-comment-dots"></i>
<div class="label">Eph.emer.al</div> <div class="label">Eph.emer.al</div>
</NavLink> </NavLink>
<NavLink class="nav-link" href="/now"> @if (FeatureFlags.Following && State.IsAuthorized) {
<i class="square fa-duotone fa-seedling"></i> <NavLink class="nav-link" href="/feed">
<div class="label">Now.garden</div> <i class="square fa-solid fa-list-timeline"></i>
</NavLink> <div class="label">Timeline</div>
</NavLink>
<NavLink class="l m nav-link" href="/now">
<NavLink class="l nav-link" href="/directory"> <i class="square fa-duotone fa-seedling"></i>
<i class="square fa-duotone fa-address-book"></i> <div class="label">Now.garden</div>
<div class="label">Address Directory</div> </NavLink>
</NavLink>
@if (FeatureFlags.Following) {
<AuthorizeView>
<Authorized>
<NavLink class="l nav-link" href="/feed">
<i class="square fa-solid fa-list-timeline"></i>
<div class="label">Feed</div>
</NavLink>
<FollowingList class="l"></FollowingList>
</Authorized>
</AuthorizeView>
} }
else {
<NavLink class="nav-link" href="/now">
<i class="square fa-duotone fa-seedling"></i>
<div class="label">Now.garden</div>
</NavLink>
}
<NavLink class="l m nav-link" href="/directory">
<i class="square fa-duotone fa-address-book"></i>
<div class="label">Directory</div>
</NavLink>

View file

@ -22,7 +22,7 @@
@if (messages != null) { @if (messages != null) {
foreach (MarkupString message in messages) { foreach (MarkupString message in messages) {
<article class="ephemeral center"> <article class="ephemeral">
@message @message
</article> </article>
} }

View file

@ -2,29 +2,85 @@
@implements IDisposable @implements IDisposable
@inject IJSRuntime JS @inject IJSRuntime JS
@inject State State @inject State State
@inject NavigationManager Nav
<RefreshButton></RefreshButton> <RefreshButton></RefreshButton>
<PageHeading title="Feed" icon="fa-solid fa-list-timeline"> <PageHeading title="Timeline" icon="fa-solid fa-list-timeline">
<Description>A feed of all the statuses and pics of the people you follow.</Description> <Description>A feed of all the statuses and pics of the people you follow.</Description>
</PageHeading> </PageHeading>
@if (feed != null) foreach (StatusOrPic item in feed) { @if(!(State.Following?.Any() ?? false)) {
if (item.IsStatus) { <PageHeading title="" icon="fa-light fa-face-sad-sweat">
<StatusCard Status="@item.Status"></StatusCard> <Description>
} It looks like you're not following anyone yet.
else if (item.IsPic) { </Description>
<PicCard Pic="@item.Pic"></PicCard> </PageHeading>
} <p class="center-align">Check out the <a href="/directory">Directory</a> (or other parts of the app) to find awesome people to follow.</p>
} }
else {
<div class="responsive">
<div class="tabs scroll">
<a data-ui="#feed" class="active">
<i class="fa-solid fa-list-timeline"></i>
<span>Timeline</span>
</a>
<a data-ui="#following">
<i class="fa-duotone fa-address-book"></i>
<span>Following</span>
</a>
</div>
</div>
<LoadingCard id="feedLoading" icon="fa-solid fa-list-timeline"></LoadingCard> <div class="responsive page-container">
<div id="feed" class="page no-padding active">
@if (feed != null){
foreach (StatusOrPic item in feed) {
if (item.IsStatus) {
<StatusCard Status="@item.Status"></StatusCard>
}
else if (item.IsPic) {
<PicCard Pic="@item.Pic"></PicCard>
}
}
}
<LoadingCard id="feedLoading" icon="fa-solid fa-list-timeline"></LoadingCard>
</div>
<div id="following" class="page no-padding">
<ul>
@foreach (string address in State.Following ?? new List<string>()) {
string displayAddress = address;
string linkAddress = address;
@* if (group.Key == "😀") {
try {
linkAddress = idn.GetAscii(address);
displayAddress = $"{address} {linkAddress}";
}
catch (Exception) { }
} *@
<li class="vertical-margin row padding surface-container">
<img class="round" src="https://profiles.cache.lol/@linkAddress/picture">
<div class="max">
<a href="/person/@linkAddress" class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@displayAddress</a>
</div>
<button id="follow-button" @onclick="() => UnfollowClick(address)">
<i class="fa-solid fa-minus"></i> Unfollow
</button>
</li>
}
</ul>
</div>
</div>
}
@code { @code {
private IOrderedEnumerable<StatusOrPic>? feed; private IOrderedEnumerable<StatusOrPic>? feed;
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
await base.OnInitializedAsync(); await base.OnInitializedAsync();
string fragment = new Uri(Nav.Uri).Fragment;
await JS.InvokeVoidAsync("ui", fragment);
if (feed == null || feed.Count() == 0) feed = await State.GetFeed(); if (feed == null || feed.Count() == 0) feed = await State.GetFeed();
State.PropertyChanged += StateChanged; State.PropertyChanged += StateChanged;
State.CanRefresh = true; State.CanRefresh = true;
@ -41,6 +97,12 @@
} }
} }
public async Task UnfollowClick(string address) {
await State.Unfollow(address);
feed = await State.GetFeed(forceRefresh: true);
await InvokeAsync(StateHasChanged);
}
public void Dispose() { public void Dispose() {
State.PropertyChanged -= StateChanged; State.PropertyChanged -= StateChanged;
State.CanRefresh = false; State.CanRefresh = false;

View file

@ -14,12 +14,12 @@
@if (FeatureFlags.Following) { @if (FeatureFlags.Following) {
<div class="row center-align"> <div class="row center-align">
@if (State.IsFollowing(Address)) { @if (State.IsFollowing(Address)) {
<button id="follow-button" @onclick="() => {State.Unfollow(Address);InvokeAsync(StateHasChanged);}"> <button id="follow-button" @onclick="async() => {await State.Unfollow(Address);await InvokeAsync(StateHasChanged);}">
<i class="fa-solid fa-minus"></i> Unfollow <i class="fa-solid fa-minus"></i> Unfollow
</button> </button>
} }
else { else {
<button id="follow-button" @onclick="() => {State.Follow(Address);InvokeAsync(StateHasChanged);}"> <button id="follow-button" @onclick="async() => {await State.Follow(Address);await InvokeAsync(StateHasChanged);}">
<i class="fa-solid fa-plus"></i> Follow <i class="fa-solid fa-plus"></i> Follow
</button> </button>
} }

View file

@ -1,35 +1,38 @@
@inject IJSRuntime JS @inject IJSRuntime JS
<article class="no-padding"> <article class="pic no-padding">
<img src="@Pic!.Url" loading="lazy"> <img src="@Pic!.Url" loading="lazy">
<div class="padding"> <div class="padding row">
<nav> <div class="emoji" data-emoji="🖼️">🖼️</div>
<a class="author" href="/person/@Pic.Address#pics"> <div class="max">
<i class="fa-solid fa-fw fa-at"></i>@Pic.Address <nav>
</a> <a class="author" href="/person/@Pic.Address#pics">
<span class="max"></span> <i class="fa-solid fa-fw fa-at"></i>@Pic.Address
<a class="chip transparent-border right"> </a>
<i class="fa fa-clock"></i> @Pic.RelativeTime <span class="max"></span>
</a> </nav>
</nav> @if(!string.IsNullOrWhiteSpace(Pic.Description)){
@if(!string.IsNullOrWhiteSpace(Pic.Description)){ <p>@((MarkupString)Pic.DescriptionHtml)</p>
<p>@((MarkupString)Pic.DescriptionHtml)</p>
}
else {
<div class="padding padding yellow-2-bg yellow-9-fg">
<i class="fa-solid fa-triangle-exclamation"></i>
<span>This picture needs a description in order to be shared.</span>
</div>
}
<nav>
<div class="max"></div>
@if(Editable) {
<button @onclick="EditPic"><i class="fa-solid fa-pencil"></i> Edit</button>
} }
<button class="transparent circle" @onclick="ShareClick"> else {
<i class="fa-solid fa-share-nodes"></i> <div class="padding padding yellow-2-bg yellow-9-fg">
</button> <i class="fa-solid fa-triangle-exclamation"></i>
</nav> <span>This picture needs a description in order to be shared.</span>
</div>
}
<nav>
<a class="chip transparent-border">
<i class="fa fa-clock"></i> @Pic.RelativeTime
</a>
<div class="max"></div>
@if(Editable) {
<button @onclick="EditPic"><i class="fa-solid fa-pencil"></i> Edit</button>
}
<button class="transparent circle" @onclick="ShareClick">
<i class="fa-solid fa-share-nodes"></i>
</button>
</nav>
</div>
</div> </div>
</article> </article>

View file

@ -18,6 +18,7 @@
--background: var(--gray-8); --background: var(--gray-8);
--shadow: var(--black); --shadow: var(--black);
--button-shadow: var(--gray-7); --button-shadow: var(--gray-7);
--max-article-size: 75rem;
} }
body.dark { body.dark {
@ -83,7 +84,7 @@ img {
margin: 0 auto; margin: 0 auto;
} }
.status .emoji { :is(.status,.pic) .emoji {
margin-bottom: auto; margin-bottom: auto;
inline-size: 5.5rem; inline-size: 5.5rem;
block-size: 5.5rem; block-size: 5.5rem;
@ -92,7 +93,7 @@ img {
text-indent: -10px; text-indent: -10px;
} }
.status .emoji, #status-emoji, #new-status-emoji { :is(.status,.pic) .emoji, #status-emoji, #new-status-emoji {
margin-bottom: auto; margin-bottom: auto;
inline-size: 3.5rem; inline-size: 3.5rem;
block-size: 3.5rem; block-size: 3.5rem;
@ -101,6 +102,10 @@ img {
text-indent: -6px; text-indent: -6px;
} }
:not(#feed)>.pic .emoji{
display:none;
}
:is(h1, h2, h3, h4, h5, h6) :is(i.fa-at, i:only-child){ :is(h1, h2, h3, h4, h5, h6) :is(i.fa-at, i:only-child){
margin-right: 0; margin-right: 0;
inline-size: auto; inline-size: auto;
@ -296,6 +301,10 @@ article.ephemeral {
text-align: center; text-align: center;
} }
#now-garden{
gap:1rem;
}
#now-garden > :not(.now) { #now-garden > :not(.now) {
position: absolute; position: absolute;
} }
@ -476,3 +485,14 @@ menu > details .row, menu > li > details .row {
menu > details > a:is(:hover,:focus,.active), menu > details > summary:is(:hover,:focus,.active) { menu > details > a:is(:hover,:focus,.active), menu > details > summary:is(:hover,:focus,.active) {
background-color: var(--active); background-color: var(--active);
} }
article {
width: 100%;
max-width: var(--max-article-size);
margin-inline: auto;
}
article.now {
margin: 0;
width:auto;
}