Misc Polishing (#413)

* Ensure that after we assign a role to a user, we show it immediately

* Cached libraryType api as that is not going to change in a viewing session. Moved some components around to tighten bundles.

* Cleaned up more TODOs
* Refactored Configuration to use getter and setters so that the interface is a lot cleaner. Updated HashUtil to use JWT Secret instead of Machine name (as docker machine name is random each boot).
This commit is contained in:
Joseph Milazzo 2021-07-20 21:39:44 -05:00 committed by GitHub
parent ef5b22b585
commit b8165b311c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 408 additions and 307 deletions

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using API.Data;
using API.DTOs;
using API.Entities.Enums;
using API.Extensions;
@ -13,7 +12,6 @@ using Kavita.Common;
using Kavita.Common.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace API.Controllers
@ -24,26 +22,24 @@ namespace API.Controllers
private readonly ILogger<SettingsController> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly ITaskScheduler _taskScheduler;
private readonly IConfiguration _configuration;
public SettingsController(ILogger<SettingsController> logger, IUnitOfWork unitOfWork, ITaskScheduler taskScheduler, IConfiguration configuration)
public SettingsController(ILogger<SettingsController> logger, IUnitOfWork unitOfWork, ITaskScheduler taskScheduler)
{
_logger = logger;
_unitOfWork = unitOfWork;
_taskScheduler = taskScheduler;
_configuration = configuration;
}
[HttpGet("")]
[HttpGet]
public async Task<ActionResult<ServerSettingDto>> GetSettings()
{
var settingsDto = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
settingsDto.Port = Configuration.GetPort(Program.GetAppSettingFilename());
settingsDto.LoggingLevel = Configuration.GetLogLevel(Program.GetAppSettingFilename());
settingsDto.Port = Configuration.Port;
settingsDto.LoggingLevel = Configuration.LogLevel;
return Ok(settingsDto);
}
[HttpPost("")]
[HttpPost]
public async Task<ActionResult<ServerSettingDto>> UpdateSettings(ServerSettingDto updateSettingsDto)
{
_logger.LogInformation("{UserName} is updating Server Settings", User.GetUsername());
@ -61,9 +57,6 @@ namespace API.Controllers
// We do not allow CacheDirectory changes, so we will ignore.
var currentSettings = await _unitOfWork.SettingsRepository.GetSettingsAsync();
var logLevelOptions = new LogLevelOptions();
_configuration.GetSection("Logging:LogLevel").Bind(logLevelOptions);
foreach (var setting in currentSettings)
{
if (setting.Key == ServerSettingKey.TaskBackup && updateSettingsDto.TaskBackup != setting.Value)
@ -78,24 +71,24 @@ namespace API.Controllers
_unitOfWork.SettingsRepository.Update(setting);
}
if (setting.Key == ServerSettingKey.Port && updateSettingsDto.Port + "" != setting.Value)
if (setting.Key == ServerSettingKey.Port && updateSettingsDto.Port + string.Empty != setting.Value)
{
setting.Value = updateSettingsDto.Port + "";
setting.Value = updateSettingsDto.Port + string.Empty;
// Port is managed in appSetting.json
Configuration.UpdatePort(Program.GetAppSettingFilename(), updateSettingsDto.Port);
Configuration.Port = updateSettingsDto.Port;
_unitOfWork.SettingsRepository.Update(setting);
}
if (setting.Key == ServerSettingKey.LoggingLevel && updateSettingsDto.LoggingLevel + "" != setting.Value)
if (setting.Key == ServerSettingKey.LoggingLevel && updateSettingsDto.LoggingLevel + string.Empty != setting.Value)
{
setting.Value = updateSettingsDto.LoggingLevel + "";
Configuration.UpdateLogLevel(Program.GetAppSettingFilename(), updateSettingsDto.LoggingLevel);
setting.Value = updateSettingsDto.LoggingLevel + string.Empty;
Configuration.LogLevel = updateSettingsDto.LoggingLevel;
_unitOfWork.SettingsRepository.Update(setting);
}
if (setting.Key == ServerSettingKey.AllowStatCollection && updateSettingsDto.AllowStatCollection + "" != setting.Value)
if (setting.Key == ServerSettingKey.AllowStatCollection && updateSettingsDto.AllowStatCollection + string.Empty != setting.Value)
{
setting.Value = updateSettingsDto.AllowStatCollection + "";
setting.Value = updateSettingsDto.AllowStatCollection + string.Empty;
_unitOfWork.SettingsRepository.Update(setting);
if (!updateSettingsDto.AllowStatCollection)
{
@ -108,7 +101,6 @@ namespace API.Controllers
}
}
_configuration.GetSection("Logging:LogLevel:Default").Value = updateSettingsDto.LoggingLevel + "";
if (!_unitOfWork.HasChanges()) return Ok("Nothing was updated");
if (!_unitOfWork.HasChanges() || !await _unitOfWork.CommitAsync())

View File

@ -61,11 +61,10 @@ namespace API.Data
await context.SaveChangesAsync();
// Port and LoggingLevel are managed in appSettings.json. Update the DB values to match
var configFile = Program.GetAppSettingFilename();
context.ServerSetting.FirstOrDefault(s => s.Key == ServerSettingKey.Port).Value =
Configuration.GetPort(configFile) + "";
Configuration.Port + string.Empty;
context.ServerSetting.FirstOrDefault(s => s.Key == ServerSettingKey.LoggingLevel).Value =
Configuration.GetLogLevel(configFile);
Configuration.LogLevel + string.Empty;
await context.SaveChangesAsync();

View File

@ -43,7 +43,7 @@ namespace API.Extensions
services.AddDbContext<DataContext>(options =>
{
options.UseSqlite(config.GetConnectionString("DefaultConnection"));
options.EnableSensitiveDataLogging(env.IsDevelopment() || Configuration.GetLogLevel(Program.GetAppSettingFilename()).Equals("Debug"));
options.EnableSensitiveDataLogging(env.IsDevelopment() || Configuration.LogLevel.Equals("Debug"));
});
}

View File

@ -20,37 +20,26 @@ namespace API
{
public class Program
{
private static int _httpPort;
private static readonly int HttpPort = Configuration.Port;
protected Program()
{
}
public static string GetAppSettingFilename()
{
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var isDevelopment = environment == Environments.Development;
return "appsettings" + (isDevelopment ? ".Development" : "") + ".json";
}
public static async Task Main(string[] args)
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
// Before anything, check if JWT has been generated properly or if user still has default
if (!Configuration.CheckIfJwtTokenSet(GetAppSettingFilename()) && Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != Environments.Development)
if (!Configuration.CheckIfJwtTokenSet() &&
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != Environments.Development)
{
Console.WriteLine("Generating JWT TokenKey for encrypting user sessions...");
var rBytes = new byte[128];
using (var crypto = new RNGCryptoServiceProvider()) crypto.GetBytes(rBytes);
var base64 = Convert.ToBase64String(rBytes).Replace("/", "");
Configuration.UpdateJwtToken(GetAppSettingFilename(), base64);
Configuration.JwtToken = Convert.ToBase64String(rBytes).Replace("/", string.Empty);
}
// Get HttpPort from Config
_httpPort = Configuration.GetPort(GetAppSettingFilename());
var host = CreateHostBuilder(args).Build();
using var scope = host.Services.CreateScope();
@ -67,7 +56,7 @@ namespace API
}
catch (Exception ex)
{
var logger = services.GetRequiredService <ILogger<Program>>();
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred during migration");
}
@ -80,10 +69,7 @@ namespace API
{
webBuilder.UseKestrel((opts) =>
{
opts.ListenAnyIP(_httpPort, options =>
{
options.Protocols = HttpProtocols.Http1AndHttp2;
});
opts.ListenAnyIP(HttpPort, options => { options.Protocols = HttpProtocols.Http1AndHttp2; });
});
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
@ -139,7 +125,6 @@ namespace API
scope.SetTag("culture", Thread.CurrentThread.CurrentCulture.Name);
scope.SetTag("branch", BuildInfo.Branch);
});
});
}

View File

@ -146,7 +146,7 @@ namespace API
});
}
private void OnShutdown()
private static void OnShutdown()
{
Console.WriteLine("Server is shutting down. Please allow a few seconds to stop any background jobs...");
TaskScheduler.Client.Dispose();

View File

@ -2,38 +2,80 @@
using System.IO;
using System.Text.Json;
using Kavita.Common.EnvironmentInfo;
using Microsoft.Extensions.Hosting;
namespace Kavita.Common
{
public static class Configuration
{
#region JWT Token
public static bool CheckIfJwtTokenSet(string filePath)
private static string AppSettingsFilename = GetAppSettingFilename();
public static string Branch
{
get => GetBranch(GetAppSettingFilename());
set => SetBranch(GetAppSettingFilename(), value);
}
public static int Port
{
get => GetPort(GetAppSettingFilename());
set => SetPort(GetAppSettingFilename(), value);
}
public static string JwtToken
{
get => GetJwtToken(GetAppSettingFilename());
set => SetJwtToken(GetAppSettingFilename(), value);
}
public static string LogLevel
{
get => GetLogLevel(GetAppSettingFilename());
set => SetLogLevel(GetAppSettingFilename(), value);
}
private static string GetAppSettingFilename()
{
if (!string.IsNullOrEmpty(AppSettingsFilename))
{
return AppSettingsFilename;
}
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var isDevelopment = environment == Environments.Development;
return "appsettings" + (isDevelopment ? ".Development" : "") + ".json";
}
#region JWT Token
private static string GetJwtToken(string filePath)
{
try
{
try {
var json = File.ReadAllText(filePath);
var jsonObj = JsonSerializer.Deserialize<dynamic>(json);
const string key = "TokenKey";
if (jsonObj.TryGetProperty(key, out JsonElement tokenElement))
{
return tokenElement.GetString() != "super secret unguessable key";
return tokenElement.GetString();
}
return false;
return string.Empty;
}
catch (Exception ex) {
Console.WriteLine("Error writing app settings: " + ex.Message);
catch (Exception ex)
{
Console.WriteLine("Error reading app settings: " + ex.Message);
}
return false;
return string.Empty;
}
public static bool UpdateJwtToken(string filePath, string token)
private static bool SetJwtToken(string filePath, string token)
{
try
{
var json = File.ReadAllText(filePath).Replace("super secret unguessable key", token);
var currentToken = GetJwtToken(filePath);
var json = File.ReadAllText(filePath)
.Replace("\"TokenKey\": \"" + currentToken, "\"TokenKey\": \"" + token);
File.WriteAllText(filePath, json);
return true;
}
@ -42,9 +84,42 @@ namespace Kavita.Common
return false;
}
}
public static bool CheckIfJwtTokenSet()
{
//string filePath
try
{
return GetJwtToken(GetAppSettingFilename()) != "super secret unguessable key";
}
catch (Exception ex)
{
Console.WriteLine("Error writing app settings: " + ex.Message);
}
return false;
}
public static bool UpdateJwtToken(string token)
{
try
{
var filePath = GetAppSettingFilename();
var json = File.ReadAllText(filePath).Replace("super secret unguessable key", token);
File.WriteAllText(GetAppSettingFilename(), json);
return true;
}
catch (Exception)
{
return false;
}
}
#endregion
#region Port
public static bool UpdatePort(string filePath, int port)
public static bool SetPort(string filePath, int port)
{
if (new OsInfo(Array.Empty<IOsVersionAdapter>()).IsDocker)
{
@ -63,15 +138,18 @@ namespace Kavita.Common
return false;
}
}
public static int GetPort(string filePath)
{
Console.WriteLine(GetAppSettingFilename());
const int defaultPort = 5000;
if (new OsInfo(Array.Empty<IOsVersionAdapter>()).IsDocker)
{
return defaultPort;
}
try {
try
{
var json = File.ReadAllText(filePath);
var jsonObj = JsonSerializer.Deserialize<dynamic>(json);
const string key = "Port";
@ -81,20 +159,25 @@ namespace Kavita.Common
return tokenElement.GetInt32();
}
}
catch (Exception ex) {
catch (Exception ex)
{
Console.WriteLine("Error writing app settings: " + ex.Message);
}
return defaultPort;
}
#endregion
#region LogLevel
public static bool UpdateLogLevel(string filePath, string logLevel)
public static bool SetLogLevel(string filePath, string logLevel)
{
try
{
var currentLevel = GetLogLevel(filePath);
var json = File.ReadAllText(filePath).Replace($"\"Default\": \"{currentLevel}\"", $"\"Default\": \"{logLevel}\"");
var json = File.ReadAllText(filePath)
.Replace($"\"Default\": \"{currentLevel}\"", $"\"Default\": \"{logLevel}\"");
File.WriteAllText(filePath, json);
return true;
}
@ -103,9 +186,11 @@ namespace Kavita.Common
return false;
}
}
public static string GetLogLevel(string filePath)
{
try {
try
{
var json = File.ReadAllText(filePath);
var jsonObj = JsonSerializer.Deserialize<dynamic>(json);
if (jsonObj.TryGetProperty("Logging", out JsonElement tokenElement))
@ -123,12 +208,53 @@ namespace Kavita.Common
}
}
}
catch (Exception ex) {
catch (Exception ex)
{
Console.WriteLine("Error writing app settings: " + ex.Message);
}
return "Information";
}
#endregion
public static string GetBranch(string filePath)
{
const string defaultBranch = "main";
try
{
var json = File.ReadAllText(filePath);
var jsonObj = JsonSerializer.Deserialize<dynamic>(json);
const string key = "Branch";
if (jsonObj.TryGetProperty(key, out JsonElement tokenElement))
{
return tokenElement.GetString();
}
}
catch (Exception ex)
{
Console.WriteLine("Error reading app settings: " + ex.Message);
}
return defaultBranch;
}
public static bool SetBranch(string filePath, string updatedBranch)
{
try
{
var currentBranch = GetBranch(filePath);
var json = File.ReadAllText(filePath)
.Replace("\"Branch\": " + currentBranch, "\"Branch\": " + updatedBranch);
File.WriteAllText(filePath, json);
return true;
}
catch (Exception)
{
return false;
}
}
}
}

View File

@ -34,7 +34,7 @@ namespace Kavita.Common
/// <returns></returns>
public static string AnonymousToken()
{
var seed = $"{Environment.ProcessorCount}_{Environment.OSVersion.Platform}_{Environment.MachineName}_{Environment.UserName}";
var seed = $"{Environment.ProcessorCount}_{Environment.OSVersion.Platform}_{Configuration.JwtToken}_{Environment.UserName}";
return CalculateCrc(seed);
}
}

View File

@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
<PackageReference Include="Sentry" Version="3.7.0" />
</ItemGroup>

View File

@ -160,7 +160,6 @@
<div [ngbNavOutlet]="nav" class="mt-3"></div>
</div>
<div class="modal-footer">
<!-- TODO: Replace secondary buttons in modals with btn-light -->
<button type="button" class="btn btn-secondary" (click)="close()">Close</button>
<button type="submit" class="btn btn-primary" (click)="save()">Save</button>
</div>

View File

@ -1,4 +1,3 @@
//TODO: Refactor this name to something better
export interface InProgressChapter {
id: number;
range: string;

View File

@ -14,7 +14,8 @@ export class LibraryService {
baseUrl = environment.apiUrl;
libraryNames: {[key:number]: string} | undefined = undefined;
private libraryNames: {[key:number]: string} | undefined = undefined;
private libraryTypes: {[key: number]: LibraryType} | undefined = undefined;
constructor(private httpClient: HttpClient) {}
@ -75,8 +76,17 @@ export class LibraryService {
}
getLibraryType(libraryId: number) {
// TODO: Cache this in browser
return this.httpClient.get<LibraryType>(this.baseUrl + 'library/type?libraryId=' + libraryId);
if (this.libraryTypes != undefined && this.libraryTypes.hasOwnProperty(libraryId)) {
return of(this.libraryTypes[libraryId]);
}
return this.httpClient.get<LibraryType>(this.baseUrl + 'library/type?libraryId=' + libraryId).pipe(map(l => {
if (this.libraryTypes === undefined) {
this.libraryTypes = {};
}
this.libraryTypes[libraryId] = l;
return this.libraryTypes[libraryId];
}));
}
search(term: string) {

View File

@ -42,7 +42,6 @@ export class DirectoryPickerComponent implements OnInit {
}
goBack() {
// BUG: When Going back to initial listing, this code gets stuck on first drive
this.routeStack.pop();
const stackPeek = this.routeStack.peek();
if (stackPeek !== undefined) {
@ -53,7 +52,6 @@ export class DirectoryPickerComponent implements OnInit {
this.currentRoot = '';
this.loadChildren(this.currentRoot);
}
}
loadChildren(path: string) {

View File

@ -30,7 +30,7 @@ export class EditRbsModalComponent implements OnInit {
}
close() {
this.modal.close(false);
this.modal.close(undefined);
}
save() {
@ -42,8 +42,10 @@ export class EditRbsModalComponent implements OnInit {
this.memberService.updateMemberRoles(this.member?.username, selectedRoles).subscribe(() => {
if (this.member) {
this.member.roles = selectedRoles;
this.modal.close(this.member);
return;
}
this.modal.close(true);
this.modal.close(undefined);
});
}

View File

@ -69,7 +69,7 @@ export class ManageLibraryComponent implements OnInit, OnDestroy {
this.libraryService.delete(library.id).pipe(take(1)).subscribe(() => {
this.deletionInProgress = false;
this.getLibraries();
this.toastr.success('Library has been removed'); // BUG: This is not causing a refresh
this.toastr.success('Library has been removed');
});
}
}

View File

@ -25,18 +25,14 @@
</div>
<div class="form-group">
<label for="stat-collection">Allow Anonymous Usage Collection</label>&nbsp;<i class="fa fa-info-circle" placement="right" [ngbTooltip]="statTooltip" role="button" tabindex="0"></i>
<ng-template #statTooltip>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 and bug fixes. Requires restart to take effect.</ng-template>
<span class="sr-only" id="logging-level-port-help">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 and bug fixes. Requires restart to take effect.</span>
<p class="accent">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 and bug fixes. Requires restart to take effect</p>
<label for="stat-collection" 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">
<input id="stat-collection" type="checkbox" aria-label="Admin" class="form-check-input" formControlName="allowStatCollection">
<label for="stat-collection" class="form-check-label">Send Data</label>
</div>
</div>
<h4>Reoccuring Tasks</h4>
<div class="form-group">
<label for="settings-tasks-scan">Library Scan</label>&nbsp;<i class="fa fa-info-circle" placement="right" [ngbTooltip]="taskScanTooltip" role="button" tabindex="0"></i>

View File

@ -82,6 +82,11 @@ export class ManageUsersComponent implements OnInit {
openEditRole(member: Member) {
const modalRef = this.modalService.open(EditRbsModalComponent);
modalRef.componentInstance.member = member;
modalRef.closed.subscribe((updatedMember: Member) => {
if (updatedMember !== undefined) {
member = updatedMember;
}
})
}
updatePassword(member: Member) {

View File

@ -37,6 +37,8 @@ import { TypeaheadModule } from './typeahead/typeahead.module';
import { AllCollectionsComponent } from './all-collections/all-collections.component';
import { EditCollectionTagsComponent } from './_modals/edit-collection-tags/edit-collection-tags.component';
import { RecentlyAddedComponent } from './recently-added/recently-added.component';
import { LibraryCardComponent } from './library-card/library-card.component';
import { SeriesCardComponent } from './series-card/series-card.component';
let sentryProviders: any[] = [];
@ -100,6 +102,8 @@ if (environment.production) {
AllCollectionsComponent,
EditCollectionTagsComponent,
RecentlyAddedComponent,
LibraryCardComponent,
SeriesCardComponent
],
imports: [
HttpClientModule,

View File

@ -27,7 +27,7 @@
<div class="webtoon-images" *ngIf="readerMode === READER_MODE.WEBTOON && !isLoading">
<app-infinite-scroller [pageNum]="pageNum" [bufferPages]="5" [goToPage]="goToPageEvent" (pageNumberChange)="handleWebtoonPageChange($event)" [totalPages]="maxPages" [urlProvider]="getPageUrl"></app-infinite-scroller>
</div>
<ng-container *ngIf="readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD"> <!--; else webtoonClickArea; TODO: See if people want this mode WEBTOON_WITH_CLICKS-->
<ng-container *ngIf="readerMode === READER_MODE.MANGA_LR || readerMode === READER_MODE.MANGA_UD"> <!--; else webtoonClickArea; See if people want this mode WEBTOON_WITH_CLICKS-->
<div class="{{readerMode === READER_MODE.MANGA_LR ? 'right' : 'top'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')"></div>
<div class="{{readerMode === READER_MODE.MANGA_LR ? 'left' : 'bottom'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, 'left')"></div>
</ng-container>

View File

@ -7,10 +7,9 @@ import { EditSeriesModalComponent } from 'src/app/_modals/edit-series-modal/edit
import { Series } from 'src/app/_models/series';
import { AccountService } from 'src/app/_services/account.service';
import { ImageService } from 'src/app/_services/image.service';
import { LibraryService } from 'src/app/_services/library.service';
import { ActionFactoryService, Action, ActionItem } from 'src/app/_services/action-factory.service';
import { SeriesService } from 'src/app/_services/series.service';
import { ConfirmService } from '../confirm.service';
import { ConfirmService } from '../shared/confirm.service';
@Component({
selector: 'app-series-card',
@ -30,9 +29,8 @@ export class SeriesCardComponent implements OnInit, OnChanges {
constructor(private accountService: AccountService, private router: Router,
private seriesService: SeriesService, private toastr: ToastrService,
private libraryService: LibraryService, private modalService: NgbModal,
private confirmService: ConfirmService, public imageService: ImageService,
private actionFactoryService: ActionFactoryService) {
private modalService: NgbModal, private confirmService: ConfirmService,
public imageService: ImageService, private actionFactoryService: ActionFactoryService) {
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (user) {
this.isAdmin = this.accountService.hasAdminRole(user);

View File

@ -3,14 +3,12 @@ import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { CardItemComponent } from './card-item/card-item.component';
import { NgbCollapseModule, NgbDropdownModule, NgbPaginationModule, NgbProgressbarModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
import { LibraryCardComponent } from './library-card/library-card.component';
import { SeriesCardComponent } from './series-card/series-card.component';
import { CardDetailsModalComponent } from './_modals/card-details-modal/card-details-modal.component';
import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.component';
import { SafeHtmlPipe } from './safe-html.pipe';
import { LazyLoadImageModule } from 'ng-lazyload-image';
import { CardActionablesComponent } from './card-item/card-actionables/card-actionables.component';
import { RegisterMemberComponent } from './register-member/register-member.component';
import { RegisterMemberComponent } from '../register-member/register-member.component';
import { ReadMoreComponent } from './read-more/read-more.component';
import { RouterModule } from '@angular/router';
import { DrawerComponent } from './drawer/drawer.component';
@ -24,8 +22,6 @@ import { A11yClickDirective } from './a11y-click.directive';
declarations: [
RegisterMemberComponent,
CardItemComponent,
LibraryCardComponent,
SeriesCardComponent,
CardDetailsModalComponent,
ConfirmDialogComponent,
SafeHtmlPipe,
@ -49,10 +45,8 @@ import { A11yClickDirective } from './a11y-click.directive';
NgbPaginationModule // CardDetailLayoutComponent
],
exports: [
RegisterMemberComponent, // TODO: Move this out and put in normal app
RegisterMemberComponent,
CardItemComponent,
LibraryCardComponent, // TODO: Move this out and put in normal app
SeriesCardComponent, // TODO: Move this out and put in normal app
SafeHtmlPipe,
CardActionablesComponent,
ReadMoreComponent,

View File

@ -27,13 +27,6 @@ export class SelectionModel<T> {
});
}
// __lookupItem(item: T) {
// if (this._propAccessor != '') {
// // TODO: Implement this code to speedup lookups (use a map rather than array)
// }
// const dataItem = this._data.filter(data => data.value == d);
// }
/**
* Will toggle if the data item is selected or not. If data option is not tracked, will add it and set state to true.
* @param data Item to toggle