mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-31 12:14:44 -04:00
More Polishing (#428)
# Added - Added: Added a new button on admin dashboard to clear cache for the whole server # Changed - Changed: Moved the download logs to the new System page - Changed: Tag Badges now show the correct cursor to help indication actions. For example, Collection badges on series detail page can be clicked, while type cannot. # Fixed - Fixed: Fixed an issue in develop builds where Pagination no longer worked due to Header not being exposed - Fixed: After Scanning a series, clear out any cached chapters ======================================================= * After Scanning a series, clear out any cached chapters. * Implemented cursor overrides for tag badges * Fixed pagination no longer working due to Pagination header not being able to be read from the UI. * Fixed some css things with icons within tagbadges not taking the selection mode styling * Moved download logs button to the system page * Implemented the ability to clear cache for the whole server from admin dashboard * Removed debug code * Up the Regex Timeout for the Github Build System
This commit is contained in:
parent
107b70226c
commit
4f3461710c
@ -34,6 +34,7 @@ namespace API.Tests.Services
|
|||||||
private readonly IImageService _imageService = Substitute.For<IImageService>();
|
private readonly IImageService _imageService = Substitute.For<IImageService>();
|
||||||
private readonly ILogger<MetadataService> _metadataLogger = Substitute.For<ILogger<MetadataService>>();
|
private readonly ILogger<MetadataService> _metadataLogger = Substitute.For<ILogger<MetadataService>>();
|
||||||
private readonly IDirectoryService _directoryService = Substitute.For<IDirectoryService>();
|
private readonly IDirectoryService _directoryService = Substitute.For<IDirectoryService>();
|
||||||
|
private readonly ICacheService _cacheService = Substitute.For<ICacheService>();
|
||||||
|
|
||||||
private readonly DbConnection _connection;
|
private readonly DbConnection _connection;
|
||||||
private readonly DataContext _context;
|
private readonly DataContext _context;
|
||||||
@ -53,7 +54,7 @@ namespace API.Tests.Services
|
|||||||
|
|
||||||
|
|
||||||
IMetadataService metadataService = Substitute.For<MetadataService>(unitOfWork, _metadataLogger, _archiveService, _bookService, _imageService);
|
IMetadataService metadataService = Substitute.For<MetadataService>(unitOfWork, _metadataLogger, _archiveService, _bookService, _imageService);
|
||||||
_scannerService = new ScannerService(unitOfWork, _logger, _archiveService, metadataService, _bookService);
|
_scannerService = new ScannerService(unitOfWork, _logger, _archiveService, metadataService, _bookService, _cacheService);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> SeedDb()
|
private async Task<bool> SeedDb()
|
||||||
|
@ -22,15 +22,17 @@ namespace API.Controllers
|
|||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
private readonly IBackupService _backupService;
|
private readonly IBackupService _backupService;
|
||||||
private readonly IArchiveService _archiveService;
|
private readonly IArchiveService _archiveService;
|
||||||
|
private readonly ICacheService _cacheService;
|
||||||
|
|
||||||
public ServerController(IHostApplicationLifetime applicationLifetime, ILogger<ServerController> logger, IConfiguration config,
|
public ServerController(IHostApplicationLifetime applicationLifetime, ILogger<ServerController> logger, IConfiguration config,
|
||||||
IBackupService backupService, IArchiveService archiveService)
|
IBackupService backupService, IArchiveService archiveService, ICacheService cacheService)
|
||||||
{
|
{
|
||||||
_applicationLifetime = applicationLifetime;
|
_applicationLifetime = applicationLifetime;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_config = config;
|
_config = config;
|
||||||
_backupService = backupService;
|
_backupService = backupService;
|
||||||
_archiveService = archiveService;
|
_archiveService = archiveService;
|
||||||
|
_cacheService = cacheService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("restart")]
|
[HttpPost("restart")]
|
||||||
@ -42,6 +44,15 @@ namespace API.Controllers
|
|||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("clear-cache")]
|
||||||
|
public ActionResult ClearCache()
|
||||||
|
{
|
||||||
|
_logger.LogInformation("{UserName} is clearing cache of server from admin dashboard", User.GetUsername());
|
||||||
|
_cacheService.Cleanup();
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns non-sensitive information about the current system
|
/// Returns non-sensitive information about the current system
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -79,7 +79,6 @@ namespace API.Extensions
|
|||||||
|
|
||||||
foreach (var subDirectory in directory.EnumerateDirectories())
|
foreach (var subDirectory in directory.EnumerateDirectories())
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Flattening {subDirectory}");
|
|
||||||
FlattenDirectory(root, subDirectory, ref directoryIndex);
|
FlattenDirectory(root, subDirectory, ref directoryIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
|
|
||||||
namespace API.Interfaces.Services
|
namespace API.Interfaces.Services
|
||||||
@ -22,11 +23,11 @@ namespace API.Interfaces.Services
|
|||||||
/// Clears cache directory of all volumes. This can be invoked from deleting a library or a series.
|
/// Clears cache directory of all volumes. This can be invoked from deleting a library or a series.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="chapterIds">Volumes that belong to that library. Assume the library might have been deleted before this invocation.</param>
|
/// <param name="chapterIds">Volumes that belong to that library. Assume the library might have been deleted before this invocation.</param>
|
||||||
void CleanupChapters(int[] chapterIds);
|
void CleanupChapters(IEnumerable<int> chapterIds);
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the absolute path of a cached page.
|
/// Returns the absolute path of a cached page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="chapter">Chapter entity with Files populated.</param>
|
/// <param name="chapter">Chapter entity with Files populated.</param>
|
||||||
/// <param name="page">Page number to look for</param>
|
/// <param name="page">Page number to look for</param>
|
||||||
@ -35,4 +36,4 @@ namespace API.Interfaces.Services
|
|||||||
|
|
||||||
void EnsureCacheDirectory();
|
void EnsureCacheDirectory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ namespace API.Parser
|
|||||||
{
|
{
|
||||||
public const string DefaultChapter = "0";
|
public const string DefaultChapter = "0";
|
||||||
public const string DefaultVolume = "0";
|
public const string DefaultVolume = "0";
|
||||||
private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(250);
|
private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(500);
|
||||||
|
|
||||||
public const string ImageFileExtensions = @"^(\.png|\.jpeg|\.jpg)";
|
public const string ImageFileExtensions = @"^(\.png|\.jpeg|\.jpg)";
|
||||||
public const string ArchiveFileExtensions = @"\.cbz|\.zip|\.rar|\.cbr|\.tar.gz|\.7zip|\.7z|\.cb7|\.cbt";
|
public const string ArchiveFileExtensions = @"\.cbz|\.zip|\.rar|\.cbr|\.tar.gz|\.7zip|\.7z|\.cb7|\.cbt";
|
||||||
@ -507,7 +507,8 @@ namespace API.Parser
|
|||||||
// If SP\d+ is in the filename, we force treat it as a special regardless if volume or chapter might have been found.
|
// If SP\d+ is in the filename, we force treat it as a special regardless if volume or chapter might have been found.
|
||||||
private static readonly Regex SpecialMarkerRegex = new Regex(
|
private static readonly Regex SpecialMarkerRegex = new Regex(
|
||||||
@"(?<Special>SP\d+)",
|
@"(?<Special>SP\d+)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled
|
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||||
|
RegexTimeout
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -301,7 +301,6 @@ namespace API.Services
|
|||||||
entry.WriteTo(ms);
|
entry.WriteTo(ms);
|
||||||
ms.Position = 0;
|
ms.Position = 0;
|
||||||
|
|
||||||
|
|
||||||
var serializer = new XmlSerializer(typeof(ComicInfo));
|
var serializer = new XmlSerializer(typeof(ComicInfo));
|
||||||
var info = (ComicInfo) serializer.Deserialize(ms);
|
var info = (ComicInfo) serializer.Deserialize(ms);
|
||||||
return info;
|
return info;
|
||||||
|
@ -379,7 +379,6 @@ namespace API.Services
|
|||||||
using var stream = StreamManager.GetStream("BookService.GetPdfPage");
|
using var stream = StreamManager.GetStream("BookService.GetPdfPage");
|
||||||
for (var pageNumber = 0; pageNumber < pages; pageNumber++)
|
for (var pageNumber = 0; pageNumber < pages; pageNumber++)
|
||||||
{
|
{
|
||||||
// IDEA! Move stream out and use the same stream
|
|
||||||
GetPdfPage(docReader, pageNumber, stream);
|
GetPdfPage(docReader, pageNumber, stream);
|
||||||
File.WriteAllBytes(Path.Combine(targetDirectory, "Page-" + pageNumber + ".png"), stream.ToArray());
|
File.WriteAllBytes(Path.Combine(targetDirectory, "Page-" + pageNumber + ".png"), stream.ToArray());
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -113,7 +114,7 @@ namespace API.Services
|
|||||||
_logger.LogInformation("Cache directory purged");
|
_logger.LogInformation("Cache directory purged");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CleanupChapters(int[] chapterIds)
|
public void CleanupChapters(IEnumerable<int> chapterIds)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Running Cache cleanup on Volumes");
|
_logger.LogInformation("Running Cache cleanup on Volumes");
|
||||||
|
|
||||||
|
@ -26,16 +26,18 @@ namespace API.Services.Tasks
|
|||||||
private readonly IArchiveService _archiveService;
|
private readonly IArchiveService _archiveService;
|
||||||
private readonly IMetadataService _metadataService;
|
private readonly IMetadataService _metadataService;
|
||||||
private readonly IBookService _bookService;
|
private readonly IBookService _bookService;
|
||||||
|
private readonly ICacheService _cacheService;
|
||||||
private readonly NaturalSortComparer _naturalSort = new ();
|
private readonly NaturalSortComparer _naturalSort = new ();
|
||||||
|
|
||||||
public ScannerService(IUnitOfWork unitOfWork, ILogger<ScannerService> logger, IArchiveService archiveService,
|
public ScannerService(IUnitOfWork unitOfWork, ILogger<ScannerService> logger, IArchiveService archiveService,
|
||||||
IMetadataService metadataService, IBookService bookService)
|
IMetadataService metadataService, IBookService bookService, ICacheService cacheService)
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_archiveService = archiveService;
|
_archiveService = archiveService;
|
||||||
_metadataService = metadataService;
|
_metadataService = metadataService;
|
||||||
_bookService = bookService;
|
_bookService = bookService;
|
||||||
|
_cacheService = cacheService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DisableConcurrentExecution(timeoutInSeconds: 360)]
|
[DisableConcurrentExecution(timeoutInSeconds: 360)]
|
||||||
@ -46,6 +48,7 @@ namespace API.Services.Tasks
|
|||||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
|
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
|
||||||
var library = await _unitOfWork.LibraryRepository.GetFullLibraryForIdAsync(libraryId, seriesId);
|
var library = await _unitOfWork.LibraryRepository.GetFullLibraryForIdAsync(libraryId, seriesId);
|
||||||
var dirs = FindHighestDirectoriesFromFiles(library, files);
|
var dirs = FindHighestDirectoriesFromFiles(library, files);
|
||||||
|
var chapterIds = await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new []{ seriesId });
|
||||||
|
|
||||||
_logger.LogInformation("Beginning file scan on {SeriesName}", series.Name);
|
_logger.LogInformation("Beginning file scan on {SeriesName}", series.Name);
|
||||||
var scanner = new ParseScannedFiles(_bookService, _logger);
|
var scanner = new ParseScannedFiles(_bookService, _logger);
|
||||||
@ -71,6 +74,7 @@ namespace API.Services.Tasks
|
|||||||
totalFiles, parsedSeries.Keys.Count, sw.ElapsedMilliseconds + scanElapsedTime, series.Name);
|
totalFiles, parsedSeries.Keys.Count, sw.ElapsedMilliseconds + scanElapsedTime, series.Name);
|
||||||
CleanupUserProgress();
|
CleanupUserProgress();
|
||||||
BackgroundJob.Enqueue(() => _metadataService.RefreshMetadata(libraryId, forceUpdate));
|
BackgroundJob.Enqueue(() => _metadataService.RefreshMetadata(libraryId, forceUpdate));
|
||||||
|
BackgroundJob.Enqueue(() => _cacheService.CleanupChapters(chapterIds));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -101,7 +101,11 @@ namespace API
|
|||||||
// Ordering is important. Cors, authentication, authorization
|
// Ordering is important. Cors, authentication, authorization
|
||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseCors(policy => policy.AllowAnyHeader().AllowAnyMethod().WithOrigins("http://localhost:4200").WithExposedHeaders("Content-Disposition"));
|
app.UseCors(policy => policy
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.WithOrigins("http://localhost:4200")
|
||||||
|
.WithExposedHeaders("Content-Disposition", "Pagination"));
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseResponseCaching();
|
app.UseResponseCaching();
|
||||||
|
@ -19,4 +19,8 @@ export class ServerService {
|
|||||||
getServerInfo() {
|
getServerInfo() {
|
||||||
return this.httpClient.get<ServerInfo>(this.baseUrl + 'server/server-info');
|
return this.httpClient.get<ServerInfo>(this.baseUrl + 'server/server-info');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearCache() {
|
||||||
|
return this.httpClient.post(this.baseUrl + 'server/clear-cache', {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Admin Dashboard</h2>
|
<h2>Admin Dashboard</h2>
|
||||||
|
|
||||||
<div class="float-right">
|
|
||||||
<button class="btn btn-secondary" (click)="fetchLogs()">Download Logs</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs">
|
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs">
|
||||||
<li *ngFor="let tab of tabs" [ngbNavItem]="tab">
|
<li *ngFor="let tab of tabs" [ngbNavItem]="tab">
|
||||||
<a ngbNavLink routerLink="." [fragment]="tab.fragment">{{ tab.title | titlecase }}</a>
|
<a ngbNavLink routerLink="." [fragment]="tab.fragment">{{ tab.title | titlecase }}</a>
|
||||||
|
@ -2,9 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { ServerService } from 'src/app/_services/server.service';
|
import { ServerService } from 'src/app/_services/server.service';
|
||||||
import { saveAs } from 'file-saver';
|
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { DownloadService } from 'src/app/shared/_services/download.service';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -25,7 +23,7 @@ export class DashboardComponent implements OnInit {
|
|||||||
active = this.tabs[0];
|
active = this.tabs[0];
|
||||||
|
|
||||||
constructor(public route: ActivatedRoute, private serverService: ServerService,
|
constructor(public route: ActivatedRoute, private serverService: ServerService,
|
||||||
private toastr: ToastrService, private titleService: Title, private downloadService: DownloadService) {
|
private toastr: ToastrService, private titleService: Title) {
|
||||||
this.route.fragment.subscribe(frag => {
|
this.route.fragment.subscribe(frag => {
|
||||||
const tab = this.tabs.filter(item => item.fragment === frag);
|
const tab = this.tabs.filter(item => item.fragment === frag);
|
||||||
if (tab.length > 0) {
|
if (tab.length > 0) {
|
||||||
@ -46,9 +44,4 @@ export class DashboardComponent implements OnInit {
|
|||||||
setTimeout(() => this.toastr.success('Please reload.'), 1000);
|
setTimeout(() => this.toastr.success('Please reload.'), 1000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchLogs() {
|
|
||||||
this.downloadService.downloadLogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,18 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
|
||||||
|
<div class="float-right">
|
||||||
|
<button class="btn btn-secondary mr-2" (click)="clearCache()" [disabled]="clearCacheInProgress">
|
||||||
|
<ng-container *ngIf="clearCacheInProgress">
|
||||||
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</ng-container>
|
||||||
|
Clear Cache
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" (click)="downloadService.downloadLogs()">
|
||||||
|
Download Logs
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>About System</h3>
|
<h3>About System</h3>
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="form-group" *ngIf="serverInfo">
|
<div class="form-group" *ngIf="serverInfo">
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { take } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
|
import { DownloadService } from 'src/app/shared/_services/download.service';
|
||||||
import { ServerService } from 'src/app/_services/server.service';
|
import { ServerService } from 'src/app/_services/server.service';
|
||||||
import { SettingsService } from '../settings.service';
|
import { SettingsService } from '../settings.service';
|
||||||
import { ServerInfo } from '../_models/server-info';
|
import { ServerInfo } from '../_models/server-info';
|
||||||
@ -18,7 +20,10 @@ export class ManageSystemComponent implements OnInit {
|
|||||||
serverSettings!: ServerSettings;
|
serverSettings!: ServerSettings;
|
||||||
serverInfo!: ServerInfo;
|
serverInfo!: ServerInfo;
|
||||||
|
|
||||||
constructor(private settingsService: SettingsService, private toastr: ToastrService, private serverService: ServerService) { }
|
clearCacheInProgress: boolean = false;
|
||||||
|
|
||||||
|
constructor(private settingsService: SettingsService, private toastr: ToastrService,
|
||||||
|
private serverService: ServerService, public downloadService: DownloadService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
||||||
@ -58,4 +63,12 @@ export class ManageSystemComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearCache() {
|
||||||
|
this.clearCacheInProgress = true;
|
||||||
|
this.serverService.clearCache().subscribe(res => {
|
||||||
|
this.clearCacheInProgress = false;
|
||||||
|
this.toastr.success('Cache has been cleared');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -62,11 +62,12 @@ export class LibraryDetailComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadPage() {
|
loadPage() {
|
||||||
|
if (this.pagination == undefined || this.pagination == null) {
|
||||||
|
this.pagination = {currentPage: 0, itemsPerPage: 30, totalItems: 0, totalPages: 1};
|
||||||
|
}
|
||||||
|
|
||||||
const page = this.route.snapshot.queryParamMap.get('page');
|
const page = this.route.snapshot.queryParamMap.get('page');
|
||||||
if (page != null) {
|
if (page != null) {
|
||||||
if (this.pagination == undefined || this.pagination == null) {
|
|
||||||
this.pagination = {currentPage: 0, itemsPerPage: 30, totalItems: 0, totalPages: 1};
|
|
||||||
}
|
|
||||||
this.pagination.currentPage = parseInt(page, 10);
|
this.pagination.currentPage = parseInt(page, 10);
|
||||||
}
|
}
|
||||||
this.loadingSeries = true;
|
this.loadingSeries = true;
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
<h5>Genres</h5>
|
<h5>Genres</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-tag-badge *ngFor="let genre of seriesMetadata.genres">{{genre}}</app-tag-badge>
|
<app-tag-badge *ngFor="let genre of seriesMetadata.genres" [selectionMode]="TagBadgeCursor.Clickable">{{genre}}</app-tag-badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.tags && seriesMetadata.tags.length > 0">
|
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.tags && seriesMetadata.tags.length > 0">
|
||||||
@ -64,7 +64,7 @@
|
|||||||
<h5>Collections</h5>
|
<h5>Collections</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-tag-badge *ngFor="let tag of seriesMetadata.tags" a11y-click="13,32" class="clickable" routerLink="/collections/{{tag.id}}">
|
<app-tag-badge *ngFor="let tag of seriesMetadata.tags" a11y-click="13,32" class="clickable" routerLink="/collections/{{tag.id}}" [selectionMode]="TagBadgeCursor.Clickable">
|
||||||
{{tag.title}}
|
{{tag.title}}
|
||||||
</app-tag-badge>
|
</app-tag-badge>
|
||||||
</div>
|
</div>
|
||||||
@ -85,7 +85,7 @@
|
|||||||
<h5>Type</h5>
|
<h5>Type</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<app-tag-badge><app-series-format [format]="series.format">{{utilityService.mangaFormat(series.format)}}</app-series-format></app-tag-badge>
|
<app-tag-badge [selectionMode]="TagBadgeCursor.NotAllowed"><app-series-format [format]="series.format">{{utilityService.mangaFormat(series.format)}}</app-series-format></app-tag-badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,7 @@ import { ToastrService } from 'ngx-toastr';
|
|||||||
import { take } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
import { ConfirmConfig } from '../shared/confirm-dialog/_models/confirm-config';
|
import { ConfirmConfig } from '../shared/confirm-dialog/_models/confirm-config';
|
||||||
import { ConfirmService } from '../shared/confirm.service';
|
import { ConfirmService } from '../shared/confirm.service';
|
||||||
|
import { TagBadgeCursor } from '../shared/tag-badge/tag-badge.component';
|
||||||
import { CardDetailsModalComponent } from '../shared/_modals/card-details-modal/card-details-modal.component';
|
import { CardDetailsModalComponent } from '../shared/_modals/card-details-modal/card-details-modal.component';
|
||||||
import { DownloadService } from '../shared/_services/download.service';
|
import { DownloadService } from '../shared/_services/download.service';
|
||||||
import { UtilityService } from '../shared/_services/utility.service';
|
import { UtilityService } from '../shared/_services/utility.service';
|
||||||
@ -74,6 +75,10 @@ export class SeriesDetailComponent implements OnInit {
|
|||||||
return MangaFormat;
|
return MangaFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get TagBadgeCursor(): typeof TagBadgeCursor {
|
||||||
|
return TagBadgeCursor;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private seriesService: SeriesService,
|
constructor(private route: ActivatedRoute, private seriesService: SeriesService,
|
||||||
private ratingConfig: NgbRatingConfig, private router: Router,
|
private ratingConfig: NgbRatingConfig, private router: Router,
|
||||||
private modalService: NgbModal, public readerService: ReaderService,
|
private modalService: NgbModal, public readerService: ReaderService,
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<div class="tagbadge">
|
<div class="tagbadge {{cursor}}">
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
@ -20,4 +20,28 @@ $bdr-color: #f2f2f2;
|
|||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .selectable-cursor {
|
||||||
|
cursor: default !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .not-allowed-cursor {
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .clickable-cursor {
|
||||||
|
cursor: pointer !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
cursor: pointer !important;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,4 +1,25 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What type of cursor to apply to the tag badge
|
||||||
|
*/
|
||||||
|
export enum TagBadgeCursor {
|
||||||
|
/**
|
||||||
|
* Allows the user to select text
|
||||||
|
* cursor: default
|
||||||
|
*/
|
||||||
|
Selectable,
|
||||||
|
/**
|
||||||
|
* Informs the user they can click and interact with badge
|
||||||
|
* cursor: pointer
|
||||||
|
*/
|
||||||
|
Clickable,
|
||||||
|
/**
|
||||||
|
* Informs the user they cannot click or interact with badge
|
||||||
|
* cursor: not-allowed
|
||||||
|
*/
|
||||||
|
NotAllowed,
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tag-badge',
|
selector: 'app-tag-badge',
|
||||||
@ -7,9 +28,24 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class TagBadgeComponent implements OnInit {
|
export class TagBadgeComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() selectionMode: TagBadgeCursor = TagBadgeCursor.Selectable;
|
||||||
|
|
||||||
|
cursor: string = 'default';
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
switch (this.selectionMode) {
|
||||||
|
case TagBadgeCursor.Selectable:
|
||||||
|
this.cursor = 'selectable-cursor';
|
||||||
|
break;
|
||||||
|
case TagBadgeCursor.NotAllowed:
|
||||||
|
this.cursor = 'not-allowed-cursor';
|
||||||
|
break;
|
||||||
|
case TagBadgeCursor.Clickable:
|
||||||
|
this.cursor = 'clickable-cursor';
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user