mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
Stablize the Styles (#1128)
* Fixed a bug where adding multiple series to reading list would throw an error on UI, but it was successful. * When a series has a reading list, we now show the connection on Series detail. * Removed all baseurl code from UI and not-connected component since we no longer use it. * Fixed tag badges not showing a border. Added last read time to the series detail page * Fixed up error interceptor to remove no-connection code * Changed implementation for series detail. Book libraries will never send chapters back. Volume 0 volumes will not be sent in volumes ever. Fixed up more renaming logic on books to send more accurate representations to the UI. * Cleaned up the selected tab and tab display logic * Fixed a bad where statement in reading lists for series * Fixed up tab logic again * Fixed a small margin on search backdrop * Made badge expander button smaller to align with badges * Fixed a few UIs due to .form-group and .form-row being removed * Updated Theme component page to help with style testing * Added more components to theme tester * Cleaned up some styling * Fixed opacity on search item hover
This commit is contained in:
parent
cb9b54b8de
commit
864d693790
@ -1152,6 +1152,56 @@ public class ReaderServiceTests
|
||||
|
||||
#region GetContinuePoint
|
||||
|
||||
[Fact]
|
||||
public async Task GetContinuePoint_ShouldReturnFirstVolume_NoProgress()
|
||||
{
|
||||
_context.Series.Add(new Series()
|
||||
{
|
||||
Name = "Test",
|
||||
Library = new Library() {
|
||||
Name = "Test LIb",
|
||||
Type = LibraryType.Manga,
|
||||
},
|
||||
Volumes = new List<Volume>()
|
||||
{
|
||||
EntityFactory.CreateVolume("0", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("95", false, new List<MangaFile>(), 1),
|
||||
EntityFactory.CreateChapter("96", false, new List<MangaFile>(), 1),
|
||||
}),
|
||||
EntityFactory.CreateVolume("1", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("1", false, new List<MangaFile>(), 1),
|
||||
EntityFactory.CreateChapter("2", false, new List<MangaFile>(), 1),
|
||||
}),
|
||||
EntityFactory.CreateVolume("2", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("21", false, new List<MangaFile>(), 1),
|
||||
EntityFactory.CreateChapter("22", false, new List<MangaFile>(), 1),
|
||||
}),
|
||||
EntityFactory.CreateVolume("3", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("31", false, new List<MangaFile>(), 1),
|
||||
EntityFactory.CreateChapter("32", false, new List<MangaFile>(), 1),
|
||||
}),
|
||||
}
|
||||
});
|
||||
|
||||
_context.AppUser.Add(new AppUser()
|
||||
{
|
||||
UserName = "majora2007"
|
||||
});
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
|
||||
var readerService = new ReaderService(_unitOfWork, Substitute.For<ILogger<ReaderService>>());
|
||||
|
||||
var nextChapter = await readerService.GetContinuePoint(1, 1);
|
||||
|
||||
Assert.Equal("1", nextChapter.Range);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetContinuePoint_ShouldReturnFirstNonSpecial()
|
||||
{
|
||||
|
@ -201,8 +201,8 @@ public class SeriesServiceTests
|
||||
Assert.Equal(6, detail.Chapters.Count());
|
||||
|
||||
Assert.NotEmpty(detail.Volumes);
|
||||
Assert.Equal(3, detail.Volumes.Count()); // This returns 3 because 0 volume will still come
|
||||
Assert.All(detail.Volumes, dto => Assert.Contains(dto.Name, new[] {"0", "2", "3"}));
|
||||
Assert.Equal(2, detail.Volumes.Count()); // Volume 0 shouldn't be sent in Volumes
|
||||
Assert.All(detail.Volumes, dto => Assert.Contains(dto.Name, new[] {"2", "3"}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -239,10 +239,11 @@ public class SeriesServiceTests
|
||||
|
||||
var detail = await _seriesService.GetSeriesDetail(1, 1);
|
||||
Assert.NotEmpty(detail.Chapters);
|
||||
Assert.Equal(3, detail.Chapters.Count()); // volume 2 has a 0 chapter aka a single chapter that is represented as a volume. We don't show in Chapters area
|
||||
// volume 2 has a 0 chapter aka a single chapter that is represented as a volume. We don't show in Chapters area
|
||||
Assert.Equal(3, detail.Chapters.Count());
|
||||
|
||||
Assert.NotEmpty(detail.Volumes);
|
||||
Assert.Equal(3, detail.Volumes.Count());
|
||||
Assert.Equal(2, detail.Volumes.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -279,6 +280,46 @@ public class SeriesServiceTests
|
||||
Assert.Equal(2, detail.Volumes.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SeriesDetail_WhenBookLibrary_ShouldReturnVolumesAndSpecial()
|
||||
{
|
||||
await ResetDb();
|
||||
|
||||
_context.Series.Add(new Series()
|
||||
{
|
||||
Name = "Test",
|
||||
Library = new Library() {
|
||||
Name = "Test LIb",
|
||||
Type = LibraryType.Book,
|
||||
},
|
||||
Volumes = new List<Volume>()
|
||||
{
|
||||
EntityFactory.CreateVolume("0", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("Ano Orokamono ni mo Kyakkou wo! - Volume 1.epub", true, new List<MangaFile>()),
|
||||
}),
|
||||
EntityFactory.CreateVolume("2", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("Ano Orokamono ni mo Kyakkou wo! - Volume 2.epub", false, new List<MangaFile>()),
|
||||
}),
|
||||
}
|
||||
});
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var detail = await _seriesService.GetSeriesDetail(1, 1);
|
||||
Assert.NotEmpty(detail.Volumes);
|
||||
Assert.Equal("2 - Ano Orokamono ni mo Kyakkou wo! - Volume 2", detail.Volumes.ElementAt(0).Name);
|
||||
|
||||
Assert.NotEmpty(detail.Specials);
|
||||
Assert.Equal("Ano Orokamono ni mo Kyakkou wo! - Volume 1.epub", detail.Specials.ElementAt(0).Range);
|
||||
|
||||
// A book library where all books are Volumes, will show no "chapters" on the UI because it doesn't make sense
|
||||
Assert.Empty(detail.Chapters);
|
||||
|
||||
Assert.Equal(1, detail.Volumes.Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SeriesDetail_ShouldSortVolumesByName()
|
||||
{
|
||||
|
@ -49,6 +49,15 @@ namespace API.Controllers
|
||||
return Ok(items);
|
||||
}
|
||||
|
||||
[HttpGet("lists-for-series")]
|
||||
public async Task<ActionResult<IEnumerable<ReadingListDto>>> GetListsForSeries(int seriesId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForSeriesAndUserAsync(userId, seriesId, true);
|
||||
|
||||
return Ok(items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches all reading list items for a given list including rich metadata around series, volume, chapters, and progress
|
||||
/// </summary>
|
||||
|
@ -18,6 +18,10 @@ namespace API.DTOs
|
||||
/// </summary>
|
||||
public int PagesRead { get; set; }
|
||||
/// <summary>
|
||||
/// DateTime representing last time the series was Read. Calculated at API-time.
|
||||
/// </summary>
|
||||
public DateTime LatestReadDate { get; set; }
|
||||
/// <summary>
|
||||
/// Rating from logged in user. Calculated at API-time.
|
||||
/// </summary>
|
||||
public int UserRating { get; set; }
|
||||
|
@ -19,6 +19,9 @@ public interface IReadingListRepository
|
||||
Task<IEnumerable<ReadingListItemDto>> AddReadingProgressModifiers(int userId, IList<ReadingListItemDto> items);
|
||||
Task<ReadingListDto> GetReadingListDtoByTitleAsync(string title);
|
||||
Task<IEnumerable<ReadingListItem>> GetReadingListItemsByIdAsync(int readingListId);
|
||||
|
||||
Task<IEnumerable<ReadingListDto>> GetReadingListDtosForSeriesAndUserAsync(int userId, int seriesId,
|
||||
bool includePromoted);
|
||||
void Remove(ReadingListItem item);
|
||||
void BulkRemove(IEnumerable<ReadingListItem> items);
|
||||
void Update(ReadingList list);
|
||||
@ -62,6 +65,18 @@ public class ReadingListRepository : IReadingListRepository
|
||||
return await PagedList<ReadingListDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReadingListDto>> GetReadingListDtosForSeriesAndUserAsync(int userId, int seriesId, bool includePromoted)
|
||||
{
|
||||
var query = _context.ReadingList
|
||||
.Where(l => l.AppUserId == userId || (includePromoted && l.Promoted ))
|
||||
.Where(l => l.Items.Any(i => i.SeriesId == seriesId))
|
||||
.OrderBy(l => l.LastModified)
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking();
|
||||
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<ReadingList> GetReadingListByIdAsync(int readingListId)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
|
@ -469,6 +469,7 @@ public class SeriesRepository : ISeriesRepository
|
||||
if (rating == null) continue;
|
||||
s.UserRating = rating.Rating;
|
||||
s.UserReview = rating.Review;
|
||||
s.LatestReadDate = userProgress.Max(p => p.LastModified);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Comparators;
|
||||
@ -217,14 +218,34 @@ public class SeriesService : ISeriesService
|
||||
var chapters = volumes.SelectMany(v => v.Chapters).ToList();
|
||||
|
||||
// For books, the Name of the Volume is remapped to the actual name of the book, rather than Volume number.
|
||||
var processedVolumes = new List<VolumeDto>();
|
||||
if (libraryType == LibraryType.Book)
|
||||
{
|
||||
foreach (var volume in volumes)
|
||||
{
|
||||
var firstChapter = volume.Chapters.First();
|
||||
if (!string.IsNullOrEmpty(firstChapter.TitleName)) volume.Name += $" - {firstChapter.TitleName}";
|
||||
// On Books, skip volumes that are specials, since these will be shown
|
||||
if (firstChapter.IsSpecial) continue;
|
||||
if (string.IsNullOrEmpty(firstChapter.TitleName))
|
||||
{
|
||||
if (!firstChapter.Range.Equals(Parser.Parser.DefaultVolume))
|
||||
{
|
||||
var title = Path.GetFileNameWithoutExtension(firstChapter.Range);
|
||||
if (string.IsNullOrEmpty(title)) continue;
|
||||
volume.Name += $" - {title}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
volume.Name += $" - {firstChapter.TitleName}";
|
||||
}
|
||||
processedVolumes.Add(volume);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
processedVolumes = volumes.Where(v => v.Number > 0).ToList();
|
||||
}
|
||||
|
||||
|
||||
var specials = new List<ChapterDto>();
|
||||
@ -233,14 +254,26 @@ public class SeriesService : ISeriesService
|
||||
chapter.Title = Parser.Parser.CleanSpecialTitle(chapter.Title);
|
||||
specials.Add(chapter);
|
||||
}
|
||||
|
||||
// Don't show chapter 0 (aka single volume chapters) in the Chapters tab or books that are just single numbers (they show as volumes)
|
||||
IEnumerable<ChapterDto> retChapters;
|
||||
if (libraryType == LibraryType.Book)
|
||||
{
|
||||
retChapters = Array.Empty<ChapterDto>();
|
||||
} else
|
||||
{
|
||||
retChapters = chapters
|
||||
.Where(ShouldIncludeChapter)
|
||||
.OrderBy(c => float.Parse(c.Number), new ChapterSortComparer());
|
||||
}
|
||||
|
||||
|
||||
|
||||
return new SeriesDetailDto()
|
||||
{
|
||||
Specials = specials,
|
||||
// Don't show chapter 0 (aka single volume chapters) in the Chapters tab or books that are just single numbers (they show as volumes)
|
||||
Chapters = chapters
|
||||
.Where(ShouldIncludeChapter)
|
||||
.OrderBy(c => float.Parse(c.Number), new ChapterSortComparer()),
|
||||
Volumes = volumes,
|
||||
Chapters = retChapters,
|
||||
Volumes = processedVolumes,
|
||||
StorylineChapters = volumes
|
||||
.Where(v => v.Number == 0)
|
||||
.SelectMany(v => v.Chapters)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpRequest,
|
||||
HttpHandler,
|
||||
@ -8,14 +8,12 @@ import {
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { Router } from '@angular/router';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { catchError, take } from 'rxjs/operators';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { AccountService } from '../_services/account.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Injectable()
|
||||
export class ErrorInterceptor implements HttpInterceptor {
|
||||
|
||||
public urlKey: string = 'kavita--no-connection-url';
|
||||
constructor(private router: Router, private toastr: ToastrService, private accountService: AccountService) {}
|
||||
|
||||
|
||||
@ -44,12 +42,6 @@ export class ErrorInterceptor implements HttpInterceptor {
|
||||
if (this.toastr.previousToastMessage !== 'Something unexpected went wrong.') {
|
||||
this.toastr.error('Something unexpected went wrong.');
|
||||
}
|
||||
|
||||
// If we are not on no-connection, redirect there and save current url so when we refersh, we redirect back there
|
||||
// if (this.router.url !== '/no-connection') {
|
||||
// localStorage.setItem(this.urlKey, this.router.url);
|
||||
// this.router.navigateByUrl('/no-connection');
|
||||
// }
|
||||
break;
|
||||
}
|
||||
return throwError(error);
|
||||
@ -126,8 +118,7 @@ export class ErrorInterceptor implements HttpInterceptor {
|
||||
private handleAuthError(error: any) {
|
||||
// NOTE: Signin has error.error or error.statusText available.
|
||||
// if statement is due to http/2 spec issue: https://github.com/angular/angular/issues/23334
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
this.accountService.logout();
|
||||
});
|
||||
this.accountService.logout();
|
||||
this.router.navigateByUrl('/login');
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
/**
|
||||
* This is for base url only. Not to be used my applicaiton, only loading and bootstrapping app
|
||||
*/
|
||||
export class ConfigData {
|
||||
baseUrl: string = '/';
|
||||
// export class ConfigData {
|
||||
// baseUrl: string = '/';
|
||||
|
||||
constructor(baseUrl: string) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
}
|
||||
// constructor(baseUrl: string) {
|
||||
// this.baseUrl = baseUrl;
|
||||
// }
|
||||
// }
|
@ -4,16 +4,41 @@ import { Volume } from './volume';
|
||||
export interface Series {
|
||||
id: number;
|
||||
name: string;
|
||||
originalName: string; // This is not shown to user
|
||||
/**
|
||||
* This is not shown to user
|
||||
*/
|
||||
originalName: string;
|
||||
localizedName: string;
|
||||
sortName: string;
|
||||
coverImageLocked: boolean;
|
||||
volumes: Volume[];
|
||||
pages: number; // Total pages in series
|
||||
pagesRead: number; // Total pages the logged in user has read
|
||||
userRating: number; // User rating
|
||||
userReview: string; // User review
|
||||
/**
|
||||
* Total pages in series
|
||||
*/
|
||||
pages: number;
|
||||
/**
|
||||
* Total pages the logged in user has read
|
||||
*/
|
||||
pagesRead: number;
|
||||
/**
|
||||
* User's rating (0-5)
|
||||
*/
|
||||
userRating: number;
|
||||
/**
|
||||
* The user's review
|
||||
*/
|
||||
userReview: string;
|
||||
libraryId: number;
|
||||
created: string; // DateTime when entity was created
|
||||
/**
|
||||
* DateTime the entity was created
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Format of the Series
|
||||
*/
|
||||
format: MangaFormat;
|
||||
/**
|
||||
* DateTime that represents last time the logged in user read this series
|
||||
*/
|
||||
latestReadDate: string;
|
||||
}
|
||||
|
@ -30,6 +30,10 @@ export class ReadingListService {
|
||||
);
|
||||
}
|
||||
|
||||
getReadingListsForSeries(seriesId: number) {
|
||||
return this.httpClient.get<ReadingList[]>(this.baseUrl + 'readinglist/lists-for-series?seriesId=' + seriesId);
|
||||
}
|
||||
|
||||
getListItems(readingListId: number) {
|
||||
return this.httpClient.get<ReadingListItem[]>(this.baseUrl + 'readinglist/items?readingListId=' + readingListId);
|
||||
}
|
||||
@ -47,7 +51,7 @@ export class ReadingListService {
|
||||
}
|
||||
|
||||
updateByMultipleSeries(readingListId: number, seriesIds: Array<number>) {
|
||||
return this.httpClient.post(this.baseUrl + 'readinglist/update-by-multiple-series', {readingListId, seriesIds});
|
||||
return this.httpClient.post(this.baseUrl + 'readinglist/update-by-multiple-series', {readingListId, seriesIds}, { responseType: 'text' as 'json' });
|
||||
}
|
||||
|
||||
updateBySeries(readingListId: number, seriesId: number) {
|
||||
|
@ -20,22 +20,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="mb-3">
|
||||
<label for="settings-baseurl">Base Url</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="baseUrlTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #baseUrlTooltip>Use this if you want to host Kavita on a base url ie) yourdomain.com/kavita</ng-template>
|
||||
<span class="visually-hidden" id="settings-baseurl-help">Use this if you want to host Kavita on a base url ie) yourdomain.com/kavita</span>
|
||||
<input id="settings-baseurl" aria-describedby="settings-baseurl-help" class="form-control" formControlName="baseUrl" type="text">
|
||||
</div> -->
|
||||
|
||||
<div class="row g-0 mb-2">
|
||||
<div class="form-group col-md-6 col-sm-12 pe-2">
|
||||
<div class="col-md-6 col-sm-12 pe-2">
|
||||
<label for="settings-port" class="form-label">Port</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="portTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #portTooltip>Port the server listens on. This is fixed if you are running on Docker. Requires restart to take effect.</ng-template>
|
||||
<span class="visually-hidden" id="settings-port-help">Port the server listens on. This is fixed if you are running on Docker. Requires restart to take effect.</span>
|
||||
<input id="settings-port" aria-describedby="settings-port-help" class="form-control" formControlName="port" type="number" step="1" min="1" onkeypress="return event.charCode >= 48 && event.charCode <= 57">
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-6 col-sm-12">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<label for="logging-level-port" class="form-label">Logging Level</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="loggingLevelTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #loggingLevelTooltip>Use debug to help identify issues. Debug can eat up a lot of disk space. Requires restart to take effect.</ng-template>
|
||||
<span class="visually-hidden" id="logging-level-port-help">Port the server listens on. Requires restart to take effect.</span>
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { LibraryDetailComponent } from './library-detail/library-detail.component';
|
||||
import { NotConnectedComponent } from './not-connected/not-connected.component';
|
||||
import { SeriesDetailComponent } from './series-detail/series-detail.component';
|
||||
import { RecentlyAddedComponent } from './recently-added/recently-added.component';
|
||||
import { UserLoginComponent } from './user-login/user-login.component';
|
||||
@ -16,7 +15,6 @@ import { ThemeTestComponent } from './theme-test/theme-test.component';
|
||||
// TODO: Once we modularize the components, use this and measure performance impact: https://angular.io/guide/lazy-loading-ngmodules#preloading-modules
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', component: UserLoginComponent},
|
||||
{
|
||||
path: 'admin',
|
||||
canActivate: [AdminGuard],
|
||||
@ -37,6 +35,10 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
loadChildren: () => import('./reading-list/reading-list.module').then(m => m.ReadingListModule)
|
||||
},
|
||||
{
|
||||
path: 'registration',
|
||||
loadChildren: () => import('../app/registration/registration.module').then(m => m.RegistrationModule)
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
runGuardsAndResolvers: 'always',
|
||||
@ -66,13 +68,10 @@ const routes: Routes = [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'registration',
|
||||
loadChildren: () => import('../app/registration/registration.module').then(m => m.RegistrationModule)
|
||||
},
|
||||
{path: 'login', component: UserLoginComponent}, // TODO: move this to registration module
|
||||
{path: 'no-connection', component: NotConnectedComponent},
|
||||
{path: 'theme', component: ThemeTestComponent},
|
||||
|
||||
{path: '', component: UserLoginComponent},
|
||||
{path: 'login', component: UserLoginComponent}, // TODO: move this to registration module
|
||||
{path: '**', component: UserLoginComponent, pathMatch: 'full'}
|
||||
];
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { AppComponent } from './app.component';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { NgbCollapseModule, NgbDropdownModule, NgbNavModule, NgbPaginationModule, NgbPopoverModule, NgbRatingModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NgbAccordionModule, NgbCollapseModule, NgbDropdownModule, NgbNavModule, NgbPaginationModule, NgbPopoverModule, NgbRatingModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NavHeaderComponent } from './nav-header/nav-header.component';
|
||||
import { JwtInterceptor } from './_interceptors/jwt.interceptor';
|
||||
import { UserLoginComponent } from './user-login/user-login.component';
|
||||
@ -17,7 +17,6 @@ import { LibraryComponent } from './library/library.component';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { LibraryDetailComponent } from './library-detail/library-detail.component';
|
||||
import { SeriesDetailComponent } from './series-detail/series-detail.component';
|
||||
import { NotConnectedComponent } from './not-connected/not-connected.component';
|
||||
import { ReviewSeriesModalComponent } from './_modals/review-series-modal/review-series-modal.component';
|
||||
import { CarouselModule } from './carousel/carousel.module';
|
||||
|
||||
@ -29,7 +28,6 @@ import { CardsModule } from './cards/cards.module';
|
||||
import { CollectionsModule } from './collections/collections.module';
|
||||
import { ReadingListModule } from './reading-list/reading-list.module';
|
||||
import { SAVER, getSaver } from './shared/_providers/saver.provider';
|
||||
import { ConfigData } from './_models/config-data';
|
||||
import { NavEventsToggleComponent } from './nav-events-toggle/nav-events-toggle.component';
|
||||
import { PersonRolePipe } from './_pipes/person-role.pipe';
|
||||
import { SeriesMetadataDetailComponent } from './series-metadata-detail/series-metadata-detail.component';
|
||||
@ -48,7 +46,6 @@ import { ThemeTestComponent } from './theme-test/theme-test.component';
|
||||
LibraryComponent,
|
||||
LibraryDetailComponent,
|
||||
SeriesDetailComponent,
|
||||
NotConnectedComponent, // Move into ExtrasModule
|
||||
ReviewSeriesModalComponent,
|
||||
RecentlyAddedComponent,
|
||||
OnDeckComponent,
|
||||
@ -85,6 +82,8 @@ import { ThemeTestComponent } from './theme-test/theme-test.component';
|
||||
ReadingListModule,
|
||||
RegistrationModule,
|
||||
|
||||
NgbAccordionModule, // ThemeTest Component only
|
||||
|
||||
|
||||
ToastrModule.forRoot({
|
||||
positionClass: 'toast-bottom-right',
|
||||
@ -99,7 +98,7 @@ import { ThemeTestComponent } from './theme-test/theme-test.component';
|
||||
{provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true},
|
||||
Title,
|
||||
{provide: SAVER, useFactory: getSaver},
|
||||
{ provide: APP_BASE_HREF, useFactory: (config: ConfigData) => config.baseUrl, deps: [ConfigData] },
|
||||
// { provide: APP_BASE_HREF, useFactory: (config: ConfigData) => config.baseUrl, deps: [ConfigData] },
|
||||
],
|
||||
entryComponents: [],
|
||||
bootstrap: [AppComponent]
|
||||
|
@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div class="modal-footer" style="justify-content: normal">
|
||||
<div style="width: 100%;">
|
||||
<div class="form-row">
|
||||
<div class="d-flex">
|
||||
<div class="col-9 col-lg-10">
|
||||
<label class="visually-hidden" class="form-label" for="add-rlist">Collection</label>
|
||||
<input width="100%" #title ngbAutofocus type="text" class="form-control mb-2" id="add-rlist" formControlName="title">
|
||||
|
@ -386,7 +386,7 @@
|
||||
|
||||
<ng-template ngbPaginationPages let-page let-pages="pages" *ngIf="pagination.totalItems / pagination.itemsPerPage > 20">
|
||||
<li class="ngb-custom-pages-item" *ngIf="pagination.totalPages > 1">
|
||||
<div class="form-group d-flex flex-nowrap px-2">
|
||||
<div class="d-flex flex-nowrap px-2">
|
||||
<label
|
||||
id="paginationInputLabel-{{id}}"
|
||||
for="paginationInput-{{id}}"
|
||||
|
@ -2,13 +2,6 @@ form {
|
||||
max-height: 38px;
|
||||
}
|
||||
|
||||
// input {
|
||||
// width: 15px;
|
||||
// opacity: 1;
|
||||
// position: relative;
|
||||
// left: 4px;
|
||||
// border: none;
|
||||
// }
|
||||
|
||||
.search-result img {
|
||||
width: 100% !important;
|
||||
@ -114,7 +107,7 @@ form {
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10px;
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.list-group {
|
||||
|
@ -72,16 +72,6 @@
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.form-inline .form-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: var(--grid-breakpoints-sm)) {
|
||||
.form-inline .form-group {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-to-top:hover {
|
||||
animation: MoveUpDown 1s linear infinite;
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
<div class="text-center">
|
||||
<h4 style="margin-top: 100px;">Oops! Looks like the server is not online.</h4>
|
||||
</div>
|
@ -1,27 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { MemberService } from '../_services/member.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-not-connected',
|
||||
templateUrl: './not-connected.component.html',
|
||||
styleUrls: ['./not-connected.component.scss']
|
||||
})
|
||||
export class NotConnectedComponent implements OnInit {
|
||||
constructor(private memberService: MemberService, private router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// BUG: TODO: This causes an infinite reload loop on the UI when the API on backend doesn't exist
|
||||
// We make a call to backend on refresh so that if it's up, we can redirect to /home
|
||||
this.memberService.adminExists().subscribe((exists) => {
|
||||
const pageResume = localStorage.getItem('kavita--no-connection-url');
|
||||
if (pageResume && pageResume !== '/no-connection') {
|
||||
localStorage.setItem('kavita--no-connection-url', '');
|
||||
this.router.navigateByUrl(pageResume);
|
||||
} else {
|
||||
this.router.navigateByUrl('/home');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -28,12 +28,12 @@
|
||||
</div>
|
||||
<div class="modal-footer" style="justify-content: normal">
|
||||
<div style="width: 100%;">
|
||||
<div class="form-row">
|
||||
<div class="d-flex">
|
||||
<div class="col-9 col-lg-10">
|
||||
<label class="form-label visually-hidden" for="add-rlist">Reading List</label>
|
||||
<input width="100%" #title ngbAutofocus type="text" class="form-control mb-2" id="add-rlist" formControlName="title">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<div class="col-2 ps-2">
|
||||
<button type="submit" class="btn btn-primary" (click)="create()">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -52,7 +52,7 @@
|
||||
<app-read-more class="user-review {{userReview ? 'mt-1' : ''}}" [text]="series?.userReview || ''" [maxLength]="250"></app-read-more>
|
||||
</div>
|
||||
<div *ngIf="seriesMetadata" class="mt-2">
|
||||
<app-series-metadata-detail [seriesMetadata]="seriesMetadata" [series]="series"></app-series-metadata-detail>
|
||||
<app-series-metadata-detail [seriesMetadata]="seriesMetadata" [readingLists]="readingLists" [series]="series"></app-series-metadata-detail>
|
||||
</div>
|
||||
|
||||
|
||||
@ -63,7 +63,7 @@
|
||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="nav nav-tabs mb-2" [destroyOnHide]="false" (navChange)="onNavChange($event)">
|
||||
<li [ngbNavItem]="TabID.Specials" *ngIf="hasSpecials">
|
||||
<a ngbNavLink>{{libraryType === LibraryType.Book ? 'Books': 'Specials'}}</a>
|
||||
<a ngbNavLink>Specials</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="row g-0">
|
||||
<ng-container *ngFor="let chapter of specials; let idx = index; trackBy: trackByChapterIdentity">
|
||||
@ -74,7 +74,7 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li [ngbNavItem]="TabID.Storyline" *ngIf="libraryType !== LibraryType.Book && (hasNonSpecialVolumeChapters || hasNonSpecialNonVolumeChapters)">
|
||||
<li [ngbNavItem]="TabID.Storyline" *ngIf="libraryType !== LibraryType.Book && (volumes.length > 0 || chapters.length > 0)">
|
||||
<a ngbNavLink>Storyline</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="row g-0">
|
||||
@ -91,7 +91,7 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li [ngbNavItem]="TabID.Volumes" *ngIf="hasNonSpecialVolumeChapters">
|
||||
<li [ngbNavItem]="TabID.Volumes" *ngIf="volumes.length > 0">
|
||||
<a ngbNavLink>{{libraryType === LibraryType.Book ? 'Books': 'Volumes'}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="row g-0">
|
||||
@ -103,7 +103,7 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li [ngbNavItem]="TabID.Chapters" *ngIf="hasNonSpecialNonVolumeChapters">
|
||||
<li [ngbNavItem]="TabID.Chapters" *ngIf="chapters.length > 0">
|
||||
<a ngbNavLink>{{utilityService.formatChapterName(libraryType) + 's'}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="row g-0">
|
||||
|
@ -19,6 +19,7 @@ import { ScanSeriesEvent } from '../_models/events/scan-series-event';
|
||||
import { SeriesRemovedEvent } from '../_models/events/series-removed-event';
|
||||
import { LibraryType } from '../_models/library';
|
||||
import { MangaFormat } from '../_models/manga-format';
|
||||
import { ReadingList } from '../_models/reading-list';
|
||||
import { Series } from '../_models/series';
|
||||
import { SeriesMetadata } from '../_models/series-metadata';
|
||||
import { Volume } from '../_models/volume';
|
||||
@ -29,6 +30,7 @@ import { ImageService } from '../_services/image.service';
|
||||
import { LibraryService } from '../_services/library.service';
|
||||
import { EVENTS, MessageHubService } from '../_services/message-hub.service';
|
||||
import { ReaderService } from '../_services/reader.service';
|
||||
import { ReadingListService } from '../_services/reading-list.service';
|
||||
import { SeriesService } from '../_services/series.service';
|
||||
|
||||
|
||||
@ -72,12 +74,11 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
hasSpecials = false;
|
||||
specials: Array<Chapter> = [];
|
||||
activeTabId = TabID.Storyline;
|
||||
hasNonSpecialVolumeChapters = false;
|
||||
hasNonSpecialNonVolumeChapters = false;
|
||||
|
||||
userReview: string = '';
|
||||
libraryType: LibraryType = LibraryType.Manga;
|
||||
seriesMetadata: SeriesMetadata | null = null;
|
||||
readingLists: Array<ReadingList> = [];
|
||||
/**
|
||||
* Poster image for the Series
|
||||
*/
|
||||
@ -171,6 +172,7 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
private confirmService: ConfirmService, private titleService: Title,
|
||||
private downloadService: DownloadService, private actionService: ActionService,
|
||||
public imageSerivce: ImageService, private messageHub: MessageHubService,
|
||||
private readingListService: ReadingListService
|
||||
) {
|
||||
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
@ -332,6 +334,9 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
this.coverImageOffset = 0;
|
||||
|
||||
this.seriesService.getMetadata(seriesId).subscribe(metadata => this.seriesMetadata = metadata);
|
||||
this.readingListService.getReadingListsForSeries(seriesId).subscribe(lists => {
|
||||
this.readingLists = lists;
|
||||
});
|
||||
this.setContinuePoint();
|
||||
|
||||
forkJoin([
|
||||
@ -352,7 +357,7 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this));
|
||||
|
||||
this.seriesService.getSeriesDetail(this.seriesId).subscribe(detail => {
|
||||
this.hasSpecials = detail.specials.length > 0
|
||||
this.hasSpecials = detail.specials.length > 0;
|
||||
this.specials = detail.specials;
|
||||
|
||||
this.chapters = detail.chapters;
|
||||
@ -373,30 +378,23 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
* This assumes loadPage() has already primed all the calculations and state variables. Do not call directly.
|
||||
*/
|
||||
updateSelectedTab() {
|
||||
// This shows Chapters/Issues tab
|
||||
|
||||
// If this has chapters that are not specials
|
||||
if (this.chapters.filter(c => !c.isSpecial).length > 0) {
|
||||
this.hasNonSpecialNonVolumeChapters = true;
|
||||
|
||||
// Book libraries only have Volumes or Specials enabled
|
||||
if (this.libraryType === LibraryType.Book) {
|
||||
if (this.volumes.length === 0) {
|
||||
this.activeTabId = TabID.Specials;
|
||||
} else {
|
||||
this.activeTabId = TabID.Volumes;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// This shows Volumes tab
|
||||
if (this.volumes.filter(v => v.number !== 0).length !== 0) {
|
||||
this.hasNonSpecialVolumeChapters = true;
|
||||
}
|
||||
|
||||
// If an update occured and we were on specials, re-activate Volumes/Chapters
|
||||
if (!this.hasSpecials && !this.hasNonSpecialVolumeChapters && this.activeTabId != TabID.Storyline) {
|
||||
this.activeTabId = TabID.Storyline;
|
||||
}
|
||||
|
||||
if (this.libraryType == LibraryType.Book && !this.hasSpecials){
|
||||
this.activeTabId = TabID.Volumes;
|
||||
} else if (this.hasNonSpecialVolumeChapters || this.hasNonSpecialNonVolumeChapters) {
|
||||
this.activeTabId = TabID.Storyline;
|
||||
} else {
|
||||
if (this.volumes.length === 0 && this.chapters.length === 0 && this.specials.length > 0) {
|
||||
this.activeTabId = TabID.Specials;
|
||||
} else {
|
||||
this.activeTabId = TabID.Storyline;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
createHTML() {
|
||||
|
@ -14,6 +14,9 @@
|
||||
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="goTo('format', series.format)" [selectionMode]="TagBadgeCursor.Clickable">
|
||||
<app-series-format [format]="series.format">{{utilityService.mangaFormat(series.format)}}</app-series-format>
|
||||
</app-tag-badge>
|
||||
<app-tag-badge title="Last Read" class="col-auto" *ngIf="series.latestReadDate && series.latestReadDate !== '' && (series.latestReadDate | date: 'shortDate') !== '1/1/01'" [selectionMode]="TagBadgeCursor.Selectable">
|
||||
Last Read: {{series.latestReadDate | date:'shortDate'}}
|
||||
</app-tag-badge>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@ -43,6 +46,25 @@
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-0 mt-1" *ngIf="readingLists && readingLists.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Reading Lists</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="readingLists">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-tag-badge a11y-click="13,32" class="col-auto" routerLink="/lists/{{item.id}}" [selectionMode]="TagBadgeCursor.Clickable">
|
||||
<!-- TODO: Build a promoted badge code -->
|
||||
<span *ngIf="item.promoted">
|
||||
<i class="fa fa-angle-double-up" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">(promoted)</span>
|
||||
</span>
|
||||
{{item.title}}
|
||||
</app-tag-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-0 mt-1" *ngIf="seriesMetadata.writers && seriesMetadata.writers.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Writers/Authors</h5>
|
||||
|
@ -3,6 +3,7 @@ import { Router } from '@angular/router';
|
||||
import { TagBadgeCursor } from '../shared/tag-badge/tag-badge.component';
|
||||
import { UtilityService } from '../shared/_services/utility.service';
|
||||
import { MangaFormat } from '../_models/manga-format';
|
||||
import { ReadingList } from '../_models/reading-list';
|
||||
import { Series } from '../_models/series';
|
||||
import { SeriesMetadata } from '../_models/series-metadata';
|
||||
import { MetadataService } from '../_services/metadata.service';
|
||||
@ -15,6 +16,10 @@ import { MetadataService } from '../_services/metadata.service';
|
||||
export class SeriesMetadataDetailComponent implements OnInit, OnChanges {
|
||||
|
||||
@Input() seriesMetadata!: SeriesMetadata;
|
||||
/**
|
||||
* Reading lists with a connection to the Series
|
||||
*/
|
||||
@Input() readingLists: Array<ReadingList> = [];
|
||||
@Input() series!: Series;
|
||||
|
||||
isCollapsed: boolean = true;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="badge-expander">
|
||||
<div class="content">
|
||||
<ng-container *ngFor="let item of visibleItems; index as i;" [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
||||
<button type="button" *ngIf="!isCollapsed && itemsLeft !== 0" class="btn btn-outline-primary" (click)="toggleVisible()" [attr.aria-expanded]="!isCollapsed">
|
||||
<button type="button" *ngIf="!isCollapsed && itemsLeft !== 0" class="btn btn-sm btn-outline-primary" (click)="toggleVisible()" [attr.aria-expanded]="!isCollapsed">
|
||||
and {{itemsLeft}} more
|
||||
</button>
|
||||
</div>
|
||||
|
@ -5,8 +5,4 @@
|
||||
.collapsed {
|
||||
height: 35px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.badge-expander {
|
||||
//display: inline-block;
|
||||
}
|
@ -8,6 +8,7 @@ import { Component, ContentChild, Input, OnInit, TemplateRef } from '@angular/co
|
||||
export class BadgeExpanderComponent implements OnInit {
|
||||
|
||||
@Input() items: Array<any> = [];
|
||||
@Input() itemsTillExpander: number = 4;
|
||||
@ContentChild('badgeExpanderItem') itemTemplate!: TemplateRef<any>;
|
||||
|
||||
|
||||
@ -15,12 +16,12 @@ export class BadgeExpanderComponent implements OnInit {
|
||||
isCollapsed: boolean = false;
|
||||
|
||||
get itemsLeft() {
|
||||
return Math.max(this.items.length - 4, 0);
|
||||
return Math.max(this.items.length - this.itemsTillExpander, 0);
|
||||
}
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.visibleItems = this.items.slice(0, 4);
|
||||
this.visibleItems = this.items.slice(0, this.itemsTillExpander);
|
||||
}
|
||||
|
||||
toggleVisible() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="tagbadge cursor clickable">
|
||||
<div class="tagbadge cursor clickable" *ngIf="person !== undefined">
|
||||
<div class="d-flex">
|
||||
<!-- <img src="..." class="align-self-center me-3" alt="..."> -->
|
||||
<i class="fa fa-user-circle align-self-center me-2" aria-hidden="true"></i>
|
||||
|
@ -39,7 +39,7 @@
|
||||
<div class="mb-3">
|
||||
<label for="stat-collection" class="form-label" aria-describedby="collection-info">Allow Anonymous Usage Collection</label>
|
||||
<div class="form-check">
|
||||
<input id="stat-collection" type="checkbox" aria-label="Stat Collection" class="form-check-input" formControlName="allowStatCollection">
|
||||
<input id="stat-collection" type="checkbox" aria-label="Stat Collection" class="form-check-input">
|
||||
<label for="stat-collection" class="form-check-label">Normal Checkbox</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -47,7 +47,7 @@
|
||||
<div class="mb-3">
|
||||
<label for="stat-collection" class="form-label" aria-describedby="collection-info">Allow Anonymous Usage Collection</label>
|
||||
<div class="form-check">
|
||||
<input id="stat-collection" type="checkbox" aria-label="Stat Collection" class="form-check-input" formControlName="allowStatCollection" [disabled]="true">
|
||||
<input id="stat-collection" type="checkbox" aria-label="Stat Collection" class="form-check-input" [disabled]="true">
|
||||
<label for="stat-collection" class="form-check-label">Disabled Checkbox</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -56,20 +56,20 @@
|
||||
<p>Labels should have form-check-label on them and inputs form-check-input</p>
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="site-dark-mode" formControlName="siteDarkMode" [value]="true" aria-labelledby="site-dark-mode-label">
|
||||
<input class="form-check-input" type="radio" id="site-dark-mode" [value]="true" aria-labelledby="site-dark-mode-label">
|
||||
<label class="form-check-label" for="site-dark-mode">True</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="site-not-dark-mode" formControlName="siteDarkMode" [value]="false" aria-labelledby="site-dark-mode-label">
|
||||
<label class="form-check-label" for="site-not-dark-mode">False</label>
|
||||
<input class="form-check-input" type="radio" id="site-not-dark-mode2" [value]="false" aria-labelledby="site-dark-mode-label">
|
||||
<label class="form-check-label" for="site-not-dark-mode2">False</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="site-not-dark-mode" formControlName="siteDarkMode" [disabled]="true" [value]="false" aria-labelledby="site-dark-mode-label">
|
||||
<label class="form-check-label" for="site-not-dark-mode">Disabled</label>
|
||||
<input class="form-check-input" type="radio" id="site-not-dark-mode3" [disabled]="true" [value]="false" aria-labelledby="site-dark-mode-label">
|
||||
<label class="form-check-label" for="site-not-dark-mode3">Disabled</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="site-not-dark-mode" formControlName="siteDarkMode" readonly [value]="false" aria-labelledby="site-dark-mode-label">
|
||||
<label class="form-check-label" for="site-not-dark-mode">Readonly</label>
|
||||
<input class="form-check-input" type="radio" id="site-not-dark-mode4" readonly [value]="false" aria-labelledby="site-dark-mode-label">
|
||||
<label class="form-check-label" for="site-not-dark-mode4">Readonly</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -116,11 +116,28 @@
|
||||
<app-tag-badge [selectionMode]="TagBadgeCursor.NotAllowed">Non Allowed</app-tag-badge>
|
||||
</div>
|
||||
|
||||
<h2>Badge</h2>
|
||||
TODO
|
||||
<h2>Person Badge with Expander</h2>
|
||||
<div class="g-2">
|
||||
<app-person-badge></app-person-badge>
|
||||
<app-badge-expander [items]="people" [itemsTillExpander]="1">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
|
||||
<h2>Switch</h2>
|
||||
TODO: New component, will be used in place of 2 way radios
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label id="auto-close-label" class="form-label"></label>
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" id="auto-close" class="form-check-input" aria-labelledby="auto-close-label">
|
||||
<label class="form-check-label" for="auto-close">Auto Close Menu</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h2>Dropdown/List Group</h2>
|
||||
<div class="dropdown" >
|
||||
@ -130,5 +147,42 @@ TODO: New component, will be used in place of 2 way radios
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2></h2>
|
||||
<h2>Accordion</h2>
|
||||
<ngb-accordion [closeOthers]="true" activeIds="reading-panel" #acc="ngbAccordion">
|
||||
<ngb-panel id="reading-panel" title="Reading">
|
||||
<ng-template ngbPanelHeader>
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button" ngbPanelToggle type="button" [attr.aria-expanded]="acc.isExpanded('reading-panel')" aria-controls="collapseOne">
|
||||
Reading
|
||||
</button>
|
||||
</h2>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<p>This is the body of the accordion...........This is the body of the accordion asdfasdf asThis is the body of the accordion asdfasdf asThis is the body of the accordion asdfasdf asThis is the body of the accordion asdfasdf asThis is the body of the accordion asdfasdf as</p>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
|
||||
<ngb-panel id="reading-panel2">
|
||||
<ng-template ngbPanelHeader>
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button" ngbPanelToggle type="button" [attr.aria-expanded]="acc.isExpanded('reading-panel')" aria-controls="collapseOne">
|
||||
Header 2
|
||||
</button>
|
||||
</h2>
|
||||
</ng-template>
|
||||
<ng-template ngbPanelContent>
|
||||
<p>This is the body of the accordion asdfasdf as
|
||||
dfas
|
||||
f asdfasdfasdf asdfasdfaaff asdf
|
||||
as fd
|
||||
asfasf asdfasdfafd
|
||||
</p>
|
||||
</ng-template>
|
||||
</ngb-panel>
|
||||
</ngb-accordion>
|
||||
|
||||
|
||||
<h2>Cards</h2>
|
||||
<app-card-item [entity]="seriesNotRead"></app-card-item>
|
||||
<app-card-item [entity]="seriesNotRead" [count]="10"></app-card-item>
|
||||
<app-card-item [entity]="seriesWithProgress"></app-card-item>
|
||||
|
@ -2,6 +2,9 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { TagBadgeCursor } from '../shared/tag-badge/tag-badge.component';
|
||||
import { ThemeService } from '../theme.service';
|
||||
import { MangaFormat } from '../_models/manga-format';
|
||||
import { Person, PersonRole } from '../_models/person';
|
||||
import { Series } from '../_models/series';
|
||||
import { NavService } from '../_services/nav.service';
|
||||
|
||||
@Component({
|
||||
@ -20,6 +23,47 @@ export class ThemeTestComponent implements OnInit {
|
||||
];
|
||||
active = this.tabs[0];
|
||||
|
||||
people: Array<Person> = [
|
||||
{id: 1, name: 'Joe', role: PersonRole.Artist},
|
||||
{id: 2, name: 'Joe 2', role: PersonRole.Artist},
|
||||
];
|
||||
|
||||
seriesNotRead: Series = {
|
||||
id: 1,
|
||||
name: 'Test Series',
|
||||
pages: 0,
|
||||
pagesRead: 10,
|
||||
format: MangaFormat.ARCHIVE,
|
||||
libraryId: 1,
|
||||
coverImageLocked: false,
|
||||
created: '',
|
||||
latestReadDate: '',
|
||||
localizedName: '',
|
||||
originalName: '',
|
||||
sortName: '',
|
||||
userRating: 0,
|
||||
userReview: '',
|
||||
volumes: []
|
||||
}
|
||||
|
||||
seriesWithProgress: Series = {
|
||||
id: 1,
|
||||
name: 'Test Series',
|
||||
pages: 5,
|
||||
pagesRead: 10,
|
||||
format: MangaFormat.ARCHIVE,
|
||||
libraryId: 1,
|
||||
coverImageLocked: false,
|
||||
created: '',
|
||||
latestReadDate: '',
|
||||
localizedName: '',
|
||||
originalName: '',
|
||||
sortName: '',
|
||||
userRating: 0,
|
||||
userReview: '',
|
||||
volumes: []
|
||||
}
|
||||
|
||||
get TagBadgeCursor(): typeof TagBadgeCursor {
|
||||
return TagBadgeCursor;
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ export class UserLoginComponent implements OnInit {
|
||||
|
||||
// Check if user came here from another url, else send to library route
|
||||
const pageResume = localStorage.getItem('kavita--auth-intersection-url');
|
||||
if (pageResume && pageResume !== '/no-connection' && pageResume !== '/login') {
|
||||
if (pageResume && pageResume !== '/login') {
|
||||
localStorage.setItem('kavita--auth-intersection-url', '');
|
||||
this.router.navigateByUrl(pageResume);
|
||||
} else {
|
||||
|
@ -59,7 +59,7 @@
|
||||
<h3 id="manga-header">Image Reader</h3>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="form-group col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-reading-direction" class="form-label">Reading Direction</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="readingDirectionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #readingDirectionTooltip>Direction to click to move to next page. Right to Left means you click on left side of screen to move to next page.</ng-template>
|
||||
<span class="visually-hidden" id="settings-reading-direction-help">Direction to click to move to next page. Right to Left means you click on left side of screen to move to next page.</span>
|
||||
@ -68,7 +68,7 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-scaling-option" class="form-label">Scaling Options</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="taskBackupTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #taskBackupTooltip>How to scale the image to your screen.</ng-template>
|
||||
<span class="visually-hidden" id="settings-scaling-option-help">How to scale the image to your screen.</span>
|
||||
@ -79,7 +79,7 @@
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="form-group col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-pagesplit-option" class="form-label">Page Splitting</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="pageSplitOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #pageSplitOptionTooltip>How to split a full width image (ie both left and right images are combined)</ng-template>
|
||||
<span class="visually-hidden" id="settings-pagesplit-option-help">How to split a full width image (ie both left and right images are combined)</span>
|
||||
@ -87,7 +87,7 @@
|
||||
<option *ngFor="let opt of pageSplitOptions" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-2">
|
||||
<label for="settings-readingmode-option" class="form-label">Reading Mode</label>
|
||||
<select class="form-select" aria-describedby="manga-header" formControlName="readerMode" id="settings-readingmode-option">
|
||||
<option *ngFor="let opt of readingModes" [value]="opt.value">{{opt.text | titlecase}}</option>
|
||||
@ -97,7 +97,6 @@
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<!-- TODO: Turn this into a checkbox -->
|
||||
<label id="auto-close-label" class="form-label"></label>
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
@ -109,7 +108,7 @@
|
||||
<hr>
|
||||
<h3>Book Reader</h3>
|
||||
<div class="row g-0">
|
||||
<div class="form-group col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label id="dark-mode-label" class="form-label"></label>
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
@ -119,7 +118,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label id="taptopaginate-label" class="form-label"></label>
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
@ -133,7 +132,7 @@
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="form-group col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-book-reading-direction" class="form-label">Book Reading Direction</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="bookReadingDirectionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookReadingDirectionTooltip>Direction to click to move to next page. Right to Left means you click on left side of screen to move to next page.</ng-template>
|
||||
<span class="visually-hidden" id="settings-reading-direction-help">Direction to click to move to next page. Right to Left means you click on left side of screen to move to next page.</span>
|
||||
@ -143,7 +142,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<div class="col-md-6 col-sm-12 pe-2 mb-3">
|
||||
<label for="settings-fontfamily-option" class="form-label">Font Family</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="fontFamilyOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #fontFamilyOptionTooltip>Font familty to load up. Default will load the book's default font</ng-template>
|
||||
<span class="visually-hidden" id="settings-fontfamily-option-help">Font familty to load up. Default will load the book's default font</span>
|
||||
@ -156,18 +155,18 @@
|
||||
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="form-group col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<label id="font-size" class="form-label">Font Size</label>
|
||||
<div class="custom-slider"><ngx-slider [options]="bookReaderFontSizeOptions" formControlName="bookReaderFontSize" aria-labelledby="font-size"></ngx-slider></div>
|
||||
</div>
|
||||
<div class="form-group col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<label class="form-label">Line Height</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="bookLineHeightOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookLineHeightOptionTooltip>How much spacing between the lines of the book</ng-template>
|
||||
<span class="visually-hidden" id="settings-booklineheight-option-help">How much spacing between the lines of the book</span>
|
||||
<div class="custom-slider"><ngx-slider [options]="bookReaderLineSpacingOptions" formControlName="bookReaderLineSpacing" aria-describedby="settings-booklineheight-option-help"></ngx-slider></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<div class="col-md-4 col-sm-12 pe-2 mb-3">
|
||||
<label class="form-label">Margin</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="bookReaderMarginOptionTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookReaderMarginOptionTooltip>How much spacing on each side of the screen. This will override to 0 on mobile devices regardless of this setting.</ng-template>
|
||||
<span class="visually-hidden" id="settings-bookmargin-option-help">How much spacing on each side of the screen. This will override to 0 on mobile devices regardless of this setting.</span>
|
||||
|
@ -1,22 +1,20 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { ConfigData } from './app/_models/config-data';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
function fetchConfig(): Promise<ConfigData> {
|
||||
return fetch(environment.apiUrl + 'settings/base-url')
|
||||
.then(response => response.text())
|
||||
.then(response => new ConfigData(response));
|
||||
}
|
||||
// function fetchConfig(): Promise<ConfigData> {
|
||||
// return fetch(environment.apiUrl + 'settings/base-url')
|
||||
// .then(response => response.text())
|
||||
// .then(response => new ConfigData(response));
|
||||
// }
|
||||
|
||||
fetchConfig().then(config => {
|
||||
platformBrowserDynamic([ { provide: ConfigData, useValue: config } ])
|
||||
.bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
});
|
||||
// fetchConfig().then(config => {
|
||||
|
||||
// });
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
@ -57,6 +57,10 @@ label, select, .clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
html, body { height: 100%; color-scheme: var(--color-scheme); }
|
||||
body {
|
||||
margin: 0;
|
||||
|
@ -1,11 +1,11 @@
|
||||
.tagbadge {
|
||||
border-color: var(--tagbadge-border-color);
|
||||
border: 1px solid var(--tagbadge-border-color);
|
||||
color: var(--tagbadge-text-color);
|
||||
background-color: var(--tagbadge-bg-color);
|
||||
}
|
||||
|
||||
.typeahead-input .tagbadge {
|
||||
border-color: var(--tagbadge-typeahead-border-color);
|
||||
border: 1px solid var(--tagbadge-typeahead-border-color);
|
||||
color: var(--tagbadge-typeahead-text-color);
|
||||
background-color: var(--tagbadge-typeahead-bg-color);
|
||||
}
|
@ -94,7 +94,7 @@
|
||||
--list-group-item-bg-color: #343a40;
|
||||
--list-group-item-border-color: rgba(239, 239, 239, 0.125);
|
||||
--list-group-hover-text-color: white;
|
||||
--list-group-hover-bg-color: var(--accordion-body-bg-color);
|
||||
--list-group-hover-bg-color: rgb(22, 27, 34);
|
||||
--list-group-active-border-color: none;
|
||||
|
||||
/* Popover */
|
||||
|
Loading…
x
Reference in New Issue
Block a user