Side Nav Redesign (#2310)

This commit is contained in:
Joe Milazzo 2023-10-14 10:07:53 -05:00 committed by GitHub
parent 5c2ebb87cc
commit 00dddaefae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
88 changed files with 5971 additions and 572 deletions

View file

@ -8,7 +8,6 @@ using API.Data;
using API.Data.Repositories;
using API.DTOs;
using API.DTOs.Account;
using API.DTOs.Dashboard;
using API.DTOs.Email;
using API.Entities;
using API.Entities.Enums;
@ -1046,123 +1045,4 @@ public class AccountController : BaseApiController
return Ok(origin + "/" + baseUrl + "api/opds/" + user!.ApiKey);
}
/// <summary>
/// Returns the layout of the user's dashboard
/// </summary>
/// <returns></returns>
[HttpGet("dashboard")]
public async Task<ActionResult<IEnumerable<DashboardStreamDto>>> GetDashboardLayout(bool visibleOnly = true)
{
var streams = await _unitOfWork.UserRepository.GetDashboardStreams(User.GetUserId(), visibleOnly);
return Ok(streams);
}
/// <summary>
/// Creates a Dashboard Stream from a SmartFilter and adds it to the user's dashboard as visible
/// </summary>
/// <param name="smartFilterId"></param>
/// <returns></returns>
[HttpPost("add-dashboard-stream")]
public async Task<ActionResult<DashboardStreamDto>> AddDashboard([FromQuery] int smartFilterId)
{
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.DashboardStreams);
if (user == null) return Unauthorized();
var smartFilter = await _unitOfWork.AppUserSmartFilterRepository.GetById(smartFilterId);
if (smartFilter == null) return NoContent();
var stream = user?.DashboardStreams.FirstOrDefault(d => d.SmartFilter?.Id == smartFilterId);
if (stream != null) return BadRequest("There is an existing stream with this Filter");
var maxOrder = user!.DashboardStreams.Max(d => d.Order);
var createdStream = new AppUserDashboardStream()
{
Name = smartFilter.Name,
IsProvided = false,
StreamType = DashboardStreamType.SmartFilter,
Visible = true,
Order = maxOrder + 1,
SmartFilter = smartFilter
};
user.DashboardStreams.Add(createdStream);
_unitOfWork.UserRepository.Update(user);
await _unitOfWork.CommitAsync();
var ret = new DashboardStreamDto()
{
Name = createdStream.Name,
IsProvided = createdStream.IsProvided,
Visible = createdStream.Visible,
Order = createdStream.Order,
SmartFilterEncoded = smartFilter.Filter,
StreamType = createdStream.StreamType
};
await _eventHub.SendMessageToAsync(MessageFactory.DashboardUpdate, MessageFactory.DashboardUpdateEvent(user.Id),
User.GetUserId());
return Ok(ret);
}
/// <summary>
/// Updates the visibility of a dashboard stream
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("update-dashboard-stream")]
public async Task<ActionResult> UpdateDashboardStream(DashboardStreamDto dto)
{
var stream = await _unitOfWork.UserRepository.GetDashboardStream(dto.Id);
if (stream == null) return BadRequest();
stream.Visible = dto.Visible;
_unitOfWork.UserRepository.Update(stream);
await _unitOfWork.CommitAsync();
var userId = User.GetUserId();
await _eventHub.SendMessageToAsync(MessageFactory.DashboardUpdate, MessageFactory.DashboardUpdateEvent(userId),
userId);
return Ok();
}
/// <summary>
/// Updates the position of a dashboard stream
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("update-dashboard-position")]
public async Task<ActionResult> UpdateDashboardStreamPosition(UpdateDashboardStreamPositionDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(),
AppUserIncludes.DashboardStreams);
var stream = user?.DashboardStreams.FirstOrDefault(d => d.Id == dto.DashboardStreamId);
if (stream == null) return BadRequest();
if (stream.Order == dto.ToPosition) return Ok();
var list = user!.DashboardStreams.ToList();
ReorderItems(list, stream.Id, dto.ToPosition);
user.DashboardStreams = list;
_unitOfWork.UserRepository.Update(user);
await _unitOfWork.CommitAsync();
await _eventHub.SendMessageToAsync(MessageFactory.DashboardUpdate, MessageFactory.DashboardUpdateEvent(user.Id),
user.Id);
return Ok();
}
private static void ReorderItems(List<AppUserDashboardStream> items, int itemId, int toPosition)
{
var item = items.Find(r => r.Id == itemId);
if (item != null)
{
items.Remove(item);
items.Insert(toPosition, item);
}
for (var i = 0; i < items.Count; i++)
{
items[i].Order = i;
}
}
}

View file

@ -87,6 +87,10 @@ public class FilterController : BaseApiController
// This needs to delete any dashboard filters that have it too
var streams = await _unitOfWork.UserRepository.GetDashboardStreamWithFilter(filter.Id);
_unitOfWork.UserRepository.Delete(streams);
var streams2 = await _unitOfWork.UserRepository.GetSideNavStreamWithFilter(filter.Id);
_unitOfWork.UserRepository.Delete(streams2);
_unitOfWork.AppUserSmartFilterRepository.Delete(filter);
await _unitOfWork.CommitAsync();
return Ok();

View file

@ -21,6 +21,7 @@ using AutoMapper;
using EasyCaching.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration.UserSecrets;
using Microsoft.Extensions.Logging;
using TaskScheduler = API.Services.TaskScheduler;
@ -97,6 +98,27 @@ public class LibraryController : BaseApiController
admin.Libraries.Add(library);
}
var userIds = admins.Select(u => u.Id).Append(User.GetUserId()).ToList();
var userNeedingNewLibrary = (await _unitOfWork.UserRepository.GetAllUsersAsync(AppUserIncludes.SideNavStreams))
.Where(u => userIds.Contains(u.Id))
.ToList();
foreach (var user in userNeedingNewLibrary)
{
var maxCount = user.SideNavStreams.Select(s => s.Order).Max();
user.SideNavStreams.Add(new AppUserSideNavStream()
{
Name = library.Name,
Order = maxCount + 1,
IsProvided = false,
StreamType = SideNavStreamType.Library,
LibraryId = library.Id,
Visible = true,
});
_unitOfWork.UserRepository.Update(user);
}
if (!await _unitOfWork.CommitAsync()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-library"));
@ -105,6 +127,8 @@ public class LibraryController : BaseApiController
_taskScheduler.ScanLibrary(library.Id);
await _eventHub.SendMessageAsync(MessageFactory.LibraryModified,
MessageFactory.LibraryModifiedEvent(library.Id, "create"), false);
await _eventHub.SendMessageAsync(MessageFactory.SideNavUpdate,
MessageFactory.SideNavUpdateEvent(User.GetUserId()), false);
await _libraryCacheProvider.RemoveByPrefixAsync(CacheKey);
return Ok();
}
@ -329,9 +353,15 @@ public class LibraryController : BaseApiController
_unitOfWork.LibraryRepository.Delete(library);
var streams = await _unitOfWork.UserRepository.GetSideNavStreamsByLibraryId(library.Id);
_unitOfWork.UserRepository.Delete(streams);
await _unitOfWork.CommitAsync();
await _libraryCacheProvider.RemoveByPrefixAsync(CacheKey);
await _eventHub.SendMessageAsync(MessageFactory.SideNavUpdate,
MessageFactory.SideNavUpdateEvent(User.GetUserId()), false);
if (chapterIds.Any())
{

View file

@ -50,4 +50,18 @@ public class PluginController : BaseApiController
KavitaVersion = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion)).Value
};
}
/// <summary>
/// Returns the version of the Kavita install
/// </summary>
/// <param name="apiKey">Required for authenticating to get result</param>
/// <returns></returns>
[AllowAnonymous]
[HttpGet("version")]
public async Task<ActionResult<string>> GetVersion([Required] string apiKey)
{
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
if (userId <= 0) return Unauthorized();
return Ok((await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion)).Value);
}
}

View file

@ -0,0 +1,186 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using API.Data;
using API.DTOs.Dashboard;
using API.DTOs.SideNav;
using API.Extensions;
using API.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace API.Controllers;
/// <summary>
/// Responsible for anything that deals with Streams (SmartFilters, ExternalSource, DashboardStream, SideNavStream)
/// </summary>
public class StreamController : BaseApiController
{
private readonly IStreamService _streamService;
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger<StreamController> _logger;
public StreamController(IStreamService streamService, IUnitOfWork unitOfWork, ILogger<StreamController> logger)
{
_streamService = streamService;
_unitOfWork = unitOfWork;
_logger = logger;
}
/// <summary>
/// Returns the layout of the user's dashboard
/// </summary>
/// <returns></returns>
[HttpGet("dashboard")]
public async Task<ActionResult<IEnumerable<DashboardStreamDto>>> GetDashboardLayout(bool visibleOnly = true)
{
return Ok(await _streamService.GetDashboardStreams(User.GetUserId(), visibleOnly));
}
/// <summary>
/// Return's the user's side nav
/// </summary>
[HttpGet("sidenav")]
public async Task<ActionResult<IEnumerable<SideNavStreamDto>>> GetSideNav(bool visibleOnly = true)
{
return Ok(await _streamService.GetSidenavStreams(User.GetUserId(), visibleOnly));
}
/// <summary>
/// Return's the user's external sources
/// </summary>
[HttpGet("external-sources")]
public async Task<ActionResult<IEnumerable<ExternalSourceDto>>> GetExternalSources()
{
return Ok(await _streamService.GetExternalSources(User.GetUserId()));
}
/// <summary>
/// Create an external Source
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("create-external-source")]
public async Task<ActionResult<ExternalSourceDto>> CreateExternalSource(ExternalSourceDto dto)
{
// Check if a host and api key exists for the current user
return Ok(await _streamService.CreateExternalSource(User.GetUserId(), dto));
}
/// <summary>
/// Updates an existing external source
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("update-external-source")]
public async Task<ActionResult<ExternalSourceDto>> UpdateExternalSource(ExternalSourceDto dto)
{
// Check if a host and api key exists for the current user
return Ok(await _streamService.UpdateExternalSource(User.GetUserId(), dto));
}
/// <summary>
/// Validates the external source by host is unique (for this user)
/// </summary>
/// <param name="host"></param>
/// <returns></returns>
[HttpGet("external-source-exists")]
public async Task<ActionResult<bool>> ExternalSourceExists(string host, string name, string apiKey)
{
return Ok(await _unitOfWork.AppUserExternalSourceRepository.ExternalSourceExists(User.GetUserId(), host, name, apiKey));
}
/// <summary>
/// Delete's the external source
/// </summary>
/// <param name="externalSourceId"></param>
/// <returns></returns>
[HttpDelete("delete-external-source")]
public async Task<ActionResult> ExternalSourceExists(int externalSourceId)
{
await _streamService.DeleteExternalSource(User.GetUserId(), externalSourceId);
return Ok();
}
/// <summary>
/// Creates a Dashboard Stream from a SmartFilter and adds it to the user's dashboard as visible
/// </summary>
/// <param name="smartFilterId"></param>
/// <returns></returns>
[HttpPost("add-dashboard-stream")]
public async Task<ActionResult<DashboardStreamDto>> AddDashboard([FromQuery] int smartFilterId)
{
return Ok(await _streamService.CreateDashboardStreamFromSmartFilter(User.GetUserId(), smartFilterId));
}
/// <summary>
/// Updates the visibility of a dashboard stream
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("update-dashboard-stream")]
public async Task<ActionResult> UpdateDashboardStream(DashboardStreamDto dto)
{
await _streamService.UpdateDashboardStream(User.GetUserId(), dto);
return Ok();
}
/// <summary>
/// Updates the position of a dashboard stream
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("update-dashboard-position")]
public async Task<ActionResult> UpdateDashboardStreamPosition(UpdateStreamPositionDto dto)
{
await _streamService.UpdateDashboardStreamPosition(User.GetUserId(), dto);
return Ok();
}
/// <summary>
/// Creates a SideNav Stream from a SmartFilter and adds it to the user's sidenav as visible
/// </summary>
/// <param name="smartFilterId"></param>
/// <returns></returns>
[HttpPost("add-sidenav-stream")]
public async Task<ActionResult<SideNavStreamDto>> AddSideNav([FromQuery] int smartFilterId)
{
return Ok(await _streamService.CreateSideNavStreamFromSmartFilter(User.GetUserId(), smartFilterId));
}
/// <summary>
/// Creates a SideNav Stream from a SmartFilter and adds it to the user's sidenav as visible
/// </summary>
/// <param name="externalSourceId"></param>
/// <returns></returns>
[HttpPost("add-sidenav-stream-from-external-source")]
public async Task<ActionResult<SideNavStreamDto>> AddSideNavFromExternalSource([FromQuery] int externalSourceId)
{
return Ok(await _streamService.CreateSideNavStreamFromExternalSource(User.GetUserId(), externalSourceId));
}
/// <summary>
/// Updates the visibility of a dashboard stream
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("update-sidenav-stream")]
public async Task<ActionResult> UpdateSideNavStream(SideNavStreamDto dto)
{
await _streamService.UpdateSideNavStream(User.GetUserId(), dto);
return Ok();
}
/// <summary>
/// Updates the position of a dashboard stream
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("update-sidenav-position")]
public async Task<ActionResult> UpdateSideNavStreamPosition(UpdateStreamPositionDto dto)
{
await _streamService.UpdateSideNavStreamPosition(User.GetUserId(), dto);
return Ok();
}
}