Add Spectre, add bookmark container creation, save selected container to localStorage

This commit is contained in:
Neil Brommer 2021-11-20 22:47:10 -08:00
parent c5403ca206
commit 3eb2b2ae98
21 changed files with 6014 additions and 57 deletions

View file

@ -11,7 +11,8 @@ trim_trailing_whitespace = true
[*.razor]
indent_style = space
indent_width = 4
indent_size = 4
tab_width = 4
trim_trailing_whitespace = true
[*.cs]

View file

@ -0,0 +1,31 @@
<div class="toast @AlertTypeToClass(this.Type)">
@ChildContent
</div>
@code {
[Parameter]
public AlertType Type { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; } = null!;
private string AlertTypeToClass(AlertType type)
{
switch (type)
{
case AlertType.Error: return "toast-error";
case AlertType.Warning: return "toast-warning";
case AlertType.Success: return "toast-success";
case AlertType.Info: return "toast-primary";
default: return "";
}
}
public enum AlertType
{
Error,
Warning,
Success,
Info,
None
}
}

View file

@ -0,0 +1,26 @@
@using Start.Shared
<h2 class="bookmarkGroupTitle" style="background-color: #@this.Group.Color">
@this.Group.Title
</h2>
<ul class="bookmarkGroupList">
@if (this.Group.Bookmarks == null || !this.Group.Bookmarks.Any())
{
<li class="bookmarkListItem noBookmarksItem"><i>No Bookmarks</i></li>
}
else
{
foreach (BookmarkDto bookmark in this.Group.Bookmarks!)
{
<li class="bookmarkListItem">
<a href="@bookmark.Url" class="bookmarkLink">@bookmark.Title</a>
</li>
}
}
</ul>
@code
{
[Parameter]
public BookmarkGroupDto Group { get; set; } = null!; // [Required] is a .net 6 feature
}

View file

@ -0,0 +1,79 @@
@using Start.Shared
@using System.IO
@inject HttpClient Http
<Dialog Title="Create Container" Active="this.IsOpen" OnClose="this.OnDialogClose">
<EditForm Model="this.model" OnValidSubmit="this.OnSubmit">
@if (displayError)
{
<Alert Type="Alert.AlertType.Error">
There was an error creating the container
</Alert>
}
<div class="form-group">
<div class="container">
<div class="columns">
<div class="column col-12">
<div>
<label for="createBookmarkContainerTitle" class="form-label">Title</label>
<InputText id="createBookmarkContainerTitle" name="createBookmarkContainerTitle"
class="form-input" @bind-Value="this.model.Title" />
</div>
</div>
</div>
</div>
<div class="container">
<div class="columns">
<div class="column col-12 text-right">
<div>
<button type="submit" class="btn btn-primary">
<i class="icon icon-plus"></i> Create
</button>
</div>
</div>
</div>
</div>
</div>
</EditForm>
</Dialog>
@code {
[Parameter]
public EventCallback<BookmarkContainerDto> OnCreated { get; set; }
[Parameter]
public bool IsOpen { get; set; }
[Parameter]
public EventCallback OnClose { get; set; }
private BookmarkContainerDto model = new("");
private bool displayError = false;
protected async void OnSubmit()
{
HttpResponseMessage response = await Http
.PostAsJsonAsync("Bookmarks/CreateBookmarkContainer", model.Title);
Stream stream = response.RequestMessage!.Content!.ReadAsStream();
StreamReader reader = new StreamReader(stream);
Console.WriteLine(reader.ReadToEnd());
BookmarkContainerDto? container = await response
!.Content
!.ReadFromJsonAsync<BookmarkContainerDto>();
if (container == null)
{
this.displayError = true;
}
else
{
await this.OnCreated.InvokeAsync(container);
}
}
protected async void OnDialogClose()
{
this.IsOpen = false;
await this.OnClose.InvokeAsync();
}
}

View file

@ -0,0 +1,31 @@
<div class="modal @(this.Active ? "active" : "")">
<a class="modal-overlay" @onclick="this.OnDialogClose" aria-label="Close"></a>
<div class="modal-container">
<div class="modal-header">
<a class="btn btn-clear float-right" @onclick="this.OnClose" aria-label="Close"></a>
<div class="modal-title h5">@this.Title</div>
</div>
<div class="modal-body">
<div class="content">
@this.ChildContent
</div>
</div>
</div>
</div>
@code {
[Parameter]
public string Title { get; set; } = null!;
[Parameter]
public RenderFragment ChildContent { get; set; } = null!;
[Parameter]
public bool Active { get; set; }
[Parameter]
public EventCallback OnClose { get; set; }
public void OnDialogClose()
{
this.Active = false;
this.OnClose.InvokeAsync();
}
}

View file

@ -0,0 +1,175 @@
@page "/Start"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Start.Client.Components
@using Start.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject Blazored.LocalStorage.ILocalStorageService localStorage
@if (bookmarkContainers == null)
{
<div class="empty">
<div class="empty-icon">
<div class="loading loading-lg"></div>
</div>
<p class="empty-title h5">Loading Containers</p>
</div>
}
else
{
<ul class="containerList tab">
@foreach (BookmarkContainerDto container in this.bookmarkContainers)
{
string itemClasses = "tab-item";
if (container.BookmarkContainerId == this.selectedBookmarkContainer?.BookmarkContainerId)
itemClasses += " active";
<li class="@itemClasses">
<a @onclick="() => OnContainerSelected(container.BookmarkContainerId)">
@container.Title
</a>
<button class="btn btn-clear"></button>
</li>
}
<li class="tab-item tab-action">
<button @onclick="OnCreateContainerClicked" class="btn btn-link"
title="Create New Container" aria-label="Create New Container">
+
</button>
</li>
</ul>
<div class="activeBookmarkContainer">
@if (this.selectedBookmarkContainer == null)
{
<div class="empty">
<div class="empty-icon">
<div class="loading loading-icon"></div>
</div>
<p class="empty-title h5">Loading Bookmarks</p>
</div>
<p class="text-center">Loading Bookmarks</p>
}
else if (!this.selectedBookmarkContainer.BookmarkGroups?.Any() ?? true)
{
<div class="empty">
<div class="empty-icon">
<i class="icon icon-3x icon-bookmark"></i>
</div>
<p class="empty-title h5">No Bookmark Groups</p>
</div>
}
else
{
@foreach (BookmarkGroupDto group in this.selectedBookmarkContainer.BookmarkGroups!)
{
<BookmarkGroup Group="group" />
}
}
</div>
<CreateContainer IsOpen="showCreateContainerForm" OnClose="this.OnCloseCreateContainer"
OnCreated="this.OnContainerCreated" />
}
@code
{
private IList<BookmarkContainerDto>? bookmarkContainers;
private BookmarkContainerDto? selectedBookmarkContainer;
private bool showCreateContainerForm = false;
private bool showCreateGroupForm = false;
private bool showCreateBookmarkForm = false;
protected override async Task OnInitializedAsync()
{
await LoadContainers();
}
protected async Task LoadContainers()
{
try {
this.bookmarkContainers = await Http
.GetFromJsonAsync<IList<BookmarkContainerDto>>(
"Bookmarks/GetAllBookmarkContainers");
if (this.bookmarkContainers == null || !this.bookmarkContainers.Any()) {
HttpResponseMessage response = await Http
.PostAsJsonAsync("Bookmarks/CreateBookmarkContainer", "Default");
BookmarkContainerDto? container = await response
.RequestMessage
!.Content
!.ReadFromJsonAsync<BookmarkContainerDto?>();
}
await this.OnContainerSelected(await this.GetSelectedContainerId());
}
catch (AccessTokenNotAvailableException e) {
e.Redirect();
}
}
protected async Task OnContainerSelected(int bookmarkContainerId)
{
try
{
BookmarkContainerDto? bookmarkContainer = await Http
.GetFromJsonAsync<BookmarkContainerDto?>(
$"Bookmarks/GetBookmarkContainer/{bookmarkContainerId}");
await this.SetSelectedContainer(bookmarkContainerId);
this.selectedBookmarkContainer = bookmarkContainer;
}
catch (AccessTokenNotAvailableException e)
{
e.Redirect();
}
}
protected async Task OnDeleteContainerClicked()
{
}
protected void OnCreateContainerClicked()
{
this.showCreateContainerForm = true;
}
protected void OnCloseCreateContainer()
{
this.showCreateContainerForm = false;
}
protected async Task OnContainerCreated(BookmarkContainerDto newContainer)
{
if (this.bookmarkContainers == null)
return;
this.bookmarkContainers.Add(newContainer);
this.showCreateContainerForm = false;
await SetSelectedContainer(newContainer.BookmarkContainerId);
}
// 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.bookmarkContainers!.First().BookmarkContainerId;
await this.SetSelectedContainer(firstContainer);
return firstContainer;
}
protected async Task SetSelectedContainer(int selectedContainerId)
{
await localStorage.SetItemAsync<int>("SelectedContainer", selectedContainerId);
}
}

View file

@ -1,32 +1,30 @@
using System;
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Blazored.LocalStorage;
namespace Start.Client
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
namespace Start.Client {
public class Program {
public static async Task Main(string[] args) {
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddHttpClient("Start.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddHttpClient("Start.ServerAPI",
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Start.ServerAPI"));
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("Start.ServerAPI"));
builder.Services.AddApiAuthorization();
builder.Services.AddApiAuthorization();
await builder.Build().RunAsync();
}
}
builder.Services.AddBlazoredLocalStorage();
await builder.Build().RunAsync();
}
}
}

View file

@ -22,6 +22,11 @@
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="Start">
<span class="oi oi-star"></span> Start
</NavLink>
</li>
</ul>
</div>

View file

@ -16,6 +16,7 @@
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="5.0.11" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
<PackageReference Include="Blazored.LocalStorage" Version="4.1.5" />
</ItemGroup>
<ItemGroup>
@ -26,4 +27,10 @@
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>
<ItemGroup>
<None Remove="Blazored.LocalStorage" />
</ItemGroup>
<ItemGroup>
<Content Remove="wwwroot\css\Spectre\" />
</ItemGroup>
</Project>

View file

@ -1,50 +1,54 @@
@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
a, .btn-link {
color: #0366d6;
color: #0366d6;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.content {
padding-top: 1.1rem;
padding-top: 1.1rem;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid red;
outline: 1px solid red;
}
.validation-message {
color: red;
color: red;
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.container {
padding: 0.4rem;
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,597 @@
/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */
.icon {
box-sizing: border-box;
display: inline-block;
font-size: inherit;
font-style: normal;
height: 1em;
position: relative;
text-indent: -9999px;
vertical-align: middle;
width: 1em;
}
.icon::before,
.icon::after {
content: "";
display: block;
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
}
.icon.icon-2x {
font-size: 1.6rem;
}
.icon.icon-3x {
font-size: 2.4rem;
}
.icon.icon-4x {
font-size: 3.2rem;
}
.accordion .icon,
.btn .icon,
.toast .icon,
.menu .icon {
vertical-align: -10%;
}
.btn-lg .icon {
vertical-align: -15%;
}
.icon-arrow-down::before,
.icon-arrow-left::before,
.icon-arrow-right::before,
.icon-arrow-up::before,
.icon-downward::before,
.icon-back::before,
.icon-forward::before,
.icon-upward::before {
border: .1rem solid currentColor;
border-bottom: 0;
border-right: 0;
height: .65em;
width: .65em;
}
.icon-arrow-down::before {
transform: translate(-50%, -75%) rotate(225deg);
}
.icon-arrow-left::before {
transform: translate(-25%, -50%) rotate(-45deg);
}
.icon-arrow-right::before {
transform: translate(-75%, -50%) rotate(135deg);
}
.icon-arrow-up::before {
transform: translate(-50%, -25%) rotate(45deg);
}
.icon-back::after,
.icon-forward::after {
background: currentColor;
height: .1rem;
width: .8em;
}
.icon-downward::after,
.icon-upward::after {
background: currentColor;
height: .8em;
width: .1rem;
}
.icon-back::after {
left: 55%;
}
.icon-back::before {
transform: translate(-50%, -50%) rotate(-45deg);
}
.icon-downward::after {
top: 45%;
}
.icon-downward::before {
transform: translate(-50%, -50%) rotate(-135deg);
}
.icon-forward::after {
left: 45%;
}
.icon-forward::before {
transform: translate(-50%, -50%) rotate(135deg);
}
.icon-upward::after {
top: 55%;
}
.icon-upward::before {
transform: translate(-50%, -50%) rotate(45deg);
}
.icon-caret::before {
border-left: .3em solid transparent;
border-right: .3em solid transparent;
border-top: .3em solid currentColor;
height: 0;
transform: translate(-50%, -25%);
width: 0;
}
.icon-menu::before {
background: currentColor;
box-shadow: 0 -.35em, 0 .35em;
height: .1rem;
width: 100%;
}
.icon-apps::before {
background: currentColor;
box-shadow: -.35em -.35em, -.35em 0, -.35em .35em, 0 -.35em, 0 .35em, .35em -.35em, .35em 0, .35em .35em;
height: 3px;
width: 3px;
}
.icon-resize-horiz::before,
.icon-resize-horiz::after,
.icon-resize-vert::before,
.icon-resize-vert::after {
border: .1rem solid currentColor;
border-bottom: 0;
border-right: 0;
height: .45em;
width: .45em;
}
.icon-resize-horiz::before,
.icon-resize-vert::before {
transform: translate(-50%, -90%) rotate(45deg);
}
.icon-resize-horiz::after,
.icon-resize-vert::after {
transform: translate(-50%, -10%) rotate(225deg);
}
.icon-resize-horiz::before {
transform: translate(-90%, -50%) rotate(-45deg);
}
.icon-resize-horiz::after {
transform: translate(-10%, -50%) rotate(135deg);
}
.icon-more-horiz::before,
.icon-more-vert::before {
background: currentColor;
border-radius: 50%;
box-shadow: -.4em 0, .4em 0;
height: 3px;
width: 3px;
}
.icon-more-vert::before {
box-shadow: 0 -.4em, 0 .4em;
}
.icon-plus::before,
.icon-minus::before,
.icon-cross::before {
background: currentColor;
height: .1rem;
width: 100%;
}
.icon-plus::after,
.icon-cross::after {
background: currentColor;
height: 100%;
width: .1rem;
}
.icon-cross::before {
width: 100%;
}
.icon-cross::after {
height: 100%;
}
.icon-cross::before,
.icon-cross::after {
transform: translate(-50%, -50%) rotate(45deg);
}
.icon-check::before {
border: .1rem solid currentColor;
border-right: 0;
border-top: 0;
height: .5em;
transform: translate(-50%, -75%) rotate(-45deg);
width: .9em;
}
.icon-stop {
border: .1rem solid currentColor;
border-radius: 50%;
}
.icon-stop::before {
background: currentColor;
height: .1rem;
transform: translate(-50%, -50%) rotate(45deg);
width: 1em;
}
.icon-shutdown {
border: .1rem solid currentColor;
border-radius: 50%;
border-top-color: transparent;
}
.icon-shutdown::before {
background: currentColor;
content: "";
height: .5em;
top: .1em;
width: .1rem;
}
.icon-refresh::before {
border: .1rem solid currentColor;
border-radius: 50%;
border-right-color: transparent;
height: 1em;
width: 1em;
}
.icon-refresh::after {
border: .2em solid currentColor;
border-left-color: transparent;
border-top-color: transparent;
height: 0;
left: 80%;
top: 20%;
width: 0;
}
.icon-search::before {
border: .1rem solid currentColor;
border-radius: 50%;
height: .75em;
left: 5%;
top: 5%;
transform: translate(0, 0) rotate(45deg);
width: .75em;
}
.icon-search::after {
background: currentColor;
height: .1rem;
left: 80%;
top: 80%;
transform: translate(-50%, -50%) rotate(45deg);
width: .4em;
}
.icon-edit::before {
border: .1rem solid currentColor;
height: .4em;
transform: translate(-40%, -60%) rotate(-45deg);
width: .85em;
}
.icon-edit::after {
border: .15em solid currentColor;
border-right-color: transparent;
border-top-color: transparent;
height: 0;
left: 5%;
top: 95%;
transform: translate(0, -100%);
width: 0;
}
.icon-delete::before {
border: .1rem solid currentColor;
border-bottom-left-radius: .1rem;
border-bottom-right-radius: .1rem;
border-top: 0;
height: .75em;
top: 60%;
width: .75em;
}
.icon-delete::after {
background: currentColor;
box-shadow: -.25em .2em, .25em .2em;
height: .1rem;
top: .05rem;
width: .5em;
}
.icon-share {
border: .1rem solid currentColor;
border-radius: .1rem;
border-right: 0;
border-top: 0;
}
.icon-share::before {
border: .1rem solid currentColor;
border-left: 0;
border-top: 0;
height: .4em;
left: 100%;
top: .25em;
transform: translate(-125%, -50%) rotate(-45deg);
width: .4em;
}
.icon-share::after {
border: .1rem solid currentColor;
border-bottom: 0;
border-radius: 75% 0;
border-right: 0;
height: .5em;
width: .6em;
}
.icon-flag::before {
background: currentColor;
height: 1em;
left: 15%;
width: .1rem;
}
.icon-flag::after {
border: .1rem solid currentColor;
border-bottom-right-radius: .1rem;
border-left: 0;
border-top-right-radius: .1rem;
height: .65em;
left: 60%;
top: 35%;
width: .8em;
}
.icon-bookmark::before {
border: .1rem solid currentColor;
border-bottom: 0;
border-top-left-radius: .1rem;
border-top-right-radius: .1rem;
height: .9em;
width: .8em;
}
.icon-bookmark::after {
border: .1rem solid currentColor;
border-bottom: 0;
border-left: 0;
border-radius: .1rem;
height: .5em;
transform: translate(-50%, 35%) rotate(-45deg) skew(15deg, 15deg);
width: .5em;
}
.icon-download,
.icon-upload {
border-bottom: .1rem solid currentColor;
}
.icon-download::before,
.icon-upload::before {
border: .1rem solid currentColor;
border-bottom: 0;
border-right: 0;
height: .5em;
transform: translate(-50%, -60%) rotate(-135deg);
width: .5em;
}
.icon-download::after,
.icon-upload::after {
background: currentColor;
height: .6em;
top: 40%;
width: .1rem;
}
.icon-upload::before {
transform: translate(-50%, -60%) rotate(45deg);
}
.icon-upload::after {
top: 50%;
}
.icon-copy::before {
border: .1rem solid currentColor;
border-bottom: 0;
border-radius: .1rem;
border-right: 0;
height: .8em;
left: 40%;
top: 35%;
width: .8em;
}
.icon-copy::after {
border: .1rem solid currentColor;
border-radius: .1rem;
height: .8em;
left: 60%;
top: 60%;
width: .8em;
}
.icon-time {
border: .1rem solid currentColor;
border-radius: 50%;
}
.icon-time::before {
background: currentColor;
height: .4em;
transform: translate(-50%, -75%);
width: .1rem;
}
.icon-time::after {
background: currentColor;
height: .3em;
transform: translate(-50%, -75%) rotate(90deg);
transform-origin: 50% 90%;
width: .1rem;
}
.icon-mail::before {
border: .1rem solid currentColor;
border-radius: .1rem;
height: .8em;
width: 1em;
}
.icon-mail::after {
border: .1rem solid currentColor;
border-right: 0;
border-top: 0;
height: .5em;
transform: translate(-50%, -90%) rotate(-45deg) skew(10deg, 10deg);
width: .5em;
}
.icon-people::before {
border: .1rem solid currentColor;
border-radius: 50%;
height: .45em;
top: 25%;
width: .45em;
}
.icon-people::after {
border: .1rem solid currentColor;
border-radius: 50% 50% 0 0;
height: .4em;
top: 75%;
width: .9em;
}
.icon-message {
border: .1rem solid currentColor;
border-bottom: 0;
border-radius: .1rem;
border-right: 0;
}
.icon-message::before {
border: .1rem solid currentColor;
border-bottom-right-radius: .1rem;
border-left: 0;
border-top: 0;
height: .8em;
left: 65%;
top: 40%;
width: .7em;
}
.icon-message::after {
background: currentColor;
border-radius: .1rem;
height: .3em;
left: 10%;
top: 100%;
transform: translate(0, -90%) rotate(45deg);
width: .1rem;
}
.icon-photo {
border: .1rem solid currentColor;
border-radius: .1rem;
}
.icon-photo::before {
border: .1rem solid currentColor;
border-radius: 50%;
height: .25em;
left: 35%;
top: 35%;
width: .25em;
}
.icon-photo::after {
border: .1rem solid currentColor;
border-bottom: 0;
border-left: 0;
height: .5em;
left: 60%;
transform: translate(-50%, 25%) rotate(-45deg);
width: .5em;
}
.icon-link::before,
.icon-link::after {
border: .1rem solid currentColor;
border-radius: 5em 0 0 5em;
border-right: 0;
height: .5em;
width: .75em;
}
.icon-link::before {
transform: translate(-70%, -45%) rotate(-45deg);
}
.icon-link::after {
transform: translate(-30%, -55%) rotate(135deg);
}
.icon-location::before {
border: .1rem solid currentColor;
border-radius: 50% 50% 50% 0;
height: .8em;
transform: translate(-50%, -60%) rotate(-45deg);
width: .8em;
}
.icon-location::after {
border: .1rem solid currentColor;
border-radius: 50%;
height: .2em;
transform: translate(-50%, -80%);
width: .2em;
}
.icon-emoji {
border: .1rem solid currentColor;
border-radius: 50%;
}
.icon-emoji::before {
border-radius: 50%;
box-shadow: -.17em -.1em, .17em -.1em;
height: .15em;
width: .15em;
}
.icon-emoji::after {
border: .1rem solid currentColor;
border-bottom-color: transparent;
border-radius: 50%;
border-right-color: transparent;
height: .5em;
transform: translate(-50%, -40%) rotate(-135deg);
width: .5em;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -6,7 +6,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Start</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<!--<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />-->
<link href="css/spectre/spectre.min.css" rel="stylesheet" />
<link href="css/spectre/spectre-icons.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="Start.Client.styles.css" rel="stylesheet" />
<link href="manifest.json" rel="manifest" />

View file

@ -9,41 +9,39 @@ using Start.Shared;
namespace Start.Server.Controllers {
[Authorize]
[ApiController]
[Route("[controller]")]
[Route("[controller]/[action]")]
public class BookmarksController : ControllerBase {
private readonly IBookmarkContainerService bookmarkContainerService;
private readonly IBookmarkGroupService bookmarkGroupService;
private readonly IBookmarkService bookmarkService;
private readonly string userId;
public BookmarksController(IBookmarkContainerService bookmarkContainerService,
IBookmarkGroupService bookmarkGroupService, IBookmarkService bookmarkService) {
this.bookmarkContainerService = bookmarkContainerService;
this.bookmarkGroupService = bookmarkGroupService;
this.bookmarkService = bookmarkService;
this.userId = this.GetAuthorizedUserId();
}
[HttpGet]
public IList<BookmarkContainerDto> GetAllBookmarkContainers() {
return this.bookmarkContainerService.GetUserBookmarkContainers(this.userId)
return this.bookmarkContainerService.GetUserBookmarkContainers(this.GetAuthorizedUserId())
.Select(bc => bc.MapToDto())
.ToList();
}
[HttpGet]
[Route("{bookmarkContainerId}")]
public BookmarkContainerDto? GetBookmarkContainer(int bookmarkContainerId) {
return this.bookmarkContainerService
.GetBookmarkContainer(this.userId, bookmarkContainerId, true, true)
.GetBookmarkContainer(this.GetAuthorizedUserId(), bookmarkContainerId, true, true)
?.MapToDto();
}
[HttpGet]
[Route("{bookmarkId}")]
public BookmarkDto? GetBookmark(int bookmarkId) {
return this.bookmarkService
.GetBookmark(this.userId, bookmarkId)
.GetBookmark(this.GetAuthorizedUserId(), bookmarkId)
?.MapToDto();
}
@ -51,15 +49,21 @@ namespace Start.Server.Controllers {
public BookmarkDto? CreateBookmark(string title, string url, string? notes,
int bookmarkGroupId) {
return this.bookmarkService
.CreateBookmark(this.userId, title, url, notes, bookmarkGroupId)
.CreateBookmark(this.GetAuthorizedUserId(), title, url, notes, bookmarkGroupId)
?.MapToDto();
}
[HttpPost]
public BookmarkContainerDto? CreateBookmarkContainer(string title) {
public BookmarkContainerDto? CreateBookmarkContainer([FromBody] string title) {
return this.bookmarkContainerService
.CreateBookmarkContainer(this.userId, title)
.CreateBookmarkContainer(this.GetAuthorizedUserId(), title)
?.MapToDto();
}
[HttpDelete]
public bool DeleteBookmarkContainer(int bookmarkContainerId) {
return this.bookmarkContainerService
.DeleteBookmarkContainer(this.GetAuthorizedUserId(), bookmarkContainerId);
}
}
}

View file

@ -50,6 +50,7 @@ namespace Start.Server.Data.Services {
BookmarkContainer newContainer = new(userId, title);
this.db.BookmarkContainers.Add(newContainer);
this.db.SaveChanges();
return newContainer;
}

Binary file not shown.

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace Start.Shared {
public class BookmarkContainerDto {
@ -18,6 +19,7 @@ namespace Start.Shared {
this.BookmarkContainerId = bookmarkContainerId;
}
[JsonConstructor]
public BookmarkContainerDto(int bookmarkContainerId, string title,
IList<BookmarkGroupDto>? bookmarkGroups) : this(bookmarkContainerId, title) {
this.BookmarkGroups = bookmarkGroups;