Add Spectre, add bookmark container creation, save selected container to localStorage
This commit is contained in:
parent
c5403ca206
commit
3eb2b2ae98
|
@ -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]
|
||||
|
|
31
Start/Client/Components/Alert.razor
Normal file
31
Start/Client/Components/Alert.razor
Normal 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
|
||||
}
|
||||
}
|
26
Start/Client/Components/BookmarkGroup.razor
Normal file
26
Start/Client/Components/BookmarkGroup.razor
Normal 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
|
||||
}
|
79
Start/Client/Components/CreateContainer.razor
Normal file
79
Start/Client/Components/CreateContainer.razor
Normal 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();
|
||||
}
|
||||
}
|
31
Start/Client/Components/Dialog.razor
Normal file
31
Start/Client/Components/Dialog.razor
Normal 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();
|
||||
}
|
||||
}
|
175
Start/Client/Pages/StartPage.razor
Normal file
175
Start/Client/Pages/StartPage.razor
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
1231
Start/Client/wwwroot/css/spectre/spectre-exp.css
Normal file
1231
Start/Client/wwwroot/css/spectre/spectre-exp.css
Normal file
File diff suppressed because it is too large
Load diff
1
Start/Client/wwwroot/css/spectre/spectre-exp.min.css
vendored
Normal file
1
Start/Client/wwwroot/css/spectre/spectre-exp.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
597
Start/Client/wwwroot/css/spectre/spectre-icons.css
Normal file
597
Start/Client/wwwroot/css/spectre/spectre-icons.css
Normal 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;
|
||||
}
|
1
Start/Client/wwwroot/css/spectre/spectre-icons.min.css
vendored
Normal file
1
Start/Client/wwwroot/css/spectre/spectre-icons.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
3760
Start/Client/wwwroot/css/spectre/spectre.css
Normal file
3760
Start/Client/wwwroot/css/spectre/spectre.css
Normal file
File diff suppressed because it is too large
Load diff
1
Start/Client/wwwroot/css/spectre/spectre.min.css
vendored
Normal file
1
Start/Client/wwwroot/css/spectre/spectre.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -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" />
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue