Add creating bookmark groups
This commit is contained in:
parent
d997655b59
commit
7841d1d1a8
20
Start/Client/Components/Bookmark.razor
Normal file
20
Start/Client/Components/Bookmark.razor
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<li>
|
||||||
|
@if (!String.IsNullOrEmpty(this.Model.Notes))
|
||||||
|
{
|
||||||
|
<details>
|
||||||
|
<summary>
|
||||||
|
<a href="@this.Model.Url" class="bookmarkLink">@this.Model.Title</a>
|
||||||
|
</summary>
|
||||||
|
@this.Model.Notes
|
||||||
|
</details>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<a href="@this.Model.Url" class="bookmarkLink">@this.Model.Title</a>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public BookmarkDto Model { get; set; } = null!;
|
||||||
|
}
|
|
@ -1,5 +1,21 @@
|
||||||
<div class="activeBookmarkContainer">
|
@using Start.Client.Store.State
|
||||||
@if (this.Container == null)
|
@using Start.Client.Store.Features.CreateGroup
|
||||||
|
@using Fluxor
|
||||||
|
|
||||||
|
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
|
||||||
|
|
||||||
|
@inject IDispatcher dispatch
|
||||||
|
@inject IState<RootState> state
|
||||||
|
|
||||||
|
<div class="activeBookmarkContainer">
|
||||||
|
@if (this.state.Value.CurrentContainerState.ErrorMessage != null)
|
||||||
|
{
|
||||||
|
<Alert Type="Alert.AlertType.Error">
|
||||||
|
@this.state.Value.CurrentContainerState.ErrorMessage
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (this.state.Value.CurrentContainerState.IsLoadingCurrentContainer)
|
||||||
{
|
{
|
||||||
<div class="empty">
|
<div class="empty">
|
||||||
<div class="empty-icon">
|
<div class="empty-icon">
|
||||||
|
@ -7,27 +23,60 @@
|
||||||
</div>
|
</div>
|
||||||
<p class="empty-title h5">Loading Bookmarks</p>
|
<p class="empty-title h5">Loading Bookmarks</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-center">Loading Bookmarks</p>
|
|
||||||
}
|
}
|
||||||
else if (!this.Container.BookmarkGroups?.Any() ?? true)
|
else if (this.state.Value.CurrentContainerState.Container == null)
|
||||||
|
{
|
||||||
|
<div class="empty">
|
||||||
|
<div class="empty-icon">
|
||||||
|
<i class="icon icon-3x icon-bookmark"></i>
|
||||||
|
</div>
|
||||||
|
<p class="empty-title h5">Failed To Load Container</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (this.state.Value.CurrentContainerState.Container.BookmarkGroups == null
|
||||||
|
|| (!(this.state.Value.CurrentContainerState.Container.BookmarkGroups?.Any()) ?? true))
|
||||||
{
|
{
|
||||||
<div class="empty">
|
<div class="empty">
|
||||||
<div class="empty-icon">
|
<div class="empty-icon">
|
||||||
<i class="icon icon-3x icon-bookmark"></i>
|
<i class="icon icon-3x icon-bookmark"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="empty-title h5">No Bookmark Groups</p>
|
<p class="empty-title h5">No Bookmark Groups</p>
|
||||||
|
<div class="empty-action">
|
||||||
|
<button class="btn btn-primary" @onclick="this.ShowCreateGroupForm">
|
||||||
|
<i class="icon icon-plus"></i> Create Group
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (BookmarkGroupDto group in this.Container.BookmarkGroups!)
|
<div id="bookmarkGroups">
|
||||||
|
@* The compiler doesn't pick up that null has already been checked for,
|
||||||
|
so the ! is needed *@
|
||||||
|
@foreach (BookmarkGroupDto group in this.state.Value.CurrentContainerState.Container.BookmarkGroups!)
|
||||||
{
|
{
|
||||||
<BookmarkGroup Group="group" />
|
<BookmarkGroup Group="group" />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<div class="addBookmarkGroupButton text-center">
|
||||||
|
<button type="button" class="btn tooltip tooltip-bottom"
|
||||||
|
@onclick="this.ShowCreateGroupForm"
|
||||||
|
aria-label="Create Group" data-tooltip="Create Group">
|
||||||
|
<i class="icon icon-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
public void ShowCreateGroupForm()
|
||||||
public BookmarkContainerDto? Container { get; set; }
|
{
|
||||||
|
if (this.state.Value.CurrentContainerState.Container == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dispatch.Dispatch(new ShowCreateGroupFormAction(
|
||||||
|
this.state.Value.CurrentContainerState.Container.BookmarkContainerId,
|
||||||
|
this.state.Value.CurrentContainerState.Container.Title));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,47 @@
|
||||||
<h2 class="bookmarkGroupTitle" style="background-color: #@this.Group.Color">
|
<div class="card bookmarkGroup">
|
||||||
@this.Group.Title
|
<div class="card-header" style="background-color: @this.Group.Color">
|
||||||
</h2>
|
<h2 class="card-title h5 d-inline-block">@this.Group.Title</h2>
|
||||||
<ul class="bookmarkGroupList">
|
<button type="button" class="addBookmarkButton btn btn-sm tooltip tooltip-left"
|
||||||
|
aria-label="Create Bookmark" data-tooltip="Create Bookmark">
|
||||||
|
<i class="icon icon-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="bookmarks">
|
||||||
@if (this.Group.Bookmarks == null || !this.Group.Bookmarks.Any())
|
@if (this.Group.Bookmarks == null || !this.Group.Bookmarks.Any())
|
||||||
{
|
{
|
||||||
<li class="bookmarkListItem noBookmarksItem"><i>No Bookmarks</i></li>
|
<li class="noBookmarksItem">
|
||||||
|
<div class="empty">
|
||||||
|
<div class="empty-icon">
|
||||||
|
<i class="icon icon-bookmark"></i>
|
||||||
|
</div>
|
||||||
|
<p class="empty-title h5">No Bookmarks</p>
|
||||||
|
<div class="empty-action">
|
||||||
|
<button type="button" class="btn btn-primary">
|
||||||
|
<i class="icon icon-plus"></i> Create Bookmark
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (BookmarkDto bookmark in this.Group.Bookmarks!)
|
foreach (BookmarkDto bookmark in this.Group.Bookmarks)
|
||||||
{
|
{
|
||||||
<li class="bookmarkListItem">
|
<Bookmark Model="bookmark" />
|
||||||
<a href="@bookmark.Url" class="bookmarkLink">@bookmark.Title</a>
|
|
||||||
</li>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public BookmarkGroupDto Group { get; set; } = null!; // [Required] is a .net 6 feature
|
public BookmarkGroupDto Group { get; set; } = null!;
|
||||||
|
|
||||||
|
protected void OnCreateBookmarkClicked()
|
||||||
|
{
|
||||||
|
// Placeholder
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
@this.state.Value.CreateContainerErrorMessage
|
@this.state.Value.CreateContainerErrorMessage
|
||||||
</Alert>
|
</Alert>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
|
|
82
Start/Client/Components/CreateGroup.razor
Normal file
82
Start/Client/Components/CreateGroup.razor
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
@using Start.Client.Store.Features.CreateGroup
|
||||||
|
@using Fluxor
|
||||||
|
|
||||||
|
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
|
||||||
|
|
||||||
|
@inject IActionSubscriber actionSubscriber
|
||||||
|
@inject IDispatcher dispatch
|
||||||
|
@inject IState<CreateGroupState> state
|
||||||
|
|
||||||
|
<Dialog Title="Create Bookmark Group" Active="this.state.Value.ShowCreateGroupForm" OnClose="this.OnDialogClose">
|
||||||
|
<EditForm Model="this.Model" OnValidSubmit="this.OnSubmit">
|
||||||
|
<DataAnnotationsValidator />
|
||||||
|
|
||||||
|
@if (this.state.Value.CreateGroupErrorMessage != null)
|
||||||
|
{
|
||||||
|
<Alert Type="Alert.AlertType.Error">
|
||||||
|
@this.state.Value.CreateGroupErrorMessage
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
|
||||||
|
<ValidationSummary />
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="container">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column col-10">
|
||||||
|
<label for="createBookmarkGroupTitle">Title</label>
|
||||||
|
<InputText id="createBookmarkGroupTitle" name="createBookmarkGroupTitle"
|
||||||
|
class="form-input" @bind-Value="this.Model.Title" />
|
||||||
|
</div>
|
||||||
|
<div class="column col-2">
|
||||||
|
<label for="createBookmarkGroupColor">Color</label>
|
||||||
|
<input type="color" name="createBookmarkGroupColor" @bind="this.Model.Color" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column col-12 text-right">
|
||||||
|
@if (this.state.Value.IsLoadingCreateGroup)
|
||||||
|
{
|
||||||
|
<button type="submit" disabled class="btn btn-primary loading">
|
||||||
|
<i class="icon icon-plus"></i> Create
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="icon icon-plus"></i> Create
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private BookmarkGroupDto Model { get; set; } = new("", "", 0);
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
|
||||||
|
this.Model = new BookmarkGroupDto("", "", state.Value.ContainerId);
|
||||||
|
|
||||||
|
// Keep the model's container ID up to date
|
||||||
|
actionSubscriber.SubscribeToAction<ShowCreateGroupFormAction>(this,
|
||||||
|
(a) => this.Model.BookmarkContainerId = a.ContainerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnSubmit()
|
||||||
|
{
|
||||||
|
dispatch.Dispatch(new SubmitCreateGroupAction(this.Model));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnDialogClose()
|
||||||
|
{
|
||||||
|
dispatch.Dispatch(new HideCreateGroupFormAction());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,8 @@
|
||||||
<div class="modal @(this.Active ? "active" : "")">
|
@using Microsoft.Extensions.Logging
|
||||||
|
|
||||||
|
@inject ILogger<Dialog> logger
|
||||||
|
|
||||||
|
<div class="modal @(this.Active ? "active" : "")">
|
||||||
<a class="modal-overlay" @onclick="this.OnDialogClose" aria-label="Close"></a>
|
<a class="modal-overlay" @onclick="this.OnDialogClose" aria-label="Close"></a>
|
||||||
<div class="modal-container">
|
<div class="modal-container">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
@using System.Linq
|
@using System.Linq
|
||||||
@using Start.Client.Components
|
@using Start.Client.Components
|
||||||
@using Start.Client.Store.State
|
@using Start.Client.Store.State
|
||||||
@using Start.Client.Store.Features.ContainersList
|
|
||||||
@using Start.Client.Store.Features.CurrentContainer
|
@using Start.Client.Store.Features.CurrentContainer
|
||||||
@using Start.Client.Store.Features.CreateContainer
|
@using Start.Client.Store.Features.CreateContainer
|
||||||
@using Start.Client.Store.Features.DeleteContainer
|
@using Start.Client.Store.Features.DeleteContainer
|
||||||
|
@ -52,27 +51,24 @@ else
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
<li class="tab-item tab-action">
|
<li class="tab-item tab-action">
|
||||||
<button @onclick="OnCreateContainerClicked" class="btn btn-link"
|
<button @onclick="OnCreateContainerClicked" class="btn btn-link tooltip tooltip-left"
|
||||||
title="Create New Container" aria-label="Create New Container">
|
title="Create New Container" aria-label="Create New Container"
|
||||||
|
data-tooltip="Create Container">
|
||||||
+
|
+
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<BookmarkContainer Container="this.state.Value.CurrentContainerState.Container" />
|
<BookmarkContainer />
|
||||||
|
|
||||||
<CreateContainer />
|
<CreateContainer />
|
||||||
<DeleteContainer />
|
<DeleteContainer />
|
||||||
|
|
||||||
|
<CreateGroup />
|
||||||
}
|
}
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
this.dispatch.Dispatch(new LoadContainerListAction());
|
|
||||||
this.dispatch.Dispatch(new LoadCurrentContainerAction(await this.GetSelectedContainerId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void OnContainerSelected(int bookmarkContainerId)
|
protected void OnContainerSelected(int bookmarkContainerId)
|
||||||
{
|
{
|
||||||
dispatch.Dispatch(new LoadCurrentContainerAction(bookmarkContainerId));
|
dispatch.Dispatch(new LoadCurrentContainerAction(bookmarkContainerId));
|
||||||
|
@ -95,26 +91,4 @@ else
|
||||||
{
|
{
|
||||||
dispatch.Dispatch(new ShowCreateContainerFormAction());
|
dispatch.Dispatch(new ShowCreateContainerFormAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the currently selected container in LocalStorage so that the same container remains
|
|
||||||
// selected between new tabs
|
|
||||||
|
|
||||||
protected async Task<int> GetSelectedContainerId()
|
|
||||||
{
|
|
||||||
bool hasValue = await localStorage.ContainKeyAsync("SelectedContainer");
|
|
||||||
|
|
||||||
if (hasValue)
|
|
||||||
return await localStorage.GetItemAsync<int>("SelectedContainer");
|
|
||||||
|
|
||||||
// Default to the first container
|
|
||||||
int firstContainer = this.state.Value.ContainerListState.Containers
|
|
||||||
.First().BookmarkContainerId;
|
|
||||||
await this.SetSelectedContainer(firstContainer);
|
|
||||||
return firstContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task SetSelectedContainer(int selectedContainerId)
|
|
||||||
{
|
|
||||||
await localStorage.SetItemAsync<int>("SelectedContainer", selectedContainerId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,10 @@ namespace Start.Client {
|
||||||
})
|
})
|
||||||
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
|
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
|
||||||
|
|
||||||
|
builder.Services.AddRefitClient<IBookmarkGroupsApi>()
|
||||||
|
.ConfigureHttpClient(c => { c.BaseAddress = new Uri(baseUri, "BookmarkGroups"); })
|
||||||
|
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
|
||||||
|
|
||||||
builder.Services.AddRefitClient<IBookmarksApi>()
|
builder.Services.AddRefitClient<IBookmarksApi>()
|
||||||
.ConfigureHttpClient(c => { c.BaseAddress = new Uri(baseUri, "Bookmarks"); })
|
.ConfigureHttpClient(c => { c.BaseAddress = new Uri(baseUri, "Bookmarks"); })
|
||||||
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
|
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
|
||||||
|
@ -44,6 +48,7 @@ namespace Start.Client {
|
||||||
builder.Services.AddFluxor(opt => {
|
builder.Services.AddFluxor(opt => {
|
||||||
opt.ScanAssemblies(typeof(Program).Assembly);
|
opt.ScanAssemblies(typeof(Program).Assembly);
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
Console.WriteLine("Enabling Redux dev tools");
|
||||||
opt.UseReduxDevTools();
|
opt.UseReduxDevTools();
|
||||||
#endif
|
#endif
|
||||||
});
|
});
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
<None Remove="Store\Features\DeleteContainer\" />
|
<None Remove="Store\Features\DeleteContainer\" />
|
||||||
<None Remove="Store\Features\ContainersList\" />
|
<None Remove="Store\Features\ContainersList\" />
|
||||||
<None Remove="Store\Features\CurrentContainer\" />
|
<None Remove="Store\Features\CurrentContainer\" />
|
||||||
|
<None Remove="Store\Features\CreateGroup\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Remove="wwwroot\css\Spectre\" />
|
<Content Remove="wwwroot\css\Spectre\" />
|
||||||
|
@ -58,5 +59,6 @@
|
||||||
<Folder Include="Store\Features\DeleteContainer\" />
|
<Folder Include="Store\Features\DeleteContainer\" />
|
||||||
<Folder Include="Store\Features\ContainersList\" />
|
<Folder Include="Store\Features\ContainersList\" />
|
||||||
<Folder Include="Store\Features\CurrentContainer\" />
|
<Folder Include="Store\Features\CurrentContainer\" />
|
||||||
|
<Folder Include="Store\Features\CreateGroup\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -4,12 +4,11 @@ using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
|
||||||
using Refit;
|
using Refit;
|
||||||
using Start.Shared;
|
using Start.Shared;
|
||||||
using Start.Shared.Api;
|
using Start.Shared.Api;
|
||||||
using Start.Client.Store.Features.CurrentContainer;
|
|
||||||
using Start.Client.Store.Features.ContainersList;
|
using Start.Client.Store.Features.ContainersList;
|
||||||
|
|
||||||
namespace Start.Client.Store.Features.CreateContainer {
|
namespace Start.Client.Store.Features.CreateContainer {
|
||||||
public class CreateContainerEffects {
|
public class CreateContainerEffects {
|
||||||
public IBookmarkContainersApi BookmarkContainersApi { get; set; }
|
public IBookmarkContainersApi BookmarkContainersApi { get; init; }
|
||||||
|
|
||||||
public CreateContainerEffects(IBookmarkContainersApi bookmarkContainersApi) {
|
public CreateContainerEffects(IBookmarkContainersApi bookmarkContainersApi) {
|
||||||
this.BookmarkContainersApi = bookmarkContainersApi;
|
this.BookmarkContainersApi = bookmarkContainersApi;
|
||||||
|
@ -26,15 +25,14 @@ namespace Start.Client.Store.Features.CreateContainer {
|
||||||
|
|
||||||
BookmarkContainerDto? container = apiResponse.Content;
|
BookmarkContainerDto? container = apiResponse.Content;
|
||||||
|
|
||||||
if (container == null)
|
if (container == null) {
|
||||||
dispatch.Dispatch(new ErrorFetchingCreateContainerAction(
|
dispatch.Dispatch(new ErrorFetchingCreateContainerAction(
|
||||||
"Failed to create container"));
|
"Failed to create container"));
|
||||||
else {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch.Dispatch(new AddContainerToListAction(container));
|
dispatch.Dispatch(new AddContainerToListAction(container));
|
||||||
dispatch.Dispatch(new ReceivedCreateContainerAction());
|
dispatch.Dispatch(new ReceivedCreateContainerAction());
|
||||||
dispatch.Dispatch(new LoadCurrentContainerAction(
|
|
||||||
container.BookmarkContainerId));
|
|
||||||
}
|
|
||||||
} catch (AccessTokenNotAvailableException e) {
|
} catch (AccessTokenNotAvailableException e) {
|
||||||
e.Redirect();
|
e.Redirect();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Fluxor;
|
using System;
|
||||||
|
using Fluxor;
|
||||||
|
|
||||||
namespace Start.Client.Store.Features.CreateContainer {
|
namespace Start.Client.Store.Features.CreateContainer {
|
||||||
public static class CreateContainerReducers {
|
public static class CreateContainerReducers {
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
using Start.Shared;
|
||||||
|
|
||||||
|
namespace Start.Client.Store.Features.CreateGroup {
|
||||||
|
public class ShowCreateGroupFormAction {
|
||||||
|
public int ContainerId { get; init; }
|
||||||
|
public string ContainerTitle { get; init; }
|
||||||
|
|
||||||
|
public ShowCreateGroupFormAction(int containerId, string containerTitle) {
|
||||||
|
this.ContainerId = containerId;
|
||||||
|
this.ContainerTitle = containerTitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HideCreateGroupFormAction { }
|
||||||
|
|
||||||
|
public class FetchCreateGroupAction { }
|
||||||
|
|
||||||
|
public class RecievedCreateGroupAction { }
|
||||||
|
|
||||||
|
public class ErrorFetchingCreateGroupAction {
|
||||||
|
public string ErrorMessage { get; init; }
|
||||||
|
|
||||||
|
public ErrorFetchingCreateGroupAction(string errorMessage) {
|
||||||
|
this.ErrorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubmitCreateGroupAction {
|
||||||
|
public BookmarkGroupDto NewGroup { get; init; }
|
||||||
|
|
||||||
|
public SubmitCreateGroupAction(BookmarkGroupDto newGroup) {
|
||||||
|
this.NewGroup = newGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
using Start.Shared.Api;
|
||||||
|
using Fluxor;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
|
||||||
|
using Refit;
|
||||||
|
using Start.Shared;
|
||||||
|
using Start.Client.Store.Features.CurrentContainer;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Start.Client.Store.Features.CreateGroup {
|
||||||
|
public class CreateGroupEffects {
|
||||||
|
public IBookmarkGroupsApi BookmarkGroupsApi { get; init; }
|
||||||
|
|
||||||
|
public CreateGroupEffects(IBookmarkGroupsApi bookmarksApi) {
|
||||||
|
this.BookmarkGroupsApi = bookmarksApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
[EffectMethod]
|
||||||
|
public async Task SubmitCreateBookmarkGroup(SubmitCreateGroupAction action,
|
||||||
|
IDispatcher dispatch) {
|
||||||
|
dispatch.Dispatch(new FetchCreateGroupAction());
|
||||||
|
|
||||||
|
try {
|
||||||
|
ApiResponse<BookmarkGroupDto?> apiResponse = await this.BookmarkGroupsApi
|
||||||
|
.CreateBookmarkGroup(action.NewGroup.Title, action.NewGroup.Color,
|
||||||
|
action.NewGroup.BookmarkContainerId);
|
||||||
|
|
||||||
|
Console.WriteLine("Status code: " + apiResponse.StatusCode);
|
||||||
|
|
||||||
|
if (!apiResponse.IsSuccessStatusCode) {
|
||||||
|
dispatch.Dispatch(new ErrorFetchingCreateGroupAction(
|
||||||
|
"Error creating bookmark group"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch.Dispatch(new AddBookmarkGroupAction(action.NewGroup));
|
||||||
|
dispatch.Dispatch(new RecievedCreateGroupAction());
|
||||||
|
dispatch.Dispatch(new HideCreateGroupFormAction());
|
||||||
|
} catch (AccessTokenNotAvailableException e) {
|
||||||
|
e.Redirect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
using Fluxor;
|
||||||
|
|
||||||
|
namespace Start.Client.Store.Features.CreateGroup {
|
||||||
|
public class CreateGroupFeature : Feature<CreateGroupState> {
|
||||||
|
public override string GetName() => "Create Group";
|
||||||
|
|
||||||
|
protected override CreateGroupState GetInitialState() {
|
||||||
|
return new CreateGroupState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
using Fluxor;
|
||||||
|
|
||||||
|
namespace Start.Client.Store.Features.CreateGroup {
|
||||||
|
public static class CreateGroupReducers {
|
||||||
|
[ReducerMethod]
|
||||||
|
public static CreateGroupState ShowCreateGroupForm(CreateGroupState state,
|
||||||
|
ShowCreateGroupFormAction action) {
|
||||||
|
return state with {
|
||||||
|
ShowCreateGroupForm = true,
|
||||||
|
ContainerId = action.ContainerId,
|
||||||
|
ContainerTitle = action.ContainerTitle,
|
||||||
|
IsLoadingCreateGroup = false,
|
||||||
|
CreateGroupErrorMessage = null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[ReducerMethod(typeof(HideCreateGroupFormAction))]
|
||||||
|
public static CreateGroupState HideCreateContainerForm(CreateGroupState state) {
|
||||||
|
return state with {
|
||||||
|
ShowCreateGroupForm = false,
|
||||||
|
IsLoadingCreateGroup = false,
|
||||||
|
CreateGroupErrorMessage = null,
|
||||||
|
ContainerId = 0,
|
||||||
|
ContainerTitle = ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[ReducerMethod(typeof(FetchCreateGroupAction))]
|
||||||
|
public static CreateGroupState FetchCreateGroup(CreateGroupState state) {
|
||||||
|
return state with {
|
||||||
|
IsLoadingCreateGroup = true,
|
||||||
|
CreateGroupErrorMessage = null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[ReducerMethod(typeof(RecievedCreateGroupAction))]
|
||||||
|
public static CreateGroupState RecievedCreateGroup(CreateGroupState state) {
|
||||||
|
return state with {
|
||||||
|
IsLoadingCreateGroup = false,
|
||||||
|
CreateGroupErrorMessage = null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[ReducerMethod]
|
||||||
|
public static CreateGroupState ErrorFetchingCreateGroup(CreateGroupState state,
|
||||||
|
ErrorFetchingCreateGroupAction action) {
|
||||||
|
return state with {
|
||||||
|
CreateGroupErrorMessage = action.ErrorMessage,
|
||||||
|
IsLoadingCreateGroup = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
Start/Client/Store/Features/CreateGroup/CreateGroupState.cs
Normal file
25
Start/Client/Store/Features/CreateGroup/CreateGroupState.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using Start.Client.Store.State;
|
||||||
|
|
||||||
|
namespace Start.Client.Store.Features.CreateGroup {
|
||||||
|
public record CreateGroupState : RootState {
|
||||||
|
public bool ShowCreateGroupForm { get; init; }
|
||||||
|
public int ContainerId { get; init; }
|
||||||
|
public string ContainerTitle { get; init; }
|
||||||
|
public bool IsLoadingCreateGroup { get; init; }
|
||||||
|
public string? CreateGroupErrorMessage { get; init; }
|
||||||
|
|
||||||
|
public CreateGroupState() {
|
||||||
|
this.ContainerTitle = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public CreateGroupState(ContainerListState containerList,
|
||||||
|
CurrentContainerState currentContainer, bool showCreateGroupForm, string containerTitle,
|
||||||
|
bool isLoadingCreateGroup, string? createGroupErrorMessage)
|
||||||
|
: base(containerList, currentContainer) {
|
||||||
|
this.ShowCreateGroupForm = showCreateGroupForm;
|
||||||
|
this.ContainerTitle = containerTitle;
|
||||||
|
this.IsLoadingCreateGroup = isLoadingCreateGroup;
|
||||||
|
this.CreateGroupErrorMessage = createGroupErrorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,4 +28,20 @@ namespace Start.Client.Store.Features.CurrentContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FixCurrentContainerAction { }
|
public class FixCurrentContainerAction { }
|
||||||
|
|
||||||
|
public class AddBookmarkGroupAction {
|
||||||
|
public BookmarkGroupDto BookmarkGroup { get; init; }
|
||||||
|
|
||||||
|
public AddBookmarkGroupAction(BookmarkGroupDto bookmarkGroup) {
|
||||||
|
this.BookmarkGroup = bookmarkGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RemoveBookmarkGroupAction {
|
||||||
|
public int BookmarkGroupId { get; init; }
|
||||||
|
|
||||||
|
public RemoveBookmarkGroupAction(int bookmarkGroupId) {
|
||||||
|
this.BookmarkGroupId = bookmarkGroupId;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ using Start.Client.Store.State;
|
||||||
using Start.Client.Store.Features.CreateContainer;
|
using Start.Client.Store.Features.CreateContainer;
|
||||||
using Start.Shared;
|
using Start.Shared;
|
||||||
using Start.Shared.Api;
|
using Start.Shared.Api;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Start.Client.Store.Features.CurrentContainer {
|
namespace Start.Client.Store.Features.CurrentContainer {
|
||||||
public class CurrentContainerEffects {
|
public class CurrentContainerEffects {
|
||||||
|
@ -35,11 +36,17 @@ namespace Start.Client.Store.Features.CurrentContainer {
|
||||||
BookmarkContainerDto? container = response.Content;
|
BookmarkContainerDto? container = response.Content;
|
||||||
|
|
||||||
if (container == null) {
|
if (container == null) {
|
||||||
|
Console.WriteLine("Error fetching container " + action.BookmarkContainerId);
|
||||||
|
Console.WriteLine(response);
|
||||||
|
|
||||||
dispatch.Dispatch(new ErrorFetchingCurrentContainerAction(
|
dispatch.Dispatch(new ErrorFetchingCurrentContainerAction(
|
||||||
"Failed to get current bookmark container"));
|
"Failed to get current bookmark container"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Recieved container " + action.BookmarkContainerId);
|
||||||
|
Console.WriteLine(response);
|
||||||
|
|
||||||
dispatch.Dispatch(new ReceivedCurrentContainerAction(container));
|
dispatch.Dispatch(new ReceivedCurrentContainerAction(container));
|
||||||
|
|
||||||
await this.LocalStorage
|
await this.LocalStorage
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
using Fluxor;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Fluxor;
|
||||||
using Start.Client.Store.State;
|
using Start.Client.Store.State;
|
||||||
|
using Start.Shared;
|
||||||
|
|
||||||
namespace Start.Client.Store.Features.CurrentContainer {
|
namespace Start.Client.Store.Features.CurrentContainer {
|
||||||
public static class CurrentContainerReducers {
|
public static class CurrentContainerReducers {
|
||||||
|
@ -37,5 +40,43 @@ namespace Start.Client.Store.Features.CurrentContainer {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ReducerMethod]
|
||||||
|
public static RootState AddBookmarkGroup(RootState state, AddBookmarkGroupAction action) {
|
||||||
|
BookmarkContainerDto? container = state.CurrentContainerState.Container;
|
||||||
|
|
||||||
|
if (container == null)
|
||||||
|
return state;
|
||||||
|
|
||||||
|
if (action.BookmarkGroup.BookmarkContainerId != container.BookmarkContainerId)
|
||||||
|
return state;
|
||||||
|
|
||||||
|
return state with {
|
||||||
|
CurrentContainerState = state.CurrentContainerState with {
|
||||||
|
Container = new BookmarkContainerDto(container.BookmarkContainerId,
|
||||||
|
container.Title, container.BookmarkGroups?
|
||||||
|
.Concat(new List<BookmarkGroupDto> { action.BookmarkGroup })
|
||||||
|
.ToList())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[ReducerMethod]
|
||||||
|
public static RootState RemoveBookmarkGroup(RootState state,
|
||||||
|
RemoveBookmarkGroupAction action) {
|
||||||
|
BookmarkContainerDto? container = state.CurrentContainerState.Container;
|
||||||
|
|
||||||
|
if (container == null)
|
||||||
|
return state;
|
||||||
|
|
||||||
|
return state with {
|
||||||
|
CurrentContainerState = state.CurrentContainerState with {
|
||||||
|
Container = new BookmarkContainerDto(container.BookmarkContainerId,
|
||||||
|
container.Title, container.BookmarkGroups?
|
||||||
|
.Where(g => g.BookmarkGroupId != action.BookmarkGroupId)
|
||||||
|
.ToList())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
41
Start/Client/Store/State/StoreInitializedEffects.cs
Normal file
41
Start/Client/Store/State/StoreInitializedEffects.cs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Fluxor;
|
||||||
|
using Blazored.LocalStorage;
|
||||||
|
using Start.Client.Store.Features.ContainersList;
|
||||||
|
using Start.Client.Store.Features.CurrentContainer;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Start.Client.Store.State {
|
||||||
|
public class StoreInitializedEffects {
|
||||||
|
public IState<RootState> State { get; init; }
|
||||||
|
public ILocalStorageService LocalStorage { get; init; }
|
||||||
|
|
||||||
|
public StoreInitializedEffects(IState<RootState> state, ILocalStorageService localStorage) {
|
||||||
|
this.State = state;
|
||||||
|
this.LocalStorage = localStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
[EffectMethod(typeof(StoreInitializedAction))]
|
||||||
|
public async Task InitialLoad(IDispatcher dispatch) {
|
||||||
|
dispatch.Dispatch(new LoadContainerListAction());
|
||||||
|
dispatch.Dispatch(new LoadCurrentContainerAction(await GetSelectedContainerId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> GetSelectedContainerId() {
|
||||||
|
bool hasValue = await this.LocalStorage.ContainKeyAsync("SelectedContainer");
|
||||||
|
|
||||||
|
if (hasValue)
|
||||||
|
return await this.LocalStorage.GetItemAsync<int>("SelectedContainer");
|
||||||
|
|
||||||
|
// Default to the first container
|
||||||
|
int firstContainer = this.State.Value.ContainerListState.Containers
|
||||||
|
.First().BookmarkContainerId;
|
||||||
|
await this.SetSelectedContainer(firstContainer);
|
||||||
|
return firstContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task SetSelectedContainer(int selectedContainerId) {
|
||||||
|
await this.LocalStorage.SetItemAsync("SelectedContainer", selectedContainerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +1,4 @@
|
||||||
@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
|
/* Validation */
|
||||||
|
|
||||||
html, body {
|
|
||||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
a, .btn-link {
|
|
||||||
color: #0366d6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #1b6ec2;
|
|
||||||
border-color: #1861ac;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding-top: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.valid.modified:not([type=checkbox]) {
|
.valid.modified:not([type=checkbox]) {
|
||||||
outline: 1px solid #26b050;
|
outline: 1px solid #26b050;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +11,8 @@ a, .btn-link {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Blazor */
|
||||||
#blazor-error-ui {
|
#blazor-error-ui {
|
||||||
background: lightyellow;
|
background: lightyellow;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
@ -49,10 +32,13 @@ a, .btn-link {
|
||||||
top: 0.5rem;
|
top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Spectre's default is no padding */
|
||||||
.container {
|
.container {
|
||||||
padding: 0.4rem;
|
padding: 0.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The splash screen */
|
||||||
.appLoadingContainer {
|
.appLoadingContainer {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -60,3 +46,73 @@ a, .btn-link {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#bookmarkGroups {
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 1280px;
|
||||||
|
display: grid;
|
||||||
|
grid-column-gap: 1.25rem;
|
||||||
|
grid-row-gap: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 960px) {
|
||||||
|
#bookmarkGroups {
|
||||||
|
grid-template-columns: repeat(1, 1fr);
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 960px) {
|
||||||
|
#bookmarkGroups {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1280px) {
|
||||||
|
#bookmarkGroups {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmarkGroup {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmarkGroup .card-header {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmarkGroup .card-title {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmarkGroup .card-body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmarkGroup .card-body:last-child {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.bookmarks {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.noBookmarksItem {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.addBookmarkGroupButton button {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.addBookmarkButton {
|
||||||
|
float: right;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
67
Start/Server/Controllers/BookmarkGroupsController.cs
Normal file
67
Start/Server/Controllers/BookmarkGroupsController.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Start.Server.Data.Services.Interfaces;
|
||||||
|
using Start.Server.Models;
|
||||||
|
using Start.Server.Extensions;
|
||||||
|
using Start.Shared;
|
||||||
|
|
||||||
|
namespace Start.Server.Controllers {
|
||||||
|
[Authorize]
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class BookmarkGroupsController : ControllerBase {
|
||||||
|
private readonly IBookmarkGroupService bookmarkGroupService;
|
||||||
|
|
||||||
|
public BookmarkGroupsController(IBookmarkGroupService bookmarkGroupService) {
|
||||||
|
this.bookmarkGroupService = bookmarkGroupService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("{bookmarkGroupId}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(BookmarkGroupDto))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetBookmarkGroup(int bookmarkGroupId) {
|
||||||
|
BookmarkGroup? group = await this.bookmarkGroupService
|
||||||
|
.GetBookmarkGroup(this.GetAuthorizedUserId(), bookmarkGroupId);
|
||||||
|
|
||||||
|
if (group == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return Ok(group.MapToDto());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("Create")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(BookmarkGroupDto))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
public async Task<IActionResult> CreateBookmarkGroup(string title, string color,
|
||||||
|
int bookmarkContainerId) {
|
||||||
|
BookmarkGroup? newGroup = await this.bookmarkGroupService
|
||||||
|
.CreateBookmarkGroup(this.GetAuthorizedUserId(), title, color, bookmarkContainerId);
|
||||||
|
|
||||||
|
if (newGroup == null)
|
||||||
|
return BadRequest();
|
||||||
|
|
||||||
|
return Created(
|
||||||
|
Url.Action(nameof(GetBookmarkGroup),
|
||||||
|
new { bookmarkGroupId = newGroup.BookmarkGroupId }),
|
||||||
|
newGroup.MapToDto());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete]
|
||||||
|
[Route("Delete/{bookmarkGroupId}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> DeleteBookmark(int bookmarkGroupId) {
|
||||||
|
bool res = await this.bookmarkGroupService
|
||||||
|
.DeleteBookmarkGroup(this.GetAuthorizedUserId(), bookmarkGroupId);
|
||||||
|
|
||||||
|
if (!res)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,12 +11,9 @@ namespace Start.Server.Controllers {
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
public class BookmarksController : ControllerBase {
|
public class BookmarksController : ControllerBase {
|
||||||
private readonly IBookmarkGroupService bookmarkGroupService;
|
|
||||||
private readonly IBookmarkService bookmarkService;
|
private readonly IBookmarkService bookmarkService;
|
||||||
|
|
||||||
public BookmarksController(IBookmarkGroupService bookmarkGroupService,
|
public BookmarksController(IBookmarkService bookmarkService) {
|
||||||
IBookmarkService bookmarkService) {
|
|
||||||
this.bookmarkGroupService = bookmarkGroupService;
|
|
||||||
this.bookmarkService = bookmarkService;
|
this.bookmarkService = bookmarkService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System.Linq;
|
||||||
using System.Linq;
|
|
||||||
using Start.Server.Models;
|
using Start.Server.Models;
|
||||||
using Start.Shared;
|
using Start.Shared;
|
||||||
|
|
||||||
|
@ -12,7 +11,8 @@ namespace Start.Server.Extensions {
|
||||||
|
|
||||||
public static BookmarkGroupDto MapToDto(this BookmarkGroup bookmarkGroup) {
|
public static BookmarkGroupDto MapToDto(this BookmarkGroup bookmarkGroup) {
|
||||||
return new BookmarkGroupDto(bookmarkGroup.BookmarkGroupId, bookmarkGroup.Title,
|
return new BookmarkGroupDto(bookmarkGroup.BookmarkGroupId, bookmarkGroup.Title,
|
||||||
bookmarkGroup.Color, bookmarkGroup.Bookmarks?.Select(b => b.MapToDto()).ToList());
|
bookmarkGroup.Color, bookmarkGroup.BookmarkContainerId,
|
||||||
|
bookmarkGroup.Bookmarks?.Select(b => b.MapToDto()).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BookmarkContainerDto MapToDto(this BookmarkContainer bookmarkContainer) {
|
public static BookmarkContainerDto MapToDto(this BookmarkContainer bookmarkContainer) {
|
||||||
|
|
17
Start/Shared/Api/IBookmarkGroupsApi.cs
Normal file
17
Start/Shared/Api/IBookmarkGroupsApi.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Refit;
|
||||||
|
|
||||||
|
namespace Start.Shared.Api {
|
||||||
|
public interface IBookmarkGroupsApi {
|
||||||
|
[Get("/{bookmarkGroupId}")]
|
||||||
|
Task<ApiResponse<BookmarkGroupDto?>> GetBookmarkGroup(int bookmarkGroupId);
|
||||||
|
|
||||||
|
[Post("/Create")]
|
||||||
|
Task<ApiResponse<BookmarkGroupDto?>> CreateBookmarkGroup(string title, string color,
|
||||||
|
int bookmarkContainerId);
|
||||||
|
|
||||||
|
[Delete("/Delete/{bookmarkGroupId}")]
|
||||||
|
Task<HttpResponseMessage> DeleteBookmarkGroup(int bookmarkGroupId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,17 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Refit;
|
using Refit;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
namespace Start.Shared.Api {
|
namespace Start.Shared.Api {
|
||||||
public interface IBookmarksApi {
|
public interface IBookmarksApi {
|
||||||
[Get("{bookmarkId}")]
|
[Get("{bookmarkId}")]
|
||||||
Task<BookmarkDto?> GetBookmark(int bookmarkId);
|
Task<ApiResponse<BookmarkDto?>> GetBookmark(int bookmarkId);
|
||||||
|
|
||||||
[Post("/Create")]
|
[Post("/Create")]
|
||||||
Task CreateBookmark(string title, string url, string? notes, int bookmarkGroupId);
|
Task<ApiResponse<BookmarkDto?>> CreateBookmark(string title, string url, string? notes,
|
||||||
|
int bookmarkGroupId);
|
||||||
|
|
||||||
[Delete("/Delete/{bookmarkId}")]
|
[Delete("/Delete/{bookmarkId}")]
|
||||||
Task DeleteBookmark(int bookmarkId);
|
Task<HttpResponseMessage> DeleteBookmark(int bookmarkId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,36 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Start.Shared {
|
namespace Start.Shared {
|
||||||
public class BookmarkGroupDto {
|
public class BookmarkGroupDto {
|
||||||
public int BookmarkGroupId { get; set; }
|
public int BookmarkGroupId { get; set; }
|
||||||
|
[Required(AllowEmptyStrings = false, ErrorMessage = "Title is required")]
|
||||||
[StringLength(300)]
|
[StringLength(300)]
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
[StringLength(6)]
|
[Required(AllowEmptyStrings = false, ErrorMessage = "Color is required")]
|
||||||
|
[StringLength(7)]
|
||||||
public string Color { get; set; }
|
public string Color { get; set; }
|
||||||
|
public int BookmarkContainerId { get; set; }
|
||||||
|
|
||||||
public IList<BookmarkDto>? Bookmarks { get; set; }
|
public IList<BookmarkDto>? Bookmarks { get; set; }
|
||||||
|
|
||||||
public BookmarkGroupDto(string title, string color) {
|
public BookmarkGroupDto(string title, string color, int bookmarkContainerId) {
|
||||||
this.Title = title;
|
this.Title = title;
|
||||||
this.Color = color;
|
this.Color = color;
|
||||||
}
|
this.BookmarkContainerId = bookmarkContainerId;
|
||||||
|
|
||||||
public BookmarkGroupDto(int bookmarkGroupId, string title, string color)
|
|
||||||
: this(title, color) {
|
|
||||||
this.BookmarkGroupId = bookmarkGroupId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BookmarkGroupDto(int bookmarkGroupId, string title, string color,
|
public BookmarkGroupDto(int bookmarkGroupId, string title, string color,
|
||||||
IList<BookmarkDto>? bookmarks) : this(bookmarkGroupId, title, color) {
|
int bookmarkContainerId)
|
||||||
|
: this(title, color, bookmarkContainerId) {
|
||||||
|
this.BookmarkGroupId = bookmarkGroupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public BookmarkGroupDto(int bookmarkGroupId, string title, string color,
|
||||||
|
int bookmarkContainerId, IList<BookmarkDto>? bookmarks)
|
||||||
|
: this(bookmarkGroupId, title, color, bookmarkContainerId) {
|
||||||
this.Bookmarks = bookmarks;
|
this.Bookmarks = bookmarks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue