diff options
| author | Jake Mannens <jake@asger.xyz> | 2023-10-16 02:01:27 +1100 |
|---|---|---|
| committer | Jake Mannens <jake@asger.xyz> | 2023-10-16 02:01:27 +1100 |
| commit | 07728d1048f34e1d048da63684b341ab30bc1d06 (patch) | |
| tree | 9c92b30be5f3cd060827edc2ff5836b6122a9cdd /Pages | |
| parent | ea89ec0c1b05ac246f2ffd5907daace27564100b (diff) | |
FeedService and AclDialog
Diffstat (limited to 'Pages')
| -rw-r--r-- | Pages/Component/AclDialog.razor | 93 | ||||
| -rw-r--r-- | Pages/Component/MiniPrincipalSelect.razor | 59 | ||||
| -rw-r--r-- | Pages/Component/MiniPrincipalSelect.razor.css | 19 | ||||
| -rw-r--r-- | Pages/Component/Titlebar.razor | 5 | ||||
| -rw-r--r-- | Pages/ViewMedia.razor | 1 | ||||
| -rw-r--r-- | Pages/_Host.cshtml | 1 |
6 files changed, 125 insertions, 53 deletions
diff --git a/Pages/Component/AclDialog.razor b/Pages/Component/AclDialog.razor index 691e984..c924b98 100644 --- a/Pages/Component/AclDialog.razor +++ b/Pages/Component/AclDialog.razor @@ -1,20 +1,24 @@ @using System.Numerics; @inject HBContext db +@inject ISecurityService securityService @implements IDialog <Dialog HeightPixels=500 WidthPixels=900 Title="Edit permissions" @ref=dialog> <div class="vcontainer"> <div class="hcontainer"> <div> - @if(obj?.Acl is not null) { - <MiniPrincipalSelect Label="Owner"/> - <table class="data-table"> - <tr> - <th>Action</th> - <th>Subject</th> - <th colspan="2">Permissions</th> - </tr> - @foreach(var rule in obj.Acl.Rules.OrderByDescending(r => r.Action)) { + <MiniPrincipalSelect + Label="Owner" + OnChange=@((sid) => obj.Owner = sid) + @ref=ownerSelect/> + <table class="data-table"> + <tr> + <th>Action</th> + <th>Subject</th> + <th colspan="2">Permissions</th> + </tr> + @if(obj?.Acl is not null) { + @foreach(var rule in obj.Acl.Rules) { <tr> <td> <div> @@ -24,24 +28,37 @@ </div> </td> <td> - @rule.Principal.ToString() + @if(rule.Principal != WellKnownSid.NullSid) { + @(securityService.TranslateName(rule.Principal)) + } else { + <i style="color:var(--col-error-pri);">Select a user/group!</i> + } + </td> + <td> + @if(rule.Permissions == 0) { + <i>None</i> + } else { + @GetActivePermissions(rule); + } </td> - <td>@GetActivePermissions(rule)</td> <td> <a title="Edit" href="javascript:;" @onclick=@(() => EditRule(rule))>🖉</a> <a title="Delete" href="javascript:;" @onclick=@(() => RemoveRule(rule))>✖</a> </td> </tr> } - </table> - <br/> - <center><a href="javascript:;" @onclick=AddRule>Add new</a></center> - } + } + </table> + <br/> + <center><a href="javascript:;" @onclick=AddRule>Add new</a></center> </div> <div> @if(ruleToEdit is not null && permissionCheckboxes is not null) { - <MiniPrincipalSelect Label="Subject"/> - var permissions = Acl.GetPermissionDescriptions(obj) + <MiniPrincipalSelect + Label="Subject" + OnChange=@((sid) => ruleToEdit.Principal = sid) + @ref=subjectSelect/> + var permissions = Acl.GetPermissionDescriptions(obj!) .OrderByDescending(kv => BitOperations.PopCount(kv.Value)) .ThenBy(kv => kv.Value); foreach(var perm in permissionCheckboxes) { @@ -60,7 +77,7 @@ <ButtonContainer> <button class="secondary" @onclick=Hide>Cancel</button> @if(obj?.Acl is not null) { - <button data-keyboard-shortcut="a" @onclick=ApplyAcl disabled=@(ApplyDisabled)> + <button data-keyboard-shortcut="a" @onclick=ApplyAcl disabled=@ApplyDisabled> <u>A</u>pply </button> } @@ -91,7 +108,9 @@ private string? editOwner; private string? editSubject; - private Dialog dialog; + private Dialog dialog; + private MiniPrincipalSelect ownerSelect; + private MiniPrincipalSelect? subjectSelect; public void Show() => Visible = true; public void Hide() => Visible = false; @@ -107,6 +126,8 @@ editOwner = null; CancelEditRule(); + db.ChangeTracker.Clear(); + obj = db.Objects .Include(o => o.Acl) .First(o => o.ObjectId == value.ObjectId); @@ -121,22 +142,29 @@ addedAcl = true; } + ownerSelect.SecurityIdentifier = obj.Owner; lastHashCode = GetAclHashCode(obj.Acl); } } public bool ApplyDisabled => - #if DEBUG - false; - #else GetAclHashCode(obj.Acl!) == lastHashCode || obj.Acl!.Rules.Select(r => r.Principal).Contains(WellKnownSid.NullSid); - #endif + + protected override void OnAfterRender(bool firstRender) { + if(subjectSelect is null || ruleToEdit is null) + return; + if(subjectSelect.SecurityIdentifier is not null) + return; + + subjectSelect.SecurityIdentifier = ruleToEdit.Principal; + StateHasChanged(); + } private string GetActivePermissions(AclRule rule) { var perms = Acl.GetPermissionDescriptions(obj) - .Where(kv => (rule.Permissions & kv.Value) == kv.Value) - .ToList(); + .Where(kv => (rule.Permissions & kv.Value) == kv.Value) + .ToList(); // Filter the list of matching permissions to include the // most relevant encapsulation permissions only. E.g. if @@ -159,6 +187,12 @@ } private void ApplyAcl() { + if(obj.Acl!.Rules.Count() == 0) { + obj.Acl = null; + if(!addedAcl) + db.Remove(obj.Acl!); + } + db.SaveChanges(); Hide(); } @@ -198,14 +232,17 @@ // Special hash function to identify only the elements of // the ACL that may have been changed by the user via this // dialog. - private int GetAclHashCode(Acl acl) => - !acl.Rules.Any() ? 0 : acl.Rules + private int GetAclHashCode(Acl acl) { + var aclHash = !acl.Rules.Any() ? 0 : acl.Rules .Select(r => ( r.Action, r.Permissions, r.Principal.GetHashCode()).GetHashCode()) .Aggregate((a, v) => HashCode.Combine(a, v)); + return HashCode.Combine(aclHash, obj.Owner.GetHashCode()); + } + private class PermissionCheckbox { public string Description { get; private init; } @@ -228,4 +265,4 @@ } } } -}
\ No newline at end of file +} diff --git a/Pages/Component/MiniPrincipalSelect.razor b/Pages/Component/MiniPrincipalSelect.razor index 2202b95..89409bd 100644 --- a/Pages/Component/MiniPrincipalSelect.razor +++ b/Pages/Component/MiniPrincipalSelect.razor @@ -1,18 +1,19 @@ @inject ISecurityService securityService +@inject IJSRuntime jsRuntime -<div> - @if(edit) { +<div @ref=div> + @if(newName is not null) { <label>@Label</label> - <input type="text" autocomplete="off" @bind=name/> - <button class="secondary" @onclick=@(() => Edit(false))>Cancel</button> + <input type="text" autocomplete="off" @bind=newName/> + <button class="secondary" @onclick=Cancel>Cancel</button> <button @onclick=Submit>OK</button> } else { <label>@(Label):</label> - <a href="javascript:;" @onclick=@(() => Edit(true))> + <a href="javascript:;" @onclick=Edit> @if(SecurityIdentifier is null || SecurityIdentifier == WellKnownSid.NullSid) { <i>Please select a user or group</i> } else { - @securityService.TranslateName(SecurityIdentifier) + @name } </a> } @@ -25,20 +26,44 @@ [Parameter] public EventCallback<SecurityIdentifier> OnChange { get; set; } - private bool edit = false; - private string name; + private bool edit = false; + private string? name; + private string? newName; - public SecurityIdentifier? SecurityIdentifier { get; set; } + private ElementReference div; - private void Edit(bool enableEdit) { - edit = enableEdit; + private HyperBooru.SecurityIdentifier? securityIdentifier; - if(enableEdit) - name = SecurityIdentifier is null ? "" : - securityService.TranslateName(SecurityIdentifier); + public SecurityIdentifier? SecurityIdentifier { + get => securityIdentifier; + set { + securityIdentifier = value; + name = value is null ? null : securityService.TranslateName(value); + } } - private void Submit() { - Edit(false); + private void Edit() { + newName = SecurityIdentifier == WellKnownSid.NullSid ? "" : name; + StateHasChanged(); } -}
\ No newline at end of file + + private async void Cancel() { + newName = null; + await jsRuntime.InvokeVoidAsync("removeClass", div, "bad-principal"); + StateHasChanged(); + } + + private async void Submit() { + var sid = securityService.TranslateName(newName!); + if(sid is null) { + await jsRuntime.InvokeVoidAsync("cycleClass", div, "bad-principal"); + return; + } + + await jsRuntime.InvokeVoidAsync("removeClass", div, "bad-principal"); + SecurityIdentifier = sid; + await OnChange.InvokeAsync(sid); + newName = null; + StateHasChanged(); + } +} diff --git a/Pages/Component/MiniPrincipalSelect.razor.css b/Pages/Component/MiniPrincipalSelect.razor.css index 4b7a217..7e410fc 100644 --- a/Pages/Component/MiniPrincipalSelect.razor.css +++ b/Pages/Component/MiniPrincipalSelect.razor.css @@ -5,14 +5,27 @@ margin-bottom: 16px; } -div * { +div > :not(div) { margin: 0; } -div :not(:last-child) { +div > :not(:last-child) { margin-right: 5px; } -div input[type="text"] { +div > input[type="text"] { flex-grow: 1; } + +div.bad-principal > :not(div) { + animation-iteration-count: 3; + animation-timing-function: linear; + animation: bad-principal 0.2s; +} + +@keyframes bad-principal { + 0% { transform: translateX(0); } + 33% { transform: translateX(-15px); } + 66% { transform: translateX(+15px); } + 100% { transform: translateX(0); } +} diff --git a/Pages/Component/Titlebar.razor b/Pages/Component/Titlebar.razor index 766787a..73700bc 100644 --- a/Pages/Component/Titlebar.razor +++ b/Pages/Component/Titlebar.razor @@ -15,10 +15,7 @@ if(resp.ok) { window.location.href = '/'; } else if(resp.status == 403) { - form.classList.remove('bad-login'); - @* TODO: improve this hacky method of triggering reflow *@ - form.offsetWidth; - form.classList.add('bad-login'); + cycleClass(form, 'bad-login'); inputs.forEach(e => e.value = null); inputs[0].focus(); } else { diff --git a/Pages/ViewMedia.razor b/Pages/ViewMedia.razor index f6a1382..05cf700 100644 --- a/Pages/ViewMedia.razor +++ b/Pages/ViewMedia.razor @@ -83,7 +83,6 @@ </div> <div id="button-container"> <ButtonContainer> - <button @onclick=@(() => { aclDialog.Object = media; aclDialog.Show(); }) class="secondary" data-keyboard-shortcut="p">Edit <u>P</u>ermissions</button> <button @onclick=@(() => deleteDialog.Show()) class="warning" data-keyboard-shortcut="d"><u>D</u>elete</button> <button @onclick=@(() => aclDialog.Show(media)) class="secondary" data-keyboard-shortcut="p">Edit <u>P</u>ermissions</button> <button @onclick=@(() => tagDialog.Show()) class="secondary" data-keyboard-shortcut="t">Add <u>T</u>ag</button> diff --git a/Pages/_Host.cshtml b/Pages/_Host.cshtml index 69bced8..20901d2 100644 --- a/Pages/_Host.cshtml +++ b/Pages/_Host.cshtml @@ -13,6 +13,7 @@ <link href="/favicon.ico" rel="icon" /> <link href="/manifest.webmanifest" rel="manifest" /> <script type="text/javascript" src="/js/keyboard.js"></script> + <script type="text/javascript" src="/js/shake.js"></script> <script type="text/javascript" src="/js/dialog.js"></script> <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" /> </head> |
