Auth, pics, ephemeral and more, oh my!
This commit is contained in:
parent
4fa8440e1f
commit
a39eeff757
27 changed files with 2127 additions and 95 deletions
|
@ -7,5 +7,6 @@ public partial class AppShell : Shell
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Routing.RegisterRoute(nameof(LoginWebViewPage), typeof(LoginWebViewPage));
|
Routing.RegisterRoute(nameof(LoginWebViewPage), typeof(LoginWebViewPage));
|
||||||
|
Routing.RegisterRoute(nameof(EphemeralWebPage), typeof(EphemeralWebPage));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
@inject NavigatorService NavigatorService
|
@inject NavigatorService NavigatorService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject State State
|
@inject State State
|
||||||
<link rel="stylesheet" href="vendor/fluent-emoji/animated.css" />
|
|
||||||
<NavMenu />
|
<NavMenu />
|
||||||
<main class="responsive max">
|
<main class="responsive max">
|
||||||
@Body
|
@Body
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
<AuthorizeView>
|
<AuthorizeView>
|
||||||
<Authorized>
|
<Authorized>
|
||||||
<button class="transparent circle large">
|
<button class="transparent circle large">
|
||||||
<img class="responsive" src="https://profiles.cache.lol/@State.SelectedAddressName/picture" alt="@State.SelectedAddressName">
|
<img class="responsive avatar" src="https://profiles.cache.lol/@State.SelectedAddressName/picture" alt="@State.SelectedAddressName">
|
||||||
<menu class="no-wrap">
|
<menu class="no-wrap">
|
||||||
@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="() => changeAddress(address)">
|
<a class="row @(address == State.SelectedAddress ? "active" : "")" @onclick="() => changeAddress(address)">
|
||||||
<img class="tiny circle" src="https://profiles.cache.lol/@address.Address/picture" alt="@address.Address" />
|
<img class="tiny circle avatar" src="https://profiles.cache.lol/@address.Address/picture" alt="@address.Address" />
|
||||||
<span class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@address.Address</span>
|
<span class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@address.Address</span>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
Hey, @State.Name. <br />
|
Hey, @State.Name. <br />
|
||||||
<span class="address"><i class="fa-solid fa-fw fa-at tiny"></i>@State.SelectedAddressName</span>
|
<a class="address" href="/person/@State.SelectedAddressName"><i class="fa-solid fa-fw fa-at tiny"></i>@State.SelectedAddressName</a>
|
||||||
</div>
|
</div>
|
||||||
</Authorized>
|
</Authorized>
|
||||||
<NotAuthorized>
|
<NotAuthorized>
|
||||||
|
@ -43,13 +43,17 @@
|
||||||
</AuthorizeView>
|
</AuthorizeView>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<NavLink class="row nav-link" href="" Match="NavLinkMatch.All">
|
|
||||||
<i class="fa-solid fa-fw fa-home"></i>
|
|
||||||
<div>Home</div>
|
|
||||||
</NavLink>
|
|
||||||
<NavLink class="row nav-link" href="/statuslog/latest">
|
<NavLink class="row nav-link" href="/statuslog/latest">
|
||||||
<i><svg 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>
|
||||||
<div>Statuslog</div>
|
<div>Status.lol</div>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink class="row nav-link" href="/pics">
|
||||||
|
<i class="fa-solid fa-images"></i>
|
||||||
|
<div>Some.pics</div>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink class="row nav-link" href="/ephemeral">
|
||||||
|
<i class="fa-light fa-comment-dots"></i>
|
||||||
|
<div>Eph.emer.al</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
@ -94,24 +98,32 @@
|
||||||
</NotAuthorized>
|
</NotAuthorized>
|
||||||
</AuthorizeView>
|
</AuthorizeView>
|
||||||
</header>
|
</header>
|
||||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
|
||||||
<i class="fa-solid fa-fw fa-home"></i>
|
|
||||||
<small>Home</small>
|
|
||||||
</NavLink>
|
|
||||||
<NavLink class="nav-link" href="/statuslog/latest">
|
<NavLink class="nav-link" href="/statuslog/latest">
|
||||||
<i><svg 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>
|
||||||
<small>Statuslog</small>
|
<small>Status.lol</small>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink class="nav-link" href="/pics">
|
||||||
|
<i class="fa-solid fa-image"></i>
|
||||||
|
<small>Some.pics</small>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink class="nav-link" href="/ephemeral">
|
||||||
|
<i class="fa-light fa-comment-dots"></i>
|
||||||
|
<small>Eph.emer.al</small>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<nav class="bottom s">
|
<nav class="bottom s">
|
||||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
|
||||||
<i class="fa-solid fa-fw fa-home"></i>
|
|
||||||
<small>Home</small>
|
|
||||||
</NavLink>
|
|
||||||
<NavLink class="nav-link" href="/statuslog/latest">
|
<NavLink class="nav-link" href="/statuslog/latest">
|
||||||
<i><svg 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>
|
||||||
<small>Statuslog</small>
|
<small>Status.lol</small>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink class="nav-link" href="/pics">
|
||||||
|
<i class="fa-solid fa-image"></i>
|
||||||
|
<small>Some.pics</small>
|
||||||
|
</NavLink>
|
||||||
|
<NavLink class="nav-link" href="/ephemeral">
|
||||||
|
<i class="fa-light fa-comment-dots"></i>
|
||||||
|
<small>Eph.emer.al</small>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
<AuthorizeView>
|
<AuthorizeView>
|
||||||
|
|
87
Components/NewStatusDialog.razor
Normal file
87
Components/NewStatusDialog.razor
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
@inject IJSRuntime JS
|
||||||
|
@inject State State
|
||||||
|
|
||||||
|
<div class="overlay" data-ui="#@id"></div>
|
||||||
|
<dialog id="@id">
|
||||||
|
<h5>Share your status</h5>
|
||||||
|
<div class="row">
|
||||||
|
<div class="min square extra">
|
||||||
|
<button class="transparent square extra no-margin">
|
||||||
|
<object id="status-emoji" class="large emoji @(statusEmoji == null ? "animated" : string.Empty)" data-emoji="@(statusEmoji ?? "🫥")">@(statusEmoji ?? "🫥")</object>
|
||||||
|
<menu class="no-wrap">
|
||||||
|
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@@^1/index.js"></script>
|
||||||
|
<emoji-picker emoji-version="15.1"></emoji-picker>
|
||||||
|
<script>
|
||||||
|
document.querySelector('emoji-picker')
|
||||||
|
.addEventListener('emoji-click', event => {
|
||||||
|
document.getElementById('status-emoji').setAttribute('data-emoji', event.detail.unicode)
|
||||||
|
const input = document.getElementById('status-emoji-input')
|
||||||
|
input.value = event.detail.unicode
|
||||||
|
var event = new Event('change');
|
||||||
|
input.dispatchEvent(event);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</menu>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="field textarea border max">
|
||||||
|
<InputTextArea @bind-Value="statusContent"></InputTextArea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav class="right-align no-space">
|
||||||
|
@if (State?.SelectedAddress?.Preferences?.Statuslog?.MastodonPosting ?? false) {
|
||||||
|
<label class="checkbox">
|
||||||
|
<InputCheckbox @bind-Value="postToMastodon"></InputCheckbox>
|
||||||
|
<span>Post this to Mastodon</span>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
<InputText id="status-emoji-input" class="invisible" @bind-Value="statusEmoji"></InputText>
|
||||||
|
<button class="transparent link" data-ui="#@id" disabled="@loading">Cancel</button>
|
||||||
|
<button @onclick="PostStatus" disabled="@loading">
|
||||||
|
@if (loading) {
|
||||||
|
<span>Saving...</span>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
<i class="fa-solid fa-floppy-disk"></i> <span>Save</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string statusContent = string.Empty;
|
||||||
|
private string? statusEmoji = null;
|
||||||
|
private bool postToMastodon = true;
|
||||||
|
private bool loading = false;
|
||||||
|
[Parameter]
|
||||||
|
public string id { get; set; }
|
||||||
|
|
||||||
|
public async Task PostStatus() {
|
||||||
|
await JS.InvokeVoidAsync("console.log", "hey from post status");
|
||||||
|
|
||||||
|
StatusPost post = new StatusPost
|
||||||
|
{
|
||||||
|
Emoji = statusEmoji,
|
||||||
|
Content = statusContent
|
||||||
|
};
|
||||||
|
|
||||||
|
if (State?.SelectedAddress?.Preferences?.Statuslog?.MastodonPosting ?? false){
|
||||||
|
post.SkipMastodonPost = !postToMastodon;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading = true;
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
RestService api = new RestService();
|
||||||
|
var result = await api.StatusPost(State.SelectedAddressName, post);
|
||||||
|
if(result != null){
|
||||||
|
State.RefreshStatuses().ContinueWith(t => InvokeAsync(StateHasChanged));
|
||||||
|
}
|
||||||
|
|
||||||
|
await JS.InvokeVoidAsync("ui", "#" + id);
|
||||||
|
statusContent = string.Empty;
|
||||||
|
statusEmoji = null;
|
||||||
|
postToMastodon = true;
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
}
|
27
Components/Pages/Ephemeral.razor
Normal file
27
Components/Pages/Ephemeral.razor
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
@page "/ephemeral"
|
||||||
|
|
||||||
|
<div class="row center-align">
|
||||||
|
<h3>
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<Virtualize Items="messages" Context="message">
|
||||||
|
<article class="ephemeral center">
|
||||||
|
@message
|
||||||
|
</article>
|
||||||
|
</Virtualize>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private List<string> messages = new List<string>();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync() {
|
||||||
|
//await Shell.Current.GoToAsync(nameof(EphemeralWebPage));
|
||||||
|
RestService api = new RestService();
|
||||||
|
messages = await api.Ephemeral();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,35 +16,57 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (statuses == null) {
|
<div class="responsive">
|
||||||
<p><em>Loading Statuses...</em></p>
|
<div class="tabs">
|
||||||
}
|
<a data-ui="#statuses" class="active">
|
||||||
else {
|
<i class="fa-solid fa-message-smile"></i>
|
||||||
<div id="statuses">
|
<span>Status.lol</span>
|
||||||
@foreach (var status in statuses) {
|
</a>
|
||||||
<StatusCard status="@status"></StatusCard>
|
<a data-ui="#pics">
|
||||||
}
|
<i class="fa-solid fa-images"></i>
|
||||||
|
<span>Some.pics</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
<div id="statuses" class="page padding active">
|
||||||
|
<StatusList StatusFunc="@GetStatuses"></StatusList>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="pics" class="page padding">
|
||||||
|
<PicList PicsFunc="@GetPics"></PicList>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
|
|
||||||
private Status[]? statuses;
|
private Status[] statuses;
|
||||||
private MarkupString? bio;
|
private MarkupString? bio;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync() {
|
protected override async Task OnInitializedAsync() {
|
||||||
RestService api = new RestService();
|
RestService api = new RestService();
|
||||||
await GetBioAsync(api);
|
await GetBioAsync(api);
|
||||||
// GetStatusesAsync(api);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task GetStatusesAsync(RestService api) {
|
|
||||||
statuses = (await api.Statuslog(Address)).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetBioAsync(RestService api) {
|
private async Task GetBioAsync(RestService api) {
|
||||||
bio = await api.StatuslogBio(Address);
|
bio = await api.StatuslogBio(Address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask<ItemsProviderResult<Status>> GetStatuses(ItemsProviderRequest request) {
|
||||||
|
// TODO: request.cancellationToken
|
||||||
|
RestService api = new RestService();
|
||||||
|
statuses = (await api.Statuslog(Address)).ToArray() ?? new Status[0];
|
||||||
|
var numStatuses = Math.Min(request.Count, statuses.Length - request.StartIndex);
|
||||||
|
return new ItemsProviderResult<Status>(statuses.Skip(request.StartIndex).Take(numStatuses), statuses.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Pic> pics;
|
||||||
|
private async ValueTask<ItemsProviderResult<Pic>> GetPics(ItemsProviderRequest request) {
|
||||||
|
// TODO: request.cancellationToken
|
||||||
|
RestService api = new RestService();
|
||||||
|
if (pics == null || pics.Count == 0) pics = await api.SomePics(Address);
|
||||||
|
var numPics = Math.Min(request.Count, pics.Count - request.StartIndex);
|
||||||
|
|
||||||
|
return new ItemsProviderResult<Pic>(pics.Skip(request.StartIndex).Take(numPics), pics.Count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
25
Components/Pages/Pics.razor
Normal file
25
Components/Pages/Pics.razor
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
@page "/pics"
|
||||||
|
|
||||||
|
<div class="row center-align">
|
||||||
|
<h3>
|
||||||
|
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>
|
||||||
|
|
||||||
|
<div id="pics" class="responsive">
|
||||||
|
<PicList PicsFunc="@GetPics"></PicList>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private List<Pic> pics;
|
||||||
|
private async ValueTask<ItemsProviderResult<Pic>> GetPics(ItemsProviderRequest request) {
|
||||||
|
// TODO: request.cancellationToken
|
||||||
|
RestService api = new RestService();
|
||||||
|
if(pics == null || pics.Count == 0) pics = await api.SomePics();
|
||||||
|
var numPics = Math.Min(request.Count, pics.Count - request.StartIndex);
|
||||||
|
return new ItemsProviderResult<Pic>(pics.Skip(request.StartIndex).Take(numPics), pics.Count);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
@page "/statuslog/latest"
|
@page "/statuslog/latest"
|
||||||
|
@inject State State
|
||||||
|
|
||||||
<div class="row center-align">
|
<div class="row center-align">
|
||||||
<h3>
|
<h3>
|
||||||
|
@ -10,33 +11,36 @@
|
||||||
<p>The latest posts from everyone at <a href="https://status.lol">status.lol</a></p>
|
<p>The latest posts from everyone at <a href="https://status.lol">status.lol</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (statuses == null) {
|
<AuthorizeView>
|
||||||
<p id="statuses" class="skeleton-container">
|
<Authorized>
|
||||||
<StatusCardSkeleton></StatusCardSkeleton>
|
<button class="fab circle extra large-elevate" data-ui="#post-modal">
|
||||||
<StatusCardSkeleton></StatusCardSkeleton>
|
<i class="fa-solid fa-pen-to-square"></i>
|
||||||
<StatusCardSkeleton></StatusCardSkeleton>
|
</button>
|
||||||
<StatusCardSkeleton></StatusCardSkeleton>
|
<NewStatusDialog id="post-modal"></NewStatusDialog>
|
||||||
<StatusCardSkeleton></StatusCardSkeleton>
|
</Authorized>
|
||||||
<StatusCardSkeleton></StatusCardSkeleton>
|
</AuthorizeView>
|
||||||
</p>
|
|
||||||
}
|
<div id="statuses" class="responsive">
|
||||||
else
|
<StatusList StatusFunc="@GetStatuses"></StatusList>
|
||||||
{
|
</div>
|
||||||
<div id="statuses" class="responsive">
|
|
||||||
@foreach (var status in statuses) {
|
|
||||||
<StatusCard status="@status"></StatusCard>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
private Status[]? statuses;
|
private Status[] statuses;
|
||||||
|
private string statusContent = string.Empty;
|
||||||
|
private string? statusEmoji = null;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
RestService api = new RestService();
|
}
|
||||||
statuses = (await api.StatuslogLatest()).ToArray();
|
|
||||||
|
private async ValueTask<ItemsProviderResult<Status>> GetStatuses(ItemsProviderRequest request)
|
||||||
|
{
|
||||||
|
// TODO: request.cancellationToken
|
||||||
|
statuses = (await State.GetStatuses()) ?? new Status[0];
|
||||||
|
var numStatuses = Math.Min(request.Count, statuses.Length - request.StartIndex);
|
||||||
|
return new ItemsProviderResult<Status>(statuses.Skip(request.StartIndex).Take(numStatuses), statuses.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
31
Components/PicList.razor
Normal file
31
Components/PicList.razor
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<Virtualize ItemsProvider="GetPics" Context="pic" ItemSize="180">
|
||||||
|
<ItemContent>
|
||||||
|
<article class="no-padding center">
|
||||||
|
<img class="responsive" src="@pic.Url">
|
||||||
|
<div class="padding">
|
||||||
|
<nav>
|
||||||
|
<a class="author" href="/person/@pic.Address">
|
||||||
|
<i class="fa-solid fa-fw fa-at"></i>@pic.Address
|
||||||
|
</a>
|
||||||
|
<span class="max"></span>
|
||||||
|
<a class="chip transparent-border right">
|
||||||
|
<i class="fa fa-clock"></i> @pic.RelativeTime
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
<p>@pic.Description</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</ItemContent>
|
||||||
|
<Placeholder>
|
||||||
|
<article class="no-padding center"></article>
|
||||||
|
</Placeholder>
|
||||||
|
</Virtualize>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public Func<ItemsProviderRequest, ValueTask<ItemsProviderResult<Pic>>> PicsFunc { get; set; }
|
||||||
|
|
||||||
|
private async ValueTask<ItemsProviderResult<Pic>> GetPics(ItemsProviderRequest request) {
|
||||||
|
return await this.PicsFunc(request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
<article class="status">
|
<article class="status">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<object class="large emoji" data-emoji="@status.EmojiOrDefault">@status.EmojiOrDefault</object>
|
<div class="large 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
|
||||||
|
|
17
Components/StatusList.razor
Normal file
17
Components/StatusList.razor
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<Virtualize ItemsProvider="GetStatuses" Context="status" ItemSize="180">
|
||||||
|
<ItemContent>
|
||||||
|
<StatusCard status="@status"></StatusCard>
|
||||||
|
</ItemContent>
|
||||||
|
<Placeholder>
|
||||||
|
<StatusCardSkeleton></StatusCardSkeleton>
|
||||||
|
</Placeholder>
|
||||||
|
</Virtualize>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public Func<ItemsProviderRequest, ValueTask<ItemsProviderResult<Status>>> StatusFunc { get; set; }
|
||||||
|
|
||||||
|
private async ValueTask<ItemsProviderResult<Status>> GetStatuses(ItemsProviderRequest request) {
|
||||||
|
return await this.StatusFunc(request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ namespace Neighbourhood.omg.lol {
|
||||||
|
|
||||||
public async Task Logout() {
|
public async Task Logout() {
|
||||||
SecureStorage.Remove("accounttoken");
|
SecureStorage.Remove("accounttoken");
|
||||||
Preferences.Default.Clear();
|
await State.RemoveAccountDetails();
|
||||||
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
|
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
EphemeralWebPage.xaml
Normal file
12
EphemeralWebPage.xaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
x:Class="Neighbourhood.omg.lol.EphemeralWebPage"
|
||||||
|
BackgroundColor="{DynamicResource PageBackgroundColor}">
|
||||||
|
<Grid>
|
||||||
|
<WebView x:Name="webview"
|
||||||
|
Navigating="webview_Navigating"
|
||||||
|
VerticalOptions="Fill"
|
||||||
|
HorizontalOptions="Fill" />
|
||||||
|
</Grid>
|
||||||
|
</ContentPage>
|
18
EphemeralWebPage.xaml.cs
Normal file
18
EphemeralWebPage.xaml.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
namespace Neighbourhood.omg.lol;
|
||||||
|
|
||||||
|
public partial class EphemeralWebPage : ContentPage
|
||||||
|
{
|
||||||
|
private NavigatorService NavigatorService { get; set; }
|
||||||
|
|
||||||
|
public EphemeralWebPage(NavigatorService navigatorService)
|
||||||
|
{
|
||||||
|
this.NavigatorService = navigatorService;
|
||||||
|
InitializeComponent();
|
||||||
|
this.webview.Source = $"https://home.omg.lol/ephemeral";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void webview_Navigating(object sender, WebNavigatingEventArgs e) {
|
||||||
|
var cookies = this.webview.Cookies;
|
||||||
|
Uri uri = new Uri(e.Url);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
|
||||||
namespace Neighbourhood.omg.lol;
|
namespace Neighbourhood.omg.lol;
|
||||||
|
|
||||||
|
@ -9,25 +11,42 @@ public partial class LoginWebViewPage : ContentPage
|
||||||
{
|
{
|
||||||
private AuthenticationStateProvider AuthStateProvider { get; set; }
|
private AuthenticationStateProvider AuthStateProvider { get; set; }
|
||||||
private NavigatorService NavigatorService { get; set; }
|
private NavigatorService NavigatorService { get; set; }
|
||||||
|
private IConfiguration Configuration { get; set; }
|
||||||
|
|
||||||
public LoginWebViewPage(AuthenticationStateProvider authStateProvider, NavigatorService navigatorService)
|
private string? client_id;
|
||||||
|
private string? client_secret;
|
||||||
|
private string? redirect_uri;
|
||||||
|
|
||||||
|
public LoginWebViewPage(AuthenticationStateProvider authStateProvider, NavigatorService navigatorService, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
this.AuthStateProvider = authStateProvider;
|
this.AuthStateProvider = authStateProvider;
|
||||||
this.NavigatorService = navigatorService;
|
this.NavigatorService = navigatorService;
|
||||||
|
this.Configuration = configuration;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
this.loginwebview.Source = "https://home.omg.lol/oauth/authorize?client_id=ea14dafd3e92cbcf93750c35cd81a031&scope=everything&redirect_uri=https://neatnik.net/adam/bucket/omgloloauth/&response_type=code";
|
client_id = configuration.GetValue<string>("client_id");
|
||||||
|
client_secret = configuration.GetValue<string>("client_secret");
|
||||||
|
redirect_uri = configuration.GetValue<string>("redirect_uri");
|
||||||
|
|
||||||
|
// make sure they're all not null first.
|
||||||
|
if ( client_id != null
|
||||||
|
&& client_secret != null
|
||||||
|
&& redirect_uri != null) {
|
||||||
|
this.loginwebview.Source = $"https://home.omg.lol/oauth/authorize?client_id={client_id}&scope=everything&redirect_uri={redirect_uri}&response_type=code";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void loginwebview_Navigating(object sender, WebNavigatingEventArgs e) {
|
public async void loginwebview_Navigating(object sender, WebNavigatingEventArgs e) {
|
||||||
if(e.Url.StartsWith("https://neatnik.net/adam/bucket/omgloloauth/")) {
|
if ( client_id != null
|
||||||
Debug.WriteLine("And here we go...");
|
&& client_secret != null
|
||||||
|
&& redirect_uri != null
|
||||||
|
&& e.Url.StartsWith(redirect_uri))
|
||||||
|
{
|
||||||
Uri uri = new Uri(e.Url);
|
Uri uri = new Uri(e.Url);
|
||||||
var query = HttpUtility.ParseQueryString(uri.Query);
|
var query = HttpUtility.ParseQueryString(uri.Query);
|
||||||
string? code = query.Get("code");
|
string? code = query.Get("code");
|
||||||
if (!string.IsNullOrEmpty(code)) {
|
if (!string.IsNullOrEmpty(code)) {
|
||||||
RestService api = new RestService();
|
RestService api = new RestService();
|
||||||
string? token = await api.OAuth(code);
|
string? token = await api.OAuth(code, client_id, client_secret, redirect_uri);
|
||||||
if (!string.IsNullOrEmpty(token)) {
|
if (!string.IsNullOrEmpty(token)) {
|
||||||
Debug.WriteLine($"Fuck yeah, a token! {token}");
|
Debug.WriteLine($"Fuck yeah, a token! {token}");
|
||||||
await ((CustomAuthenticationStateProvider)this.AuthStateProvider).Login(token);
|
await ((CustomAuthenticationStateProvider)this.AuthStateProvider).Login(token);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Neighbourhood.omg.lol.Models;
|
using Neighbourhood.omg.lol.Models;
|
||||||
|
|
||||||
|
@ -14,10 +15,14 @@ namespace Neighbourhood.omg.lol {
|
||||||
|
|
||||||
builder.Services.AddMauiBlazorWebView();
|
builder.Services.AddMauiBlazorWebView();
|
||||||
builder.Services.AddTransient<LoginWebViewPage>();
|
builder.Services.AddTransient<LoginWebViewPage>();
|
||||||
|
builder.Services.AddTransient<EphemeralWebPage>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<State>();
|
builder.Services.AddSingleton<State>();
|
||||||
builder.Services.AddSingleton<NavigatorService>();
|
builder.Services.AddSingleton<NavigatorService>();
|
||||||
|
|
||||||
|
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
|
||||||
|
builder.Services.AddSingleton<IConfiguration>(configurationBuilder.AddUserSecrets<App>().Build());
|
||||||
|
|
||||||
builder.Services.AddAuthorizationCore();
|
builder.Services.AddAuthorizationCore();
|
||||||
builder.Services.AddScoped<CustomAuthenticationStateProvider>();
|
builder.Services.AddScoped<CustomAuthenticationStateProvider>();
|
||||||
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomAuthenticationStateProvider>());
|
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomAuthenticationStateProvider>());
|
||||||
|
|
|
@ -10,13 +10,13 @@ namespace Neighbourhood.omg.lol.Models {
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
public RegistrationData Registration { get; set; }
|
public RegistrationData Registration { get; set; }
|
||||||
public ExpirationData Expiration { get; set; }
|
public ExpirationData Expiration { get; set; }
|
||||||
|
public PreferenceData? Preferences { get; set; }
|
||||||
|
|
||||||
public class TimeData {
|
public class TimeData {
|
||||||
public long? UnixEpochTime { get; set; }
|
public long? UnixEpochTime { get; set; }
|
||||||
public string? Iso8601Time { get; set; }
|
public string? Iso8601Time { get; set; }
|
||||||
public string? Rfc2822Time { get; set; }
|
public string? Rfc2822Time { get; set; }
|
||||||
public string? RelativeTime { get; set; }
|
public string? RelativeTime { get; set; }
|
||||||
public PreferenceData? Preferences { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RegistrationData : TimeData {
|
public class RegistrationData : TimeData {
|
||||||
|
|
36
Models/Pic.cs
Normal file
36
Models/Pic.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Neighbourhood.omg.lol.Models {
|
||||||
|
public class Pic {
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
public string Address { get; set; }
|
||||||
|
public long Created { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
public string Mime { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("exif")]
|
||||||
|
public string ExifJson { get; set; }
|
||||||
|
|
||||||
|
public string RelativeTime {
|
||||||
|
get {
|
||||||
|
DateTimeOffset createdTime = DateTimeOffset.UnixEpoch.AddSeconds(Created);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
Models/SomePicsResponseData.cs
Normal file
12
Models/SomePicsResponseData.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Neighbourhood.omg.lol.Models {
|
||||||
|
public class SomePicsResponseData : IOmgLolResponseData {
|
||||||
|
public string Message { get; set; }
|
||||||
|
public List<Pic>? Pics { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
@ -16,6 +17,8 @@ namespace Neighbourhood.omg.lol.Models {
|
||||||
public AddressResponseData? SelectedAddress { get; set; }
|
public AddressResponseData? SelectedAddress { get; set; }
|
||||||
public string? SelectedAddressName { get => SelectedAddress?.Address; }
|
public string? SelectedAddressName { get => SelectedAddress?.Address; }
|
||||||
|
|
||||||
|
public List<Status>? Statuses { get; set; }
|
||||||
|
|
||||||
public async Task PopulateAccountDetails(string token) {
|
public async Task PopulateAccountDetails(string token) {
|
||||||
RestService api = new RestService(token);
|
RestService api = new RestService(token);
|
||||||
|
|
||||||
|
@ -45,5 +48,28 @@ namespace Neighbourhood.omg.lol.Models {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task RemoveAccountDetails() {
|
||||||
|
Preferences.Default.Clear();
|
||||||
|
AccountInfo = null;
|
||||||
|
AddressList = null;
|
||||||
|
SelectedAddress = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Status[]?> GetStatuses(bool forceRefresh = false) {
|
||||||
|
RestService api = new RestService();
|
||||||
|
if (forceRefresh || this.Statuses == null || this.Statuses.Count == 0) {
|
||||||
|
Debug.WriteLine("Downloading statuses from server");
|
||||||
|
this.Statuses = await api.StatuslogLatest();
|
||||||
|
}
|
||||||
|
//else Task.Run(async () => this.Statuses = await api.StatuslogLatest()); // not awaited on purpose
|
||||||
|
return this.Statuses.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RefreshStatuses() {
|
||||||
|
RestService api = new RestService();
|
||||||
|
this.Statuses = await api.StatuslogLatest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
Models/StatusPost.cs
Normal file
14
Models/StatusPost.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 StatusPost {
|
||||||
|
public string? Emoji { get; set; }
|
||||||
|
public string? Content { get; set; }
|
||||||
|
public string? ExternalUrl { get; set; }
|
||||||
|
public bool? SkipMastodonPost { get; set; }
|
||||||
|
}
|
||||||
|
}
|
15
Models/StatusPostResponseData.cs
Normal file
15
Models/StatusPostResponseData.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Neighbourhood.omg.lol.Models {
|
||||||
|
public class StatusPostResponseData : IOmgLolResponseData {
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public string? Id { get; set; }
|
||||||
|
public string? Url { get; set; }
|
||||||
|
public string? ExternalUrl { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,7 @@
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||||
|
<UserSecretsId>9c986eaa-84a8-45d5-adff-b206367a1d08</UserSecretsId>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -63,6 +64,7 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.6" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
||||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
|
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="$(MauiVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="$(MauiVersion)" />
|
||||||
|
@ -74,6 +76,9 @@
|
||||||
<MauiXaml Update="AppShell.xaml">
|
<MauiXaml Update="AppShell.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</MauiXaml>
|
</MauiXaml>
|
||||||
|
<MauiXaml Update="EphemeralWebPage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</MauiXaml>
|
||||||
<MauiXaml Update="LoginWebViewPage.xaml">
|
<MauiXaml Update="LoginWebViewPage.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</MauiXaml>
|
</MauiXaml>
|
||||||
|
|
|
@ -4,6 +4,7 @@ using Neighbourhood.omg.lol.Models;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Neighbourhood.omg.lol {
|
namespace Neighbourhood.omg.lol {
|
||||||
public class RestService {
|
public class RestService {
|
||||||
|
@ -43,37 +44,73 @@ namespace Neighbourhood.omg.lol {
|
||||||
return responseData;
|
return responseData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Status>> StatuslogLatest() {
|
private async Task<TResponse?> Post<TResponse, TData>(Uri uri, TData data) where TResponse : IOmgLolResponseData {
|
||||||
Uri uri = new Uri($"{BaseUrl}/statuslog/latest");
|
TResponse? responseData = default(TResponse);
|
||||||
return (await Get<StatusResponseData>(uri))?.Statuses ?? new List<Status>();
|
try {
|
||||||
|
HttpResponseMessage response = await _client.PostAsJsonAsync(uri, data);
|
||||||
|
if (response.IsSuccessStatusCode) {
|
||||||
|
OmgLolResponse<TResponse>? responseObj = await response.Content.ReadFromJsonAsync<OmgLolResponse<TResponse>>(_serializerOptions);
|
||||||
|
if (responseObj != null && responseObj.Request.Success) {
|
||||||
|
responseData = responseObj.Response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
Debug.WriteLine(@"\tERROR {0}", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Status>> Statuslog(string address) {
|
return responseData;
|
||||||
Uri uri = new Uri($"{BaseUrl}/address/{address}/statuses");
|
|
||||||
return (await Get<StatusResponseData>(uri))?.Statuses ?? new List<Status>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<Status>> StatuslogLatest() =>
|
||||||
|
(await Get<StatusResponseData>(new Uri($"{BaseUrl}/statuslog/latest")))?.Statuses ?? new List<Status>();
|
||||||
|
|
||||||
|
public async Task<List<Status>> Statuslog(string address) =>
|
||||||
|
(await Get<StatusResponseData>(new Uri($"{BaseUrl}/address/{address}/statuses")))?.Statuses ?? new List<Status>();
|
||||||
|
|
||||||
public async Task<MarkupString> StatuslogBio(string address) {
|
public async Task<MarkupString> StatuslogBio(string address) {
|
||||||
Uri uri = new Uri($"{BaseUrl}/address/{address}/statuses/bio");
|
StatusBioResponseData? responseData = await Get<StatusBioResponseData>(new Uri($"{BaseUrl}/address/{address}/statuses/bio"));
|
||||||
StatusBioResponseData? responseData = await Get<StatusBioResponseData>(uri);
|
|
||||||
return (MarkupString)Markdown.ToHtml(responseData?.Bio ?? "");
|
return (MarkupString)Markdown.ToHtml(responseData?.Bio ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AccountResponseData?> AccountInfo() {
|
public async Task<AccountResponseData?> AccountInfo() =>
|
||||||
Uri uri = new Uri($"{BaseUrl}/account/application/info");
|
await Get<AccountResponseData>(new Uri($"{BaseUrl}/account/application/info"));
|
||||||
AccountResponseData? responseData = await Get<AccountResponseData>(uri);
|
|
||||||
return responseData;
|
public async Task<AddressResponseList?> Addresses() =>
|
||||||
|
await Get<AddressResponseList>(new Uri($"{BaseUrl}/account/application/addresses"));
|
||||||
|
|
||||||
|
public async Task<StatusPostResponseData?> StatusPost(string address, StatusPost statusPost) =>
|
||||||
|
await Post<StatusPostResponseData, StatusPost>(new Uri($"{BaseUrl}/address/{address}/statuses"), statusPost);
|
||||||
|
|
||||||
|
public async Task<List<Pic>> SomePics() =>
|
||||||
|
(await Get<SomePicsResponseData>(new Uri($"{BaseUrl}/pics")))?.Pics ?? new List<Pic>();
|
||||||
|
|
||||||
|
public async Task<List<Pic>> SomePics(string address) =>
|
||||||
|
(await Get<SomePicsResponseData>(new Uri($"{BaseUrl}/address/{address}/pics")))?.Pics ?? new List<Pic>();
|
||||||
|
public async Task<Pic?> SomePic(string address, string id) =>
|
||||||
|
(await Get<Pic>(new Uri($"{BaseUrl}/address/{address}/pics/{id}")));
|
||||||
|
|
||||||
|
public async Task<List<string>> Ephemeral() {
|
||||||
|
List<string> notes = new List<string>();
|
||||||
|
Uri Uri = new Uri($"https://eph.emer.al/");
|
||||||
|
try {
|
||||||
|
var response = await _client.GetAsync(Uri);
|
||||||
|
var str = await response.Content.ReadAsStringAsync();
|
||||||
|
string pattern = @"<p class=""post"">(.*?)<\/p>";
|
||||||
|
var matches = Regex.Matches(str, pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
||||||
|
foreach (Match match in matches) {
|
||||||
|
notes.Add(match.Groups[1].Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
Debug.WriteLine(ex);
|
||||||
|
}
|
||||||
|
return notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AddressResponseList?> Addresses() {
|
public async Task<string?> OAuth(string code, string client_id, string client_secret, string redirect_uri) {
|
||||||
Uri uri = new Uri($"{BaseUrl}/account/application/addresses");
|
|
||||||
AddressResponseList? responseData = await Get<AddressResponseList>(uri);
|
|
||||||
return responseData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string?> OAuth(string code) {
|
|
||||||
string? token = null;
|
string? token = null;
|
||||||
Uri uri = new Uri($"{BaseUrl}/oauth/?code={code}&client_id=ea14dafd3e92cbcf93750c35cd81a031&client_secret=ec28b8653f1d98b4eef3f7a20858c43b&redirect_uri=https://neatnik.net/adam/bucket/omgloloauth/&scope=everything");
|
Uri uri = new Uri($"{BaseUrl}/oauth/?code={code}&client_id={client_id}&client_secret={client_secret}&redirect_uri={redirect_uri}&scope=everything");
|
||||||
try {
|
try {
|
||||||
HttpResponseMessage response = await _client.GetAsync(uri);
|
HttpResponseMessage response = await _client.GetAsync(uri);
|
||||||
if (response.IsSuccessStatusCode) {
|
if (response.IsSuccessStatusCode) {
|
||||||
|
|
|
@ -2,10 +2,14 @@
|
||||||
@import url(../vendor/beer.min.css);
|
@import url(../vendor/beer.min.css);
|
||||||
@import url(../vendor/type.css);
|
@import url(../vendor/type.css);
|
||||||
@import url(../vendor/fluent-emoji/SegoeUIEmoji.css);
|
@import url(../vendor/fluent-emoji/SegoeUIEmoji.css);
|
||||||
|
/*@import url(../vendor/fluent-emoji/3d.css);*/
|
||||||
|
@import url(../vendor/fluent-emoji/animated-optional.css);
|
||||||
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--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>');
|
||||||
}
|
}
|
||||||
html, body {
|
html, body {
|
||||||
font-family: var(--font);
|
font-family: var(--font);
|
||||||
|
@ -47,14 +51,13 @@ img {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status .emoji {
|
.status .emoji, #status-emoji {
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
inline-size: 3.5rem;
|
inline-size: 3.5rem;
|
||||||
block-size: 3.5rem;
|
block-size: 3.5rem;
|
||||||
font-size: 3.5rem;
|
font-size: 3.1rem;
|
||||||
line-height: 3.5rem;
|
line-height: 3.5rem;
|
||||||
text-indent: -10px;
|
text-indent: -6px;
|
||||||
/*visibility:hidden;*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile.avatar {
|
.profile.avatar {
|
||||||
|
@ -75,7 +78,7 @@ img {
|
||||||
|
|
||||||
@keyframes skeleton-loading {
|
@keyframes skeleton-loading {
|
||||||
0% {
|
0% {
|
||||||
background-color: hsl(200, 20%, 80%);
|
background-color: hsl(200, 20%, 70%);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
|
@ -137,3 +140,61 @@ i[class*=fa-at] {
|
||||||
vertical-align:unset;
|
vertical-align:unset;
|
||||||
font-size: .75em;
|
font-size: .75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fab {
|
||||||
|
position:fixed;
|
||||||
|
right: 2rem;
|
||||||
|
bottom: 2rem;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
overflow:visible;
|
||||||
|
width:80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invisible {
|
||||||
|
max-width:0px;
|
||||||
|
max-height:0px;
|
||||||
|
border: none;
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.fab {
|
||||||
|
bottom: 7rem;
|
||||||
|
}
|
||||||
|
*:has(> main.responsive) {
|
||||||
|
/*flex-direction: row;*/
|
||||||
|
max-block-size: calc(100vh - 5rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar::after {
|
||||||
|
content: '';
|
||||||
|
position:absolute;
|
||||||
|
left:0; right:0;
|
||||||
|
top:0; bottom:0;
|
||||||
|
background-color: var(--gray-8);
|
||||||
|
background-image: var(--prami-svg);
|
||||||
|
background-size: contain;
|
||||||
|
z-index:1;
|
||||||
|
}
|
||||||
|
|
||||||
|
article.ephemeral {
|
||||||
|
max-width: 50rem;
|
||||||
|
border: 2px dashed var(--gray-7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pics article {
|
||||||
|
max-width: 50rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs a {
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
2
wwwroot/vendor/fluent-emoji/SegoeUIEmoji.css
vendored
2
wwwroot/vendor/fluent-emoji/SegoeUIEmoji.css
vendored
|
@ -1 +1,3 @@
|
||||||
@font-face{font-family:SegoeUIEmoji;src:url(seguiemj.ttf);}
|
@font-face{font-family:SegoeUIEmoji;src:url(seguiemj.ttf);}
|
||||||
|
@font-face {font-family: 'EmojiMart';src:url(seguiemj.ttf) format('truetype');}
|
||||||
|
emoji-picker {--emoji-font-family: SegoeUIEmoji;}
|
1545
wwwroot/vendor/fluent-emoji/animated-optional.css
vendored
Normal file
1545
wwwroot/vendor/fluent-emoji/animated-optional.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue