Custom Theme Support (#1077)
* Started the migration to bootstrap 5. Introduced a breakpoint system that bootstrap reflects for our screens. * sr only migrated * mr/ml -> me/ms * pl/pr -> ps/pe * btn-block * removed input-group-append * Added form-label to all labels * Added some style overrides for inputs * Replaced form-group with mb-3 * Ignore journal files * Update media to d-flex/flex-grow-1 * Fixed reading list detail page * For develop builds, don't inline critical styles * Fixed some downstream security issues * Fixed a layout issue in series detail * Fixed issue with btn-light not having background color. Updated layout for series detail metadata * Cleaned up nav search * Laid out the organization for custom theme components. Update _inputs.scss with variable overrides and depending on theme, it will just work. * Lots of theming work * Added inputs to the theme page * Login and input placeholder changes - Fixed login screen centering issue on all devices - Changed the format of the login screen - Change the input placeholder color * Added checkbox styles * Refactored tagbadges and removed some ngdeep selectors * Added nav bar component and refactored some styles into event widget * Cleaned nav events again and made dedicated popover body * Finished pagination component * Fixed up some styles with buttons * refactored dropdown component * Update accordion component * Refactored breadcrumbs and rating star. Fixed a missing style for cards * Fixed some styling issues on person badge, added modal component, and some global styles * Finished moving everything within dark to component files * Fixed up filter buttons, move card styles into a component theme, fixed slider style * Refactored library card and grouped typeahead * Updated normal typeahead component and reduced amount of ngdeep selector * Refactored grid breakpoints to be available by css variable, but it's hardcoded into the app * Ensure breakpoints are defined per theme * Fixed up some styling overrides and customization for nav links and alt button * Removed some deep styles, moved css out of splash container and brough back labels for login page * Finished css variable refactor * Refactored all the theme variable definitions into files for each theme. * Added back bootstrap overrides * Added a note about bootstrap theme colors being not-possible to swap out at runtime * Cleaned up some dead code * Implemented the ability to set a custom theme on the site. Cleaned up misc code throughout. * Additional changes - Fixed nav where "kavita" was not hiding correctly on small viewports - Fixed search bar to make the behavior more consistent - Fixed accordion buttons - Changed accordion buttons to be more responsive - Added radio button colors - Fixed radios on theme test page - Changed login and reset password card layouts to be more consistent. - Added primary color shade for when darker shading is needed. * Built a basic site, allow the user to apply different themes, refactored nav service code out. * Implemented the ability update a user's theme * Added unit tests for Scan and Get Content in SiteThemeService. * Fixed a bug in the login code and Pref code which wasn't joining on SiteTheme table. Wrote Unit tests and the UI component to manage current theme. * Implemented scan so that it manages custom themes with unit tests * Component updates - Repositioning style ordering - Adding indicator override - Adding select styles * SignlaR integration, some fixes when creating custom entities, one single migration. Just login functionality left. * More ui updated - Added .no-hover to prevent hover on elements where not needed - Changed all selects I could find to appropriate class - Changed up nav tabs to work more like bootstrap tabs than pills - Added padding to top of some containers to make styles consistent - Added ability to change navbar fontawesome icon colors - removed some unecessary inline styling - Changed radio button to appropriate class - Toned down primate color, a bit too bright for dark theme. - Added ability to change button fontawesome icon color * nav-tab fix for series-detail * Added themes folder to gitignore * Adding card overlay * Fixing up light theme * Everything is done. Only bug is that color-scheme isn't being set properly from css variable. * Checkboxes have pointer by default. Confirm/Confirm email use default (dark) theme by default * Fixed an error where color-scheme wasn't reflecting correctly on themes on first load * Fixed user preferences not available on login * Changing dual radios to switches and color tweaks * disabled primary APCA fix * button APCA fixes * Fixed some timing issues with first load and image service * Fixed swiper issues from upgrade * Changed themes to be scss files again and adjusted Seed code * Migrated carousel to css variables. Fixed a broken animation for search. * Cleaned up some backend smells * Fixed white border outline on nav tabs, added some variables for header * Nav bar has been css variable-ified * Added some basic eink stuff to make the app useable Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
parent
c776ca3b72
commit
568ea9fd3a
168 changed files with 4710 additions and 1666 deletions
|
|
@ -2,29 +2,29 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">
|
||||
{{series.name}} Review</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form [formGroup]="reviewGroup">
|
||||
<div class="form-group">
|
||||
<label for="rating">Rating</label>
|
||||
<div class="row g-0">
|
||||
<label for="rating" class="form-label">Rating</label>
|
||||
<div>
|
||||
<ngb-rating style="margin-top: 2px; font-size: 1.5rem;" formControlName="rating"></ngb-rating>
|
||||
<button class="btn btn-information ml-2" (click)="clearRating()"><i aria-hidden="true" class="fa fa-ban"></i><span class="phone-hidden"> Clear</span></button>
|
||||
<button class="btn btn-icon ms-2" (click)="clearRating()"><i aria-hidden="true" class="fa fa-ban"></i><span class="phone-hidden"> Clear</span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="review">Review</label>
|
||||
<div class="row g-0">
|
||||
<label for="review" class="form-label">Review</label>
|
||||
<textarea id="review" class="form-control" formControlName="review" rows="3"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-secondary" (click)="close()">Close</button>
|
||||
<button class="btn btn-secondary" (click)="close()">Close</button>
|
||||
<button type="submit" class="btn btn-primary" (click)="save()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
export interface SiteThemeProgressEvent {
|
||||
totalUpdates: number;
|
||||
currentCount: number;
|
||||
themeName: string;
|
||||
progress: number;
|
||||
eventTime: string;
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import { PageSplitOption } from './page-split-option';
|
|||
import { READER_MODE } from './reader-mode';
|
||||
import { ReadingDirection } from './reading-direction';
|
||||
import { ScalingOption } from './scaling-option';
|
||||
import { SiteTheme } from './site-theme';
|
||||
|
||||
export interface Preferences {
|
||||
// Manga Reader
|
||||
|
|
@ -22,7 +23,7 @@ export interface Preferences {
|
|||
bookReaderReadingDirection: ReadingDirection;
|
||||
|
||||
// Global
|
||||
siteDarkMode: boolean;
|
||||
theme: SiteTheme;
|
||||
}
|
||||
|
||||
export const readingDirections = [{text: 'Left to Right', value: ReadingDirection.LeftToRight}, {text: 'Right to Left', value: ReadingDirection.RightToLeft}];
|
||||
|
|
|
|||
22
UI/Web/src/app/_models/preferences/site-theme.ts
Normal file
22
UI/Web/src/app/_models/preferences/site-theme.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Where does the theme come from
|
||||
*/
|
||||
export enum ThemeProvider {
|
||||
System = 1,
|
||||
User = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme for the whole instance
|
||||
*/
|
||||
export interface SiteTheme {
|
||||
id: number;
|
||||
name: string;
|
||||
filePath: string;
|
||||
isDefault: boolean;
|
||||
provider: ThemeProvider;
|
||||
/**
|
||||
* The actual class the root is defined against. It is generated at the backend.
|
||||
*/
|
||||
selector: string;
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import { Preferences } from '../_models/preferences/preferences';
|
|||
import { User } from '../_models/user';
|
||||
import { Router } from '@angular/router';
|
||||
import { MessageHubService } from './message-hub.service';
|
||||
import { ThemeService } from '../theme.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
@ -33,7 +34,7 @@ export class AccountService implements OnDestroy {
|
|||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
constructor(private httpClient: HttpClient, private router: Router,
|
||||
private messageHub: MessageHubService) {}
|
||||
private messageHub: MessageHubService, private themeService: ThemeService) {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
|
|
@ -61,6 +62,7 @@ export class AccountService implements OnDestroy {
|
|||
map((response: User) => {
|
||||
const user = response;
|
||||
if (user) {
|
||||
console.log('Login: ', user);
|
||||
this.setCurrentUser(user);
|
||||
this.messageHub.createHubConnection(user, this.hasAdminRole(user));
|
||||
}
|
||||
|
|
@ -77,6 +79,11 @@ export class AccountService implements OnDestroy {
|
|||
|
||||
localStorage.setItem(this.userKey, JSON.stringify(user));
|
||||
localStorage.setItem(this.lastLoginKey, user.username);
|
||||
if (user.preferences) {
|
||||
this.themeService.setTheme(user.preferences.theme.name);
|
||||
} else {
|
||||
this.themeService.setTheme(this.themeService.defaultTheme);
|
||||
}
|
||||
}
|
||||
|
||||
this.currentUserSource.next(user);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { Inject, Injectable, OnDestroy } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { ThemeService } from '../theme.service';
|
||||
import { RecentlyAddedItem } from '../_models/recently-added-item';
|
||||
import { AccountService } from './account.service';
|
||||
import { NavService } from './nav.service';
|
||||
|
|
@ -19,9 +21,9 @@ export class ImageService implements OnDestroy {
|
|||
|
||||
private onDestroy: Subject<void> = new Subject();
|
||||
|
||||
constructor(private navSerivce: NavService, private accountService: AccountService) {
|
||||
this.navSerivce.darkMode$.subscribe(res => {
|
||||
if (res) {
|
||||
constructor(private accountService: AccountService, private themeService: ThemeService) {
|
||||
this.themeService.currentTheme$.pipe(takeUntil(this.onDestroy)).subscribe(theme => {
|
||||
if (this.themeService.isDarkTheme()) {
|
||||
this.placeholderImage = 'assets/images/image-placeholder.dark-min.png';
|
||||
this.errorImage = 'assets/images/error-placeholder2.dark-min.png';
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
import { EventEmitter, Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { BehaviorSubject, ReplaySubject } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { UpdateNotificationModalComponent } from '../shared/update-notification/update-notification-modal.component';
|
||||
import { RefreshMetadataEvent } from '../_models/events/refresh-metadata-event';
|
||||
import { ProgressEvent } from '../_models/events/scan-library-progress-event';
|
||||
import { ScanSeriesEvent } from '../_models/events/scan-series-event';
|
||||
import { SeriesAddedEvent } from '../_models/events/series-added-event';
|
||||
import { SiteThemeProgressEvent } from '../_models/events/site-theme-progress-event';
|
||||
import { User } from '../_models/user';
|
||||
|
||||
export enum EVENTS {
|
||||
|
|
@ -25,6 +23,10 @@ export enum EVENTS {
|
|||
BackupDatabaseProgress = 'BackupDatabaseProgress',
|
||||
CleanupProgress = 'CleanupProgress',
|
||||
DownloadProgress = 'DownloadProgress',
|
||||
/**
|
||||
* A custom user site theme is added or removed during a scan
|
||||
*/
|
||||
SiteThemeProgress = 'SiteThemeProgress',
|
||||
/**
|
||||
* A cover is updated
|
||||
*/
|
||||
|
|
@ -122,6 +124,13 @@ export class MessageHubService {
|
|||
});
|
||||
});
|
||||
|
||||
this.hubConnection.on(EVENTS.SiteThemeProgress, resp => {
|
||||
this.messagesSource.next({
|
||||
event: EVENTS.SiteThemeProgress,
|
||||
payload: resp.body as SiteThemeProgressEvent
|
||||
});
|
||||
});
|
||||
|
||||
this.hubConnection.on(EVENTS.SeriesAddedToCollection, resp => {
|
||||
this.messagesSource.next({
|
||||
event: EVENTS.SeriesAddedToCollection,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Injectable, Renderer2, RendererFactory2 } from '@angular/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
|
|
@ -9,14 +9,7 @@ export class NavService {
|
|||
private navbarVisibleSource = new ReplaySubject<boolean>(1);
|
||||
navbarVisible$ = this.navbarVisibleSource.asObservable();
|
||||
|
||||
private darkMode: boolean = true;
|
||||
private darkModeSource = new ReplaySubject<boolean>(1);
|
||||
darkMode$ = this.darkModeSource.asObservable();
|
||||
|
||||
private renderer: Renderer2;
|
||||
|
||||
constructor(rendererFactory: RendererFactory2) {
|
||||
this.renderer = rendererFactory.createRenderer(null, null);
|
||||
constructor() {
|
||||
this.showNavBar();
|
||||
}
|
||||
|
||||
|
|
@ -27,26 +20,4 @@ export class NavService {
|
|||
hideNavBar() {
|
||||
this.navbarVisibleSource.next(false);
|
||||
}
|
||||
|
||||
toggleDarkMode() {
|
||||
this.darkMode = !this.darkMode;
|
||||
this.updateColorScheme();
|
||||
this.darkModeSource.next(this.darkMode);
|
||||
}
|
||||
|
||||
setDarkMode(mode: boolean) {
|
||||
this.darkMode = mode;
|
||||
this.updateColorScheme();
|
||||
this.darkModeSource.next(this.darkMode);
|
||||
}
|
||||
|
||||
private updateColorScheme() {
|
||||
if (this.darkMode) {
|
||||
this.renderer.setStyle(document.querySelector('html'), 'color-scheme', 'dark');
|
||||
} else {
|
||||
this.renderer.setStyle(document.querySelector('html'), 'color-scheme', 'light');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,25 @@
|
|||
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">Choose a Directory</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="filter">Filter</label>
|
||||
<div class="mb-3">
|
||||
<label for="filter" class="form-label">Filter</label>
|
||||
<div class="input-group">
|
||||
<input id="filter" autocomplete="off" class="form-control" [(ngModel)]="filterQuery" type="text" aria-describedby="reset-input">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" id="reset-input" (click)="filterQuery = '';">Clear</button>
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary" type="button" id="reset-input" (click)="filterQuery = '';">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<nav aria-label="directory breadcrumb">
|
||||
<ol class="breadcrumb" *ngIf="routeStack.peek() !== undefined; else noBreadcrumb">
|
||||
<li class="breadcrumb-item {{route === routeStack.peek() ? 'active' : ''}}" *ngFor="let route of routeStack.items; let index = index">
|
||||
<li class="breadcrumb-item {{route === routeStack.peek() ? 'active' : ''}}" *ngFor="let route of routeStack.items; let index = index">
|
||||
<ng-container *ngIf="route === routeStack.peek(); else nonActive">
|
||||
{{route}}
|
||||
</ng-container>
|
||||
<ng-template #nonActive>
|
||||
<a href="javascript:void(0);" (click)="navigateTo(index)">{{route}}</a>
|
||||
</ng-template>
|
||||
</li>
|
||||
</li>
|
||||
</ol>
|
||||
<ng-template #noBreadcrumb>
|
||||
<div class="breadcrumb">Select a folder to view breadcrumb. Don't see your directory, try checking / first.</div>
|
||||
|
|
@ -33,17 +28,17 @@
|
|||
<ul class="list-group">
|
||||
<div class="list-group-item list-group-item-action">
|
||||
<button (click)="goBack()" class="btn btn-secondary" [disabled]="routeStack.peek() === undefined">
|
||||
<i class="fa fa-arrow-left mr-2" aria-hidden="true"></i>
|
||||
<i class="fa fa-arrow-left me-2" aria-hidden="true"></i>
|
||||
Back
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-primary float-right" [disabled]="routeStack.peek() === undefined" (click)="shareFolder('', $event)">Share</button>
|
||||
<button type="button" class="btn btn-primary float-end" [disabled]="routeStack.peek() === undefined" (click)="shareFolder('', $event)">Share</button>
|
||||
</div>
|
||||
</ul>
|
||||
<ul class="list-group scrollable">
|
||||
<button *ngFor="let folder of folders | filter: filterFolder" class="list-group-item list-group-item-action" (click)="selectNode(folder)">
|
||||
<span>{{getStem(folder)}}</span>
|
||||
<button type="button" class="btn btn-primary float-right" (click)="shareFolder(folder, $event)">Share</button>
|
||||
<button type="button" class="btn btn-primary float-end" (click)="shareFolder(folder, $event)">Share</button>
|
||||
</button>
|
||||
<div class="list-group-item text-center" *ngIf="folders.length === 0">
|
||||
There are no folders here
|
||||
|
|
@ -51,6 +46,6 @@
|
|||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a class="btn btn-info" *ngIf="helpUrl.length > 0" href="{{helpUrl}}" target="_blank">Help</a>
|
||||
<a class="btn btn-icon" *ngIf="helpUrl.length > 0" href="{{helpUrl}}" target="_blank">Help</a>
|
||||
<button type="button" class="btn btn-secondary" (click)="close()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">Library Access</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
|
|
|||
|
|
@ -2,35 +2,35 @@
|
|||
<form [formGroup]="libraryForm">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{this.library !== undefined ? 'Edit' : 'New'}} Library</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info" *ngIf="errorMessage !== ''">
|
||||
<strong>Error: </strong> {{errorMessage}}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="library-name">Name</label>
|
||||
<div class="mb-3">
|
||||
<label for="library-name" class="form-label">Name</label>
|
||||
<input id="library-name" class="form-control" formControlName="name" type="text">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="library-type">Type</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="typeTooltip" role="button" tabindex="0"></i>
|
||||
<div class="mb-3">
|
||||
<label for="library-type" class="form-label">Type</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="typeTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #typeTooltip>Library type determines how filenames are parsed and if the UI shows Chapters (Manga) vs Issues (Comics). Book work the same way as Manga but fall back to embedded data.</ng-template>
|
||||
<span class="sr-only" id="library-type-help">Library type determines how filenames are parsed and if the UI shows Chapters (Manga) vs Issues (Comics). Book work the same way as Manga but fall back to embedded data.</span>
|
||||
<select class="form-control" id="library-type" formControlName="type" [attr.disabled]="this.library" aria-describedby="library-type-help">
|
||||
<span class="visually-hidden" id="library-type-help">Library type determines how filenames are parsed and if the UI shows Chapters (Manga) vs Issues (Comics). Book work the same way as Manga but fall back to embedded data.</span>
|
||||
<select class="form-select" id="library-type" formControlName="type" [attr.disabled]="this.library" aria-describedby="library-type-help">
|
||||
<option [value]="i" *ngFor="let opt of libraryTypes; let i = index">{{opt}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<h4>Folders <button type="button" class="btn float-right btn-sm" (click)="openDirectoryPicker()"><i class="fa fa-plus" aria-hidden="true"></i></button></h4>
|
||||
<h4>Folders <button type="button" class="btn float-end btn-sm" (click)="openDirectoryPicker()"><i class="fa fa-plus" aria-hidden="true"></i></button></h4>
|
||||
|
||||
<ul class="list-group" style="width: 100%">
|
||||
<li class="list-group-item" *ngFor="let folder of selectedFolders; let i = index">
|
||||
{{folder}}
|
||||
<button class="btn float-right btn-sm" (click)="removeFolder(folder)"><i class="fa fa-times-circle" aria-hidden="true"></i></button>
|
||||
<button class="btn float-end btn-sm" (click)="removeFolder(folder)"><i class="fa fa-times-circle" aria-hidden="true"></i></button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
<form [formGroup]="resetPasswordForm">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">Reset {{member.username | sentenceCase}}'s Password</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info" *ngIf="errorMessage !== ''">
|
||||
<strong>Error: </strong> {{errorMessage}}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">New Password</label>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">New Password</label>
|
||||
<input id="password" class="form-control" minlength="4" formControlName="password" type="password">
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
<div class="card w-100 mb-2" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{{update.updateTitle}}
|
||||
<span class="badge badge-secondary" *ngIf="update.updateVersion === installedVersion">Installed</span>
|
||||
<span class="badge badge-secondary" *ngIf="update.updateVersion > installedVersion">Available</span>
|
||||
<span class="badge bg-secondary" *ngIf="update.updateVersion === installedVersion">Installed</span>
|
||||
<span class="badge bg-secondary" *ngIf="update.updateVersion > installedVersion">Available</span>
|
||||
</h4>
|
||||
<h6 class="card-subtitle mb-2 text-muted">Published: {{update.publishDate | date: 'short'}}</h6>
|
||||
|
||||
<pre class="card-text update-body" [innerHtml]="update.updateBody | safeHtml"></pre>
|
||||
<a *ngIf="!update.isDocker" href="{{update.updateUrl}}" class="btn btn-{{indx === 0 ? 'primary' : 'secondary'}} float-right" target="_blank">Download</a>
|
||||
<a *ngIf="!update.isDocker" href="{{update.updateUrl}}" class="btn btn-{{indx === 0 ? 'primary' : 'secondary'}} float-end" target="_blank">Download</a>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div class="container">
|
||||
<h2>Admin Dashboard</h2>
|
||||
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs nav-pills">
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav nav-tabs">
|
||||
<li *ngFor="let tab of tabs" [ngbNavItem]="tab">
|
||||
<a ngbNavLink routerLink="." [fragment]="tab.fragment">{{ tab.title | sentenceCase }}</a>
|
||||
<ng-template ngbNavContent>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
.container {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">Edit {{member.username | sentenceCase}}</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<form [formGroup]="userForm">
|
||||
<div class="row no-gutters">
|
||||
<div class="col-md-6 col-sm-12 pr-2">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6 col-sm-12 pe-2">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input id="username" class="form-control" formControlName="username" type="text">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="userForm.dirty || userForm.touched">
|
||||
<div *ngIf="userForm.get('username')?.errors?.required">
|
||||
|
|
@ -20,8 +20,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="form-group" style="width:100%">
|
||||
<label for="email">Email</label>
|
||||
<div class="mb-3" style="width:100%">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input class="form-control" type="email" id="email" formControlName="email" [disabled]="true">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="userForm.dirty || userForm.touched">
|
||||
<div *ngIf="userForm.get('email')?.errors?.required">
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6">
|
||||
<app-role-selector (selected)="updateRoleSelection($event)" [allowAdmin]="true" [member]="member"></app-role-selector>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">Invite User</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
|
@ -16,9 +16,9 @@
|
|||
|
||||
|
||||
<form [formGroup]="inviteForm">
|
||||
<div class="row no-gutters">
|
||||
<div class="form-group" style="width:100%">
|
||||
<label for="email">Email</label>
|
||||
<div class="row g-0">
|
||||
<div class="mb-3" style="width:100%">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input class="form-control" type="email" id="email" formControlName="email" required>
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="inviteForm.dirty || inviteForm.touched">
|
||||
<div *ngIf="email?.errors?.required">
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
<a class="email-link" href="{{emailLink}}" target="_blank">{{emailLink}}</a>
|
||||
</ng-container>
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-6">
|
||||
<app-role-selector (selected)="updateRoleSelection($event)" [allowAdmin]="true"></app-role-selector>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-8"><h3>Libraries</h3></div>
|
||||
<div class="col-4"><button class="btn btn-primary float-right" (click)="addLibrary()"><i class="fa fa-plus" aria-hidden="true"></i><span class="phone-hidden"> Add Library</span></button></div>
|
||||
<div class="col-4"><button class="btn btn-primary float-end" (click)="addLibrary()"><i class="fa fa-plus" aria-hidden="true"></i><span class="phone-hidden"> Add Library</span></button></div>
|
||||
</div>
|
||||
<ul class="list-group" *ngIf="!createLibraryToggle; else createLibrary">
|
||||
<li *ngFor="let library of libraries; let idx = index; trackby: trackbyLibrary" class="list-group-item">
|
||||
<li *ngFor="let library of libraries; let idx = index; trackby: trackbyLibrary" class="list-group-item no-hover">
|
||||
<div>
|
||||
<h4>
|
||||
<span id="library-name--{{idx}}">{{library.name}}</span>
|
||||
<div class="spinner-border text-primary" style="width: 1.5rem; height: 1.5rem;" role="status" *ngIf="scanInProgress.hasOwnProperty(library.id) && scanInProgress[library.id].progress" title="Scan in progress. Started at {{scanInProgress[library.id].timestamp | date: 'short'}}">
|
||||
<span class="sr-only">Scan for {{library.name}} in progress</span>
|
||||
<span class="visually-hidden">Scan for {{library.name}} in progress</span>
|
||||
</div>
|
||||
<div class="float-right">
|
||||
<button class="btn btn-secondary mr-2 btn-sm" (click)="scanLibrary(library)" placement="top" ngbTooltip="Scan Library" attr.aria-label="Scan Library"><i class="fa fa-sync-alt" title="Scan"></i></button>
|
||||
<button class="btn btn-danger mr-2 btn-sm" [disabled]="deletionInProgress" (click)="deleteLibrary(library)"><i class="fa fa-trash" placement="top" ngbTooltip="Delete Library" attr.aria-label="Delete {{library.name | sentenceCase}}"></i></button>
|
||||
<div class="float-end">
|
||||
<button class="btn btn-secondary me-2 btn-sm" (click)="scanLibrary(library)" placement="top" ngbTooltip="Scan Library" attr.aria-label="Scan Library"><i class="fa fa-sync-alt" title="Scan"></i></button>
|
||||
<button class="btn btn-danger me-2 btn-sm" [disabled]="deletionInProgress" (click)="deleteLibrary(library)"><i class="fa fa-trash" placement="top" ngbTooltip="Delete Library" attr.aria-label="Delete {{library.name | sentenceCase}}"></i></button>
|
||||
<button class="btn btn-primary btn-sm" (click)="editLibrary(library)"><i class="fa fa-pen" placement="top" ngbTooltip="Edit" attr.aria-label="Edit {{library.name | sentenceCase}}"></i></button>
|
||||
</div>
|
||||
</h4>
|
||||
|
|
|
|||
|
|
@ -1,65 +1,63 @@
|
|||
<div class="container-fluid">
|
||||
<form [formGroup]="settingsForm" *ngIf="serverSettings !== undefined">
|
||||
<p class="text-warning pt-2">Port and Logging Level require a manual restart of Kavita to take effect.</p>
|
||||
<div class="form-group">
|
||||
<label for="settings-cachedir">Cache Directory</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="cacheDirectoryTooltip" role="button" tabindex="0"></i>
|
||||
<div class="mb-3">
|
||||
<label for="settings-cachedir" class="form-label">Cache Directory</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="cacheDirectoryTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #cacheDirectoryTooltip>Where the server place temporary files when reading. This will be cleaned up on a regular basis.</ng-template>
|
||||
<span class="sr-only" id="settings-cachedir-help">Where the server place temporary files when reading. This will be cleaned up on a regular basis.</span>
|
||||
<span class="visually-hidden" id="settings-cachedir-help">Where the server place temporary files when reading. This will be cleaned up on a regular basis.</span>
|
||||
<input readonly id="settings-cachedir" aria-describedby="settings-cachedir-help" class="form-control" formControlName="cacheDirectory" type="text">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="settings-bookmarksdir">Bookmarks Directory</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="bookmarksDirectoryTooltip" role="button" tabindex="0"></i>
|
||||
<div class="mb-3">
|
||||
<label for="settings-bookmarksdir" class="form-label">Bookmarks Directory</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="bookmarksDirectoryTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #bookmarksDirectoryTooltip>Location where bookmarks will be stored. Bookmarks are source files and can be large. Choose a location with adequate storage. Directory is managed, other files within directory will be deleted.</ng-template>
|
||||
<span class="sr-only" id="settings-bookmarksdir-help"><ng-container [ngTemplateOutlet]="bookmarksDirectoryTooltip"></ng-container></span>
|
||||
<span class="visually-hidden" id="settings-bookmarksdir-help"><ng-container [ngTemplateOutlet]="bookmarksDirectoryTooltip"></ng-container></span>
|
||||
<div class="input-group">
|
||||
<input readonly id="settings-bookmarksdir" aria-describedby="settings-bookmarksdir-help" class="form-control" formControlName="bookmarksDirectory" type="text" aria-describedby="change-bookmarks-dir">
|
||||
<div class="input-group-append">
|
||||
<button id="change-bookmarks-dir" class="btn btn-primary" (click)="openDirectoryChooser(settingsForm.get('bookmarksDirectory')?.value, 'bookmarksDirectory')">
|
||||
Change
|
||||
</button>
|
||||
</div>
|
||||
<button id="change-bookmarks-dir" class="btn btn-primary" (click)="openDirectoryChooser(settingsForm.get('bookmarksDirectory')?.value, 'bookmarksDirectory')">
|
||||
Change
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="form-group">
|
||||
<!-- <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="sr-only" id="settings-baseurl-help">Use this if you want to host Kavita on a base url ie) yourdomain.com/kavita</span>
|
||||
<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 no-gutters">
|
||||
<div class="form-group col-md-6 col-sm-12 pr-2">
|
||||
<label for="settings-port">Port</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="portTooltip" role="button" tabindex="0"></i>
|
||||
<div class="row g-0 mb-2">
|
||||
<div class="form-group 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="sr-only" id="settings-port-help">Port the server listens on. This is fixed if you are running on Docker. Requires restart to take effect.</span>
|
||||
<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">
|
||||
<label for="logging-level-port">Logging Level</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="loggingLevelTooltip" role="button" tabindex="0"></i>
|
||||
<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="sr-only" id="logging-level-port-help">Port the server listens on. Requires restart to take effect.</span>
|
||||
<select id="logging-level-port" aria-describedby="logging-level-port-help" class="form-control" aria-describedby="settings-tasks-scan-help" formControlName="loggingLevel">
|
||||
<span class="visually-hidden" id="logging-level-port-help">Port the server listens on. Requires restart to take effect.</span>
|
||||
<select id="logging-level-port" aria-describedby="logging-level-port-help" class="form-select" aria-describedby="settings-tasks-scan-help" formControlName="loggingLevel">
|
||||
<option *ngFor="let level of logLevels" [value]="level">{{level | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="stat-collection" aria-describedby="collection-info">Allow Anonymous Usage Collection</label>
|
||||
<div class="mb-3">
|
||||
<label for="stat-collection" class="form-label" aria-describedby="collection-info">Allow Anonymous Usage Collection</label>
|
||||
<p class="accent" id="collection-info">Send anonymous usage and error information to Kavita's servers. This includes information on your browser, error reporting as well as OS and runtime version. We will use this information to prioritize features, bug fixes, and preformance tuning. Requires restart to take effect.</p>
|
||||
<div class="form-check">
|
||||
<div class="form-check form-switch">
|
||||
<input id="stat-collection" type="checkbox" aria-label="Stat Collection" class="form-check-input" formControlName="allowStatCollection">
|
||||
<label for="stat-collection" class="form-check-label">Send Data</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="opds" aria-describedby="opds-info">OPDS</label>
|
||||
<div class="mb-3">
|
||||
<label for="opds" aria-describedby="opds-info" class="form-label">OPDS</label>
|
||||
<p class="accent" id="opds-info">OPDS support will allow all users to use OPDS to read and download content from the server. If OPDS is enabled, a user will not need download permissions to download media while using it.</p>
|
||||
<div class="form-check">
|
||||
<div class="form-check form-switch">
|
||||
<input id="opds" type="checkbox" aria-label="OPDS Support" class="form-check-input" formControlName="enableOpds">
|
||||
<label for="opds" class="form-check-label">Enable OPDS</label>
|
||||
</div>
|
||||
|
|
@ -70,47 +68,45 @@
|
|||
email service. Set the url of the email service and use the Test button to ensure it works. At any time you can reset to ours. There is no way to disable emails althought confirmation links will always
|
||||
be saved to logs.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="settings-emailservice">Email Service Url</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="emailServiceTooltip" role="button" tabindex="0"></i>
|
||||
<div class="mb-3">
|
||||
<label for="settings-emailservice" class="form-label">Email Service Url</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="emailServiceTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #emailServiceTooltip>Use fully qualified url of the email service. Do not include ending slash.</ng-template>
|
||||
<span class="sr-only" id="settings-emailservice-help"><ng-container [ngTemplateOutlet]="emailServiceTooltip"></ng-container></span>
|
||||
<span class="visually-hidden" id="settings-emailservice-help"><ng-container [ngTemplateOutlet]="emailServiceTooltip"></ng-container></span>
|
||||
<div class="input-group">
|
||||
<input id="settings-emailservice" aria-describedby="settings-emailservice-help" class="form-control" formControlName="emailServiceUrl" type="text" aria-describedby="change-bookmarks-dir">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-secondary" (click)="resetEmailServiceUrl()">
|
||||
Reset
|
||||
</button>
|
||||
<button class="btn btn-secondary" (click)="testEmailServiceUrl()">
|
||||
Test
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary" (click)="resetEmailServiceUrl()">
|
||||
Reset
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" (click)="testEmailServiceUrl()">
|
||||
Test
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h4>Reoccuring Tasks</h4>
|
||||
<div class="form-group">
|
||||
<label for="settings-tasks-scan">Library Scan</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="taskScanTooltip" role="button" tabindex="0"></i>
|
||||
<div class="mb-3">
|
||||
<label for="settings-tasks-scan" class="form-label">Library Scan</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="taskScanTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #taskScanTooltip>How often Kavita will scan and refresh metatdata around manga files.</ng-template>
|
||||
<span class="sr-only" id="settings-tasks-scan-help">How often Kavita will scan and refresh metatdata around manga files.</span>
|
||||
<select class="form-control" aria-describedby="settings-tasks-scan-help" formControlName="taskScan" id="settings-tasks-scan">
|
||||
<span class="visually-hidden" id="settings-tasks-scan-help">How often Kavita will scan and refresh metatdata around manga files.</span>
|
||||
<select class="form-select" aria-describedby="settings-tasks-scan-help" formControlName="taskScan" id="settings-tasks-scan">
|
||||
<option *ngFor="let freq of taskFrequencies" [value]="freq">{{freq | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="settings-tasks-backup">Library Database Backup</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="taskBackupTooltip" role="button" tabindex="0"></i>
|
||||
<div class="mb-3">
|
||||
<label for="settings-tasks-backup" class="form-label">Library Database Backup</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="taskBackupTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #taskBackupTooltip>How often Kavita will backup the database.</ng-template>
|
||||
<span class="sr-only" id="settings-tasks-backup-help">How often Kavita will backup the database.</span>
|
||||
<select class="form-control" aria-describedby="settings-tasks-backup-help" formControlName="taskBackup" id="settings-tasks-backup">
|
||||
<span class="visually-hidden" id="settings-tasks-backup-help">How often Kavita will backup the database.</span>
|
||||
<select class="form-select" aria-describedby="settings-tasks-backup-help" formControlName="taskBackup" id="settings-tasks-backup">
|
||||
<option *ngFor="let freq of taskFrequencies" [value]="freq">{{freq | titlecase}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="float-right">
|
||||
<button type="button" class="btn btn-secondary mr-2" (click)="resetToDefaults()">Reset to Default</button>
|
||||
<button type="button" class="btn btn-secondary mr-2" (click)="resetForm()">Reset</button>
|
||||
<button type="submit" class="btn btn-primary" (click)="saveSettings()" [disabled]="!settingsForm.touched && !settingsForm.dirty">Save</button>
|
||||
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end">
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetToDefaults()">Reset to Default</button>
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()">Reset</button>
|
||||
<button type="submit" class="flex-fill btn btn-primary" (click)="saveSettings()" [disabled]="!settingsForm.touched && !settingsForm.dirty">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
<div class="container-fluid">
|
||||
|
||||
<div class="float-right">
|
||||
<div class="float-end">
|
||||
<div class="d-inline-block" ngbDropdown #myDrop="ngbDropdown">
|
||||
<button class="btn btn-outline-primary mr-2" id="dropdownManual" ngbDropdownToggle>
|
||||
<button class="btn btn-outline-primary me-2" id="dropdownManual" ngbDropdownToggle>
|
||||
<ng-container *ngIf="backupDBInProgress || clearCacheInProgress || isCheckingForUpdate || downloadLogsInProgress">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="sr-only">Loading...</span>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</ng-container>
|
||||
Actions
|
||||
</button>
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
<h3>About System</h3>
|
||||
<hr/>
|
||||
<div class="form-group" *ngIf="serverInfo">
|
||||
<div class="mb-3" *ngIf="serverInfo">
|
||||
<dl>
|
||||
<dt>Version</dt>
|
||||
<dd>{{serverInfo.kavitaVersion}}</dd>
|
||||
|
|
|
|||
|
|
@ -4,28 +4,28 @@
|
|||
<ng-container>
|
||||
<div class="row mb-2">
|
||||
<div class="col-8"><h3>Pending Invites</h3></div>
|
||||
<div class="col-4"><button class="btn btn-primary float-right" (click)="inviteUser()"><i class="fa fa-plus" aria-hidden="true"></i><span class="phone-hidden"> Invite</span></button></div>
|
||||
<div class="col-4"><button class="btn btn-primary float-end" (click)="inviteUser()"><i class="fa fa-plus" aria-hidden="true"></i><span class="phone-hidden"> Invite</span></button></div>
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item" *ngFor="let invite of pendingInvites; let idx = index;">
|
||||
<li class="list-group-item no-hover" *ngFor="let invite of pendingInvites; let idx = index;">
|
||||
<div>
|
||||
<h4>
|
||||
<span id="member-name--{{idx}}">{{invite.username | titlecase}} </span>
|
||||
<div class="float-right">
|
||||
<button class="btn btn-danger mr-2" (click)="deleteUser(invite)">Cancel</button>
|
||||
<button class="btn btn-secondary mr-2" (click)="resendEmail(invite)">Resend</button>
|
||||
<div class="float-end">
|
||||
<button class="btn btn-danger me-2" (click)="deleteUser(invite)">Cancel</button>
|
||||
<button class="btn btn-secondary me-2" (click)="resendEmail(invite)">Resend</button>
|
||||
</div>
|
||||
</h4>
|
||||
|
||||
<div>Invited: {{invite.created | date: 'short'}}</div>
|
||||
</div>
|
||||
</li>
|
||||
<li *ngIf="loadingMembers" class="list-group-item">
|
||||
<li *ngIf="loadingMembers" class="list-group-item no-hover">
|
||||
<div class="spinner-border text-secondary" role="status">
|
||||
<span class="invisible">Loading...</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item" *ngIf="pendingInvites.length === 0 && !loadingMembers">
|
||||
<li class="list-group-item no-hover" *ngIf="pendingInvites.length === 0 && !loadingMembers">
|
||||
There are no invited Users
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -35,18 +35,18 @@
|
|||
|
||||
<h3 class="mt-3">Active Users</h3>
|
||||
<ul class="list-group">
|
||||
<li *ngFor="let member of members; let idx = index;" class="list-group-item">
|
||||
<li *ngFor="let member of members; let idx = index;" class="list-group-item no-hover">
|
||||
<div>
|
||||
<h4>
|
||||
<i class="presence fa fa-circle" title="Active" aria-hidden="true" *ngIf="false && (messageHub.onlineUsers$ | async)?.includes(member.username)"></i>
|
||||
<span id="member-name--{{idx}}">{{member.username | titlecase}} </span>
|
||||
<span *ngIf="member.username === loggedInUsername">
|
||||
<i class="fas fa-star" aria-hidden="true"></i>
|
||||
<span class="sr-only">(You)</span>
|
||||
<span class="visually-hidden">(You)</span>
|
||||
</span>
|
||||
<div class="float-right" *ngIf="canEditMember(member)">
|
||||
<button class="btn btn-danger mr-2" (click)="deleteUser(member)" placement="top" ngbTooltip="Delete User" attr.aria-label="Delete User {{member.username | titlecase}}"><i class="fa fa-trash" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-secondary mr-2" (click)="updatePassword(member)" placement="top" ngbTooltip="Change Password" attr.aria-label="Change Password for {{member.username | titlecase}}"><i class="fa fa-key" aria-hidden="true"></i></button>
|
||||
<div class="float-end" *ngIf="canEditMember(member)">
|
||||
<button class="btn btn-danger me-2" (click)="deleteUser(member)" placement="top" ngbTooltip="Delete User" attr.aria-label="Delete User {{member.username | titlecase}}"><i class="fa fa-trash" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-secondary me-2" (click)="updatePassword(member)" placement="top" ngbTooltip="Change Password" attr.aria-label="Change Password for {{member.username | titlecase}}"><i class="fa fa-key" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-primary" (click)="openEditUser(member)" placement="top" ngbTooltip="Edit" attr.aria-label="Edit {{member.username | titlecase}}"><i class="fa fa-pen" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</h4>
|
||||
|
|
@ -57,10 +57,10 @@
|
|||
</ng-template>
|
||||
</div>
|
||||
<div *ngIf="!hasAdminRole(member)">Sharing: {{formatLibraries(member)}}</div>
|
||||
<div>
|
||||
<div class="row g-0">
|
||||
Roles: <span *ngIf="getRoles(member).length === 0; else showRoles">None</span>
|
||||
<ng-template #showRoles>
|
||||
<app-tag-badge *ngFor="let role of getRoles(member)">{{role}}</app-tag-badge>
|
||||
<app-tag-badge *ngFor="let role of getRoles(member)" class="col-auto">{{role}}</app-tag-badge>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { OnDeckComponent } from './on-deck/on-deck.component';
|
|||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
import { AllSeriesComponent } from './all-series/all-series.component';
|
||||
import { AdminGuard } from './_guards/admin.guard';
|
||||
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
|
||||
|
||||
|
|
@ -71,6 +72,7 @@ const routes: Routes = [
|
|||
},
|
||||
{path: 'login', component: UserLoginComponent}, // TODO: move this to registration module
|
||||
{path: 'no-connection', component: NotConnectedComponent},
|
||||
{path: 'theme', component: ThemeTestComponent},
|
||||
{path: '**', component: UserLoginComponent, pathMatch: 'full'}
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<app-nav-header></app-nav-header>
|
||||
<div [ngStyle]="(navService?.navbarVisible$ | async) ? {'padding-top': 'calc(56px + 5px)', 'height': '100%'} : {}">
|
||||
<div [ngStyle]="(navService?.navbarVisible$ | async) ? {'padding-top': 'calc(57px)', 'height': '100%'} : {}">
|
||||
<a id="content"></a>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, HostListener, Inject, OnInit } from '@angular/core';
|
||||
import { NavigationStart, Router } from '@angular/router';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { AccountService } from './_services/account.service';
|
||||
|
|
@ -7,6 +7,8 @@ import { MessageHubService } from './_services/message-hub.service';
|
|||
import { NavService } from './_services/nav.service';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { NgbModal, NgbRatingConfig } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { ThemeService } from './theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
|
|
@ -17,7 +19,8 @@ export class AppComponent implements OnInit {
|
|||
|
||||
constructor(private accountService: AccountService, public navService: NavService,
|
||||
private messageHub: MessageHubService, private libraryService: LibraryService,
|
||||
private router: Router, private ngbModal: NgbModal, private ratingConfig: NgbRatingConfig) {
|
||||
router: Router, private ngbModal: NgbModal, ratingConfig: NgbRatingConfig,
|
||||
@Inject(DOCUMENT) private document: Document) {
|
||||
|
||||
// Setup default rating config
|
||||
ratingConfig.max = 5;
|
||||
|
|
@ -33,22 +36,34 @@ export class AppComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.setCurrentUser();
|
||||
@HostListener('resize')
|
||||
onResize() {
|
||||
this.setDocHeight();
|
||||
}
|
||||
|
||||
@HostListener('orientationchange')
|
||||
onOrientationChange() {
|
||||
this.setDocHeight();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.setCurrentUser();
|
||||
|
||||
this.setDocHeight();
|
||||
}
|
||||
|
||||
setCurrentUser() {
|
||||
const user = this.accountService.getUserFromLocalStorage();
|
||||
this.accountService.setCurrentUser(user);
|
||||
|
||||
if (user) {
|
||||
this.navService.setDarkMode(user.preferences.siteDarkMode);
|
||||
this.messageHub.createHubConnection(user, this.accountService.hasAdminRole(user));
|
||||
this.libraryService.getLibraryNames().pipe(take(1)).subscribe(() => {/* No Operation */});
|
||||
} else {
|
||||
this.navService.setDarkMode(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDocHeight() {
|
||||
// Sets a CSS variable for the actual device viewport height. Needed for mobile dev.
|
||||
this.document.documentElement.style.setProperty('--vh', `${window.innerHeight/100}px`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import { AllSeriesComponent } from './all-series/all-series.component';
|
|||
import { PublicationStatusPipe } from './publication-status.pipe';
|
||||
import { RegistrationModule } from './registration/registration.module';
|
||||
import { GroupedTypeaheadComponent } from './grouped-typeahead/grouped-typeahead.component';
|
||||
import { ThemeTestComponent } from './theme-test/theme-test.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
|
|
@ -58,6 +59,7 @@ import { GroupedTypeaheadComponent } from './grouped-typeahead/grouped-typeahead
|
|||
SeriesMetadataDetailComponent,
|
||||
AllSeriesComponent,
|
||||
GroupedTypeaheadComponent,
|
||||
ThemeTestComponent,
|
||||
],
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<div class="container-flex {{darkMode ? 'dark-mode' : ''}} reader-container" tabindex="0" #reader>
|
||||
<div class="fixed-top" #stickyTop>
|
||||
<a class="sr-only sr-only-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a>
|
||||
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a>
|
||||
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
|
||||
<app-drawer #commentDrawer="drawer" [isOpen]="drawerOpen" [style.--drawer-width]="'300px'" [options]="{topOffset: topOffset}" [style.--drawer-background-color]="drawerBackgroundColor" (drawerClosed)="closeDrawer()">
|
||||
<div header>
|
||||
<h2 style="margin-top: 0.5rem">Book Settings
|
||||
<button type="button" class="close" aria-label="Close" (click)="commentDrawer.close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="commentDrawer.close()">
|
||||
|
||||
</button>
|
||||
</h2>
|
||||
|
||||
|
|
@ -16,8 +16,8 @@
|
|||
<div class="controls">
|
||||
|
||||
<form [formGroup]="settingsForm">
|
||||
<div class="form-group">
|
||||
<label for="library-type">Font Family</label>
|
||||
<div class="mb-3">
|
||||
<label for="library-type" class="form-label">Font Family</label>
|
||||
<select class="form-control" id="library-type" formControlName="bookReaderFontFamily">
|
||||
<option [value]="opt" *ngFor="let opt of fontFamilies; let i = index">{{opt | titlecase}}</option>
|
||||
</select>
|
||||
|
|
@ -25,42 +25,42 @@
|
|||
</form>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<label id="fontsize">Font Size</label>
|
||||
<label id="fontsize" class="form-label">Font Size</label>
|
||||
<button (click)="updateFontSize(-10)" class="btn btn-icon" title="Decrease" aria-labelledby="fontsize"><i class="fa fa-minus" aria-hidden="true"></i></button>
|
||||
<span>{{pageStyles['font-size']}}</span>
|
||||
<button (click)="updateFontSize(10)" class="btn btn-icon" title="Increase" aria-labelledby="fontsize"><i class="fa fa-plus" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<label id="linespacing">Line Spacing</label>
|
||||
<label id="linespacing" class="form-label">Line Spacing</label>
|
||||
<button (click)="updateLineSpacing(-10)" class="btn btn-icon" title="Decrease" aria-labelledby="linespacing"><i class="fa fa-minus" aria-hidden="true"></i></button>
|
||||
<span>{{pageStyles['line-height']}}</span>
|
||||
<button (click)="updateLineSpacing(10)" class="btn btn-icon" title="Increase" aria-labelledby="linespacing"><i class="fa fa-plus" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<label id="margin">Margin</label>
|
||||
<label id="margin" class="form-label">Margin</label>
|
||||
<button (click)="updateMargin(-5)" class="btn btn-icon" title="Remove Margin" aria-labelledby="margin"><i class="fa fa-minus" aria-hidden="true"></i></button>
|
||||
<span>{{pageStyles['margin-right']}}</span>
|
||||
<button (click)="updateMargin(5)" class="btn btn-icon" title="Add Margin" aria-labelledby="margin"><i class="fa fa-plus" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<label id="readingdirection">Reading Direction</label>
|
||||
<label id="readingdirection" class="form-label">Reading Direction</label>
|
||||
<button (click)="toggleReadingDirection()" class="btn btn-icon" aria-labelledby="readingdirection" title="{{readingDirection === 0 ? 'Left to Right' : 'Right to Left'}}"><i class="fa {{readingDirection === 0 ? 'fa-arrow-right' : 'fa-arrow-left'}} " aria-hidden="true"></i><span class="phone-hidden"> {{readingDirection === 0 ? 'Left to Right' : 'Right to Left'}}</span></button>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<label id="darkmode">Dark Mode</label>
|
||||
<label id="darkmode" class="form-label">Dark Mode</label>
|
||||
<button (click)="toggleDarkMode(false)" class="btn btn-icon" aria-labelledby="darkmode" title="Off"><i class="fa fa-sun" aria-hidden="true"></i></button>
|
||||
<button (click)="toggleDarkMode(true)" class="btn btn-icon" aria-labelledby="darkmode" title="On"><i class="fa fa-moon" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<label id="tap-pagination">Tap Pagination <i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="tapPaginationTooltip" role="button" tabindex="0" aria-describedby="tap-pagination-help"></i></label>
|
||||
<label id="tap-pagination" class="form-label">Tap Pagination <i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="tapPaginationTooltip" role="button" tabindex="0" aria-describedby="tap-pagination-help"></i></label>
|
||||
<ng-template #tapPaginationTooltip>The ability to click the sides of the page to page left and right</ng-template>
|
||||
<span class="sr-only" id="tap-pagination-help">The ability to click the sides of the page to page left and right</span>
|
||||
<span class="visually-hidden" id="tap-pagination-help">The ability to click the sides of the page to page left and right</span>
|
||||
<button (click)="toggleClickToPaginate()" class="btn btn-icon" aria-labelledby="tap-pagination"><i class="fa fa-arrows-alt-h {{clickToPaginate ? 'icon-primary-color' : ''}}" aria-hidden="true"></i><span *ngIf="darkMode"> {{clickToPaginate ? 'On' : 'Off'}}</span></button>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<label id="fullscreen">Fullscreen <i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="fullscreenTooltip" role="button" tabindex="0" aria-describedby="fullscreen-help"></i></label>
|
||||
<label id="fullscreen" class="form-label">Fullscreen <i class="fa fa-info-circle" aria-hidden="true" placement="top" [ngbTooltip]="fullscreenTooltip" role="button" tabindex="0" aria-describedby="fullscreen-help"></i></label>
|
||||
<ng-template #fullscreenTooltip>Put reader in fullscreen mode</ng-template>
|
||||
<span class="sr-only" id="fullscreen-help">
|
||||
<span class="visually-hidden" id="fullscreen-help">
|
||||
<ng-container [ngTemplateOutlet]="fullscreenTooltip"></ng-container>
|
||||
</span>
|
||||
<button (click)="toggleFullscreen()" class="btn btn-icon" aria-labelledby="fullscreen">
|
||||
|
|
@ -68,11 +68,11 @@
|
|||
<span *ngIf="darkMode"> {{isFullscreen ? 'Exit' : 'Enter'}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="row no-gutters justify-content-between">
|
||||
<div class="row g-0 justify-content-between">
|
||||
<button (click)="resetSettings()" class="btn btn-primary col">Reset to Defaults</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="prevChapterDisabled" (click)="loadPrevChapter()" title="Prev Chapter/Volume"><i class="fa fa-fast-backward" aria-hidden="true"></i></button>
|
||||
<div class="col-1 page-stub">{{pageNum}}</div>
|
||||
<div class="col-8" style="margin-top: 15px;padding-right:10px">
|
||||
|
|
@ -125,7 +125,7 @@
|
|||
</div>
|
||||
|
||||
<ng-template #actionBar>
|
||||
<div class="reading-bar row no-gutters justify-content-between">
|
||||
<div class="reading-bar row g-0 justify-content-between">
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="prevPage()"
|
||||
[disabled]="IsPrevDisabled"
|
||||
title="{{readingDirection === ReadingDirection.LeftToRight ? 'Previous' : 'Next'}} Page">
|
||||
|
|
@ -137,12 +137,12 @@
|
|||
<div class="book-title col-2 phone-hidden">
|
||||
<ng-container *ngIf="isLoading; else showTitle">
|
||||
<div class="spinner-border spinner-border-sm text-primary" style="border-radius: 50%;" role="status">
|
||||
<span class="sr-only">Loading book...</span>
|
||||
<span class="visually-hidden">Loading book...</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #showTitle>
|
||||
{{bookTitle}}
|
||||
<span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" aria-label="Incognito mode is on. Toggle to turn off.">(<i class="fa fa-glasses" aria-hidden="true"></i><span class="sr-only">Incognito Mode</span>)</span>
|
||||
<span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" aria-label="Incognito mode is on. Toggle to turn off.">(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">Incognito Mode</span>)</span>
|
||||
</ng-template>
|
||||
</div>
|
||||
<button class="btn btn-secondary col-2 col-xs-1" (click)="closeReader()"><i class="fa fa-times-circle" aria-hidden="true"></i><span class="phone-hidden"> Close</span></button>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { ScrollService } from 'src/app/scroll.service';
|
|||
import { MangaFormat } from 'src/app/_models/manga-format';
|
||||
import { LibraryService } from 'src/app/_services/library.service';
|
||||
import { LibraryType } from 'src/app/_models/library';
|
||||
import { ThemeService } from 'src/app/theme.service';
|
||||
|
||||
|
||||
interface PageStyle {
|
||||
|
|
@ -260,7 +261,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
private renderer: Renderer2, private navService: NavService, private toastr: ToastrService,
|
||||
private domSanitizer: DomSanitizer, private bookService: BookService, private memberService: MemberService,
|
||||
private scrollService: ScrollService, private utilityService: UtilityService, private libraryService: LibraryService,
|
||||
@Inject(DOCUMENT) private document: Document) {
|
||||
@Inject(DOCUMENT) private document: Document, private themeService: ThemeService) {
|
||||
this.navService.hideNavBar();
|
||||
|
||||
this.darkModeStyleElem = this.renderer.createElement('style');
|
||||
|
|
@ -382,7 +383,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
const bodyNode = this.document.querySelector('body');
|
||||
if (bodyNode !== undefined && bodyNode !== null && this.originalBodyColor !== undefined) {
|
||||
bodyNode.style.background = this.originalBodyColor;
|
||||
if (this.user.preferences.siteDarkMode) {
|
||||
if (this.themeService.isDarkTheme()) {
|
||||
bodyNode.classList.add('bg-dark');
|
||||
}
|
||||
}
|
||||
|
|
@ -968,7 +969,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
setOverrideStyles() {
|
||||
const bodyNode = this.document.querySelector('body');
|
||||
if (bodyNode !== undefined && bodyNode !== null) {
|
||||
if (this.user.preferences.siteDarkMode) {
|
||||
if (this.themeService.isDarkTheme()) {
|
||||
bodyNode.classList.remove('bg-dark');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{title}} Bookmarks</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
</p>
|
||||
<ng-template #noBookmarks>No bookmarks yet</ng-template>
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div *ngFor="let bookmark of bookmarks; let idx = index">
|
||||
<app-bookmark [bookmark]="bookmark" (bookmarkRemoved)="removeBookmark(bookmark, idx)" class="col-auto"></app-bookmark>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">Add to Collection</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<form style="width: 100%" [formGroup]="listForm">
|
||||
<div class="modal-body">
|
||||
<div class="form-group" *ngIf="lists.length >= 5">
|
||||
<label for="filter">Filter</label>
|
||||
<div class="mb-3" *ngIf="lists.length >= 5">
|
||||
<label for="filter" class="form-label">Filter</label>
|
||||
<div class="input-group">
|
||||
<input id="filter" autocomplete="off" class="form-control" formControlName="filterQuery" type="text" aria-describedby="reset-input">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" id="reset-input" (click)="listForm.get('filterQuery')?.setValue('');">Clear</button>
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary" type="button" id="reset-input" (click)="listForm.get('filterQuery')?.setValue('');">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
|
|
@ -23,7 +21,7 @@
|
|||
<li class="list-group-item" *ngIf="lists.length === 0 && !loading">No collections created yet</li>
|
||||
<li class="list-group-item" *ngIf="loading">
|
||||
<div class="spinner-border text-secondary" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -32,7 +30,7 @@
|
|||
<div style="width: 100%;">
|
||||
<div class="form-row">
|
||||
<div class="col-9 col-lg-10">
|
||||
<label class="sr-only" for="add-rlist">Collection</label>
|
||||
<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">
|
||||
</div>
|
||||
<div class="col-2">
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
<ng-template #comicHeader><h4 class="modal-title" id="modal-basic-title">
|
||||
{{parentName}} - {{data.number != 0 ? (isChapter ? 'Issue #' : 'Volume ') + data.number : 'Special'}} Details</h4>
|
||||
</ng-template>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body scrollable-modal" *ngIf="utilityService.isChapter(data)">
|
||||
|
|
@ -18,15 +18,15 @@
|
|||
<h4 *ngIf="utilityService.isVolume(data)">Information</h4>
|
||||
|
||||
<ng-container *ngIf="utilityService.isVolume(data) || utilityService.isChapter(data)">
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
Id: {{data.id}}
|
||||
</div>
|
||||
<div class="col" *ngIf="series !== undefined">
|
||||
Format: <span class="badge badge-secondary">{{utilityService.mangaFormat(series.format) | sentenceCase}}</span>
|
||||
Format: <span class="badge bg-secondary">{{utilityService.mangaFormat(series.format) | sentenceCase}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col" *ngIf="data.hasOwnProperty('created')">
|
||||
Added: {{(data.created | date: 'short') || '-'}}
|
||||
</div>
|
||||
|
|
@ -38,18 +38,18 @@
|
|||
|
||||
<h4 *ngIf="!utilityService.isChapter(data)">{{utilityService.formatChapterName(libraryType) + 's'}}</h4>
|
||||
<ul class="list-unstyled">
|
||||
<li class="media my-4" *ngFor="let chapter of chapters">
|
||||
<li class="d-flex my-4" *ngFor="let chapter of chapters">
|
||||
<a (click)="readChapter(chapter)" href="javascript:void(0);" title="Read {{libraryType !== LibraryType.Comic ? 'Chapter ' : 'Issue #'}} {{chapter.number}}">
|
||||
<app-image class="mr-2" width="74px" [imageUrl]="chapter.coverImage"></app-image>
|
||||
<app-image class="me-2" width="74px" [imageUrl]="chapter.coverImage"></app-image>
|
||||
</a>
|
||||
<div class="media-body">
|
||||
<div class="flex-grow-1">
|
||||
<h5 class="mt-0 mb-1">
|
||||
<span *ngIf="chapter.number !== '0'; else specialHeader">
|
||||
<span >
|
||||
<app-card-actionables (actionHandler)="performAction($event, chapter)" [actions]="chapterActions" [labelBy]="utilityService.formatChapterName(libraryType, true, true) + formatChapterNumber(chapter)"></app-card-actionables>
|
||||
{{utilityService.formatChapterName(libraryType, true, false) }} {{formatChapterNumber(chapter)}}
|
||||
</span>
|
||||
<span class="badge badge-primary badge-pill">
|
||||
<span class="badge bg-primary rounded-pill">
|
||||
<span *ngIf="chapter.pagesRead > 0 && chapter.pagesRead < chapter.pages">{{chapter.pagesRead}} / {{chapter.pages}}</span>
|
||||
<span *ngIf="chapter.pagesRead === 0">UNREAD</span>
|
||||
<span *ngIf="chapter.pagesRead === chapter.pages">READ</span>
|
||||
|
|
@ -58,9 +58,9 @@
|
|||
<ng-template #specialHeader>File(s)</ng-template>
|
||||
</h5>
|
||||
<ul class="list-group">
|
||||
<li *ngFor="let file of chapter.files" class="list-group-item">
|
||||
<li *ngFor="let file of chapter.files" class="list-group-item no-hover">
|
||||
<span>{{file.filePath}}</span>
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
Pages: {{file.pages}}
|
||||
</div>
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-info" [disabled]="!isAdmin" (click)="updateCover()">Update Cover</button>
|
||||
<button type="button" class="btn btn-secondary" [disabled]="!isAdmin" (click)="updateCover()">Update Cover</button>
|
||||
<button type="submit" class="btn btn-primary" (click)="close()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">Edit {{tag?.title}} Collection</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
|
@ -16,8 +16,8 @@
|
|||
<a ngbNavLink>{{tabs[0]}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<form [formGroup]="collectionTagForm">
|
||||
<div class="form-group">
|
||||
<label for="summary">Summary</label>
|
||||
<div class="mb-3">
|
||||
<label for="summary" class="form-label">Summary</label>
|
||||
<textarea id="summary" class="form-control" formControlName="summary" rows="3"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -67,6 +67,6 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" (click)="close()">Cancel</button>
|
||||
<button type="button" class="btn btn-info" (click)="togglePromotion()">{{tag?.promoted ? 'Demote' : 'Promote'}}</button>
|
||||
<button type="button" class="btn btn-secondary alt" (click)="togglePromotion()">{{tag?.promoted ? 'Demote' : 'Promote'}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="save()">Save</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
{{this.series.name}} Details</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body scrollable-modal {{utilityService.getActiveBreakpoint() === Breakpoint.Mobile ? '' : 'd-flex'}}">
|
||||
|
|
@ -13,52 +13,52 @@
|
|||
<a ngbNavLink>{{tabs[0]}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<form [formGroup]="editSeriesForm">
|
||||
<div class="row no-gutters">
|
||||
<div class="form-group" style="width: 100%">
|
||||
<label for="name">Name</label>
|
||||
<div class="row g-0">
|
||||
<div class="mb-3" style="width: 100%">
|
||||
<label for="name" class="form-label">Name</label>
|
||||
<input id="name" class="form-control" formControlName="name" type="text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="form-group" style="width: 100%">
|
||||
<label for="sort-name">Sort Name</label>
|
||||
<div class="row g-0">
|
||||
<div class="mb-3" style="width: 100%">
|
||||
<label for="sort-name" class="form-label">Sort Name</label>
|
||||
<input id="sort-name" class="form-control" formControlName="sortName" type="text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="form-group" style="width: 100%">
|
||||
<label for="localized-name">Localized Name</label>
|
||||
<div class="row g-0">
|
||||
<div class="mb-3" style="width: 100%">
|
||||
<label for="localized-name" class="form-label">Localized Name</label>
|
||||
<input id="localized-name" class="form-control" formControlName="localizedName" type="text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters" *ngIf="metadata">
|
||||
<div class="row g-0" *ngIf="metadata">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="author">Author</label>
|
||||
<div class="mb-3">
|
||||
<label for="author" class="form-label">Author</label>
|
||||
<input id="author" class="form-control" placeholder="Not Implemented" readonly="true" formControlName="author" type="text">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="artist">Artist</label>
|
||||
<div class="mb-3">
|
||||
<label for="artist" class="form-label">Artist</label>
|
||||
<input id="artist" class="form-control" placeholder="Not Implemented" readonly="true" formControlName="artist" type="text">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters" *ngIf="metadata">
|
||||
<div class="row g-0" *ngIf="metadata">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="genres">Genres</label>
|
||||
<div class="mb-3">
|
||||
<label for="genres" class="form-label">Genres</label>
|
||||
<input id="genres" class="form-control" placeholder="Not Implemented" readonly="true" formControlName="genres" type="text">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="collections">Collections</label>
|
||||
<div class="mb-3">
|
||||
<label for="collections" class="form-label">Collections</label>
|
||||
<app-typeahead (selectedData)="updateCollections($event)" [settings]="settings">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
|
|
@ -71,9 +71,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="form-group" style="width: 100%">
|
||||
<label for="summary">Summary</label>
|
||||
<div class="row g-0">
|
||||
<div class="mb-3" style="width: 100%">
|
||||
<label for="summary" class="form-label">Summary</label>
|
||||
<textarea id="summary" class="form-control" formControlName="summary" rows="4"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
<a ngbNavLink>{{tabs[2]}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<h4>Information</h4>
|
||||
<div class="row no-gutters mb-2">
|
||||
<div class="row g-0 mb-2">
|
||||
<div class="col-md-6" *ngIf="libraryName">Library: {{libraryName | sentenceCase}}</div>
|
||||
<div class="col-md-6">Format: <app-tag-badge>{{utilityService.mangaFormat(series.format)}}</app-tag-badge></div>
|
||||
</div>
|
||||
|
|
@ -103,12 +103,12 @@
|
|||
<span class="invisible">Loading...</span>
|
||||
</div>
|
||||
<ul class="list-unstyled" *ngIf="!isLoadingVolumes">
|
||||
<li class="media my-4" *ngFor="let volume of seriesVolumes">
|
||||
<app-image class="mr-3" style="width: 74px;" width="74px" [imageUrl]="imageService.getVolumeCoverImage(volume.id)"></app-image>
|
||||
<div class="media-body">
|
||||
<li class="d-flex my-4" *ngFor="let volume of seriesVolumes">
|
||||
<app-image class="me-3" style="width: 74px;" width="74px" [imageUrl]="imageService.getVolumeCoverImage(volume.id)"></app-image>
|
||||
<div class="flex-grow-1">
|
||||
<h5 class="mt-0 mb-1">Volume {{volume.name}}</h5>
|
||||
<div>
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
Added: {{volume.created | date: 'short'}}
|
||||
</div>
|
||||
|
|
@ -116,7 +116,7 @@
|
|||
Last Modified: {{volume.lastModified | date: 'short'}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
<button type="button" class="btn btn-outline-primary" (click)="collapse.toggle()" [attr.aria-expanded]="!volumeCollapsed[volume.name]">
|
||||
View Files
|
||||
|
|
@ -131,7 +131,7 @@
|
|||
<ul class="list-group mt-2">
|
||||
<li *ngFor="let file of volume.volumeFiles.sort()" class="list-group-item">
|
||||
<span>{{file.filePath}}</span>
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
Chapter: {{file.chapter}}
|
||||
</div>
|
||||
|
|
@ -153,7 +153,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<div [ngbNavOutlet]="nav" class="tab-content {{utilityService.getActiveBreakpoint() === Breakpoint.Mobile ? 'mt-3' : 'ml-4 flex-fill'}}"></div>
|
||||
<div [ngbNavOutlet]="nav" class="tab-content {{utilityService.getActiveBreakpoint() === Breakpoint.Mobile ? 'mt-3' : 'ms-4 flex-fill'}}"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" (click)="close()">Close</button>
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@
|
|||
<span class="card-title" tabindex="0">
|
||||
Page {{bookmark.page + 1}}
|
||||
</span>
|
||||
<span class="card-actions float-right" *ngIf="series != undefined">
|
||||
<span class="card-actions float-end" *ngIf="series != undefined">
|
||||
<button attr.aria-labelledby="series--{{series.name}}" class="btn btn-danger btn-sm" (click)="removeBookmark()"
|
||||
[disabled]="isClearing" placement="top" ngbTooltip="Remove Bookmark" attr.aria-label="Remove Bookmark">
|
||||
<ng-container *ngIf="isClearing; else notClearing">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="sr-only">Loading...</span>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</ng-container>
|
||||
<ng-template #notClearing>
|
||||
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="d-flex justify-content-around align-items-center">
|
||||
<span class="highlight"><i class="fa fa-check" aria-hidden="true"></i> {{bulkSelectionService.totalSelections()}} selected</span>
|
||||
<app-card-actionables [actions]="actions" labelBy="bulk-actions-header" iconClass="fa-ellipsis-h" (actionHandler)="performAction($event)"></app-card-actionables>
|
||||
<span id="bulk-actions-header" class="sr-only">Bulk Actions</span>
|
||||
<span id="bulk-actions-header" class="visually-hidden">Bulk Actions</span>
|
||||
<button class="btn btn-icon" (click)="bulkSelectionService.deselectAll()"><i class="fa fa-times" aria-hidden="true"></i> Deselect All</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,15 +1,9 @@
|
|||
@use "../../../theme/colors";
|
||||
|
||||
.bulk-select {
|
||||
background-color: colors.$dark-form-background-no-opacity;
|
||||
border-bottom: 2px solid colors.$primary-color;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
color: white;
|
||||
background-color: var(--navbar-bg-color);
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
color: var(--navbar-text-color);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: colors.$primary-color !important;
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
|
|
@ -1,18 +1,20 @@
|
|||
<div class="container-fluid" style="padding-top: 10px">
|
||||
<div class="row no-gutters pb-2">
|
||||
<div class="col mr-auto">
|
||||
<div class="row g-0 pb-2">
|
||||
<div class="col me-auto">
|
||||
<h2 style="display: inline-block">
|
||||
<span *ngIf="actions.length > 0" class="">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="header"></app-card-actionables>
|
||||
</span>{{header}}
|
||||
<span class="badge badge-primary badge-pill" attr.aria-label="{{pagination.totalItems}} total items" *ngIf="pagination != undefined">{{pagination.totalItems}}</span>
|
||||
<span class="badge bg-primary rounded-pill" attr.aria-label="{{pagination.totalItems}} total items" *ngIf="pagination != undefined">{{pagination.totalItems}}</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<button *ngIf="!filteringDisabled" class="btn btn-secondary btn-small" (click)="collapse.toggle()" [attr.aria-expanded]="!filteringCollapsed" placement="left" ngbTooltip="{{filteringCollapsed ? 'Open' : 'Close'}} Filtering and Sorting" attr.aria-label="{{filteringCollapsed ? 'Open' : 'Close'}} Filtering and Sorting">
|
||||
<i class="fa fa-filter" aria-hidden="true"></i>
|
||||
<span class="sr-only">Sort / Filter</span>
|
||||
</button>
|
||||
<div class="col-auto align-self-end">
|
||||
<button *ngIf="!filteringDisabled" class="btn btn-secondary btn-small" (click)="collapse.toggle()" [attr.aria-expanded]="!filteringCollapsed" placement="left" ngbTooltip="{{filteringCollapsed ? 'Open' : 'Close'}} Filtering and Sorting" attr.aria-label="{{filteringCollapsed ? 'Open' : 'Close'}} Filtering and Sorting">
|
||||
<i class="fa fa-filter" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">Sort / Filter</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="phone-hidden">
|
||||
|
|
@ -25,8 +27,8 @@
|
|||
<app-drawer #commentDrawer="drawer" [isOpen]="!filteringCollapsed" [style.--drawer-width]="'300px'" [style.--drawer-background-color]="'#010409'" (drawerClosed)="filteringCollapsed = !filteringCollapsed">
|
||||
<div header>
|
||||
<h2 style="margin-top: 0.5rem">Book Settings
|
||||
<button type="button" class="close" aria-label="Close" (click)="commentDrawer.close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="commentDrawer.close()">
|
||||
|
||||
</button>
|
||||
</h2>
|
||||
|
||||
|
|
@ -40,11 +42,11 @@
|
|||
<ng-template #filterSection>
|
||||
<ng-template #globalFilterTooltip>This is library agnostic</ng-template>
|
||||
<div class="filter-section mx-auto pb-3">
|
||||
<div class="row justify-content-center no-gutters">
|
||||
<div class="col-md-2 mr-3" *ngIf="!filterSettings.formatDisabled">
|
||||
<div class="form-group">
|
||||
<label for="format">Format</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="globalFilterTooltip" role="button" tabindex="0"></i>
|
||||
<span class="sr-only" id="filter-global-format-help"><ng-container [ngTemplateOutlet]="globalFilterTooltip"></ng-container></span>
|
||||
<div class="row justify-content-center g-0">
|
||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.formatDisabled">
|
||||
<div class="mb-3">
|
||||
<label for="format" class="form-label">Format</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="globalFilterTooltip" role="button" tabindex="0"></i>
|
||||
<span class="visually-hidden" id="filter-global-format-help"><ng-container [ngTemplateOutlet]="globalFilterTooltip"></ng-container></span>
|
||||
<app-typeahead (selectedData)="updateFormatFilters($event)" [settings]="formatSettings" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
|
|
@ -56,9 +58,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3"*ngIf="!filterSettings.libraryDisabled">
|
||||
<div class="form-group">
|
||||
<label for="libraries">Libraries</label>
|
||||
<div class="col-md-2 me-3"*ngIf="!filterSettings.libraryDisabled">
|
||||
<div class="mb-3">
|
||||
<label for="libraries" class="form-label">Libraries</label>
|
||||
<app-typeahead (selectedData)="updateLibraryFilters($event)" [settings]="librarySettings" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
|
|
@ -70,10 +72,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="!filterSettings.collectionDisabled">
|
||||
<div class="form-group">
|
||||
<label for="collections">Collections</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="globalFilterTooltip" role="button" tabindex="0"></i>
|
||||
<span class="sr-only" id="filter-global-collections-help"><ng-container [ngTemplateOutlet]="globalFilterTooltip"></ng-container></span>
|
||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.collectionDisabled">
|
||||
<div class="mb-3">
|
||||
<label for="collections" class="form-label">Collections</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="globalFilterTooltip" role="button" tabindex="0"></i>
|
||||
<span class="visually-hidden" id="filter-global-collections-help"><ng-container [ngTemplateOutlet]="globalFilterTooltip"></ng-container></span>
|
||||
<app-typeahead (selectedData)="updateCollectionFilters($event)" [settings]="collectionSettings" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
|
|
@ -85,9 +87,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="!filterSettings.genresDisabled">
|
||||
<div class="form-group">
|
||||
<label for="genres">Genres</label>
|
||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.genresDisabled">
|
||||
<div class="mb-3">
|
||||
<label for="genres" class="form-label">Genres</label>
|
||||
<app-typeahead (selectedData)="updateGenreFilters($event)" [settings]="genreSettings" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
|
|
@ -99,9 +101,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="!filterSettings.tagsDisabled">
|
||||
<div class="form-group">
|
||||
<label for="tags">Tags</label>
|
||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.tagsDisabled">
|
||||
<div class="mb-3">
|
||||
<label for="tags" class="form-label">Tags</label>
|
||||
<app-typeahead (selectedData)="updateTagFilters($event)" [settings]="tagsSettings" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
|
|
@ -113,11 +115,11 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center no-gutters">
|
||||
<div class="row justify-content-center g-0">
|
||||
<!-- The People row -->
|
||||
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.CoverArtist)">
|
||||
<div class="form-group">
|
||||
<label for="cover-artist">Cover Artists</label>
|
||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.CoverArtist)">
|
||||
<div class="mb-3">
|
||||
<label for="cover-artist" class="form-label">Cover Artists</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.CoverArtist)" [settings]="getPersonsSettings(PersonRole.CoverArtist)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
|
|
@ -129,9 +131,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Writer)">
|
||||
<div class="form-group">
|
||||
<label for="writers">Writers</label>
|
||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Writer)">
|
||||
<div class="mb-3">
|
||||
<label for="writers" class="form-label">Writers</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Writer)" [settings]="getPersonsSettings(PersonRole.Writer)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
|
|
@ -143,9 +145,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Publisher)">
|
||||
<div class="form-group">
|
||||
<label for="publisher">Publisher</label>
|
||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Publisher)">
|
||||
<div class="mb-3">
|
||||
<label for="publisher" class="form-label">Publisher</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Publisher)" [settings]="getPersonsSettings(PersonRole.Publisher)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
|
|
@ -157,9 +159,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Penciller)">
|
||||
<div class="form-group">
|
||||
<label for="penciller">Penciller</label>
|
||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Penciller)">
|
||||
<div class="mb-3">
|
||||
<label for="penciller" class="form-label">Penciller</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Penciller)" [settings]="getPersonsSettings(PersonRole.Penciller)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
|
|
@ -171,9 +173,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Letterer)">
|
||||
<div class="form-group">
|
||||
<label for="letterer">Letterer</label>
|
||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Letterer)">
|
||||
<div class="mb-3">
|
||||
<label for="letterer" class="form-label">Letterer</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Letterer)" [settings]="getPersonsSettings(PersonRole.Letterer)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
|
|
@ -185,9 +187,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Inker)">
|
||||
<div class="form-group">
|
||||
<label for="inker">Inker</label>
|
||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Inker)">
|
||||
<div class="mb-3">
|
||||
<label for="inker" class="form-label">Inker</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Inker)" [settings]="getPersonsSettings(PersonRole.Inker)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
|
|
@ -199,9 +201,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Editor)">
|
||||
<div class="form-group">
|
||||
<label for="editor">Editor</label>
|
||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Editor)">
|
||||
<div class="mb-3">
|
||||
<label for="editor" class="form-label">Editor</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Editor)" [settings]="getPersonsSettings(PersonRole.Editor)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
|
|
@ -213,9 +215,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Colorist)">
|
||||
<div class="form-group">
|
||||
<label for="colorist">Colorist</label>
|
||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Colorist)">
|
||||
<div class="mb-3">
|
||||
<label for="colorist" class="form-label">Colorist</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Colorist)" [settings]="getPersonsSettings(PersonRole.Colorist)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
|
|
@ -227,9 +229,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Character)">
|
||||
<div class="form-group">
|
||||
<label for="character">Character</label>
|
||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Character)">
|
||||
<div class="mb-3">
|
||||
<label for="character" class="form-label">Character</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Character)" [settings]="getPersonsSettings(PersonRole.Character)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
|
|
@ -241,9 +243,9 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Translator)">
|
||||
<div class="form-group">
|
||||
<label for="translators">Translators</label>
|
||||
<div class="col-md-2 me-3" *ngIf="peopleSettings.hasOwnProperty(PersonRole.Translator)">
|
||||
<div class="mb-3">
|
||||
<label for="translators" class="form-label">Translators</label>
|
||||
<app-typeahead (selectedData)="updatePersonFilters($event, PersonRole.Translator)" [settings]="getPersonsSettings(PersonRole.Translator)" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.name}}
|
||||
|
|
@ -255,9 +257,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center no-gutters">
|
||||
<div class="col-md-2 mr-3" *ngIf="!filterSettings.readProgressDisabled">
|
||||
<label>Read Progress</label>
|
||||
<div class="row justify-content-center g-0">
|
||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.readProgressDisabled">
|
||||
<label class="form-label">Read Progress</label>
|
||||
<form [formGroup]="readProgressGroup">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" id="notread" formControlName="notRead">
|
||||
|
|
@ -274,8 +276,8 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="!filterSettings.ratingDisabled">
|
||||
<label for="ratings">Rating</label>
|
||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.ratingDisabled">
|
||||
<label for="ratings" class="form-label">Rating</label>
|
||||
<form class="form-inline">
|
||||
<ngb-rating class="rating-star" [(rate)]="filter.rating" (rateChange)="updateRating($event)" [resettable]="true">
|
||||
<ng-template let-fill="fill" let-index="index">
|
||||
|
|
@ -285,8 +287,8 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="!filterSettings.ageRatingDisabled">
|
||||
<label for="age-rating">Age Rating</label>
|
||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.ageRatingDisabled">
|
||||
<label for="age-rating" class="form-label">Age Rating</label>
|
||||
<app-typeahead (selectedData)="updateAgeRating($event)" [settings]="ageRatingSettings" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
|
|
@ -297,8 +299,8 @@
|
|||
</app-typeahead>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="!filterSettings.languageDisabled">
|
||||
<label for="languages">Language</label>
|
||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.languageDisabled">
|
||||
<label for="languages" class="form-label">Language</label>
|
||||
<app-typeahead (selectedData)="updateLanguageRating($event)" [settings]="languageSettings" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
|
|
@ -309,8 +311,8 @@
|
|||
</app-typeahead>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 mr-3" *ngIf="!filterSettings.publicationStatusDisabled">
|
||||
<label for="publication-status">Publication Status</label>
|
||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.publicationStatusDisabled">
|
||||
<label for="publication-status" class="form-label">Publication Status</label>
|
||||
<app-typeahead (selectedData)="updatePublicationStatus($event)" [settings]="publicationStatusSettings" [reset]="resetTypeaheads">
|
||||
<ng-template #badgeItem let-item let-position="idx">
|
||||
{{item.title}}
|
||||
|
|
@ -320,13 +322,13 @@
|
|||
</ng-template>
|
||||
</app-typeahead>
|
||||
</div>
|
||||
<div class="col-md-2 mr-3"></div>
|
||||
<div class="col-md-2 me-3"></div>
|
||||
</div>
|
||||
<div class="row justify-content-center no-gutters">
|
||||
<div class="col-md-2 mr-3" *ngIf="!filterSettings.sortDisabled">
|
||||
<div class="row justify-content-center g-0">
|
||||
<div class="col-md-2 me-3" *ngIf="!filterSettings.sortDisabled">
|
||||
<form [formGroup]="sortGroup">
|
||||
<div class="form-group">
|
||||
<label for="sort-options">Sort By</label>
|
||||
<div class="mb-3">
|
||||
<label for="sort-options" class="form-label">Sort By</label>
|
||||
<button class="btn btn-sm btn-secondary-outline" (click)="updateSortOrder()" style="height: 25px; padding-bottom: 0px;">
|
||||
<i class="fa fa-arrow-up" title="Ascending" *ngIf="isAscendingSort; else descSort"></i>
|
||||
<ng-template #descSort>
|
||||
|
|
@ -341,14 +343,14 @@
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-2 mr-3" *ngIf="filterSettings.sortDisabled"></div>
|
||||
<div class="col-md-2 mr-3"></div>
|
||||
<div class="col-md-2 mr-3"></div>
|
||||
<div class="col-md-2 mr-3 mt-4">
|
||||
<button class="btn btn-secondary btn-block" (click)="clear()">Clear</button>
|
||||
<div class="col-md-2 me-3" *ngIf="filterSettings.sortDisabled"></div>
|
||||
<div class="col-md-2 me-3"></div>
|
||||
<div class="col-md-2 me-3"></div>
|
||||
<div class="col-md-2 me-3">
|
||||
<button class="btn btn-secondary col-12" (click)="clear()">Clear</button>
|
||||
</div>
|
||||
<div class="col-md-2 mr-3 mt-4">
|
||||
<button class="btn btn-primary btn-block" (click)="apply()">Apply</button>
|
||||
<div class="col-md-2 me-3">
|
||||
<button class="btn btn-primary col-12" (click)="apply()">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -357,7 +359,7 @@
|
|||
<ng-container [ngTemplateOutlet]="paginationTemplate" [ngTemplateOutletContext]="{ id: 'top' }"></ng-container>
|
||||
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col-auto" *ngFor="let item of items; trackBy:trackByIdentity; index as i">
|
||||
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
||||
</div>
|
||||
|
|
@ -388,7 +390,7 @@
|
|||
<label
|
||||
id="paginationInputLabel-{{id}}"
|
||||
for="paginationInput-{{id}}"
|
||||
class="col-form-label mr-2 ml-1"
|
||||
class="col-form-label me-2 ms-1 form-label"
|
||||
>Page</label>
|
||||
<input #i
|
||||
type="text"
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
@use '../../../theme/colors';
|
||||
|
||||
.star {
|
||||
font-size: 1.5rem;
|
||||
color: colors.$rating-empty;
|
||||
}
|
||||
.filled {
|
||||
color: colors.$rating-filled;
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<span class="download" *ngIf="download$ | async as download">
|
||||
<app-circular-loader [currentValue]="download.progress"></app-circular-loader>
|
||||
<span class="sr-only" role="status">
|
||||
<span class="visually-hidden" role="status">
|
||||
{{download.progress}}% downloaded
|
||||
</span>
|
||||
</span>
|
||||
|
|
@ -27,8 +27,9 @@
|
|||
</div>
|
||||
|
||||
<div class="count" *ngIf="count > 1">
|
||||
<span class="badge badge-primary">{{count}}</span>
|
||||
<span class="badge bg-primary">{{count}}</span>
|
||||
</div>
|
||||
<div class="card-overlay"></div>
|
||||
</div>
|
||||
|
||||
<div class="card-body" *ngIf="title.length > 0 || actions.length > 0">
|
||||
|
|
@ -36,12 +37,12 @@
|
|||
<span class="card-title" placement="top" id="{{title}}_{{entity?.id}}" [ngbTooltip]="tooltipTitle" (click)="handleClick()" tabindex="0">
|
||||
<span *ngIf="isPromoted()">
|
||||
<i class="fa fa-angle-double-up" aria-hidden="true"></i>
|
||||
<span class="sr-only">(promoted)</span>
|
||||
<span class="visually-hidden">(promoted)</span>
|
||||
</span>
|
||||
<i class="fa {{utilityService.mangaFormatIcon(format)}}" aria-hidden="true" *ngIf="format != MangaFormat.UNKNOWN" title="{{utilityService.mangaFormat(format)}}"></i><span class="sr-only">{{utilityService.mangaFormat(format)}}</span>
|
||||
<i class="fa {{utilityService.mangaFormatIcon(format)}}" aria-hidden="true" *ngIf="format != MangaFormat.UNKNOWN" title="{{utilityService.mangaFormat(format)}}"></i><span class="visually-hidden">{{utilityService.mangaFormat(format)}}</span>
|
||||
{{title}}
|
||||
</span>
|
||||
<span class="card-actions float-right">
|
||||
<span class="card-actions float-end">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="title"></app-card-actionables>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@use '../../../theme/colors';
|
||||
|
||||
|
||||
$triangle-size: 30px;
|
||||
$image-height: 230px;
|
||||
|
|
@ -7,7 +7,7 @@ $image-width: 160px;
|
|||
.error-banner {
|
||||
width: 160px;
|
||||
height: 18px;
|
||||
background-color: colors.$error-color;
|
||||
background-color: var(--toast-error-bg-color);
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
|
|
@ -25,6 +25,11 @@ $image-width: 160px;
|
|||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
background-color: var(--card-bg-color);
|
||||
color: var(--card-text-color);
|
||||
border-color: var(--card-border-color);
|
||||
|
||||
}
|
||||
|
||||
.card-title {
|
||||
|
|
@ -39,7 +44,7 @@ $image-width: 160px;
|
|||
}
|
||||
|
||||
.selected-highlight {
|
||||
outline: 2px solid colors.$primary-color;
|
||||
outline: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -52,7 +57,7 @@ $image-width: 160px;
|
|||
height: 5px;
|
||||
|
||||
.progress {
|
||||
color: colors.$primary-color;
|
||||
color: var(--card-progress-bar-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
|
@ -73,7 +78,7 @@ $image-width: 160px;
|
|||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 $triangle-size $triangle-size 0;
|
||||
border-color: transparent colors.$primary-color transparent transparent;
|
||||
border-color: transparent var(--primary-color) transparent transparent;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -106,10 +111,12 @@ $image-width: 160px;
|
|||
|
||||
.bulk-mode {
|
||||
visibility: visible;
|
||||
z-index: 110;
|
||||
}
|
||||
|
||||
.overlay-item {
|
||||
visibility: visible;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -142,3 +149,17 @@ $image-width: 160px;
|
|||
text-decoration: none;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.card-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 230px;
|
||||
z-index: 10;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.card-overlay:hover {
|
||||
opacity: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
|
||||
|
||||
<!-- Arc Information -->
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
Id: {{chapter.id}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
Title: {{chapter.titleName || '-'}}
|
||||
</div>
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col" *ngIf="chapter.hasOwnProperty('created')">
|
||||
Added: {{(chapter.created | date: 'short') || '-'}}
|
||||
</div>
|
||||
|
|
@ -30,11 +30,11 @@
|
|||
</div>
|
||||
|
||||
<ul class="list-unstyled" >
|
||||
<li class="media my-4">
|
||||
<li class="d-flex my-4">
|
||||
<a (click)="readChapter(chapter)" href="javascript:void(0);" title="Read {{libraryType !== LibraryType.Comic ? 'Chapter ' : 'Issue #'}} {{chapter.number}}">
|
||||
<app-image class="mr-3" width="74px" [imageUrl]="chapter.coverImage"></app-image>
|
||||
<app-image class="me-3" width="74px" [imageUrl]="chapter.coverImage"></app-image>
|
||||
</a>
|
||||
<div class="media-body">
|
||||
<div class="flex-grow-1">
|
||||
<h5 class="mt-0 mb-1">
|
||||
<span *ngIf="chapter.number !== '0'; else specialHeader">
|
||||
<!-- TODO: Add back in
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
<app-card-actionables (actionHandler)="performAction($event, chapter)" [actions]="chapterActions" [labelBy]="utilityService.formatChapterName(libraryType, true, true) + formatChapterNumber(chapter)"></app-card-actionables>
|
||||
{{utilityService.formatChapterName(libraryType, true, false) }} {{formatChapterNumber(chapter)}}
|
||||
</span> -->
|
||||
<span class="badge badge-primary badge-pill">
|
||||
<span class="badge bg-primary rounded-pill">
|
||||
<span *ngIf="chapter.pagesRead > 0 && chapter.pagesRead < chapter.pages">{{chapter.pagesRead}} / {{chapter.pages}}</span>
|
||||
<span *ngIf="chapter.pagesRead === 0">UNREAD</span>
|
||||
<span *ngIf="chapter.pagesRead === chapter.pages">READ</span>
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
|
||||
|
||||
<ng-container>
|
||||
<div class="row no-gutters mt-1" *ngIf="chapter.writers && chapter.writers.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="chapter.writers && chapter.writers.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Writers</h5>
|
||||
</div>
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters mt-1" *ngIf="chapter.coverArtist && chapter.coverArtist.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="chapter.coverArtist && chapter.coverArtist.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Artists</h5>
|
||||
</div>
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters mt-1" *ngIf="chapter.publisher && chapter.publisher.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="chapter.publisher && chapter.publisher.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Publishers</h5>
|
||||
</div>
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
Arc Information
|
||||
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
Id: {{chapter.id}}
|
||||
</div>
|
||||
|
|
@ -107,7 +107,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col" *ngIf="chapter.hasOwnProperty('created')">
|
||||
Added: {{(chapter.created | date: 'short') || '-'}}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
<ngx-file-drop (onFileDrop)="dropped($event)"
|
||||
(onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)" accept=".png,.jpg,.jpeg" [directory]="false" dropZoneClassName="file-upload" contentClassName="file-upload-zone" [directory]="false">
|
||||
<ng-template ngx-file-drop-content-tmp let-openFileSelector="openFileSelector">
|
||||
<div class="row no-gutters mt-3 pb-3" *ngIf="mode === 'all'">
|
||||
<div class="row g-0 mt-3 pb-3" *ngIf="mode === 'all'">
|
||||
<div class="mx-auto">
|
||||
<div class="row no-gutters mb-3">
|
||||
<div class="row g-0 mb-3">
|
||||
<i class="fa fa-file-upload mx-auto" style="font-size: 24px;" aria-hidden="true"></i>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="mx-auto">
|
||||
<a class="col" style="padding-right:0px" href="javascript:void(0)" (click)="mode = 'url'; setupEnterHandler()"><span class="phone-hidden">Enter a </span>Url</a>
|
||||
<span class="col" style="padding-right:0px">•</span>
|
||||
|
|
@ -23,17 +23,16 @@
|
|||
|
||||
|
||||
<ng-container *ngIf="mode === 'url'">
|
||||
<div class="row no-gutters mt-3 pb-3 ml-md-2 mr-md-2">
|
||||
<div class="input-group col-md-10 mr-md-2" style="width: 100%">
|
||||
<div class="row g-0 mt-3 pb-3 ms-md-2 me-md-2">
|
||||
<div class="input-group col-md-10 me-md-2" style="width: 100%">
|
||||
<!-- TOOD: Bootstrap migration: This should be replaced with just the label-->
|
||||
<div class="input-group-prepend">
|
||||
<label class="input-group-text" for="load-image">Url</label>
|
||||
<label class="input-group-text form-label" for="load-image">Url</label>
|
||||
</div>
|
||||
<input type="text" autocomplete="off" class="form-control" formControlName="coverImageUrl" placeholder="https://" id="load-image" class="form-control">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" id="load-image-addon" (click)="loadImage(); mode='all';" [disabled]="form.get('coverImageUrl')?.value.length === 0">
|
||||
Load
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary" type="button" id="load-image-addon" (click)="loadImage(); mode='all';" [disabled]="form.get('coverImageUrl')?.value.length === 0">
|
||||
Load
|
||||
</button>
|
||||
</div>
|
||||
<button class="col btn btn-secondary" href="javascript:void(0)" (click)="mode = 'all'" aria-label="Back">
|
||||
<i class="fas fa-share" aria-hidden="true" style="transform: rotateY(180deg)"></i>
|
||||
|
|
@ -51,7 +50,7 @@
|
|||
</ng-template>
|
||||
</form>
|
||||
|
||||
<div class="row no-gutters chooser" style="padding-top: 10px">
|
||||
<div class="row g-0 chooser" style="padding-top: 10px">
|
||||
<div class="image-card col-auto {{selectedIndex === idx ? 'selected' : ''}}" *ngFor="let url of imageUrls; let idx = index;" tabindex="0" attr.aria-label="Image {{idx + 1}}" (click)="selectImage(idx)">
|
||||
<app-image class="card-img-top" height="230px" width="158px" [imageUrl]="url"></app-image>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
@use '../../../theme/colors';
|
||||
$image-height: 230px;
|
||||
$image-width: 160px;
|
||||
|
||||
|
|
@ -14,7 +13,7 @@ $image-width: 160px;
|
|||
}
|
||||
|
||||
.selected {
|
||||
outline: 5px solid colors.$primary-color;
|
||||
outline: 5px solid var(--primary-color);
|
||||
outline-width: medium;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
|
@ -22,7 +21,7 @@ $image-width: 160px;
|
|||
ngx-file-drop ::ng-deep > div {
|
||||
// styling for the outer drop box
|
||||
width: 100%;
|
||||
border: 2px solid colors.$primary-color;
|
||||
border: 2px solid var(--primary-color);
|
||||
border-radius: 5px;
|
||||
height: 100px;
|
||||
margin: auto;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<li class="list-group-item">
|
||||
<span>{{file.filePath}}</span>
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
Pages: {{file.pages}}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
$primary-color: #cc7b19;
|
||||
|
||||
.card {
|
||||
margin: 10px;
|
||||
|
|
@ -28,7 +27,6 @@ $primary-color: #cc7b19;
|
|||
.overlay {
|
||||
height: 160px;
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
visibility: visible;
|
||||
|
||||
.overlay-item {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<div class="carousel-container" *ngIf="items.length > 0">
|
||||
<div>
|
||||
<h2 style="display: inline-block;"><a href="javascript:void(0)" (click)="sectionClicked($event)" class="section-title">{{title}}</a></h2>
|
||||
<div class="float-right">
|
||||
<button class="btn btn-icon" [disabled]="isBeginning" (click)="prevPage()"><i class="fa fa-angle-left" aria-hidden="true"></i><span class="sr-only">Previous Items</span></button>
|
||||
<button class="btn btn-icon" [disabled]="isEnd" (click)="nextPage()"><i class="fa fa-angle-right" aria-hidden="true"></i><span class="sr-only">Next Items</span></button>
|
||||
<div class="float-end">
|
||||
<button class="btn btn-icon" [disabled]="isBeginning" (click)="prevPage()"><i class="fa fa-angle-left" aria-hidden="true"></i><span class="visually-hidden">Previous Items</span></button>
|
||||
<button class="btn btn-icon" [disabled]="isEnd" (click)="nextPage()"><i class="fa fa-angle-right" aria-hidden="true"></i><span class="visually-hidden">Next Items</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -8,41 +8,20 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 400;
|
||||
margin-left: 10px;
|
||||
color: black;
|
||||
|
||||
:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .bg-light {
|
||||
.carousel-container {
|
||||
.section-title {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
}
|
||||
// These are needed due to nested css within another component
|
||||
::ng-deep .bg-dark {
|
||||
.section-title {
|
||||
color: #efefef !important;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 400;
|
||||
margin-left: 10px;
|
||||
color: var(--carousel-header-text-color);
|
||||
text-decoration: var(--carousel-header-text-decoration);
|
||||
|
||||
&:hover, &:focus, &:active {
|
||||
text-decoration: var(--carousel-hover-header-text-decoration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .swiper-slide {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
|
||||
::ng-deep .swiper-slide {
|
||||
width: auto !important;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Component, ContentChild, EventEmitter, Input, OnInit, Output, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { SwiperComponent } from 'swiper/angular';
|
||||
//import Swiper from 'swiper';
|
||||
//import { SwiperEvents, Swiper } from 'swiper/types';
|
||||
import { Swiper, SwiperEvents } from 'swiper/types';
|
||||
|
||||
@Component({
|
||||
selector: 'app-carousel-reel',
|
||||
|
|
@ -15,18 +14,17 @@ export class CarouselReelComponent implements OnInit {
|
|||
@Input() title = '';
|
||||
@Output() sectionClick = new EventEmitter<string>();
|
||||
|
||||
@ViewChild('swiper', { static: false }) swiper?: SwiperComponent;
|
||||
swiper: Swiper | undefined;
|
||||
|
||||
|
||||
//swiper!: Swiper;
|
||||
trackByIdentity: (index: number, item: any) => string;
|
||||
|
||||
get isEnd() {
|
||||
return this.swiper?.swiperRef.isEnd;
|
||||
return this.swiper?.isEnd;
|
||||
}
|
||||
|
||||
get isBeginning() {
|
||||
return this.swiper?.swiperRef.isBeginning;
|
||||
return this.swiper?.isBeginning;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
|
|
@ -36,14 +34,16 @@ export class CarouselReelComponent implements OnInit {
|
|||
ngOnInit(): void {}
|
||||
|
||||
nextPage() {
|
||||
if (this.isEnd) return;
|
||||
if (this.swiper) {
|
||||
this.swiper.swiperRef.setProgress(this.swiper.swiperRef.progress + 0.25, 600);
|
||||
this.swiper.setProgress(this.swiper.progress + 0.25, 600);
|
||||
}
|
||||
}
|
||||
|
||||
prevPage() {
|
||||
if (this.isBeginning) return;
|
||||
if (this.swiper) {
|
||||
this.swiper.swiperRef.setProgress(this.swiper.swiperRef.progress - 0.25, 600);
|
||||
this.swiper.setProgress(this.swiper.progress - 0.25, 600);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,14 +51,7 @@ export class CarouselReelComponent implements OnInit {
|
|||
this.sectionClick.emit(this.title);
|
||||
}
|
||||
|
||||
// onSwiper(eventParams: Parameters<SwiperEvents['init']>) {
|
||||
// console.log('swiper: ', eventParams);
|
||||
// [this.swiper] = eventParams;
|
||||
// }
|
||||
|
||||
// onSwiper(params: Swiper) {
|
||||
// // const [swiper] = params;
|
||||
// // console.log(swiper);
|
||||
// // return params;
|
||||
// }
|
||||
onSwiper(eventParams: Parameters<SwiperEvents['init']>) {
|
||||
[this.swiper] = eventParams;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
<app-image class="poster" maxWidth="481px" [imageUrl]="tagImage"></app-image>
|
||||
</div>
|
||||
<div class="col-md-10 col-xs-8 col-sm-6">
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<h2>
|
||||
|
||||
{{collectionTag.title}}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="row no-gutters mt-2 mb-2">
|
||||
<div class="ml-2" *ngIf="isAdmin">
|
||||
<div class="row g-0 mt-2 mb-2">
|
||||
<div class="ms-2" *ngIf="isAdmin">
|
||||
<button class="btn btn-secondary" (click)="openEditCollectionTagModal(collectionTag)" title="Edit Series information">
|
||||
<span>
|
||||
<i class="fa fa-pen" aria-hidden="true"></i>
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<app-read-more [text]="collectionTag.summary" [maxLength]="250"></app-read-more>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
<form [formGroup]="typeaheadForm" class="grouped-typeahead">
|
||||
<div class="typeahead-input" [ngClass]="{'focused': hasFocus == true}" (click)="onInputFocus($event)">
|
||||
<div>
|
||||
<div class="search">
|
||||
<input #input [id]="id" type="text" autocomplete="off" formControlName="typeahead" [placeholder]="placeholder"
|
||||
aria-haspopup="listbox" aria-owns="dropdown" aria-expanded="hasFocus && (grouppedData.persons.length || grouppedData.collections.length || grouppedData.series.length || grouppedData.persons.length || grouppedData.tags.length || grouppedData.genres.length)"
|
||||
aria-autocomplete="list" (focusout)="close($event)" (focus)="open($event)"
|
||||
>
|
||||
<div class="spinner-border spinner-border-sm" role="status" *ngIf="isLoading">
|
||||
<span class="sr-only">Loading...</span>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<button type="button" class="close" aria-label="Close" (click)="resetField()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close float-end" aria-label="Close" (click)="resetField()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
@use "../../theme/colors";
|
||||
form {
|
||||
max-height: 38px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 15px;
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
left: 4px;
|
||||
border: none;
|
||||
}
|
||||
// input {
|
||||
// width: 15px;
|
||||
// opacity: 1;
|
||||
// position: relative;
|
||||
// left: 4px;
|
||||
// border: none;
|
||||
// }
|
||||
|
||||
.search-result img {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
margin-top: 8px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
.typeahead-input {
|
||||
border: 1px solid transparent;
|
||||
|
|
@ -27,32 +31,35 @@ input {
|
|||
box-sizing: border-box;
|
||||
box-shadow: none;
|
||||
cursor: text;
|
||||
background-color: #fff;
|
||||
background-color: var(--input-bg-color);
|
||||
color: var(--body-text-color);
|
||||
min-height: 38px;
|
||||
transition-property: all;
|
||||
transition-duration: 0.3s;
|
||||
display: block;
|
||||
|
||||
|
||||
.close {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 10px;
|
||||
.search {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media only screen and (max-width:650px) {
|
||||
.close {
|
||||
top: 50%;
|
||||
transform: translate(0, -60%);
|
||||
}
|
||||
}
|
||||
// .close {
|
||||
// cursor: pointer;
|
||||
// position: absolute;
|
||||
// top: 7px;
|
||||
// right: 10px;
|
||||
// }
|
||||
|
||||
// @media only screen and (max-width:650px) {
|
||||
// .close {
|
||||
// top: 50%;
|
||||
// transform: translate(0, -60%);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
input {
|
||||
outline: 0 !important;
|
||||
border-radius: .28571429rem;
|
||||
display: inline-block !important;
|
||||
padding: 0px !important;
|
||||
min-height: 0px !important;
|
||||
max-width: 100% !important;
|
||||
|
|
@ -60,32 +67,35 @@ input {
|
|||
text-indent: 0 !important;
|
||||
line-height: inherit !important;
|
||||
box-shadow: none !important;
|
||||
width: 300px;
|
||||
width: 200px;
|
||||
transition-property: all;
|
||||
transition-duration: 0.3s;
|
||||
display: block;
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
left: 4px;
|
||||
border: none;
|
||||
|
||||
&:focus-visible {
|
||||
width: calc(100vw - 400px);
|
||||
}
|
||||
|
||||
&:empty {
|
||||
padding-top: 6px !important;
|
||||
}
|
||||
}
|
||||
|
||||
input:focus-visible {
|
||||
width: calc(100vw - 400px);
|
||||
&.focused {
|
||||
width: 100%;
|
||||
border-color: var(--input-focused-border-color);
|
||||
}
|
||||
|
||||
input:empty {
|
||||
padding-top: 6px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.typeahead-input.focused {
|
||||
width: 100%;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
|
||||
/* small devices (phones, 650px and down) */
|
||||
@media only screen and (max-width:650px) {
|
||||
.typeahead-input {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%
|
||||
}
|
||||
|
|
@ -95,21 +105,20 @@ input {
|
|||
}
|
||||
}
|
||||
|
||||
::ng-deep .bg-dark .typeahead-input {
|
||||
color: #efefef;
|
||||
background-color: colors.$dark-bg-color;
|
||||
}
|
||||
|
||||
// Causes bleedover
|
||||
::ng-deep .bg-dark .dropdown .list-group-item.hover {
|
||||
background-color: colors.$dark-hover-color;
|
||||
}
|
||||
.section-header {
|
||||
color: var(--body-text-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--list-group-item-bg-color) !important;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
width: 100vw;
|
||||
height: calc(100vh - 57px); //header offset
|
||||
background: rgba(0,0,0,0.5);
|
||||
background: var(--dropdown-overlay-color);
|
||||
position: fixed;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
|
|
@ -155,39 +164,6 @@ ul ul {
|
|||
border-radius: 0px !important;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::ng-deep .bg-dark {
|
||||
& .section-header {
|
||||
|
||||
background: colors.$dark-item-accent-bg;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
& .section-header:hover {
|
||||
background-color: colors.$dark-item-accent-bg !important;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .bg-light {
|
||||
& .section-header {
|
||||
|
||||
background: colors.$white-item-accent-bg;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
& .section-header:hover, .list-group-item.section-header:hover {
|
||||
background: colors.$white-item-accent-bg !important;
|
||||
}
|
||||
|
||||
& .list-group-item:hover {
|
||||
background-color: colors.$primary-color !important;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.spinner-border {
|
||||
position: absolute;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
<button class="btn btn-icon mx-auto">
|
||||
<i class="fa fa-angle-double-up animate" aria-hidden="true"></i>
|
||||
</button>
|
||||
<span class="sr-only">Scroll up to move to next chapter</span>
|
||||
<span class="visually-hidden">Scroll up to move to next chapter</span>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngFor="let item of webtoonImages | async; let index = index;">
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
<button class="btn btn-icon mx-auto">
|
||||
<i class="fa fa-angle-double-down animate" aria-hidden="true"></i>
|
||||
</button>
|
||||
<span class="sr-only">Scroll down to move to next chapter</span>
|
||||
<span class="visually-hidden">Scroll down to move to next chapter</span>
|
||||
</div>
|
||||
<div style="height: 200px"></div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,17 +3,17 @@
|
|||
<div style="display: flex; margin-top: 5px;">
|
||||
<button class="btn btn-icon" style="height: 100%" title="Back" (click)="closeReader()">
|
||||
<i class="fa fa-arrow-left" aria-hidden="true"></i>
|
||||
<span class="sr-only">Back</span>
|
||||
<span class="visually-hidden">Back</span>
|
||||
</button>
|
||||
|
||||
<div>
|
||||
<div style="font-weight: bold;">{{title}} <span class="clickable" *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" aria-label="Incognito mode is on. Toggle to turn off.">(<i class="fa fa-glasses" aria-hidden="true"></i><span class="sr-only">Incognito Mode:</span>)</span></div>
|
||||
<div style="font-weight: bold;">{{title}} <span class="clickable" *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" aria-label="Incognito mode is on. Toggle to turn off.">(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">Incognito Mode:</span>)</span></div>
|
||||
<div class="subtitle">
|
||||
{{subtitle}}
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-left: auto; padding-right: 3%;">
|
||||
<button class="btn btn-icon btn-small" role="checkbox" [attr.aria-checked]="pageBookmarked" title="{{pageBookmarked ? 'Unbookmark Page' : 'Bookmark Page'}}" (click)="bookmarkPage()"><i class="{{pageBookmarked ? 'fa' : 'far'}} fa-bookmark" aria-hidden="true"></i><span class="sr-only">{{pageBookmarked ? 'Unbookmark Page' : 'Bookmark Page'}}</span></button>
|
||||
<button class="btn btn-icon btn-small" role="checkbox" [attr.aria-checked]="pageBookmarked" title="{{pageBookmarked ? 'Unbookmark Page' : 'Bookmark Page'}}" (click)="bookmarkPage()"><i class="{{pageBookmarked ? 'fa' : 'far'}} fa-bookmark" aria-hidden="true"></i><span class="visually-hidden">{{pageBookmarked ? 'Unbookmark Page' : 'Bookmark Page'}}</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -56,9 +56,9 @@
|
|||
</div>
|
||||
|
||||
<div class="fixed-bottom overlay" *ngIf="menuOpen" [@slideFromBottom]="menuOpen">
|
||||
<div class="form-group" *ngIf="pageOptions != undefined && pageOptions.ceil != undefined">
|
||||
<span class="sr-only" id="slider-info"></span>
|
||||
<div class="row no-gutters">
|
||||
<div class="mb-3" *ngIf="pageOptions != undefined && pageOptions.ceil != undefined">
|
||||
<span class="visually-hidden" id="slider-info"></span>
|
||||
<div class="row g-0">
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="prevChapterDisabled" (click)="loadPrevChapter();resetMenuCloseTimer();" title="Prev Chapter/Volume"><i class="fa fa-fast-backward" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-small btn-icon col-1" [disabled]="prevPageDisabled || pageNum === 0" (click)="goToPage(0);resetMenuCloseTimer();" title="First Page"><i class="fa fa-step-backward" aria-hidden="true"></i></button>
|
||||
<div class="col custom-slider" *ngIf="pageOptions.ceil > 0; else noSlider">
|
||||
|
|
@ -75,29 +75,29 @@
|
|||
|
||||
|
||||
</div>
|
||||
<div class="row pt-4 ml-2 mr-2">
|
||||
<div class="row pt-4 ms-2 me-2">
|
||||
<div class="col">
|
||||
<button class="btn btn-icon" (click)="setReadingDirection();resetMenuCloseTimer();" [disabled]="readerMode === READER_MODE.WEBTOON || readerMode === READER_MODE.MANGA_UD" aria-describedby="reading-direction" title="Reading Direction: {{readingDirection === ReadingDirection.LeftToRight ? 'Left to Right' : 'Right to Left'}}">
|
||||
<i class="fa fa-angle-double-{{readingDirection === ReadingDirection.LeftToRight ? 'right' : 'left'}}" aria-hidden="true"></i>
|
||||
<span id="reading-direction" class="sr-only">{{readingDirection === ReadingDirection.LeftToRight ? 'Left to Right' : 'Right to Left'}}</span>
|
||||
<span id="reading-direction" class="visually-hidden">{{readingDirection === ReadingDirection.LeftToRight ? 'Left to Right' : 'Right to Left'}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="btn btn-icon" title="Reading Mode" (click)="toggleReaderMode();resetMenuCloseTimer();">
|
||||
<i class="fa {{readerModeIcon}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">Reading Mode</span>
|
||||
<span class="visually-hidden">Reading Mode</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="btn btn-icon" title="{{this.isFullscreen ? 'Collapse' : 'Fullscreen'}}" (click)="toggleFullscreen();resetMenuCloseTimer();">
|
||||
<i class="fa {{this.isFullscreen ? 'fa-compress-alt' : 'fa-expand-alt'}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{this.isFullscreen ? 'Collapse' : 'Fullscreen'}}</span>
|
||||
<span class="visually-hidden">{{this.isFullscreen ? 'Collapse' : 'Fullscreen'}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="btn btn-icon" title="Settings" (click)="settingsOpen = !settingsOpen;resetMenuCloseTimer();">
|
||||
<i class="fa fa-sliders-h" aria-hidden="true"></i>
|
||||
<span class="sr-only">Settings</span>
|
||||
<span class="visually-hidden">Settings</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -105,13 +105,13 @@
|
|||
<form [formGroup]="generalSettingsForm">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<label for="page-splitting">Image Splitting</label>
|
||||
<label for="page-splitting" class="form-label">Image Splitting</label>
|
||||
<div class="split fa fa-image">
|
||||
<div class="{{splitIconClass}}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<div class="mb-3">
|
||||
<select class="form-control" id="page-splitting" formControlName="pageSplitOption">
|
||||
<option *ngFor="let opt of pageSplitOptions" [value]="opt.value">{{opt.text}}</option>
|
||||
</select>
|
||||
|
|
@ -121,7 +121,7 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<label for="page-fitting">Image Scaling</label> <i class="fa {{getFittingIcon()}}" aria-hidden="true"></i>
|
||||
<label for="page-fitting" class="form-label">Image Scaling</label> <i class="fa {{getFittingIcon()}}" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<select class="form-control" id="page-fitting" formControlName="fittingOption">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
@use '../../theme/colors';
|
||||
|
||||
$center-width: 50%;
|
||||
$side-width: 25%;
|
||||
|
||||
|
|
@ -16,16 +14,16 @@ $pointer-offset: 5px;
|
|||
}
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
color: white;
|
||||
}
|
||||
// .btn-icon {
|
||||
// color: white;
|
||||
// }
|
||||
|
||||
canvas {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.reader {
|
||||
background-color: black;
|
||||
background-color: var(--manga-reader-bg-color);
|
||||
overflow: auto;
|
||||
|
||||
img {
|
||||
|
|
@ -58,9 +56,9 @@ canvas {
|
|||
|
||||
|
||||
.overlay {
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
backdrop-filter: blur(10px); // BUG: This doesn't work on Firefox
|
||||
color: white;
|
||||
background-color: var(--manga-reader-overlay-bg-color);
|
||||
backdrop-filter: var(--manga-reader-overlay-filter); // BUG: This doesn't work on Firefox
|
||||
color: var(--manga-reader-overlay-text-color);
|
||||
}
|
||||
|
||||
// Fitting Options
|
||||
|
|
@ -175,7 +173,7 @@ canvas {
|
|||
height: 2px;
|
||||
}
|
||||
.custom-slider .ngx-slider .ngx-slider-selection {
|
||||
background: colors.$primary-color;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-pointer {
|
||||
|
|
@ -183,7 +181,7 @@ canvas {
|
|||
height: 16px;
|
||||
top: auto; /* to remove the default positioning */
|
||||
bottom: 0;
|
||||
background-color: colors.$primary-color; // #333;
|
||||
background-color: var(--primary-color); // #333;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
|
@ -214,7 +212,7 @@ canvas {
|
|||
}
|
||||
|
||||
.custom-slider .ngx-slider .ngx-slider-tick.ngx-slider-selected {
|
||||
background: colors.$primary-color;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,12 +233,12 @@ canvas {
|
|||
}
|
||||
|
||||
.highlight {
|
||||
background-color: rgba(65, 225, 100, 0.5) !important;
|
||||
background-color: var(--manga-reader-next-highlight-bg-color) !important;
|
||||
animation: fadein .5s both;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.highlight-2 {
|
||||
background-color: rgba(65, 105, 225, 0.5) !important;
|
||||
background-color: var(--manga-reader-prev-highlight-bg-color) !important;
|
||||
animation: fadein .5s both;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
|
@ -255,6 +253,6 @@ canvas {
|
|||
border: 0px;
|
||||
}
|
||||
50% {
|
||||
border: 5px solid colors.$primary-color;
|
||||
border: 5px solid var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<button type="button" class="btn btn-icon {{(progressEventsSource.getValue().length > 0 || updateAvailable) ? 'colored' : ''}}"
|
||||
[ngbPopover]="popContent" title="Activity" placement="bottom" [popoverClass]="'nav-events'">
|
||||
<i aria-hidden="true" class="fa fa-wave-square"></i>
|
||||
<i aria-hidden="true" class="fa fa-wave-square nav"></i>
|
||||
</button>
|
||||
|
||||
<ng-template #popContent>
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<div class="spinner-border text-primary small-spinner"
|
||||
role="status" title="Started at {{event.timestamp | date: 'short'}}"
|
||||
attr.aria-valuetext="{{prettyPrintProgress(event.progress)}}%" [attr.aria-valuenow]="prettyPrintProgress(event.progress)">
|
||||
<span class="sr-only">Scan for {{event.libraryName}} in progress</span>
|
||||
<span class="visually-hidden">Scan for {{event.libraryName}} in progress</span>
|
||||
</div>
|
||||
{{prettyPrintProgress(event.progress)}}%
|
||||
{{prettyPrintEvent(event.eventType, event)}} {{event.libraryName}}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,28 @@
|
|||
@use "../../theme/colors";
|
||||
// NOTE: I'm leaving this not fully customized because I'm planning to rewrite the whole design in v0.5.2/3
|
||||
// These are customizations for events nav
|
||||
.dark-menu {
|
||||
background-color: var(--navbar-bg-color);
|
||||
border-color: rgba(1, 4, 9, 0.5);
|
||||
}
|
||||
|
||||
.dark-menu-item {
|
||||
color: var(--body-text-color);
|
||||
background-color: rgb(1, 4, 9);
|
||||
border-color: rgba(1, 4, 9, 0.5);
|
||||
}
|
||||
|
||||
// Popovers need to be their own component
|
||||
::ng-deep .bs-popover-bottom > .popover-arrow::after, .bs-popover-bottom > .popover-arrow::before {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.nav-events {
|
||||
background-color: var(--navbar-bg-color);
|
||||
}
|
||||
|
||||
// .nav-events {
|
||||
// background-color: white;
|
||||
// }
|
||||
|
||||
|
||||
.btn:focus, .btn:hover {
|
||||
|
|
@ -10,9 +34,7 @@
|
|||
height: 1rem;
|
||||
}
|
||||
|
||||
.nav-events {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
||||
.nav-events .popover-body {
|
||||
padding: 0px;
|
||||
|
|
@ -23,7 +45,7 @@
|
|||
}
|
||||
|
||||
.colored {
|
||||
background-color: colors.$primary-color;
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 60px;
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +53,7 @@
|
|||
cursor: pointer;
|
||||
|
||||
i.fa {
|
||||
color: colors.$primary-color !important;
|
||||
color: var(--primary-color) !important;
|
||||
}
|
||||
color: colors.$primary-color;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
|
@ -19,8 +19,9 @@ interface ProcessedEvent {
|
|||
|
||||
type ProgressType = EVENTS.ScanLibraryProgress | EVENTS.RefreshMetadataProgress | EVENTS.BackupDatabaseProgress | EVENTS.CleanupProgress;
|
||||
|
||||
const acceptedEvents = [EVENTS.ScanLibraryProgress, EVENTS.RefreshMetadataProgress, EVENTS.BackupDatabaseProgress, EVENTS.CleanupProgress, EVENTS.DownloadProgress];
|
||||
const acceptedEvents = [EVENTS.ScanLibraryProgress, EVENTS.RefreshMetadataProgress, EVENTS.BackupDatabaseProgress, EVENTS.CleanupProgress, EVENTS.DownloadProgress, EVENTS.SiteThemeProgress];
|
||||
|
||||
// TODO: Rename this to events widget
|
||||
@Component({
|
||||
selector: 'app-nav-events-toggle',
|
||||
templateUrl: './nav-events-toggle.component.html',
|
||||
|
|
|
|||
|
|
@ -1,112 +1,106 @@
|
|||
<nav class="navbar navbar-expand-md navbar-dark fixed-top" *ngIf="navService?.navbarVisible$ | async">
|
||||
<div class="container-fluid">
|
||||
<a class="sr-only sr-only-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a>
|
||||
<a class="navbar-brand dark-exempt" routerLink="/library" routerLinkActive="active"><img class="logo" src="../../assets/images/logo.png" alt="kavita icon" aria-hidden="true"/><span class="phone-hidden"> Kavita</span></a>
|
||||
<ul class="navbar-nav col mr-auto">
|
||||
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a>
|
||||
<a class="navbar-brand dark-exempt" routerLink="/library" routerLinkActive="active"><img class="logo" src="../../assets/images/logo.png" alt="kavita icon" aria-hidden="true"/><span class="d-none d-md-inline"> Kavita</span></a>
|
||||
<ul class="navbar-nav col me-auto">
|
||||
|
||||
<div class="nav-item" *ngIf="(accountService.currentUser$ | async) as user">
|
||||
<div>
|
||||
<fieldset class="form-inline">
|
||||
<div class="form-group" style="margin-bottom: 0px;">
|
||||
<label for="nav-search" class="sr-only">Search series</label>
|
||||
<div class="ng-autocomplete">
|
||||
<app-grouped-typeahead
|
||||
#search
|
||||
id="nav-search"
|
||||
[minQueryLength]="2"
|
||||
initialValue=""
|
||||
placeholder="Search…"
|
||||
[grouppedData]="searchResults"
|
||||
(inputChanged)="onChangeSearch($event)"
|
||||
(clearField)="clearSearch()"
|
||||
(focusChanged)="focusUpdate($event)"
|
||||
>
|
||||
<label for="nav-search" class="form-label visually-hidden">Search series</label>
|
||||
<div class="ng-autocomplete">
|
||||
<app-grouped-typeahead
|
||||
#search
|
||||
id="nav-search"
|
||||
[minQueryLength]="2"
|
||||
initialValue=""
|
||||
placeholder="Search…"
|
||||
[grouppedData]="searchResults"
|
||||
(inputChanged)="onChangeSearch($event)"
|
||||
(clearField)="clearSearch()"
|
||||
(focusChanged)="focusUpdate($event)"
|
||||
>
|
||||
|
||||
<ng-template #libraryTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickLibraryResult(item)">
|
||||
<div class="ml-1">
|
||||
<span>{{item.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #seriesTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickSeriesSearchResult(item)">
|
||||
<div style="width: 24px" class="mr-1">
|
||||
<app-image class="mr-3 search-result" width="24px" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"></app-image>
|
||||
</div>
|
||||
<div class="ml-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
<span *ngIf="item.name.toLowerCase().trim().indexOf(searchTerm) >= 0; else localizedName">{{item.name}}</span>
|
||||
<ng-template #localizedName>
|
||||
<span [innerHTML]="item.localizedName"></span>
|
||||
</ng-template>
|
||||
<span class="form-text" style="font-size: 0.8rem;">in {{item.libraryName}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #collectionTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickCollectionSearchResult(item)">
|
||||
<div style="width: 24px" class="mr-1">
|
||||
<app-image class="mr-3 search-result" width="24px" [imageUrl]="imageService.getCollectionCoverImage(item.id)"></app-image>
|
||||
</div>
|
||||
<div class="ml-1">
|
||||
<span>{{item.title}}</span>
|
||||
<span *ngIf="item.promoted">
|
||||
<i class="fa fa-angle-double-up" aria-hidden="true" title="Promoted"></i>
|
||||
<span class="sr-only">(promoted)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #readingListTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickReadingListSearchResult(item)">
|
||||
<div class="ml-1">
|
||||
<span>{{item.title}}</span>
|
||||
<span *ngIf="item.promoted">
|
||||
<i class="fa fa-angle-double-up" aria-hidden="true" title="Promoted"></i>
|
||||
<span class="sr-only">(promoted)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #tagTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="goTo('tags', item.id)">
|
||||
<div class="ml-1">
|
||||
<span>{{item.title}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #personTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="goToPerson(item.role, item.id)">
|
||||
<div class="ml-1">
|
||||
|
||||
<div [innerHTML]="item.name"></div>
|
||||
<div>{{item.role | personRole}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #genreTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="goTo('genres', item.id)">
|
||||
<div class="ml-1">
|
||||
<div [innerHTML]="item.title"></div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #noResultsTemplate let-notFound>
|
||||
No results found
|
||||
</ng-template>
|
||||
|
||||
</app-grouped-typeahead>
|
||||
</div>
|
||||
<ng-template #libraryTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickLibraryResult(item)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.name}}</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #seriesTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickSeriesSearchResult(item)">
|
||||
<div style="width: 24px" class="me-1">
|
||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"></app-image>
|
||||
</div>
|
||||
<div class="ms-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
<span *ngIf="item.name.toLowerCase().trim().indexOf(searchTerm) >= 0; else localizedName">{{item.name}}</span>
|
||||
<ng-template #localizedName>
|
||||
<span [innerHTML]="item.localizedName"></span>
|
||||
</ng-template>
|
||||
<div class="form-text" style="font-size: 0.8rem;">in {{item.libraryName}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #collectionTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickCollectionSearchResult(item)">
|
||||
<div style="width: 24px" class="me-1">
|
||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getCollectionCoverImage(item.id)"></app-image>
|
||||
</div>
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
<span *ngIf="item.promoted">
|
||||
<i class="fa fa-angle-double-up" aria-hidden="true" title="Promoted"></i>
|
||||
<span class="visually-hidden">(promoted)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #readingListTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickReadingListSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
<span *ngIf="item.promoted">
|
||||
<i class="fa fa-angle-double-up" aria-hidden="true" title="Promoted"></i>
|
||||
<span class="visually-hidden">(promoted)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #tagTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="goTo('tags', item.id)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #personTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="goToPerson(item.role, item.id)">
|
||||
<div class="ms-1">
|
||||
|
||||
<div [innerHTML]="item.name"></div>
|
||||
<div>{{item.role | personRole}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #genreTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="goTo('genres', item.id)">
|
||||
<div class="ms-1">
|
||||
<div [innerHTML]="item.title"></div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #noResultsTemplate let-notFound>
|
||||
No results found
|
||||
</ng-template>
|
||||
|
||||
</app-grouped-typeahead>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
|
|
@ -114,8 +108,8 @@
|
|||
<ng-container *ngIf="!searchFocused">
|
||||
<div class="back-to-top">
|
||||
<button class="btn btn-icon scroll-to-top" (click)="scrollToTop()" *ngIf="backToTopNeeded">
|
||||
<i class="fa fa-angle-double-up" style="color: white" aria-hidden="true"></i>
|
||||
<span class="sr-only">Scroll to Top</span>
|
||||
<i class="fa fa-angle-double-up nav" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">Scroll to Top</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -123,10 +117,10 @@
|
|||
<div class="nav-item">
|
||||
<app-nav-events-toggle [user]="user"></app-nav-events-toggle>
|
||||
</div>
|
||||
<div class="nav-item pr-2 not-xs-only">
|
||||
<a routerLink="/admin/dashboard" *ngIf="user.roles.includes('Admin')" class="dark-exempt btn btn-icon">
|
||||
<i class="fa fa-cogs" aria-hidden="true" style="color: white"></i>
|
||||
<span class="sr-only">Server Settings</span>
|
||||
<div class="nav-item pe-2 not-xs-only">
|
||||
<a routerLink="/admin/dashboard" *ngIf="user.roles.includes('Admin')" class="dark-exempt btn btn-icon" title="Server Settings">
|
||||
<i class="fa fa-cogs nav" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">Server Settings</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,21 @@
|
|||
@import '~bootstrap/scss/mixins/_breakpoints.scss';
|
||||
|
||||
$primary-color: white;
|
||||
$bg-color: rgb(22, 27, 34);
|
||||
|
||||
.btn:focus, .btn:hover {
|
||||
box-shadow: 0 0 0 0.1rem rgba(255, 255, 255, 1);
|
||||
box-shadow: 0 0 0 0.1rem var(--navbar-btn-hover-outline-color);
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: $bg-color;
|
||||
background-color: var(--navbar-bg-color);
|
||||
}
|
||||
|
||||
/* small devices (phones, 650px and down) */
|
||||
@media only screen and (max-width:650px) { //370
|
||||
@media only screen and (max-width:650px) {
|
||||
.navbar-nav {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// On Really small screens, hide the server settings wheel and show it in nav
|
||||
// TODO: Look into doing this with bootstrap 5 (and moving to _utilities.scss)
|
||||
.xs-only {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -39,7 +36,7 @@ $bg-color: rgb(22, 27, 34);
|
|||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-family: "Spartan", sans-serif;
|
||||
font-family: var(--brand-font-family);
|
||||
font-weight: bold;
|
||||
|
||||
.logo {
|
||||
|
|
@ -54,7 +51,7 @@ $bg-color: rgb(22, 27, 34);
|
|||
|
||||
.focus-visible:focus {
|
||||
visibility: visible;
|
||||
color: white;
|
||||
color: var(--nav-header-text-color);
|
||||
}
|
||||
|
||||
.ng-autocomplete {
|
||||
|
|
@ -62,7 +59,7 @@ $bg-color: rgb(22, 27, 34);
|
|||
}
|
||||
|
||||
.primary-text {
|
||||
color: $primary-color;
|
||||
color: var(--nav-header-text-color);
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
|
@ -75,27 +72,12 @@ $bg-color: rgb(22, 27, 34);
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
@media (min-width: var(--grid-breakpoints-sm)) {
|
||||
.form-inline .form-group {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) {
|
||||
.ng-autocomplete {
|
||||
width: 100%; // 232px
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-to-top:hover {
|
||||
animation: MoveUpDown 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes MoveUpDown {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
|
@ -50,15 +50,15 @@ export class NavHeaderComponent implements OnInit, OnDestroy {
|
|||
private scrollService: ScrollService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.navService.darkMode$.pipe(takeUntil(this.onDestroy)).subscribe(res => {
|
||||
if (res) {
|
||||
this.document.body.classList.remove('bg-light');
|
||||
this.document.body.classList.add('bg-dark');
|
||||
} else {
|
||||
this.document.body.classList.remove('bg-dark');
|
||||
this.document.body.classList.add('bg-light');
|
||||
}
|
||||
});
|
||||
// this.navService.darkMode$.pipe(takeUntil(this.onDestroy)).subscribe(res => {
|
||||
// if (res) {
|
||||
// this.document.body.classList.remove('bg-light');
|
||||
// this.document.body.classList.add('bg-dark');
|
||||
// } else {
|
||||
// this.document.body.classList.remove('bg-dark');
|
||||
// this.document.body.classList.add('bg-light');
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
@HostListener("window:scroll", [])
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">Add to Reading List</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<form style="width: 100%" [formGroup]="listForm">
|
||||
<div class="modal-body">
|
||||
<div class="form-group" *ngIf="lists.length >= 5">
|
||||
<label for="filter">Filter</label>
|
||||
<div class="mb-3" *ngIf="lists.length >= 5">
|
||||
<label for="filter" class="form-label">Filter</label>
|
||||
<div class="input-group">
|
||||
<input id="filter" autocomplete="off" class="form-control" formControlName="filterQuery" type="text" aria-describedby="reset-input">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" id="reset-input" (click)="listForm.get('filterQuery')?.setValue('');">Clear</button>
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary" type="button" id="reset-input" (click)="listForm.get('filterQuery')?.setValue('');">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
|
|
@ -23,7 +21,7 @@
|
|||
<li class="list-group-item" *ngIf="lists.length === 0 && !loading">No lists created yet</li>
|
||||
<li class="list-group-item" *ngIf="loading">
|
||||
<div class="spinner-border text-secondary" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -32,7 +30,7 @@
|
|||
<div style="width: 100%;">
|
||||
<div class="form-row">
|
||||
<div class="col-9 col-lg-10">
|
||||
<label class="sr-only" for="add-rlist">Reading List</label>
|
||||
<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">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
|
||||
@use '../../../../theme/colors';
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clickable:hover, .clickable:focus {
|
||||
background-color: colors.$primary-color;
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">Edit {{readingList.title}} Reading List</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
|
@ -11,20 +11,20 @@
|
|||
Promotion means that the list can be seen server-wide, not just for admin users. All series that are within this list will still have user-access restrictions placed on them.
|
||||
</p>
|
||||
<form [formGroup]="reviewGroup">
|
||||
<div class="form-group">
|
||||
<label for="title">Name</label>
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Name</label>
|
||||
<input id="title" class="form-control" formControlName="title" type="text">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="summary">Summary</label>
|
||||
<div class="mb-3">
|
||||
<label for="summary" class="form-label">Summary</label>
|
||||
<textarea id="summary" class="form-control" formControlName="summary" rows="3"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" (click)="close()">Close</button>
|
||||
<button type="button" class="btn btn-info" (click)="togglePromotion()">{{readingList.promoted ? 'Demote' : 'Promote'}}</button>
|
||||
<button type="button" class="btn btn-secondary alt" (click)="togglePromotion()">{{readingList.promoted ? 'Demote' : 'Promote'}}</button>
|
||||
<button type="submit" class="btn btn-primary" [disabled]="reviewGroup.get('title')?.value.trim().length === 0" (click)="save()">Save</button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
<div cdkDropList class="{{items.length > 0 ? 'example-list list-group-flush' : ''}}" (cdkDropListDropped)="drop($event)">
|
||||
<div class="example-box" *ngFor="let item of items; index as i" cdkDrag [cdkDragData]="item" cdkDragBoundary=".example-list">
|
||||
<div class="mr-3 align-middle">
|
||||
<div class="me-3 align-middle">
|
||||
<i class="fa fa-grip-vertical drag-handle" aria-hidden="true" cdkDragHandle></i>
|
||||
</div>
|
||||
|
||||
<ng-container style="display: inline-block" [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
|
||||
|
||||
<div class="align-middle" style="padding-top: 40px">
|
||||
<label for="reorder-{{i}}" class="sr-only">Reorder</label>
|
||||
<label for="reorder-{{i}}" class="form-label visually-hidden">Reorder</label>
|
||||
<input *ngIf="accessibilityMode" id="reorder-{{i}}" type="number" min="0" [max]="items.length - 1" [value]="i" style="width: 40px" (focusout)="updateIndex(i, item)" (keydown.enter)="updateIndex(i, item)" aria-describedby="instructions">
|
||||
</div>
|
||||
<button class="btn btn-icon pull-right" (click)="removeItem(item, i)">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
<span class="sr-only" attr.aria-labelledby="item.id--{{i}}">Remove item</span>
|
||||
<span class="visually-hidden" attr.aria-labelledby="item.id--{{i}}">Remove item</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="sr-only" id="instructions">
|
||||
<p class="visually-hidden" id="instructions">
|
||||
|
||||
</p>
|
||||
|
|
@ -1,19 +1,19 @@
|
|||
<div class="container-fluid mt-2" *ngIf="readingList">
|
||||
<div class="mb-3">
|
||||
<!-- Title row-->
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
|
||||
<h2 style="display: inline-block">
|
||||
<span *ngIf="actions.length > 0">
|
||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [labelBy]="readingList.title"></app-card-actionables>
|
||||
</span>
|
||||
{{readingList.title}} <span *ngIf="readingList?.promoted">(<i class="fa fa-angle-double-up" aria-hidden="true"></i>)</span>
|
||||
<span class="badge badge-primary badge-pill" attr.aria-label="{{items.length}} total items">{{items.length}}</span>
|
||||
<span class="badge bg-primary rounded-pill" attr.aria-label="{{items.length}} total items">{{items.length}}</span>
|
||||
</h2>
|
||||
</div>
|
||||
<!-- Action row-->
|
||||
<div class="row no-gutters">
|
||||
<div class="mr-2">
|
||||
<div class="row g-0">
|
||||
<div class="col-auto me-2">
|
||||
<button class="btn btn-primary" title="Read" (click)="read()">
|
||||
<span>
|
||||
<i class="fa fa-book-open" aria-hidden="true"></i>
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-secondary" (click)="removeRead()" [disabled]="readingList?.promoted && !this.isAdmin">
|
||||
<span>
|
||||
<i class="fa fa-check"></i>
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
<span class="read-btn--text"> Remove Read</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-2 mt-2" *ngIf="!(readingList?.promoted && !this.isAdmin)">
|
||||
<div class="col-auto ms-2 mt-2" *ngIf="!(readingList?.promoted && !this.isAdmin)">
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="checkbox" id="accessibilit-mode" [value]="accessibilityMode" (change)="accessibilityMode = !accessibilityMode">
|
||||
<label class="form-check-label" for="accessibilit-mode">Order Numbers</label>
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- Summary row-->
|
||||
<div class="row no-gutters mt-2">
|
||||
<div class="row g-0 mt-2">
|
||||
<app-read-more [text]="readingList.summary" [maxLength]="250"></app-read-more>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -48,18 +48,18 @@
|
|||
|
||||
<app-dragable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" (itemRemove)="itemRemoved($event)" [accessibilityMode]="accessibilityMode">
|
||||
<ng-template #draggableItem let-item let-position="idx">
|
||||
<div class="media" style="width: 100%;">
|
||||
<app-image width="74px" class="img-top mr-3" [imageUrl]="imageService.getChapterCoverImage(item.chapterId)"></app-image>
|
||||
<div class="media-body">
|
||||
<div class="d-flex" style="width: 100%;">
|
||||
<app-image width="74px" class="img-top me-3" [imageUrl]="imageService.getChapterCoverImage(item.chapterId)"></app-image>
|
||||
<div class="flex-grow-1">
|
||||
<h5 class="mt-0 mb-1" id="item.id--{{position}}">{{formatTitle(item)}}
|
||||
<span class="badge badge-primary badge-pill">
|
||||
<span class="badge bg-primary rounded-pill">
|
||||
<span *ngIf="item.pagesRead > 0 && item.pagesRead < item.pagesTotal">{{item.pagesRead}} / {{item.pagesTotal}}</span>
|
||||
<span *ngIf="item.pagesRead === 0">UNREAD</span>
|
||||
<span *ngIf="item.pagesRead === item.pagesTotal">READ</span>
|
||||
</span>
|
||||
</h5>
|
||||
<i class="fa {{utilityService.mangaFormatIcon(item.seriesFormat)}}" aria-hidden="true" *ngIf="item.seriesFormat != MangaFormat.UNKNOWN" title="{{utilityService.mangaFormat(item.seriesFormat)}}"></i>
|
||||
<span class="sr-only">{{utilityService.mangaFormat(item.seriesFormat)}}</span>
|
||||
<span class="visually-hidden">{{utilityService.mangaFormat(item.seriesFormat)}}</span>
|
||||
|
||||
<a href="/library/{{item.libraryId}}/series/{{item.seriesId}}">{{item.seriesName}}</a>
|
||||
<span *ngIf="item.promoted">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">Account Migration</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
<p class="text-danger" *ngIf="error.length > 0">{{error}}</p>
|
||||
|
||||
<form [formGroup]="registerForm">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input id="username" class="form-control" formControlName="username" type="text">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('username')?.errors?.required">
|
||||
|
|
@ -22,8 +22,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="width:100%">
|
||||
<label for="email">Email</label>
|
||||
<div class="mb-3" style="width:100%">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input class="form-control" type="email" id="email" formControlName="email" required>
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('email')?.errors?.required">
|
||||
|
|
@ -35,8 +35,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input id="password" class="form-control" maxlength="32" minlength="6" formControlName="password" type="password" aria-describedby="password-help">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('password')?.errors?.required">
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
</ul>
|
||||
</div>
|
||||
<form [formGroup]="registerForm" (ngSubmit)="submit()">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input id="username" class="form-control" formControlName="username" type="text">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('username')?.errors?.required">
|
||||
|
|
@ -20,8 +20,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="width:100%">
|
||||
<label for="email">Email</label>
|
||||
<div class="mb-3" style="width:100%">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input class="form-control" type="email" id="email" formControlName="email" required>
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('email')?.errors?.required">
|
||||
|
|
@ -33,12 +33,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="passwordTooltip" role="button" tabindex="0"></i>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="passwordTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #passwordTooltip>
|
||||
Password must be between 6 and 32 characters in length
|
||||
</ng-template>
|
||||
<span class="sr-only" id="password-help"><ng-container [ngTemplateOutlet]="passwordTooltip"></ng-container></span>
|
||||
<span class="visually-hidden" id="password-help"><ng-container [ngTemplateOutlet]="passwordTooltip"></ng-container></span>
|
||||
<input id="password" class="form-control" maxlength="32" minlength="6" formControlName="password" type="password" aria-describedby="password-help">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('password')?.errors?.required">
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="float-right">
|
||||
<div class="float-end">
|
||||
<button class="btn btn-secondary alt" type="submit">Register</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Component, Input, OnInit } from '@angular/core';
|
|||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { ThemeService } from 'src/app/theme.service';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
|
||||
@Component({
|
||||
|
|
@ -10,8 +11,6 @@ import { AccountService } from 'src/app/_services/account.service';
|
|||
styleUrls: ['./confirm-email.component.scss']
|
||||
})
|
||||
export class ConfirmEmailComponent implements OnInit {
|
||||
|
||||
|
||||
/**
|
||||
* Email token used for validating
|
||||
*/
|
||||
|
|
@ -29,8 +28,9 @@ export class ConfirmEmailComponent implements OnInit {
|
|||
errors: Array<string> = [];
|
||||
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, private toastr: ToastrService) {
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService,
|
||||
private toastr: ToastrService, private themeService: ThemeService) {
|
||||
this.themeService.setTheme(this.themeService.defaultTheme);
|
||||
const token = this.route.snapshot.queryParamMap.get('token');
|
||||
const email = this.route.snapshot.queryParamMap.get('email');
|
||||
if (token == undefined || token === '' || token === null) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { ThemeService } from 'src/app/theme.service';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
|
||||
@Component({
|
||||
|
|
@ -10,8 +11,9 @@ import { AccountService } from 'src/app/_services/account.service';
|
|||
})
|
||||
export class ConfirmMigrationEmailComponent implements OnInit {
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, private toastr: ToastrService) {
|
||||
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, private toastr: ToastrService, private themeService: ThemeService) {
|
||||
|
||||
this.themeService.setTheme(this.themeService.defaultTheme);
|
||||
const token = this.route.snapshot.queryParamMap.get('token');
|
||||
const email = this.route.snapshot.queryParamMap.get('email');
|
||||
if (token === undefined || token === '' || token === null || email === undefined || email === '' || email === null) {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
<ng-container body>
|
||||
<p>Enter the email of your account. We will send you an email </p>
|
||||
<form [formGroup]="registerForm" (ngSubmit)="submit()">
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="passwordTooltip" role="button" tabindex="0"></i>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="passwordTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #passwordTooltip>
|
||||
Password must be between 6 and 32 characters in length
|
||||
</ng-template>
|
||||
<span class="sr-only" id="password-help"><ng-container [ngTemplateOutlet]="passwordTooltip"></ng-container></span>
|
||||
<span class="visually-hidden" id="password-help"><ng-container [ngTemplateOutlet]="passwordTooltip"></ng-container></span>
|
||||
<input id="password" class="form-control" maxlength="32" minlength="6" formControlName="password" type="password" aria-describedby="password-help">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('password')?.errors?.required">
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="float-right">
|
||||
<div class="float-end">
|
||||
<button class="btn btn-secondary alt" type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
<ng-container body>
|
||||
<p>Complete the form to register an admin account</p>
|
||||
<form [formGroup]="registerForm" (ngSubmit)="submit()">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input id="username" class="form-control" formControlName="username" type="text">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('username')?.errors?.required">
|
||||
|
|
@ -13,8 +13,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="width:100%">
|
||||
<label for="email">Email</label>
|
||||
<div class="mb-3" style="width:100%">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input class="form-control" type="email" id="email" formControlName="email" required>
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('email')?.errors?.required">
|
||||
|
|
@ -26,12 +26,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="passwordTooltip" role="button" tabindex="0"></i>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="passwordTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #passwordTooltip>
|
||||
Password must be between 6 and 32 characters in length
|
||||
</ng-template>
|
||||
<span class="sr-only" id="password-help"><ng-container [ngTemplateOutlet]="passwordTooltip"></ng-container></span>
|
||||
<span class="visually-hidden" id="password-help"><ng-container [ngTemplateOutlet]="passwordTooltip"></ng-container></span>
|
||||
<input id="password" class="form-control" maxlength="32" minlength="6" formControlName="password" type="password" aria-describedby="password-help">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('password')?.errors?.required">
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="float-right">
|
||||
<div class="float-end">
|
||||
<button class="btn btn-secondary alt" type="submit">Register</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
<ng-container body>
|
||||
<p>Enter the email of your account. We will send you an email </p>
|
||||
<form [formGroup]="registerForm" (ngSubmit)="submit()">
|
||||
<div class="form-group" style="width:100%">
|
||||
<label for="email">Email</label>
|
||||
<input class="form-control" type="email" id="email" formControlName="email" required>
|
||||
<div class="mb-3" style="width:100%">
|
||||
<label for="email" class="form-label">Email</label>
|
||||
<input class="form-control custom-input" type="email" id="email" formControlName="email" required>
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="registerForm.dirty || registerForm.touched">
|
||||
<div *ngIf="registerForm.get('email')?.errors?.required">
|
||||
This field is required
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="float-right">
|
||||
<div>
|
||||
<button class="btn btn-secondary alt" type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
.btn {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.custom-input {
|
||||
background-color: #fff !important;
|
||||
color: black;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<div class="mx-auto login">
|
||||
|
||||
<div class="row row-cols-4 row-cols-md-4 row-cols-sm-2 row-cols-xs-2">
|
||||
<div class="col align-self-center card p-3 m-3" style="max-width: 12rem;">
|
||||
<div class="col align-self-center card p-3">
|
||||
<span>
|
||||
<div class="logo-container">
|
||||
<h3 class="card-title text-center">
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
@use "../../../theme/colors";
|
||||
|
||||
|
||||
|
||||
.login {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: -61px; // To offset the navbar
|
||||
height: calc(100vh);
|
||||
height: calc(var(--vh, 1vh) * 100 - 57px);
|
||||
min-height: 289px;
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
|
|
@ -33,14 +28,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-top: 10vh;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: colors.$primary-color;
|
||||
background-color: var(--primary-color);
|
||||
color: #fff;
|
||||
min-width: 300px;
|
||||
max-width: 12rem;
|
||||
|
||||
&:focus {
|
||||
border: 2px solid white;
|
||||
|
|
@ -59,34 +51,5 @@
|
|||
.card-text {
|
||||
font-family: "EBGaramond", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
.alt {
|
||||
background-color: #424c72;
|
||||
border-color: #444f75;
|
||||
}
|
||||
|
||||
.alt:hover {
|
||||
background-color: #3b4466;
|
||||
}
|
||||
|
||||
.alt:focus {
|
||||
background-color: #343c59;
|
||||
box-shadow: 0 0 0 0.2rem rgb(68 79 117 / 50%);
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep input {
|
||||
background-color: #fff !important;
|
||||
color: black;
|
||||
}
|
||||
|
||||
::ng-deep a {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.invalid-feedback {
|
||||
display: inline-block;
|
||||
color: #343c59;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
<app-image class="poster" maxWidth="300px" [imageUrl]="seriesImage"></app-image>
|
||||
</div>
|
||||
<div class="col-md-10 col-xs-8 col-sm-6">
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
|
||||
<h2>
|
||||
{{series?.name}}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="row no-gutters">
|
||||
<div>
|
||||
<div class="row g-0">
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-primary" (click)="read()" [disabled]="isLoading">
|
||||
<span>
|
||||
<i class="fa {{showBook ? 'fa-book-open' : 'fa-book'}}"></i>
|
||||
|
|
@ -19,35 +19,35 @@
|
|||
<span class="read-btn--text"> {{(hasReadingProgress) ? 'Continue' : 'Read'}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-2" *ngIf="isAdmin">
|
||||
<div class="col-auto ms-2" *ngIf="isAdmin">
|
||||
<button class="btn btn-secondary" (click)="openEditSeriesModal()" title="Edit Series information">
|
||||
<span>
|
||||
<i class="fa fa-pen" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-2" *ngIf="isAdmin || hasDownloadingRole">
|
||||
<div class="col-auto ms-2" *ngIf="isAdmin || hasDownloadingRole">
|
||||
<button class="btn btn-secondary" (click)="downloadSeries()" title="Download Series" [disabled]="downloadInProgress">
|
||||
<ng-container *ngIf="downloadInProgress; else notDownloading">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="sr-only">Downloading...</span>
|
||||
<span class="visually-hidden">Downloading...</span>
|
||||
</ng-container>
|
||||
<ng-template #notDownloading>
|
||||
<i class="fa fa-arrow-alt-circle-down" aria-hidden="true"></i>
|
||||
</ng-template>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<div class="col-auto ms-2">
|
||||
<div class="card-actions">
|
||||
<app-card-actionables [disabled]="actionInProgress" (actionHandler)="performAction($event)" [actions]="seriesActions" [labelBy]="series.name" iconClass="fa-ellipsis-h" btnClass="btn-secondary"></app-card-actionables>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<div class="col-auto ms-2">
|
||||
<ngb-rating class="rating-star" [(rate)]="series!.userRating" (rateChange)="updateRating($event)" (click)="promptToReview()"></ngb-rating>
|
||||
<button *ngIf="series?.userRating || series.userRating" class="btn btn-sm btn-icon" (click)="openReviewModal(true)" placement="top" ngbTooltip="Edit Review" attr.aria-label="Edit Review"><i class="fa fa-pen" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<!-- TODO: This will be the first of reviews section. Reviews will show your plus other peoples reviews in media cards like Plex does and this will be below metadata -->
|
||||
<app-read-more class="user-review {{userReview ? 'mt-1' : ''}}" [text]="series?.userReview || ''" [maxLength]="250"></app-read-more>
|
||||
</div>
|
||||
|
|
@ -59,13 +59,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ng-container>
|
||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="nav-tabs nav-pills" [destroyOnHide]="false" (navChange)="onNavChange($event)">
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="nav nav-tabs" [destroyOnHide]="false" (navChange)="onNavChange($event)">
|
||||
<li [ngbNavItem]="TabID.Specials" *ngIf="hasSpecials">
|
||||
<a ngbNavLink>Specials</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<div *ngFor="let chapter of specials; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<app-card-item class="col-auto" *ngIf="chapter.isSpecial" [entity]="chapter" [title]="chapter.title || chapter.range" (click)="openChapter(chapter)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(chapter.id)"
|
||||
|
|
@ -77,47 +77,47 @@
|
|||
<li [ngbNavItem]="TabID.Storyline" *ngIf="libraryType !== LibraryType.Book && (hasNonSpecialVolumeChapters || hasNonSpecialNonVolumeChapters)">
|
||||
<a ngbNavLink>Storyline</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="row no-gutters">
|
||||
<div *ngFor="let volume of volumes; let idx = index; trackBy: trackByVolumeIdentity">
|
||||
<div class="row g-0">
|
||||
<ng-container *ngFor="let volume of volumes; let idx = index; trackBy: trackByVolumeIdentity">
|
||||
<app-card-item class="col-auto" *ngIf="volume.number != 0" [entity]="volume" [title]="formatVolumeTitle(volume)" (click)="openVolume(volume)"
|
||||
[imageUrl]="imageService.getVolumeCoverImage(volume.id) + '&offset=' + coverImageOffset"
|
||||
[read]="volume.pagesRead" [total]="volume.pages" [actions]="volumeActions" (selection)="bulkSelectionService.handleCardSelection('volume', idx, volumes.length, $event)" [selected]="bulkSelectionService.isCardSelected('volume', idx)" [allowSelection]="true"></app-card-item>
|
||||
</div>
|
||||
<div *ngFor="let chapter of storyChapters; let idx = index; trackBy: trackByChapterIdentity">
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let chapter of storyChapters; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<app-card-item class="col-auto" *ngIf="!chapter.isSpecial" [entity]="chapter" [title]="formatChapterTitle(chapter)" (click)="openChapter(chapter)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(chapter.id) + '&offset=' + coverImageOffset"
|
||||
[read]="chapter.pagesRead" [total]="chapter.pages" [actions]="chapterActions" (selection)="bulkSelectionService.handleCardSelection('chapter', idx, storyChapters.length, $event)" [selected]="bulkSelectionService.isCardSelected('chapter', idx)" [allowSelection]="true"></app-card-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li [ngbNavItem]="TabID.Volumes" *ngIf="hasNonSpecialVolumeChapters">
|
||||
<a ngbNavLink>Volumes</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="row no-gutters">
|
||||
<div *ngFor="let volume of volumes; let idx = index; trackBy: trackByVolumeIdentity">
|
||||
<div class="row g-0">
|
||||
<ng-container *ngFor="let volume of volumes; let idx = index; trackBy: trackByVolumeIdentity">
|
||||
<app-card-item class="col-auto" *ngIf="volume.number != 0" [entity]="volume" [title]="formatVolumeTitle(volume)" (click)="openVolume(volume)"
|
||||
[imageUrl]="imageService.getVolumeCoverImage(volume.id) + '&offset=' + coverImageOffset"
|
||||
[read]="volume.pagesRead" [total]="volume.pages" [actions]="volumeActions" (selection)="bulkSelectionService.handleCardSelection('volume', idx, volumes.length, $event)" [selected]="bulkSelectionService.isCardSelected('volume', idx)" [allowSelection]="true"></app-card-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
<li [ngbNavItem]="TabID.Chapters" *ngIf="hasNonSpecialNonVolumeChapters">
|
||||
<a ngbNavLink>{{utilityService.formatChapterName(libraryType) + 's'}}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<div class="row no-gutters">
|
||||
<div *ngFor="let chapter of chapters; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<div class="row g-0">
|
||||
<ng-container *ngFor="let chapter of chapters; let idx = index; trackBy: trackByChapterIdentity">
|
||||
<app-card-item class="col-auto" *ngIf="!chapter.isSpecial" [entity]="chapter" [title]="formatChapterTitle(chapter)" (click)="openChapter(chapter)"
|
||||
[imageUrl]="imageService.getChapterCoverImage(chapter.id) + '&offset=' + coverImageOffset"
|
||||
[read]="chapter.pagesRead" [total]="chapter.pages" [actions]="chapterActions" (selection)="bulkSelectionService.handleCardSelection('chapter', idx, chapters.length, $event)" [selected]="bulkSelectionService.isCardSelected('chapter', idx)" [allowSelection]="true"></app-card-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
<div [ngbNavOutlet]="nav"></div>
|
||||
</div>
|
||||
</ul>
|
||||
<div [ngbNavOutlet]="nav"></div>
|
||||
</ng-container>
|
||||
|
||||
<div class="mx-auto" *ngIf="isLoading" style="width: 200px;">
|
||||
<div class="spinner-border text-secondary loading" role="status">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
@import '~bootstrap/scss/mixins/_breakpoints.scss';
|
||||
|
||||
|
||||
.user-review {
|
||||
font-style: italic;
|
||||
|
||||
}
|
||||
|
||||
.rating-star {
|
||||
|
|
@ -16,15 +12,13 @@
|
|||
max-width: 300px;
|
||||
}
|
||||
|
||||
|
||||
// Including breakpoint has issue with resovling variable, so copying gridpoints here
|
||||
@include media-breakpoint-down(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) {
|
||||
.poster {
|
||||
display: none;
|
||||
}
|
||||
@media(max-width: var(--grid-breakpoints-sm)) {
|
||||
.poster {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) {
|
||||
@media(max-width: var(--grid-breakpoints-sm)) {
|
||||
.read-btn--text {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,190 +1,190 @@
|
|||
<div class="row no-gutters mt-2 mb-2">
|
||||
<div class="row g-0 mt-2 mb-2">
|
||||
<app-read-more [text]="seriesSummary" [maxLength]="250"></app-read-more>
|
||||
</div>
|
||||
|
||||
<!-- This first row will have random information about the series-->
|
||||
<div class="row no-gutters mb-2">
|
||||
<app-tag-badge title="Age Rating" *ngIf="seriesMetadata.ageRating" a11y-click="13,32" class="clickable" (click)="goTo('ageRating', seriesMetadata.ageRating)" [selectionMode]="TagBadgeCursor.Clickable">{{metadataService.getAgeRating(this.seriesMetadata.ageRating) | async}}</app-tag-badge>
|
||||
<div class="row g-0 mb-2">
|
||||
<app-tag-badge title="Age Rating" *ngIf="seriesMetadata.ageRating" a11y-click="13,32" class="clickable col-auto" (click)="goTo('ageRating', seriesMetadata.ageRating)" [selectionMode]="TagBadgeCursor.Clickable">{{metadataService.getAgeRating(this.seriesMetadata.ageRating) | async}}</app-tag-badge>
|
||||
<ng-container *ngIf="series">
|
||||
<!-- Maybe we can put the library this resides in to make it easier to get back -->
|
||||
<!-- tooltip here explaining how this is year of first issue -->
|
||||
<app-tag-badge *ngIf="seriesMetadata.releaseYear > 0" title="Release date">{{seriesMetadata.releaseYear}}</app-tag-badge>
|
||||
<app-tag-badge *ngIf="seriesMetadata.language !== null && seriesMetadata.language !== ''" title="Language" a11y-click="13,32" class="clickable" (click)="goTo('languages', seriesMetadata.language)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.language}}</app-tag-badge>
|
||||
<app-tag-badge title="Publication Status" a11y-click="13,32" class="clickable" (click)="goTo('publicationStatus', seriesMetadata.publicationStatus)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.publicationStatus | publicationStatus}}</app-tag-badge>
|
||||
<app-tag-badge a11y-click="13,32" class="clickable" (click)="goTo('format', series.format)" [selectionMode]="TagBadgeCursor.Clickable">
|
||||
<app-tag-badge *ngIf="seriesMetadata.releaseYear > 0" title="Release date" class="col-auto">{{seriesMetadata.releaseYear}}</app-tag-badge>
|
||||
<app-tag-badge *ngIf="seriesMetadata.language !== null && seriesMetadata.language !== ''" title="Language" a11y-click="13,32" class="col-auto" (click)="goTo('languages', seriesMetadata.language)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.language}}</app-tag-badge>
|
||||
<app-tag-badge title="Publication Status" a11y-click="13,32" class="col-auto" (click)="goTo('publicationStatus', seriesMetadata.publicationStatus)" [selectionMode]="TagBadgeCursor.Clickable">{{seriesMetadata.publicationStatus | publicationStatus}}</app-tag-badge>
|
||||
<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>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters" *ngIf="seriesMetadata.genres && seriesMetadata.genres.length > 0">
|
||||
<div class="row g-0" *ngIf="seriesMetadata.genres && seriesMetadata.genres.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Genres</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.genres">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-tag-badge a11y-click="13,32" class="clickable" (click)="goTo('genres', item.id)" [selectionMode]="TagBadgeCursor.Clickable">{{item.title}}</app-tag-badge>
|
||||
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="goTo('genres', item.id)" [selectionMode]="TagBadgeCursor.Clickable">{{item.title}}</app-tag-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.collectionTags && seriesMetadata.collectionTags.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="seriesMetadata.collectionTags && seriesMetadata.collectionTags.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Collections</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.collectionTags">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-tag-badge a11y-click="13,32" class="clickable" routerLink="/collections/{{item.id}}" [selectionMode]="TagBadgeCursor.Clickable">
|
||||
<app-tag-badge a11y-click="13,32" class="col-auto" routerLink="/collections/{{item.id}}" [selectionMode]="TagBadgeCursor.Clickable">
|
||||
{{item.title}}
|
||||
</app-tag-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.writers && seriesMetadata.writers.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="seriesMetadata.writers && seriesMetadata.writers.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Writers/Authors</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.writers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('writers', item.id)" [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('writers', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="row g-0">
|
||||
<hr class="col-md-11" *ngIf="hasExtendedProperites" >
|
||||
<a [class.hidden]="hasExtendedProperites" *ngIf="hasExtendedProperites" class="col-md-1 read-more-link" (click)="toggleView()"> <i aria-hidden="true" class="fa fa-caret-{{isCollapsed ? 'down' : 'up'}}" aria-controls="extended-series-metadata"></i> See {{isCollapsed ? 'More' : 'Less'}}</a>
|
||||
</div>
|
||||
|
||||
<div #collapse="ngbCollapse" [(ngbCollapse)]="isCollapsed" id="extended-series-metadata">
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.coverArtists && seriesMetadata.coverArtists.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="seriesMetadata.coverArtists && seriesMetadata.coverArtists.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Cover Artists</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.coverArtists">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('coverArtists', item.id)" [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('coverArtists', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.characters && seriesMetadata.characters.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="seriesMetadata.characters && seriesMetadata.characters.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Characters</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.characters">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('character', item.id)" [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('character', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.colorists && seriesMetadata.colorists.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="seriesMetadata.colorists && seriesMetadata.colorists.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Colorists</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.colorists">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('colorist', item.id)" [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('colorist', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.editors && seriesMetadata.editors.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="seriesMetadata.editors && seriesMetadata.editors.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Editors</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.editors">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('editor', item.id)" [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('editor', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.inkers && seriesMetadata.inkers.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="seriesMetadata.inkers && seriesMetadata.inkers.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Inkers</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.inkers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('inker', item.id)" [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('inker', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.letterers && seriesMetadata.letterers.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="seriesMetadata.letterers && seriesMetadata.letterers.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Letterers</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.letterers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('letterer', item.id)" [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('letterer', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters" *ngIf="seriesMetadata.tags && seriesMetadata.tags.length > 0">
|
||||
<div class="row g-0" *ngIf="seriesMetadata.tags && seriesMetadata.tags.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Tags</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.tags">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-tag-badge a11y-click="13,32" class="clickable" (click)="goTo('tags', item.id)" [selectionMode]="TagBadgeCursor.Clickable">{{item.title}}</app-tag-badge>
|
||||
<app-tag-badge a11y-click="13,32" class="col-auto" (click)="goTo('tags', item.id)" [selectionMode]="TagBadgeCursor.Clickable">{{item.title}}</app-tag-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.translators && seriesMetadata.translators.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="seriesMetadata.translators && seriesMetadata.translators.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Translators</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.translators">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('translators', item.id)" [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('translators', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.pencillers && seriesMetadata.pencillers.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="seriesMetadata.pencillers && seriesMetadata.pencillers.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Pencillers</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.pencillers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('penciller', item.id)" [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('penciller', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters mt-1" *ngIf="seriesMetadata.publishers && seriesMetadata.publishers.length > 0">
|
||||
<div class="row g-0 mt-1" *ngIf="seriesMetadata.publishers && seriesMetadata.publishers.length > 0">
|
||||
<div class="col-md-4">
|
||||
<h5>Publishers</h5>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<app-badge-expander [items]="seriesMetadata.publishers">
|
||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||
<app-person-badge a11y-click="13,32" class="clickable" (click)="goTo('publisher', item.id)" [person]="item"></app-person-badge>
|
||||
<app-person-badge a11y-click="13,32" class="col-auto" (click)="goTo('publisher', item.id)" [person]="item"></app-person-badge>
|
||||
</ng-template>
|
||||
</app-badge-expander>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
.read-more-link {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { FilterSettings } from 'src/app/cards/card-detail-layout/card-detail-layout.component';
|
||||
import { Chapter } from 'src/app/_models/chapter';
|
||||
import { LibraryType } from 'src/app/_models/library';
|
||||
import { MangaFormat } from 'src/app/_models/manga-format';
|
||||
import { AgeRating } from 'src/app/_models/metadata/age-rating';
|
||||
import { Series } from 'src/app/_models/series';
|
||||
import { SeriesFilter } from 'src/app/_models/series-filter';
|
||||
import { Volume } from 'src/app/_models/volume';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<!-- <div class="circular">
|
||||
<div class="inner"></div>
|
||||
<div class="number">
|
||||
<span class="sr-only">{{currentValue}}%</span>
|
||||
<span class="visually-hidden">{{currentValue}}%</span>
|
||||
<i class="fa fa-angle-double-down" style="font-size: 36px;" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="circle">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
$background: rgba(0, 0, 0, .4);
|
||||
$primary-color: #4ac694;
|
||||
|
||||
|
||||
.number {
|
||||
position: absolute;
|
||||
top:50%;
|
||||
|
|
@ -9,15 +5,6 @@ $primary-color: #4ac694;
|
|||
z-index:10;
|
||||
font-size:18px;
|
||||
font-weight:500;
|
||||
color:$primary-color;
|
||||
color: var(--primary-color);
|
||||
animation: MoveUpDown 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes MoveUpDown {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{config.header}}</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" style="overflow-x: auto" [innerHtml]="config.content | safeHtml">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<div class="tagbadge cursor clickable" style="cursor: pointer;">
|
||||
<div class="media">
|
||||
<!-- <img src="..." class="align-self-center mr-3" alt="..."> -->
|
||||
<i class="fa fa-user-circle align-self-center mr-2" aria-hidden="true"></i>
|
||||
<div class="media-body">
|
||||
<div class="tagbadge cursor clickable">
|
||||
<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>
|
||||
<div class="flex-grow-1">
|
||||
<span class="mt-0 mb-0">{{person.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,18 @@
|
|||
@use '../../../theme/colors';
|
||||
|
||||
$bg-color: #c9c9c9;
|
||||
$bdr-color: #f2f2f2;
|
||||
|
||||
.badge {
|
||||
//background-color: $bg-color;
|
||||
.tagbadge {
|
||||
background-color: var(--tagbadge-bg-color);
|
||||
transition: all .3s ease-out;
|
||||
margin: 3px 5px 3px 0px;
|
||||
padding: 2px 15px;
|
||||
padding: 2px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
border: 1px solid $bdr-color;
|
||||
border: 1px solid var(--tagbadge-border-color);
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
width: auto;
|
||||
|
||||
i {
|
||||
color: colors.$primary-color;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-left: 10px;
|
||||
margin-right: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
.read-more-link {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
}
|
||||
// .read-more-link {
|
||||
// font-weight: bold;
|
||||
// text-decoration: none;
|
||||
// cursor: pointer;
|
||||
// color: var(--body-text-color) !important;
|
||||
// }
|
||||
|
|
@ -1,16 +1,14 @@
|
|||
$bg-color: #c9c9c9;
|
||||
$bdr-color: #f2f2f2;
|
||||
|
||||
::ng-deep .tagbadge {
|
||||
background-color: $bg-color;
|
||||
.tagbadge {
|
||||
background-color: var(--tagbadge-bg-color);
|
||||
transition: all .3s ease-out;
|
||||
margin: 3px 5px 3px 0px;
|
||||
padding: 2px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
border: 1px solid $bdr-color;
|
||||
border: 1px solid var(--tagbadge-border-color);
|
||||
display: inline-block;
|
||||
cursor: default;
|
||||
width: auto;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
|
|
@ -20,15 +18,15 @@ $bdr-color: #f2f2f2;
|
|||
}
|
||||
}
|
||||
|
||||
::ng-deep .selectable-cursor {
|
||||
cursor: default !important;
|
||||
.selectable-cursor {
|
||||
cursor: text !important;
|
||||
|
||||
i {
|
||||
cursor: default !important;
|
||||
cursor: text !important;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .not-allowed-cursor {
|
||||
.not-allowed-cursor {
|
||||
cursor: not-allowed !important;
|
||||
|
||||
i {
|
||||
|
|
@ -36,7 +34,7 @@ $bdr-color: #f2f2f2;
|
|||
}
|
||||
}
|
||||
|
||||
::ng-deep .clickable-cursor {
|
||||
.clickable-cursor {
|
||||
cursor: pointer !important;
|
||||
|
||||
i {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title">New Update Available!</h4>
|
||||
<button type="button" class="close" aria-label="Close" (click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
|
|
|||
134
UI/Web/src/app/theme-test/theme-test.component.html
Normal file
134
UI/Web/src/app/theme-test/theme-test.component.html
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
<h1>Themes</h1>
|
||||
<button class="btn btn-primary" (click)="themeService?.setTheme('dark')">Dark</button>
|
||||
<button class="btn btn-primary" (click)="themeService?.setTheme('light')">Light</button>
|
||||
<button class="btn btn-primary" (click)="themeService?.setTheme('E-Ink')">E-ink</button>
|
||||
<button class="btn btn-primary" (click)="themeService?.setTheme('custom')">Custom</button>
|
||||
|
||||
|
||||
|
||||
<h2>Buttons</h2>
|
||||
<button class="btn btn-primary">Primary</button>
|
||||
<button class="btn btn-secondary">secondary</button>
|
||||
<button class="btn btn-secondary alt">secondary alt</button>
|
||||
<button class="btn btn-outline-primary">outline primary</button>
|
||||
<button class="btn btn-outline-secondary">outline secondary</button>
|
||||
<button class="btn btn-link">btn link</button>
|
||||
<button class="btn btn-icon">
|
||||
<i class="fa fa-angle-left"></i> Icon
|
||||
</button>
|
||||
|
||||
<h2>Toastr</h2>
|
||||
|
||||
<button class="btn btn-primary" (click)="toastr.success('Test')">Success</button>
|
||||
<button class="btn btn-danger" (click)="toastr.error('Test')">Error</button>
|
||||
<button class="btn btn-secondary" (click)="toastr.warning('Test')">Warning</button>
|
||||
<button class="btn btn-link" (click)="toastr.info('Test')">Info</button>
|
||||
|
||||
<h2>Inputs</h2>
|
||||
<p>Inputs should always have class="form-control" on them</p>
|
||||
<label>Normal</label>
|
||||
<input type="text" class="form-control">
|
||||
<label>Readonly</label>
|
||||
<input type="text" readonly class="form-control">
|
||||
<label>Placeholder</label>
|
||||
<input type="text" placeholder="Hello, I'm a placeholder" class="form-control">
|
||||
<label>Disabled</label>
|
||||
<input type="text" placeholder="Hello, I'm a placeholder" [disabled]="true" class="form-control">
|
||||
|
||||
<h2>Checkbox</h2>
|
||||
<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">
|
||||
<label for="stat-collection" class="form-check-label">Normal Checkbox</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<label for="stat-collection" class="form-check-label">Disabled Checkbox</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Radio</h2>
|
||||
<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">
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Nav tabs</h2>
|
||||
<h3>Tabs</h3>
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav-tabs nav-pills">
|
||||
<li *ngFor="let tab of tabs" [ngbNavItem]="tab">
|
||||
<a ngbNavLink routerLink=".">{{ tab.title | sentenceCase }}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container>
|
||||
Tab 1
|
||||
</ng-container>
|
||||
<ng-container>
|
||||
Tab 2
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
<div [ngbNavOutlet]="nav" class="mt-3"></div>
|
||||
|
||||
<h3>Tabs</h3>
|
||||
<nav role="navigation">
|
||||
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav nav-pills justify-content-center mt-3" role="tab">
|
||||
<li *ngFor="let tab of tabs" [ngbNavItem]="tab" class="nav-item">
|
||||
<a ngbNavLink routerLink="." [fragment]="tab.fragment">{{ tab.title | titlecase }}</a>
|
||||
<ng-template ngbNavContent>
|
||||
<ng-container>
|
||||
Tab 1
|
||||
</ng-container>
|
||||
<ng-container>
|
||||
Tab 2
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div [ngbNavOutlet]="nav" class="mt-3"></div>
|
||||
|
||||
<h2>Tag Badge</h2>
|
||||
<div class="g-2">
|
||||
<app-tag-badge [selectionMode]="TagBadgeCursor.Selectable">Selectable</app-tag-badge>
|
||||
<app-tag-badge [selectionMode]="TagBadgeCursor.Clickable">Clickable</app-tag-badge>
|
||||
<app-tag-badge [selectionMode]="TagBadgeCursor.NotAllowed">Non Allowed</app-tag-badge>
|
||||
</div>
|
||||
|
||||
<h2>Badge</h2>
|
||||
TODO
|
||||
|
||||
<h2>Switch</h2>
|
||||
TODO: New component, will be used in place of 2 way radios
|
||||
|
||||
<h2>Dropdown/List Group</h2>
|
||||
<div class="dropdown" >
|
||||
<ul class="list-group" role="listbox" id="dropdown">
|
||||
<li class="list-group-item">Item 1</li>
|
||||
<li class="list-group-item">Item 2</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2></h2>
|
||||
|
||||
0
UI/Web/src/app/theme-test/theme-test.component.scss
Normal file
0
UI/Web/src/app/theme-test/theme-test.component.scss
Normal file
32
UI/Web/src/app/theme-test/theme-test.component.ts
Normal file
32
UI/Web/src/app/theme-test/theme-test.component.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
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 { NavService } from '../_services/nav.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-theme-test',
|
||||
templateUrl: './theme-test.component.html',
|
||||
styleUrls: ['./theme-test.component.scss']
|
||||
})
|
||||
export class ThemeTestComponent implements OnInit {
|
||||
|
||||
tabs: Array<{title: string, fragment: string}> = [
|
||||
{title: 'General', fragment: ''},
|
||||
{title: 'Users', fragment: 'users'},
|
||||
{title: 'Libraries', fragment: 'libraries'},
|
||||
{title: 'System', fragment: 'system'},
|
||||
{title: 'Changelog', fragment: 'changelog'},
|
||||
];
|
||||
active = this.tabs[0];
|
||||
|
||||
get TagBadgeCursor(): typeof TagBadgeCursor {
|
||||
return TagBadgeCursor;
|
||||
}
|
||||
|
||||
constructor(public toastr: ToastrService, public navService: NavService, public themeService: ThemeService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
133
UI/Web/src/app/theme.service.ts
Normal file
133
UI/Web/src/app/theme.service.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
import { DOCUMENT } from '@angular/common';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Inject, Injectable, OnDestroy, Renderer2, RendererFactory2, SecurityContext } from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { map, ReplaySubject, Subject, takeUntil } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { ConfirmService } from './shared/confirm.service';
|
||||
import { SiteThemeProgressEvent } from './_models/events/site-theme-progress-event';
|
||||
import { SiteTheme, ThemeProvider } from './_models/preferences/site-theme';
|
||||
import { EVENTS, MessageHubService } from './_services/message-hub.service';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ThemeService implements OnDestroy {
|
||||
|
||||
public defaultTheme: string = 'dark';
|
||||
|
||||
private currentThemeSource = new ReplaySubject<SiteTheme>(1);
|
||||
public currentTheme$ = this.currentThemeSource.asObservable();
|
||||
|
||||
private themesSource = new ReplaySubject<SiteTheme[]>(1);
|
||||
public themes$ = this.themesSource.asObservable();
|
||||
|
||||
/**
|
||||
* Maintain a cache of themes. SignalR will inform us if we need to refresh cache
|
||||
*/
|
||||
private themeCache: Array<SiteTheme> = [];
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
private renderer: Renderer2;
|
||||
private baseUrl = environment.apiUrl;
|
||||
|
||||
|
||||
constructor(rendererFactory: RendererFactory2, @Inject(DOCUMENT) private document: Document, private httpClient: HttpClient,
|
||||
messageHub: MessageHubService, private domSantizer: DomSanitizer, private confirmService: ConfirmService) {
|
||||
this.renderer = rendererFactory.createRenderer(null, null);
|
||||
|
||||
this.getThemes();
|
||||
|
||||
messageHub.messages$.pipe(takeUntil(this.onDestroy)).subscribe(message => {
|
||||
if (message.event === EVENTS.SiteThemeProgress) {
|
||||
if ((message.payload as SiteThemeProgressEvent).progress === 1) {
|
||||
this.getThemes().subscribe(() => {});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
getColorScheme() {
|
||||
return getComputedStyle(this.document.body).getPropertyValue('--color-scheme').trim();
|
||||
}
|
||||
|
||||
isDarkTheme() {
|
||||
console.log('color scheme: ', getComputedStyle(this.document.body).getPropertyValue('--color-scheme').trim().toLowerCase());
|
||||
return this.getColorScheme().toLowerCase() === 'dark';
|
||||
}
|
||||
|
||||
getThemes() {
|
||||
return this.httpClient.get<SiteTheme[]>(this.baseUrl + 'theme').pipe(map(themes => {
|
||||
this.themeCache = themes;
|
||||
this.themesSource.next(themes);
|
||||
return themes;
|
||||
}));
|
||||
}
|
||||
|
||||
setDefault(themeId: number) {
|
||||
return this.httpClient.post(this.baseUrl + 'theme/update-default', {themeId: themeId}).pipe(map(() => {
|
||||
// Refresh the cache when a default state is changed
|
||||
this.getThemes().subscribe(() => {});
|
||||
}));
|
||||
}
|
||||
|
||||
scan() {
|
||||
return this.httpClient.post(this.baseUrl + 'theme/scan', {});
|
||||
}
|
||||
|
||||
|
||||
setTheme(themeName: string) {
|
||||
const theme = this.themeCache.find(t => t.name.toLowerCase() === themeName.toLowerCase());
|
||||
if (theme) {
|
||||
this.unsetThemes();
|
||||
this.renderer.addClass(this.document.querySelector('body'), theme.selector);
|
||||
|
||||
if (theme.provider === ThemeProvider.User && !this.hasThemeInHead(theme.name)) {
|
||||
// We need to load the styles into the browser
|
||||
this.fetchThemeContent(theme.id).subscribe(async (content) => {
|
||||
if (content === null) {
|
||||
await this.confirmService.alert('There is invalid or unsafe css in the theme. Please reach out to your admin to have this corrected. Defaulting to dark theme.');
|
||||
this.setTheme('dark');
|
||||
return;
|
||||
}
|
||||
const styleElem = document.createElement('style');
|
||||
styleElem.id = 'theme-' + theme.name;
|
||||
styleElem.appendChild(this.document.createTextNode(content));
|
||||
|
||||
this.renderer.appendChild(this.document.head, styleElem);
|
||||
this.currentThemeSource.next(theme);
|
||||
});
|
||||
} else {
|
||||
this.currentThemeSource.next(theme);
|
||||
}
|
||||
} else {
|
||||
// Only time themes isn't already loaded is on first load
|
||||
this.getThemes().subscribe(themes => {
|
||||
this.setTheme(themeName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private hasThemeInHead(themeName: string) {
|
||||
const id = 'theme-' + themeName.toLowerCase();
|
||||
return Array.from(this.document.head.children).filter(el => el.tagName === 'STYLE' && el.id.toLowerCase() === id).length > 0;
|
||||
}
|
||||
|
||||
private fetchThemeContent(themeId: number) {
|
||||
// TODO: Refactor {responseType: 'text' as 'json'} into a type so i don't have to retype it
|
||||
return this.httpClient.get<string>(this.baseUrl + 'theme/download-content?themeId=' + themeId, {responseType: 'text' as 'json'}).pipe(map(encodedCss => {
|
||||
return this.domSantizer.sanitize(SecurityContext.STYLE, encodedCss);
|
||||
}));
|
||||
}
|
||||
|
||||
private unsetThemes() {
|
||||
this.themeCache.forEach(theme => this.document.body.classList.remove(theme.selector));
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<input #input [id]="settings.id" type="text" autocomplete="off" formControlName="typeahead">
|
||||
<div class="spinner-border spinner-border-sm" role="status" *ngIf="isLoadingOptions">
|
||||
<span class="sr-only">Loading...</span>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<!-- TODO: Add a clear all button -->
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
@use "../../theme/colors";
|
||||
|
||||
input {
|
||||
width: 15px;
|
||||
opacity: 1px;
|
||||
|
|
@ -9,7 +7,6 @@ input {
|
|||
}
|
||||
|
||||
.typeahead-input {
|
||||
border: 1px solid #ccc;
|
||||
padding: 0px 6px;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
|
@ -20,9 +17,12 @@ input {
|
|||
box-shadow: none;
|
||||
border-radius: 4px;
|
||||
cursor: text;
|
||||
background-color: #fff;
|
||||
min-height: 38px;
|
||||
|
||||
background-color: var(--input-bg-color);
|
||||
border: 1px solid var(--input-border-color);
|
||||
color: var(--body-text-color);
|
||||
|
||||
input {
|
||||
outline: 0 !important;
|
||||
border-radius: .28571429rem;
|
||||
|
|
@ -40,19 +40,9 @@ input {
|
|||
}
|
||||
}
|
||||
|
||||
::ng-deep .bg-dark .typeahead-input {
|
||||
color: #efefef;
|
||||
background-color: colors.$dark-bg-color;
|
||||
}
|
||||
|
||||
// Causes bleedover
|
||||
::ng-deep .bg-dark .dropdown .list-group-item.active {
|
||||
background-color: colors.$dark-hover-color;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
width: 100%;
|
||||
background: white;
|
||||
background: var(--input-bg-color);
|
||||
z-index:1000;
|
||||
margin: 2px 0 0;
|
||||
border-radius: 4px;
|
||||
|
|
|
|||
|
|
@ -4,22 +4,22 @@
|
|||
<ng-container *ngIf="isLoaded">
|
||||
<form [formGroup]="loginForm" (ngSubmit)="login()" novalidate class="needs-validation" *ngIf="!firstTimeFlow">
|
||||
<div class="card-text">
|
||||
<div class="form-group" style="width:100%">
|
||||
<label for="username">Username</label>
|
||||
<input class="form-control" formControlName="username" id="username" type="text">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label visually-hidden">Username</label>
|
||||
<input class="form-control custom-input" formControlName="username" id="username" type="text" autofocus placeholder="Username">
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="width:100%">
|
||||
<label for="password">Password</label>
|
||||
<input class="form-control" formControlName="password" id="password" type="password" autofocus>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label visually-hidden">Password</label>
|
||||
<input class="form-control custom-input" formControlName="password" id="password" type="password" placeholder="Password">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="/registration/reset-password">Forgot Password?</a>
|
||||
<div class="mb-3">
|
||||
<a href="/registration/reset-password" style="color: white">Forgot Password?</a>
|
||||
</div>
|
||||
|
||||
<div class="float-right">
|
||||
<button class="btn btn-secondary alt" type="submit">Login</button>
|
||||
<div>
|
||||
<button class="btn btn-secondary alt" type="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,91 +1,21 @@
|
|||
// @use "../../theme/colors";
|
||||
.btn {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
// .login {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// margin-top: -61px; // To offset the navbar
|
||||
// height: calc(100vh);
|
||||
// min-height: 289px;
|
||||
// position: relative;
|
||||
// width: 100vw;
|
||||
// max-width: 100vw;
|
||||
div {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
// &::before {
|
||||
// content: "";
|
||||
// background-image: url('../../assets/images/login-bg.jpg');
|
||||
// background-size: cover;
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// right: 0;
|
||||
// bottom: 0;
|
||||
// opacity: 0.1;
|
||||
// width: 100%;
|
||||
// }
|
||||
|
||||
// .logo-container {
|
||||
// .logo {
|
||||
// display:inline-block;
|
||||
// height: 50px;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .row {
|
||||
// margin-top: 10vh;
|
||||
// }
|
||||
|
||||
// .card {
|
||||
// background-color: colors.$primary-color;
|
||||
// color: #fff;
|
||||
// cursor: pointer;
|
||||
// min-width: 300px;
|
||||
|
||||
// &:focus {
|
||||
// border: 2px solid white;
|
||||
|
||||
// }
|
||||
|
||||
|
||||
// .card-title {
|
||||
// font-family: 'Spartan', sans-serif;
|
||||
// font-weight: bold;
|
||||
// display: inline-block;
|
||||
// vertical-align: middle;
|
||||
// width: 280px;
|
||||
// }
|
||||
|
||||
// .card-text {
|
||||
// font-family: "EBGaramond", "Helvetica Neue", sans-serif;
|
||||
// }
|
||||
|
||||
// .alt {
|
||||
// background-color: #424c72;
|
||||
// border-color: #444f75;
|
||||
// }
|
||||
|
||||
// .alt:hover {
|
||||
// background-color: #3b4466;
|
||||
// }
|
||||
|
||||
// .alt:focus {
|
||||
// background-color: #343c59;
|
||||
// box-shadow: 0 0 0 0.2rem rgb(68 79 117 / 50%);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// .invalid-feedback {
|
||||
// display: inline-block;
|
||||
// color: #343c59;
|
||||
// }
|
||||
|
||||
// input {
|
||||
// background-color: #fff !important;
|
||||
// color: black;
|
||||
// }
|
||||
|
||||
input {
|
||||
.custom-input {
|
||||
background-color: #fff !important;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.invalid-feedback {
|
||||
display: inline-block;
|
||||
color: #343c59;
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
<div class="form-group">
|
||||
<label for="api-key--{{title}}">{{title}}</label><span *ngIf="tooltipText.length > 0"> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="tooltip" role="button" tabindex="0"></i></span>
|
||||
<div class="mb-3">
|
||||
<label for="api-key--{{title}}" class="form-label">{{title}}</label><span *ngIf="tooltipText.length > 0"> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="tooltip" role="button" tabindex="0"></i></span>
|
||||
<ng-template #tooltip>{{tooltipText}}</ng-template>
|
||||
<div class="input-group">
|
||||
<input #apiKey type="text" readonly class="form-control" id="api-key--{{title}}" aria-describedby="button-addon4" [value]="key" (click)="selectAll()">
|
||||
<div class="input-group-append" id="button-addon4">
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="copy()"><span class="sr-only">Copy</span><i class="fa fa-copy" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-danger" type="button" (click)="refresh()" *ngIf="showRefresh"><span class="sr-only">Regenerate</span><i class="fa fa-sync-alt" aria-hidden="true"></i></button>
|
||||
<div id="button-addon4">
|
||||
<button class="btn btn-outline-secondary" type="button" (click)="copy()"><span class="visually-hidden">Copy</span><i class="fa fa-copy" aria-hidden="true"></i></button>
|
||||
<button class="btn btn-danger" type="button" (click)="refresh()" *ngIf="showRefresh"><span class="visually-hidden">Regenerate</span><i class="fa fa-sync-alt" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -6,27 +6,27 @@
|
|||
<div>
|
||||
<h4>
|
||||
<a id="series--{{series.name}}" href="javascript:void(0);" (click)="viewBookmarks(series)">{{series.name | titlecase}}</a>
|
||||
<span class="badge badge-secondary badge-pill">{{getBookmarkPages(series.id)}}</span>
|
||||
<div class="float-right">
|
||||
<button attr.aria-labelledby="series--{{series.name}}" class="btn btn-danger mr-2 btn-sm" (click)="clearBookmarks(series)" [disabled]="clearingSeries[series.id]" placement="top" ngbTooltip="Clear Bookmarks" attr.aria-label="Clear Bookmarks">
|
||||
<span class="badge bg-secondary rounded-pill">{{getBookmarkPages(series.id)}}</span>
|
||||
<div class="float-end">
|
||||
<button attr.aria-labelledby="series--{{series.name}}" class="btn btn-danger me-2 btn-sm" (click)="clearBookmarks(series)" [disabled]="clearingSeries[series.id]" placement="top" ngbTooltip="Clear Bookmarks" attr.aria-label="Clear Bookmarks">
|
||||
<ng-container *ngIf="clearingSeries[series.id]; else notClearing">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="sr-only">Loading...</span>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</ng-container>
|
||||
<ng-template #notClearing>
|
||||
<i class="fa fa-trash-alt" aria-hidden="true"></i>
|
||||
</ng-template>
|
||||
</button>
|
||||
<button attr.aria-labelledby="series--{{series.name}}" class="btn btn-secondary mr-2 btn-sm" (click)="downloadBookmarks(series)" [disabled]="downloadingSeries[series.id]" placement="top" ngbTooltip="Download Bookmarks" attr.aria-label="Download Bookmarks">
|
||||
<button attr.aria-labelledby="series--{{series.name}}" class="btn btn-secondary me-2 btn-sm" (click)="downloadBookmarks(series)" [disabled]="downloadingSeries[series.id]" placement="top" ngbTooltip="Download Bookmarks" attr.aria-label="Download Bookmarks">
|
||||
<ng-container *ngIf="downloadingSeries[series.id]; else notDownloading">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="sr-only">Downloading...</span>
|
||||
<span class="visually-hidden">Downloading...</span>
|
||||
</ng-container>
|
||||
<ng-template #notDownloading>
|
||||
<i class="fa fa-arrow-alt-circle-down" aria-hidden="true"></i>
|
||||
</ng-template>
|
||||
</button>
|
||||
<button attr.aria-labelledby="series--{{series.name}}" class="btn btn-primary mr-2 btn-sm" routerLink="/library/{{series.libraryId}}/series/{{series.id}}" placement="top" ngbTooltip="Open Series" attr.aria-label="Open Series">
|
||||
<button attr.aria-labelledby="series--{{series.name}}" class="btn btn-primary me-2 btn-sm" routerLink="/library/{{series.libraryId}}/series/{{series.id}}" placement="top" ngbTooltip="Open Series" attr.aria-label="Open Series">
|
||||
<i class="fa fa-eye" title="Open Series"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-8"><h3>Theme Manager</h3></div>
|
||||
<div class="col-4" *ngIf="isAdmin">
|
||||
<button class="btn btn-primary float-end" (click)="scan()">
|
||||
<i class="fa fa-refresh" aria-hidden="true"></i>&ngsp; Scan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-0">
|
||||
<ng-container *ngFor="let theme of (themeService.themes$ | async)">
|
||||
<div class="card col-auto me-3" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{theme.name | sentenceCase}}</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">{{theme.provider === ThemeProvider.System ? 'System' : 'User'}}</h6>
|
||||
<!-- <p class="card-text">
|
||||
<i class="far fa-file-code" style="font-size: 24px" aria-hidden="true"></i>
|
||||
</p> -->
|
||||
<button class="btn btn-secondary me-2" [disabled]="theme.isDefault" *ngIf="isAdmin" (click)="updateDefault(theme)">Set Default</button>
|
||||
<button class="btn btn-primary" (click)="applyTheme(theme)" [disabled]="currentTheme?.id === theme.id">{{currentTheme?.id === theme.id ? 'Applied' : 'Apply'}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { distinctUntilChanged, Subject, take, takeUntil } from 'rxjs';
|
||||
import { ThemeService } from 'src/app/theme.service';
|
||||
import { SiteTheme, ThemeProvider } from 'src/app/_models/preferences/site-theme';
|
||||
import { User } from 'src/app/_models/user';
|
||||
import { AccountService } from 'src/app/_services/account.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-theme-manager',
|
||||
templateUrl: './theme-manager.component.html',
|
||||
styleUrls: ['./theme-manager.component.scss']
|
||||
})
|
||||
export class ThemeManagerComponent implements OnInit, OnDestroy {
|
||||
|
||||
currentTheme!: SiteTheme;
|
||||
isAdmin: boolean = false;
|
||||
user: User | undefined;
|
||||
|
||||
private readonly onDestroy = new Subject<void>();
|
||||
|
||||
get ThemeProvider() {
|
||||
return ThemeProvider;
|
||||
}
|
||||
|
||||
constructor(public themeService: ThemeService, private accountService: AccountService, private toastr: ToastrService) {
|
||||
themeService.currentTheme$.pipe(takeUntil(this.onDestroy), distinctUntilChanged()).subscribe(theme => {
|
||||
this.currentTheme = theme;
|
||||
});
|
||||
|
||||
accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
if (user) {
|
||||
this.user = user;
|
||||
this.isAdmin = accountService.hasAdminRole(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy.next();
|
||||
this.onDestroy.complete();
|
||||
}
|
||||
|
||||
applyTheme(theme: SiteTheme) {
|
||||
|
||||
if (this.user) {
|
||||
const pref = Object.assign({}, this.user.preferences);
|
||||
pref.theme = theme;
|
||||
this.accountService.updatePreferences(pref).subscribe(updatedPref => {
|
||||
if (this.user) {
|
||||
this.user.preferences = updatedPref;
|
||||
}
|
||||
this.themeService.setTheme(theme.name);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateDefault(theme: SiteTheme) {
|
||||
this.themeService.setDefault(theme.id).subscribe(() => {
|
||||
this.toastr.success('Site default has been updated to ' + theme.name);
|
||||
});
|
||||
}
|
||||
|
||||
scan() {
|
||||
this.themeService.scan().subscribe(() => {
|
||||
this.toastr.info('A site theme scan has been queued');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue