[frontend] Overhaul compose menu, add custom dropdown component

This commit is contained in:
Lilian 2024-05-07 02:41:59 +02:00
parent 0163721d7f
commit 55b586d0b5
No known key found for this signature in database
GPG key ID: 007CA12D692829E1
8 changed files with 185 additions and 18 deletions

View file

@ -8,21 +8,26 @@
<button @onclick="CloseDialog"> <button @onclick="CloseDialog">
<Icon Name="Icons.X"/> <Icon Name="Icons.X"/>
</button> </button>
<select @bind="NoteDraft.Visibility"> <Dropdown TBind="NoteVisibility" Elements="@DropDownCreate()" @bind-Value="NoteDraft.Visibility">
@foreach (var vis in Enum.GetValues<NoteVisibility>()) </Dropdown>
{ <button @onclick="SendNote" class="post-btn">Post<Icon Name="Icons.PaperPlaneRight"/></button>
<option value="@vis">@vis</option>
}
</select>
<button @onclick="SendNote">Send!</button>
</div> </div>
@if (NoteDraft.Cw != null) { @if (NoteDraft.Cw != null)
<input @bind="NoteDraft.Cw" class="input" /> {
<input @bind="NoteDraft.Cw" class="input cw-field" placeholder="Content Warning"/>
<hr class="separator"/>
} }
<textarea @bind="NoteDraft.Text" class="textarea" rows="5" cols="35"></textarea> <textarea @bind="NoteDraft.Text" class="textarea" placeholder="What's on your mind?" rows="5" cols="35"></textarea>
<div class="footer"> <div class="footer">
<button @onclick="ToggleCw"><Icon Name="Icons.EyeSlash"></Icon></button> <button class="footer-btn" @onclick="OpenUpload">
<InputFile OnChange="Upload">Upload!</InputFile> <Icon Name="Icons.Upload" Size="1.3rem"></Icon>
</button>
<button class="footer-btn" @onclick="ToggleCw">
<Icon Name="Icons.EyeSlash" Size="1.3rem"></Icon>
</button>
<div class="file-input">
<InputFile @ref="UploadInput" OnChange="Upload">Upload!</InputFile>
</div>
</div> </div>
</dialog> </dialog>
@ -30,6 +35,8 @@
private ElementReference Dialog { get; set; } private ElementReference Dialog { get; set; }
private IJSObjectReference? _module; private IJSObjectReference? _module;
private IList<DriveFileResponse> Attachments { get; set; } = []; private IList<DriveFileResponse> Attachments { get; set; } = [];
private InputFile UploadInput { get; set; }
private NoteCreateRequest NoteDraft { get; set; } = new NoteCreateRequest private NoteCreateRequest NoteDraft { get; set; } = new NoteCreateRequest
{ {
Text = "", Text = "",
@ -37,6 +44,45 @@
Cw = null Cw = null
}; };
RenderFragment DropdownIcon(NoteVisibility vis)
{
return vis switch
{
NoteVisibility.Public => (@<Icon Name="Icons.Globe"/>),
NoteVisibility.Home => (@<Icon Name="Icons.House"/>),
NoteVisibility.Followers => (@<Icon Name="Icons.Lock"/>),
NoteVisibility.Specified => (@<Icon Name="Icons.Envelope"/>),
_ => throw new ArgumentOutOfRangeException()
};
}
RenderFragment DropdownContent(NoteVisibility vis)
{
return vis switch
{
NoteVisibility.Public => (@<span class="dropdown-title">Public</span>),
NoteVisibility.Home => (@<span class="dropdown-title">Unlisted</span>),
NoteVisibility.Followers => (@<span class="dropdown-title">Followers</span>),
NoteVisibility.Specified => (@<span class="dropdown-title">Direct</span>),
_ => throw new ArgumentOutOfRangeException()
};
}
private IList<DropdownElement<NoteVisibility>> DropDownCreate()
{
return Enum.GetValues<NoteVisibility>()
.Select(vis =>
new DropdownElement<NoteVisibility> { Icon = DropdownIcon(vis), Content = DropdownContent(vis), Selection = vis })
.ToList();
}
// The <InputFile> Component is hidden, and triggered by a sepperate button.
// That way we get it's functionality, without the styling limitations of the InputFile component
private async Task OpenUpload()
{
await _module.InvokeVoidAsync("openUpload", UploadInput.Element);
}
public async Task OpenDialog() public async Task OpenDialog()
{ {
await _module.InvokeVoidAsync("openDialog", Dialog); await _module.InvokeVoidAsync("openDialog", Dialog);
@ -53,6 +99,7 @@
{ {
NoteDraft.MediaIds = Attachments.Select(x => x.Id).ToList(); NoteDraft.MediaIds = Attachments.Select(x => x.Id).ToList();
} }
await ApiService.Notes.CreateNote(NoteDraft); await ApiService.Notes.CreateNote(NoteDraft);
await CloseDialog(); await CloseDialog();
// FIXME: Implement timeline refresh and call it here. // FIXME: Implement timeline refresh and call it here.

View file

@ -2,11 +2,49 @@
background-color: var(--background-color); background-color: var(--background-color);
border-radius: 1rem; border-radius: 1rem;
margin: 0 auto; margin: 0 auto;
top: 10% top: 10%;
padding: 1rem;
}
.compose::backdrop {
background-color: black;
opacity: 50%;
}
.header {
margin-bottom: 0.5rem;
} }
.textarea { .textarea {
display: block; display: block;
background-color: var(--background-color);
border: none;
outline: none;
} }
.input { .input {
display: block; display: block;
background-color: var(--background-color);
border: none;
outline: none;
}
.cw-field {
margin-bottom: 0.5rem;
width: 100%;
}
.post-btn {
background: var(--accent-color);
float: right;
}
.footer-btn {
width: 2.5rem;
height: max-content;
background-color: var(--background-color);
}
.separator {
color: var(--highlight-color);
margin-top: 0.25rem;
margin-bottom: 0.25rem;
}
.file-input {
display: none;
} }

View file

@ -1,8 +1,10 @@
export function openDialog(element){ export function openDialog(element){
console.log("opening JS")
element.showModal() element.showModal()
} }
export function closeDialog(element){ export function closeDialog(element){
console.log("closing JS")
element.close() element.close()
} }
export function openUpload(element){
element.click();
}

View file

@ -0,0 +1,42 @@
@typeparam TBind
<div @onclick="Toggle" class="dropdown-root">
@CurrentIcon
</div>
@if (Visible)
{
<div class="dropdown-menu">
@foreach (var entry in Elements)
{
<DropdownElement TBind="TBind" OnSelect="UpdateSelected" Icon="entry.Icon" Content="entry.Content" Selection="entry.Selection"/>
}
</div>
}
@code {
[Parameter] [EditorRequired] public required IEnumerable<DropdownElement<TBind>> Elements { get; set; }
[Parameter] [EditorRequired] public required TBind Value { get; set; }
[Parameter] public EventCallback<TBind> ValueChanged { get; set; }
private DropdownElement<TBind>? CurrentSelection { get; set; }
private RenderFragment? CurrentIcon { get; set; }
private bool Visible { get; set; } = false;
private void UpdateSelected(DropdownElement<TBind> element)
{
CurrentSelection = element;
CurrentIcon = CurrentSelection.Icon;
ValueChanged.InvokeAsync(element.Selection);
Visible = false;
}
private void Toggle()
{
Visible = !Visible;
}
protected override void OnInitialized()
{
UpdateSelected(Elements.FirstOrDefault() ?? throw new InvalidOperationException());
}
}

View file

@ -0,0 +1,10 @@
.dropdown-menu {
position: absolute;
top: 5rem;
background-color: var(--background-color);
padding: 0.5rem;
border-color: var(--highlight-color);
border-style: solid;
border-width: 0.1rem;
border-radius: 0.5rem;
}

View file

@ -0,0 +1,5 @@
export class Dropdown {
}
window.Dropdown = Dropdown;

View file

@ -0,0 +1,19 @@
@typeparam TBind
<div @onclick="OnClick" class="dropdown-element">
<span class="icon">@Icon</span>
<span class="content">@Content</span>
</div>
@code {
[Parameter] [EditorRequired] public required RenderFragment Icon { get; set; }
[Parameter] [EditorRequired] public required RenderFragment Content { get; set; }
[Parameter] [EditorRequired] public EventCallback<DropdownElement<TBind>> OnSelect { get; set; }
[Parameter] [EditorRequired] public required TBind Selection { get; set; }
private async Task OnClick()
{
await OnSelect.InvokeAsync(this);
}
}

View file

@ -0,0 +1,4 @@
.dropdown-element {
margin: 0.25em;
cursor: pointer;
}