Editing pics and other changes
This commit is contained in:
parent
bacd83fe07
commit
0e1f734f9f
10 changed files with 242 additions and 29 deletions
64
Components/EditPicDialog.razor
Normal file
64
Components/EditPicDialog.razor
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
@inject IJSRuntime JS
|
||||||
|
@inject State State
|
||||||
|
|
||||||
|
<div class="overlay" data-ui="#@id"></div>
|
||||||
|
<dialog id="@id">
|
||||||
|
<img src="@Pic.Url" />
|
||||||
|
<div class="row">
|
||||||
|
<div class="field textarea label border max">
|
||||||
|
<InputTextArea @bind-Value="Description"></InputTextArea>
|
||||||
|
<label>Description</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav class="right-align no-space">
|
||||||
|
<button class="transparent link" data-ui="#@id" disabled="@loading">Cancel</button>
|
||||||
|
<button @onclick="PostPic" disabled="@loading">
|
||||||
|
@if (loading) {
|
||||||
|
<span>Saving...</span>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
<i class="fa-solid fa-floppy-disk"></i> <span>Save</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private Pic? _pic;
|
||||||
|
[Parameter]
|
||||||
|
public Pic? Pic {
|
||||||
|
get => _pic;
|
||||||
|
set {
|
||||||
|
_pic = value;
|
||||||
|
Description = _pic?.Description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string? Description { get; set; }
|
||||||
|
private bool loading = false;
|
||||||
|
[Parameter]
|
||||||
|
public string id { get; set; }
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync() {
|
||||||
|
Description = Pic?.Description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PostPic() {
|
||||||
|
loading = true;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
RestService api = new RestService();
|
||||||
|
if(!string.IsNullOrEmpty(Pic.Id)) {
|
||||||
|
await api.PostPicDescription(State.SelectedAddressName, Pic.Id, Description);
|
||||||
|
await State.RefreshPics();
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
await JS.InvokeVoidAsync("ui", "#" + id);
|
||||||
|
// clear input
|
||||||
|
Description = string.Empty;
|
||||||
|
Pic = null;
|
||||||
|
loading = false;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,15 +5,18 @@
|
||||||
<dialog id="@id">
|
<dialog id="@id">
|
||||||
<h5>Share a picture</h5>
|
<h5>Share a picture</h5>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="field label prefix border">
|
<button @onclick="PicFromMedia"><i class="fa-solid fa-image"></i> Select a picture</button>
|
||||||
|
<button @onclick="PicFromPhoto"><i class="fa-solid fa-camera"></i> Take a photo</button>
|
||||||
|
@* <div class="field label prefix border">
|
||||||
<i class="fa-solid fa-image"></i>
|
<i class="fa-solid fa-image"></i>
|
||||||
<InputFile OnChange="@ChangeFile" accept="image/gif, image/heic, image/heif, image/jpeg, image/png, image/svg+xml, image/webp"></InputFile>
|
<InputFile OnChange="@ChangeFile" accept="image/gif, image/heic, image/heif, image/jpeg, image/png, image/svg+xml, image/webp"></InputFile>
|
||||||
<input type="text">
|
<input type="text">
|
||||||
<label>Select a picture</label>
|
<label>Select a picture</label>
|
||||||
</div>
|
</div> *@
|
||||||
@if(File != null){
|
@if(File != null && Base64File != null && FileSize != null){
|
||||||
|
<img class="extra" src="@Base64Url">
|
||||||
<small>
|
<small>
|
||||||
@File.ContentType (@formatSizeUnits(File.Size))
|
@File.ContentType (@formatSizeUnits(FileSize))
|
||||||
</small>
|
</small>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +41,17 @@
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private IBrowserFile? File { get; set; }
|
// private IBrowserFile? File { get; set; }
|
||||||
|
private FileResult? File { get; set; }
|
||||||
|
private string? Base64File { get; set; }
|
||||||
|
private long? FileSize { get; set; }
|
||||||
|
private string? Base64Url {
|
||||||
|
get {
|
||||||
|
if(File == null || Base64File == null) return null;
|
||||||
|
|
||||||
|
return $"data:{File.ContentType};base64,{Base64File}";
|
||||||
|
}
|
||||||
|
}
|
||||||
private string Description { get; set; }
|
private string Description { get; set; }
|
||||||
private bool loading = false;
|
private bool loading = false;
|
||||||
[Parameter]
|
[Parameter]
|
||||||
|
@ -65,11 +78,12 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ChangeFile(InputFileChangeEventArgs e){
|
// private async Task ChangeFile(InputFileChangeEventArgs e){
|
||||||
File = e.File;
|
// File = e.File;
|
||||||
}
|
// }
|
||||||
|
|
||||||
private string formatSizeUnits(long bytes){
|
private string formatSizeUnits(long? bytes){
|
||||||
|
if(bytes == null) return "?? bytes";
|
||||||
string formatted = "0 bytes";
|
string formatted = "0 bytes";
|
||||||
if (bytes >= 1073741824) { formatted = $"{(bytes / 1073741824):.##} GB"; }
|
if (bytes >= 1073741824) { formatted = $"{(bytes / 1073741824):.##} GB"; }
|
||||||
else if (bytes >= 1048576) { formatted = $"{(bytes / 1048576):.##} MB"; }
|
else if (bytes >= 1048576) { formatted = $"{(bytes / 1048576):.##} MB"; }
|
||||||
|
@ -79,4 +93,21 @@
|
||||||
|
|
||||||
return formatted;
|
return formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private async Task PicFromMedia(EventArgs e) {
|
||||||
|
File = await MediaPicker.Default.PickPhotoAsync();
|
||||||
|
await PopulateFileDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PicFromPhoto(EventArgs e) {
|
||||||
|
File = await MediaPicker.Default.CapturePhotoAsync();
|
||||||
|
await PopulateFileDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PopulateFileDetails() {
|
||||||
|
FileSize = await State.FileSize(File);
|
||||||
|
Base64File = await State.Base64FromFile(File);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="pics" class="page padding">
|
<div id="pics" class="page padding">
|
||||||
<PicList PicsFunc="@State.VirtualPicsFunc(Address)"></PicList>
|
@if(Editable){
|
||||||
|
<EditPicDialog @ref="editPicDialog" id="EditPicModal"></EditPicDialog>
|
||||||
|
}
|
||||||
|
<PicList PicsFunc="@State.VirtualPicsFunc(Address)" Editable="@Editable" Dialog="@editPicDialog"></PicList>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -42,6 +45,12 @@
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
|
|
||||||
|
private EditPicDialog? editPicDialog { get; set; }
|
||||||
|
|
||||||
|
private bool Editable {
|
||||||
|
get => Address == State.SelectedAddressName;
|
||||||
|
}
|
||||||
|
|
||||||
private MarkupString? bio;
|
private MarkupString? bio;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync() {
|
protected override async Task OnInitializedAsync() {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</AuthorizeView>
|
</AuthorizeView>
|
||||||
|
|
||||||
<div id="pics" class="responsive">
|
<div id="pics" class="responsive card-grid">
|
||||||
<PicList PicsFunc="@State.VirtualPicsFunc()"></PicList>
|
<PicList PicsFunc="@State.VirtualPicsFunc()"></PicList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
50
Components/PicCard.razor
Normal file
50
Components/PicCard.razor
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
@inject IJSRuntime JS
|
||||||
|
|
||||||
|
<article class="no-padding">
|
||||||
|
<img src="@Pic.Url">
|
||||||
|
<div class="padding">
|
||||||
|
<nav>
|
||||||
|
<a class="author" href="/person/@Pic.Address#pics">
|
||||||
|
<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>
|
||||||
|
<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">
|
||||||
|
<i class="fa-solid fa-share-nodes"></i>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public Pic Pic {get; set;}
|
||||||
|
[Parameter]
|
||||||
|
public bool Editable { get; set; } = false;
|
||||||
|
[Parameter]
|
||||||
|
public EditPicDialog? Dialog { get; set; }
|
||||||
|
|
||||||
|
private async Task EditPic(EventArgs e){
|
||||||
|
Dialog.Pic = Pic;
|
||||||
|
// await InvokeAsync(StateHasChanged);
|
||||||
|
await JS.InvokeVoidAsync("ui", "#" + Dialog?.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ShareClick(EventArgs e){
|
||||||
|
await Share.Default.RequestAsync(new ShareTextRequest{
|
||||||
|
Uri = Pic.Url,
|
||||||
|
Text = Pic.Description,
|
||||||
|
Title = "I saw this on some.pics",
|
||||||
|
Subject = "I saw this on some.pics"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +1,20 @@
|
||||||
<Virtualize ItemsProvider="GetPics" Context="pic" ItemSize="180">
|
@inject IJSRuntime JS
|
||||||
|
<Virtualize ItemsProvider="GetPics" Context="pic" ItemSize="500">
|
||||||
<ItemContent>
|
<ItemContent>
|
||||||
<article class="no-padding center">
|
<PicCard Pic="@pic" Editable="@Editable" Dialog="@Dialog"></PicCard>
|
||||||
<img class="responsive" src="@pic.Url">
|
|
||||||
<div class="padding">
|
|
||||||
<nav>
|
|
||||||
<a class="author" href="/person/@pic.Address#pics">
|
|
||||||
<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>
|
</ItemContent>
|
||||||
<Placeholder>
|
<Placeholder>
|
||||||
<article class="no-padding center"></article>
|
<article class="no-padding center" style="min-height:500px;"></article>
|
||||||
</Placeholder>
|
</Placeholder>
|
||||||
</Virtualize>
|
</Virtualize>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Func<ItemsProviderRequest, ValueTask<ItemsProviderResult<Pic>>> PicsFunc { get; set; }
|
public Func<ItemsProviderRequest, ValueTask<ItemsProviderResult<Pic>>> PicsFunc { get; set; }
|
||||||
|
[Parameter]
|
||||||
|
public bool Editable { get; set; } = false;
|
||||||
|
[Parameter]
|
||||||
|
public EditPicDialog? Dialog { get; set; }
|
||||||
|
|
||||||
private async ValueTask<ItemsProviderResult<Pic>> GetPics(ItemsProviderRequest request) {
|
private async ValueTask<ItemsProviderResult<Pic>> GetPics(ItemsProviderRequest request) {
|
||||||
return await this.PicsFunc(request);
|
return await this.PicsFunc(request);
|
||||||
|
|
|
@ -15,10 +15,22 @@
|
||||||
<a class="chip transparent-border" href="@status.ExternalUrl" target="_blank">
|
<a class="chip transparent-border" href="@status.ExternalUrl" target="_blank">
|
||||||
<i class="fa fa-message-dots"></i> Respond
|
<i class="fa fa-message-dots"></i> Respond
|
||||||
</a>
|
</a>
|
||||||
|
<div class="max"></div>
|
||||||
|
<button class="transparent circle" @onclick="ShareClick">
|
||||||
|
<i class="fa-solid fa-share-nodes"></i>
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Status status { get; set; }
|
public Status status { get; set; }
|
||||||
|
|
||||||
|
public async Task ShareClick(EventArgs e){
|
||||||
|
await Share.Default.RequestAsync(new ShareTextRequest{
|
||||||
|
Text = $"{status.Content}\n- from [@{status.Address}]({status.Url})",
|
||||||
|
Title = "I saw this on status.lol",
|
||||||
|
Subject = "I saw this on status.lol"
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,8 +117,8 @@ namespace Neighbourhood.omg.lol.Models {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Pic>?> GetPics(bool forceRefresh = false) {
|
public async Task<List<Pic>?> GetPics(bool forceRefresh = false) {
|
||||||
RestService api = new RestService();
|
|
||||||
if(forceRefresh || this.Pics == null || this.Pics.Count == 0) {
|
if(forceRefresh || this.Pics == null || this.Pics.Count == 0) {
|
||||||
|
RestService api = new RestService();
|
||||||
this.Pics = await api.SomePics();
|
this.Pics = await api.SomePics();
|
||||||
}
|
}
|
||||||
return this.Pics;
|
return this.Pics;
|
||||||
|
@ -147,6 +147,19 @@ namespace Neighbourhood.omg.lol.Models {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<long> FileSize(FileResult file) {
|
||||||
|
using var fileStream = await file.OpenReadAsync();
|
||||||
|
return fileStream.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> Base64FromFile(FileResult file) {
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
using var fileStream = await file.OpenReadAsync();
|
||||||
|
await fileStream.CopyToAsync(memoryStream);
|
||||||
|
byte[] bytes = memoryStream.ToArray();
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,6 +132,16 @@ namespace Neighbourhood.omg.lol {
|
||||||
return await PutPic(address, bytes);
|
return await PutPic(address, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<PutPicResponseData?> PutPic(string address, FileResult file) {
|
||||||
|
byte[] bytes;
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
using var fileStream = await file.OpenReadAsync();
|
||||||
|
await fileStream.CopyToAsync(memoryStream);
|
||||||
|
bytes = memoryStream.ToArray();
|
||||||
|
|
||||||
|
return await PutPic(address, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<PutPicResponseData?> PutPic(string address, byte[] bytes) =>
|
public async Task<PutPicResponseData?> PutPic(string address, byte[] bytes) =>
|
||||||
await PutPic(address, Convert.ToBase64String(bytes));
|
await PutPic(address, Convert.ToBase64String(bytes));
|
||||||
|
|
||||||
|
|
|
@ -198,3 +198,36 @@ article.ephemeral {
|
||||||
.tabs a {
|
.tabs a {
|
||||||
text-decoration:none;
|
text-decoration:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-grid{
|
||||||
|
display: flex;
|
||||||
|
flex-direction:row;
|
||||||
|
flex-wrap:wrap;
|
||||||
|
gap: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pics article {
|
||||||
|
margin: 1rem auto;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pics article>:not(:first-child) {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pics article nav {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pics.card-grid article{
|
||||||
|
max-width: 24rem;
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 895px) {
|
||||||
|
#pics.card-grid article {
|
||||||
|
max-width: calc(100% - 1rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pics article > img:first-child {
|
||||||
|
text-align: center;
|
||||||
|
}
|
Loading…
Reference in a new issue