diff --git a/API/API.csproj b/API/API.csproj index 0e494734e..29686bf30 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -76,6 +76,17 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/API/Controllers/AccountController.cs b/API/Controllers/AccountController.cs index 9442ff9d8..c691817bd 100644 --- a/API/Controllers/AccountController.cs +++ b/API/Controllers/AccountController.cs @@ -603,7 +603,7 @@ public class AccountController : BaseApiController { var invitedUser = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email); if (await _userManager.IsEmailConfirmedAsync(invitedUser!)) - return BadRequest(await _localizationService.Translate(User.GetUserId(), "user-already-registered", invitedUser!.UserName)); + return BadRequest(await _localizationService.Translate(User.GetUserId(), "user-already-registered", invitedUser.UserName)); return BadRequest(await _localizationService.Translate(User.GetUserId(), "user-already-invited")); } @@ -684,7 +684,6 @@ public class AccountController : BaseApiController { var emailLink = await _accountService.GenerateEmailLink(Request, user.ConfirmationToken, "confirm-email", dto.Email); _logger.LogCritical("[Invite User]: Email Link for {UserName}: {Link}", user.UserName, emailLink); - _logger.LogCritical("[Invite User]: Token {UserName}: {Token}", user.UserName, user.ConfirmationToken); if (!_emailService.IsValidEmail(dto.Email)) { @@ -706,7 +705,6 @@ public class AccountController : BaseApiController InvitingUser = adminUser.UserName!, ServerConfirmationLink = emailLink })); - } return Ok(new InviteUserResponse @@ -834,13 +832,13 @@ public class AccountController : BaseApiController public async Task> ConfirmForgotPassword(ConfirmPasswordResetDto dto) { var user = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email); + if (user == null) + { + return BadRequest(await _localizationService.Get("en", "bad-credentials")); + } + try { - if (user == null) - { - return BadRequest(await _localizationService.Get("en", "bad-credentials")); - } - var result = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, "ResetPassword", dto.Token); if (!result) diff --git a/API/DTOs/MemberDto.cs b/API/DTOs/MemberDto.cs index 31b5e62be..226d47eae 100644 --- a/API/DTOs/MemberDto.cs +++ b/API/DTOs/MemberDto.cs @@ -18,7 +18,9 @@ public class MemberDto public bool IsPending { get; init; } public AgeRestrictionDto? AgeRestriction { get; init; } public DateTime Created { get; init; } + public DateTime CreatedUtc { get; init; } public DateTime LastActive { get; init; } + public DateTime LastActiveUtc { get; init; } public IEnumerable? Libraries { get; init; } public IEnumerable? Roles { get; init; } } diff --git a/API/Data/Repositories/UserRepository.cs b/API/Data/Repositories/UserRepository.cs index e230eba09..0b604e59e 100644 --- a/API/Data/Repositories/UserRepository.cs +++ b/API/Data/Repositories/UserRepository.cs @@ -665,7 +665,9 @@ public class UserRepository : IUserRepository Username = u.UserName, Email = u.Email, Created = u.Created, + CreatedUtc = u.CreatedUtc, LastActive = u.LastActive, + LastActiveUtc = u.LastActiveUtc, Roles = u.UserRoles.Select(r => r.Role.Name).ToList(), IsPending = !u.EmailConfirmed, AgeRestriction = new AgeRestrictionDto() diff --git a/API/Data/Seed.cs b/API/Data/Seed.cs index 488d58ad1..b807e4dcd 100644 --- a/API/Data/Seed.cs +++ b/API/Data/Seed.cs @@ -120,6 +120,7 @@ public static class Seed } }); + public static async Task SeedRoles(RoleManager roleManager) { var roles = typeof(PolicyConstants) diff --git a/API/Services/StatisticService.cs b/API/Services/StatisticService.cs index 2c8552749..7c48ac5dd 100644 --- a/API/Services/StatisticService.cs +++ b/API/Services/StatisticService.cs @@ -78,10 +78,8 @@ public class StatisticService : IStatisticService .CountAsync(); var lastActive = await _context.AppUserProgresses - .OrderByDescending(p => p.LastModified) .Where(p => p.AppUserId == userId) - .Select(p => p.LastModified) - .FirstOrDefaultAsync(); + .MaxAsync(p => p.LastModified); // First get the total pages per library @@ -103,15 +101,28 @@ public class StatisticService : IStatisticService }) .ToListAsync(); - var averageReadingTimePerWeek = _context.AppUserProgresses + + // New solution. Calculate total hours then divide by number of weeks from time account was created (or min reading event) till now + var averageReadingTimePerWeek = await _context.AppUserProgresses .Where(p => p.AppUserId == userId) .Join(_context.Chapter, p => p.ChapterId, c => c.Id, (p, c) => new { - AverageReadingHours = Math.Min((float) p.PagesRead / (float) c.Pages, 1.0) * ((float) c.AvgHoursToRead) + AverageReadingHours = Math.Min((float) p.PagesRead / (float) c.Pages, 1.0) * + ((float) c.AvgHoursToRead) }) .Select(x => x.AverageReadingHours) - .Average() * 7.0; + .SumAsync(); + + var earliestReadDate = await _context.AppUserProgresses + .Where(p => p.AppUserId == userId) + .MinAsync(p => p.Created); + + var timeDifference = DateTime.Now - earliestReadDate; + var deltaWeeks = (int)Math.Ceiling(timeDifference.TotalDays / 7); + + averageReadingTimePerWeek /= deltaWeeks; + return new UserReadStatistics() { diff --git a/UI/Web/angular.json b/UI/Web/angular.json index 30a197ec3..7ce634e46 100644 --- a/UI/Web/angular.json +++ b/UI/Web/angular.json @@ -47,11 +47,7 @@ "src/styles.scss", "node_modules/@fortawesome/fontawesome-free/css/all.min.css" ], - "scripts": [ - "node_modules/lazysizes/lazysizes.min.js", - "node_modules/lazysizes/plugins/rias/ls.rias.min.js", - "node_modules/lazysizes/plugins/attrchange/ls.attrchange.min.js" - ], + "scripts": [], "sourceMap": { "hidden": false, "scripts": true, diff --git a/UI/Web/package-lock.json b/UI/Web/package-lock.json index 03e677260..8cb067001 100644 --- a/UI/Web/package-lock.json +++ b/UI/Web/package-lock.json @@ -8,23 +8,23 @@ "name": "kavita-webui", "version": "0.4.2", "dependencies": { - "@angular/animations": "^16.2.9", - "@angular/cdk": "^16.2.8", - "@angular/common": "^16.2.9", - "@angular/compiler": "^16.2.9", - "@angular/core": "^16.2.9", - "@angular/forms": "^16.2.9", - "@angular/localize": "^16.2.9", - "@angular/platform-browser": "^16.2.9", - "@angular/platform-browser-dynamic": "^16.2.9", - "@angular/router": "^16.2.9", + "@angular/animations": "^16.2.12", + "@angular/cdk": "^16.2.11", + "@angular/common": "^16.2.12", + "@angular/compiler": "^16.2.12", + "@angular/core": "^16.2.12", + "@angular/forms": "^16.2.12", + "@angular/localize": "^16.2.12", + "@angular/platform-browser": "^16.2.12", + "@angular/platform-browser-dynamic": "^16.2.12", + "@angular/router": "^16.2.12", "@fortawesome/fontawesome-free": "^6.4.2", "@iharbeck/ngx-virtual-scroller": "^16.0.0", "@iplab/ngx-file-upload": "^16.0.2", "@lithiumjs/angular": "^7.3.0", "@lithiumjs/ngx-virtual-scroll": "^0.3.0", "@microsoft/signalr": "^7.0.12", - "@ng-bootstrap/ng-bootstrap": "^15.1.1", + "@ng-bootstrap/ng-bootstrap": "^15.1.2", "@ngneat/transloco": "^6.0.0", "@ngneat/transloco-locale": "^5.1.1", "@ngneat/transloco-persist-lang": "^5.0.0", @@ -33,16 +33,18 @@ "@popperjs/core": "^2.11.7", "@swimlane/ngx-charts": "^20.1.2", "@tweenjs/tween.js": "^21.0.0", - "@types/file-saver": "^2.0.5", + "@types/file-saver": "^2.0.6", + "angular-animations": "^0.11.0", "bootstrap": "^5.3.1", + "charts.css": "^1.1.0", "eventsource": "^2.0.2", "file-saver": "^2.0.5", - "lazysizes": "^5.3.2", "luxon": "^3.4.3", "ng-circle-progress": "^1.7.1", + "ng-lazyload-image": "^9.1.3", "ng-select2-component": "^13.0.9", "ngx-color-picker": "^15.0.0", - "ngx-extended-pdf-viewer": "^18.0.2", + "ngx-extended-pdf-viewer": "^18.1.4", "ngx-file-drop": "^16.0.0", "ngx-slider-v2": "^16.0.2", "ngx-stars": "^1.6.5", @@ -54,20 +56,20 @@ "zone.js": "^0.13.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^16.2.6", + "@angular-devkit/build-angular": "^16.2.9", "@angular-eslint/builder": "^16.2.0", "@angular-eslint/eslint-plugin": "^16.2.0", "@angular-eslint/eslint-plugin-template": "^16.2.0", "@angular-eslint/schematics": "^16.2.0", "@angular-eslint/template-parser": "^16.2.0", - "@angular/cli": "^16.2.6", - "@angular/compiler-cli": "^16.2.9", - "@types/d3": "^7.4.1", - "@types/luxon": "^3.3.2", - "@types/node": "^20.8.6", + "@angular/cli": "^16.2.9", + "@angular/compiler-cli": "^16.2.12", + "@types/d3": "^7.4.2", + "@types/luxon": "^3.3.3", + "@types/node": "^20.8.10", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", - "eslint": "^8.51.0", + "eslint": "^8.52.0", "jsonminify": "^0.4.2", "karma-coverage": "~2.2.0", "ts-node": "~10.9.1", @@ -97,12 +99,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1602.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.6.tgz", - "integrity": "sha512-b1NNV3yNg6Rt86ms20bJIroWUI8ihaEwv5k+EoijEXLoMs4eNs5PhqL+QE8rTj+q9pa1gSrWf2blXor2JGwf1g==", + "version": "0.1602.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.9.tgz", + "integrity": "sha512-U3vfb/e2sFfg0D9FyyRBXRPP7g4FBFtGK8Q3JPmvAVsHHwi5AUFRNR7YBChB/T5TMNY077HcTyEirVh2FeUpdA==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.6", + "@angular-devkit/core": "16.2.9", "rxjs": "7.8.1" }, "engines": { @@ -112,15 +114,15 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.6.tgz", - "integrity": "sha512-QdU/q77K1P8CPEEZGxw1QqLcnA9ofboDWS7vcLRBmFmk2zydtLTApbK0P8GNDRbnmROOKkoaLo+xUTDJz9gvPA==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.9.tgz", + "integrity": "sha512-S1C4UYxRVyNt3C0wCxbT2jZ1dN5i37kS0mol3PQjbR8gQ0GQzHmzhjTBl1oImo8aouET9yhrk9etk65oat4mBQ==", "dev": true, "dependencies": { "@ampproject/remapping": "2.2.1", - "@angular-devkit/architect": "0.1602.6", - "@angular-devkit/build-webpack": "0.1602.6", - "@angular-devkit/core": "16.2.6", + "@angular-devkit/architect": "0.1602.9", + "@angular-devkit/build-webpack": "0.1602.9", + "@angular-devkit/core": "16.2.9", "@babel/core": "7.22.9", "@babel/generator": "7.22.9", "@babel/helper-annotate-as-pure": "7.22.5", @@ -132,7 +134,7 @@ "@babel/runtime": "7.22.6", "@babel/template": "7.22.5", "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "16.2.6", + "@ngtools/webpack": "16.2.9", "@vitejs/plugin-basic-ssl": "1.0.1", "ansi-colors": "4.1.3", "autoprefixer": "10.4.14", @@ -702,12 +704,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1602.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.6.tgz", - "integrity": "sha512-BJPR6xdq7gRJ6bVWnZ81xHyH75j7lyLbegCXbvUNaM8TWVBkwWsSdqr2NQ717dNLLn5umg58SFpU/pWMq6CxMQ==", + "version": "0.1602.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.9.tgz", + "integrity": "sha512-+3IxovfBPR2Vy730mGa0SVKkd5LQVom85gjXOs7WcnnnZmfc1q/BtFlqTgW1UWvTxP8IQdm7UYWVclQfL/WExw==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1602.6", + "@angular-devkit/architect": "0.1602.9", "rxjs": "7.8.1" }, "engines": { @@ -721,9 +723,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.6.tgz", - "integrity": "sha512-iez/8NYXQT6fqVQLlKmZUIRkFUEZ88ACKbTwD4lBmk0+hXW+bQBxI7JOnE3C4zkcM2YeuTXIYsC5SebTKYiR4Q==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.9.tgz", + "integrity": "sha512-dcHWjHBNGm3yCeNz19y8A1At4KgyC6XHNnbFL0y+nnZYiaESXjUoXJYKASedI6A+Bpl0HNq2URhH6bL6Af3+4w==", "dev": true, "dependencies": { "ajv": "8.12.0", @@ -748,12 +750,12 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.6.tgz", - "integrity": "sha512-PhpRYHCJ3WvZXmng6Qk8TXeQf83jeBMAf7AIzI8h0fgeBocOl97Xf7bZpLg6GymiU+rVn15igQ4Rz9rKAay8bQ==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.9.tgz", + "integrity": "sha512-lB51CGCILpcSI37CwKUAGDLxMqh7zmuRbiPo9s9mSkCM4ccqxFlaL+VFTq2/laneARD6aikpOHnkVm5myNzQPw==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.6", + "@angular-devkit/core": "16.2.9", "jsonc-parser": "3.2.0", "magic-string": "0.30.1", "ora": "5.4.1", @@ -864,9 +866,9 @@ } }, "node_modules/@angular/animations": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.9.tgz", - "integrity": "sha512-J+nsc2x/ZQuh+YwwTzxXUrV+7SBpJq6DDStfTFkZls9PWGRj9fjqQeRCWrfNLllpxopAEjhFkoyK06oSjcwqAw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.12.tgz", + "integrity": "sha512-MD0ElviEfAJY8qMOd6/jjSSvtqER2RDAi0lxe6EtUacC1DHCYkaPrKW4vLqY+tmZBg1yf+6n+uS77pXcHHcA3w==", "dependencies": { "tslib": "^2.3.0" }, @@ -874,13 +876,13 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/core": "16.2.9" + "@angular/core": "16.2.12" } }, "node_modules/@angular/cdk": { - "version": "16.2.8", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.8.tgz", - "integrity": "sha512-DvqxH909mgSSxWbc5xM5xKLjDMPXY3pzzSVAllngvc9KGPFw240WCs3tSpPaVJI50Esbzdu5O0CyTBfu9jUy4g==", + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.11.tgz", + "integrity": "sha512-FcJ9xd9ptjULdScnBNg7YkVnY9NKePFfmvvs2zt841Hd489L8BUkTUdbvtCLhMJTTSN+k+D+RYFhevZuhPKVVg==", "dependencies": { "tslib": "^2.3.0" }, @@ -894,15 +896,15 @@ } }, "node_modules/@angular/cli": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.6.tgz", - "integrity": "sha512-9poPvUEmlufOAW1Cjk+aA5e2x3mInLtbYYSL/EYviDN2ugmavsSIvxAE/WLnxq6cPWqhNDbHDaqvcmqkcFM3Cw==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.9.tgz", + "integrity": "sha512-wkpV/Ni26LUeDmhee2TPXXEq3feEdZMSG8+nkfUK9kqIcxm0IjI1GLPeiVOX7aQobuKNe2cCAFNwsrXWjj+2og==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1602.6", - "@angular-devkit/core": "16.2.6", - "@angular-devkit/schematics": "16.2.6", - "@schematics/angular": "16.2.6", + "@angular-devkit/architect": "0.1602.9", + "@angular-devkit/core": "16.2.9", + "@angular-devkit/schematics": "16.2.9", + "@schematics/angular": "16.2.9", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.3", "ini": "4.1.1", @@ -961,9 +963,9 @@ "dev": true }, "node_modules/@angular/common": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.9.tgz", - "integrity": "sha512-5Lh5KsxCkaoBDeSAghKNF5lCi0083ug4X2X7wnafsSd6Z3xt/rDjH9hDOP5SF5IDLtCVjJgHfs3cCLSTjRuNwg==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.12.tgz", + "integrity": "sha512-B+WY/cT2VgEaz9HfJitBmgdk4I333XG/ybC98CMC4Wz8E49T8yzivmmxXB3OD6qvjcOB6ftuicl6WBqLbZNg2w==", "dependencies": { "tslib": "^2.3.0" }, @@ -971,14 +973,14 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/core": "16.2.9", + "@angular/core": "16.2.12", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.9.tgz", - "integrity": "sha512-lh799pnbdvzTVShJHOY1JC6c1pwBsZC4UIgB3Itklo9dskGybQma/gP+lE6RhqM4FblNfaaBXGlCMUuY8HkmEQ==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.12.tgz", + "integrity": "sha512-6SMXUgSVekGM7R6l1Z9rCtUGtlg58GFmgbpMCsGf+VXxP468Njw8rjT2YZkf5aEPxEuRpSHhDYjqz7n14cwCXQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -986,7 +988,7 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/core": "16.2.9" + "@angular/core": "16.2.12" }, "peerDependenciesMeta": { "@angular/core": { @@ -995,12 +997,12 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.9.tgz", - "integrity": "sha512-ecH2oOlijJdDqioD9IfgdqJGoRRHI6hAx5rwBxIaYk01ywj13KzvXWPrXbCIupeWtV/XUZUlbwf47nlmL5gxZg==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.12.tgz", + "integrity": "sha512-pWSrr152562ujh6lsFZR8NfNc5Ljj+zSTQO44DsuB0tZjwEpnRcjJEgzuhGXr+CoiBf+jTSPZKemtSktDk5aaA==", "dev": true, "dependencies": { - "@babel/core": "7.22.5", + "@babel/core": "7.23.2", "@jridgewell/sourcemap-codec": "^1.4.14", "chokidar": "^3.0.0", "convert-source-map": "^1.5.1", @@ -1018,14 +1020,14 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/compiler": "16.2.9", + "@angular/compiler": "16.2.12", "typescript": ">=4.9.3 <5.2" } }, "node_modules/@angular/core": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.9.tgz", - "integrity": "sha512-chvPX29ZBcMDuh7rLIgb0Cru6oJ/0FaqRzfOI3wT4W2F9W1HOlCtipovzmPYaUAmXBWfVP4EBO9TOWnpog0S0w==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.12.tgz", + "integrity": "sha512-GLLlDeke/NjroaLYOks0uyzFVo6HyLl7VOm0K1QpLXnYvW63W9Ql/T3yguRZa7tRkOAeFZ3jw+1wnBD4O8MoUA==", "dependencies": { "tslib": "^2.3.0" }, @@ -1038,9 +1040,9 @@ } }, "node_modules/@angular/forms": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.9.tgz", - "integrity": "sha512-rxlg2iNJNBH/uc7b5YqybfYc8BkLzzPv1d/nMsQUlY0O2UV2zwNRpcIiWbWd7+ZaKjcyPynVe9FsXC8wgWIABw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.12.tgz", + "integrity": "sha512-1Eao89hlBgLR3v8tU91vccn21BBKL06WWxl7zLpQmG6Hun+2jrThgOE4Pf3os4fkkbH4Apj0tWL2fNIWe/blbw==", "dependencies": { "tslib": "^2.3.0" }, @@ -1048,18 +1050,18 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/common": "16.2.9", - "@angular/core": "16.2.9", - "@angular/platform-browser": "16.2.9", + "@angular/common": "16.2.12", + "@angular/core": "16.2.12", + "@angular/platform-browser": "16.2.12", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/localize": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-16.2.9.tgz", - "integrity": "sha512-t5002NgBj+wjd81IXwg+yc2ypaBk6OWLAka1GXmWua3x7hwGw1yMtPFmzOE1cCNdXgWlluLxWclFjCUrAbGEww==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-16.2.12.tgz", + "integrity": "sha512-sNIHDlZKENPQqx64qGF99g2sOCy9i9O4VOmjKD/FZbeE8O5qBbaQlkwOlFoQIt35/cnvtAtf7oQF6tqmiVtS2w==", "dependencies": { - "@babel/core": "7.22.5", + "@babel/core": "7.23.2", "fast-glob": "3.3.0", "yargs": "^17.2.1" }, @@ -1072,8 +1074,8 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/compiler": "16.2.9", - "@angular/compiler-cli": "16.2.9" + "@angular/compiler": "16.2.12", + "@angular/compiler-cli": "16.2.12" } }, "node_modules/@angular/localize/node_modules/fast-glob": { @@ -1092,9 +1094,9 @@ } }, "node_modules/@angular/platform-browser": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.9.tgz", - "integrity": "sha512-9Je7+Jmx0AOyRzBBumraVJG3M0R6YbT4c9jTUbLGJCcPxwDI3/u2ZzvW3rBqpmrDaqLxN5f1LcZeTZx287QeqQ==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.12.tgz", + "integrity": "sha512-NnH7ju1iirmVEsUq432DTm0nZBGQsBrU40M3ZeVHMQ2subnGiyUs3QyzDz8+VWLL/T5xTxWLt9BkDn65vgzlIQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -1102,9 +1104,9 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/animations": "16.2.9", - "@angular/common": "16.2.9", - "@angular/core": "16.2.9" + "@angular/animations": "16.2.12", + "@angular/common": "16.2.12", + "@angular/core": "16.2.12" }, "peerDependenciesMeta": { "@angular/animations": { @@ -1113,9 +1115,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.9.tgz", - "integrity": "sha512-ztpo0939vTZ/5CWVSvo41Yl6YPoTZ0If+yTrs7dk1ce0vFgaZXMlc+y5ZwjJIiMM5CvHbhL48Uk+HJNIojP98A==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.12.tgz", + "integrity": "sha512-ya54jerNgreCVAR278wZavwjrUWImMr2F8yM5n9HBvsMBbFaAQ83anwbOEiHEF2BlR+gJiEBLfpuPRMw20pHqw==", "dependencies": { "tslib": "^2.3.0" }, @@ -1123,16 +1125,16 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/common": "16.2.9", - "@angular/compiler": "16.2.9", - "@angular/core": "16.2.9", - "@angular/platform-browser": "16.2.9" + "@angular/common": "16.2.12", + "@angular/compiler": "16.2.12", + "@angular/core": "16.2.12", + "@angular/platform-browser": "16.2.12" } }, "node_modules/@angular/router": { - "version": "16.2.9", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.9.tgz", - "integrity": "sha512-5vrJNMblTDx3WC3dtaqLddWNtR0P9iwpqffeZL1uobBIwP4hbJx+8Dos3TwxGR4hnopFKahoDQ5nC0NOQslyog==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.12.tgz", + "integrity": "sha512-aU6QnYSza005V9P3W6PpkieL56O0IHps96DjqI1RS8yOJUl3THmokqYN4Fm5+HXy4f390FN9i6ftadYQDKeWmA==", "dependencies": { "tslib": "^2.3.0" }, @@ -1140,9 +1142,9 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/common": "16.2.9", - "@angular/core": "16.2.9", - "@angular/platform-browser": "16.2.9", + "@angular/common": "16.2.12", + "@angular/core": "16.2.12", + "@angular/platform-browser": "16.2.12", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -1173,25 +1175,25 @@ } }, "node_modules/@babel/core": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", - "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helpers": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1201,6 +1203,38 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dependencies": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -1213,6 +1247,7 @@ "version": "7.22.9", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", + "dev": true, "dependencies": { "@babel/types": "^7.22.5", "@jridgewell/gen-mapping": "^0.3.2", @@ -1571,13 +1606,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", - "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.6", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers/node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -2863,6 +2911,7 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.22.5", "@babel/parser": "^7.22.5", @@ -3417,9 +3466,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", + "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3435,12 +3484,12 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -3462,9 +3511,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@iharbeck/ngx-virtual-scroller": { @@ -3711,9 +3760,9 @@ } }, "node_modules/@ng-bootstrap/ng-bootstrap": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-15.1.1.tgz", - "integrity": "sha512-nZlIMMggtI3IHkGs0XPrUIUdpeEzQvfGV9M4I9IvCqiS2n4RwWoUvWK1ICo4csZqFNBDlCQx956gO6ZZUSL2mw==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-15.1.2.tgz", + "integrity": "sha512-mM2yiGnt9o7KZLIFp8K1vjfmVfu7HR3d8dhH5SszfArbgn9DvvQ4P5D5TDGygzyBSzeyZe18p7I8rX8vgA6DKw==", "dependencies": { "tslib": "^2.3.0" }, @@ -3817,9 +3866,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.6.tgz", - "integrity": "sha512-d8ZlZL6dOtWmHdjG9PTGBkdiJMcsXD2tp6WeFRVvTEuvCI3XvKsUXBvJDE+mZOhzn5pUEYt+1TR5DHjDZbME3w==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.9.tgz", + "integrity": "sha512-rOclD7FfT4OSwVA0nDnULbJS6TORJ0+sQiuT2ebaNFErYr3LOm6Zut05tnmzFw8q1cePrILbG+xpnbggNr9Pyw==", "dev": true, "engines": { "node": "^16.14.0 || >=18.10.0", @@ -4243,13 +4292,13 @@ } }, "node_modules/@schematics/angular": { - "version": "16.2.6", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.2.6.tgz", - "integrity": "sha512-fM09WPqST+nhVGV5Q3fhG7WKo96kgSVMsbz3wGS0DmTn4zge7ZWnrW3VvbxnMapmGoKa9DFPqdqNln4ADcdIMQ==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.2.9.tgz", + "integrity": "sha512-uiU2YbZRVHgk1N1DDsek/5CKhfpZ8myJYNJk8eHV5LswnXOP3aqvH23VhneaAgOYwK5fISC7eMG0pLVKMvFfZQ==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.6", - "@angular-devkit/schematics": "16.2.6", + "@angular-devkit/core": "16.2.9", + "@angular-devkit/schematics": "16.2.9", "jsonc-parser": "3.2.0" }, "engines": { @@ -4434,9 +4483,9 @@ } }, "node_modules/@types/d3": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.1.tgz", - "integrity": "sha512-lBpYmbHTCtFKO1DB1R7E9dXp9/g1F3JXSGOF7iKPZ+wRmYg/Q6tCRHODGOc5Qk25fJRe2PI60EDRf2HLPUncMA==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.2.tgz", + "integrity": "sha512-Y4g2Yb30ZJmmtqAJTqMRaqXwRawfvpdpVmyEYEcyGNhrQI/Zvkq3k7yE1tdN07aFSmNBfvmegMQ9Fe2qy9ZMhw==", "dev": true, "dependencies": { "@types/d3-array": "*", @@ -4737,9 +4786,9 @@ } }, "node_modules/@types/file-saver": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz", - "integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.6.tgz", + "integrity": "sha512-Mw671DVqoMHbjw0w4v2iiOro01dlT/WhWp5uwecBa0Wg8c+bcZOjgF1ndBnlaxhtvFCgTRBtsGivSVhrK/vnag==" }, "node_modules/@types/geojson": { "version": "7946.0.10", @@ -4769,9 +4818,9 @@ "dev": true }, "node_modules/@types/luxon": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.2.tgz", - "integrity": "sha512-l5cpE57br4BIjK+9BSkFBOsWtwv6J9bJpC7gdXIzZyI0vuKvNTk0wZZrkQxMGsUAuGW9+WMNWF2IJMD7br2yeQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.3.tgz", + "integrity": "sha512-/BJF3NT0pRMuxrenr42emRUF67sXwcZCd+S1ksG/Fcf9O7C3kKCY4uJSbKBE4KDUIYr3WMsvfmWD8hRjXExBJQ==", "dev": true }, "node_modules/@types/mime": { @@ -4781,12 +4830,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz", - "integrity": "sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==", + "version": "20.8.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", + "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", "dev": true, "dependencies": { - "undici-types": "~5.25.1" + "undici-types": "~5.26.4" } }, "node_modules/@types/qs": { @@ -5322,6 +5371,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@vitejs/plugin-basic-ssl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.0.1.tgz", @@ -5839,6 +5894,17 @@ "ajv": "^8.8.2" } }, + "node_modules/angular-animations": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/angular-animations/-/angular-animations-0.11.0.tgz", + "integrity": "sha512-P2RuOe+T97bhgGDLtOYK9V45QA5y+kFUxoJfRAua8Ymo0bI5lWyw8oiVmBoEIZUU+nooYoJvQXgVKuZJA7/z3g==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/animations": ">=6.0.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -6458,6 +6524,11 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "node_modules/charts.css": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/charts.css/-/charts.css-1.1.0.tgz", + "integrity": "sha512-K1Qyb8ZKsu5cDrVbZeHECk/xSq6iOl8IDTR35uaMdhr/Vyyxvg9nYQy3KNB3aidxJ2E251afX5q2725N0uL3Vw==" + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -6724,7 +6795,8 @@ "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true }, "node_modules/cookie": { "version": "0.5.0", @@ -7852,18 +7924,19 @@ } }, "node_modules/eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", + "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/js": "8.52.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -10285,11 +10358,6 @@ "shell-quote": "^1.8.1" } }, - "node_modules/lazysizes": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/lazysizes/-/lazysizes-5.3.2.tgz", - "integrity": "sha512-22UzWP+Vedi/sMeOr8O7FWimRVtiNJV2HCa+V8+peZOw6QbswN9k58VUhd7i6iK5bw5QkYrF01LJbeJe0PV8jg==" - }, "node_modules/less": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", @@ -11160,6 +11228,19 @@ "rxjs": ">=6.4.0" } }, + "node_modules/ng-lazyload-image": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/ng-lazyload-image/-/ng-lazyload-image-9.1.3.tgz", + "integrity": "sha512-GlajmzbKhQCvg9pcrASq4fe/MNv9KoifGe6N+xRbseaBrNj2uwU4Vwic041NlmAQFEkpDM1H2EJCAjjmJeF7Hg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=11.0.0", + "@angular/core": ">=11.0.0", + "rxjs": ">=6.0.0" + } + }, "node_modules/ng-select2-component": { "version": "13.0.9", "resolved": "https://registry.npmjs.org/ng-select2-component/-/ng-select2-component-13.0.9.tgz", @@ -11188,9 +11269,9 @@ } }, "node_modules/ngx-extended-pdf-viewer": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/ngx-extended-pdf-viewer/-/ngx-extended-pdf-viewer-18.0.2.tgz", - "integrity": "sha512-CgRR39Fb0ZLsuzvNbiFPXuJL72bAqnvJyhytVQjsOo66ZQMnQA9dkU/YbkDKr25Btk/+PtAJmSkbBpPgyNxj1Q==", + "version": "18.1.4", + "resolved": "https://registry.npmjs.org/ngx-extended-pdf-viewer/-/ngx-extended-pdf-viewer-18.1.4.tgz", + "integrity": "sha512-adKahqatF/AgUfDLkEIpT1SQoBjDKgr+/0Tzdsy13uWvPgECw/2hzg1f0m0JvWkR2zeAUY44/yK5BBrOBnVbbQ==", "dependencies": { "lodash.deburr": "^4.1.0", "tslib": "^2.3.0" @@ -14481,9 +14562,9 @@ } }, "node_modules/undici-types": { - "version": "5.25.3", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", - "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, "node_modules/unicode-canonical-property-names-ecmascript": { diff --git a/UI/Web/package.json b/UI/Web/package.json index ba4128128..f384f90b2 100644 --- a/UI/Web/package.json +++ b/UI/Web/package.json @@ -13,23 +13,23 @@ }, "private": true, "dependencies": { - "@angular/animations": "^16.2.9", - "@angular/cdk": "^16.2.8", - "@angular/common": "^16.2.9", - "@angular/compiler": "^16.2.9", - "@angular/core": "^16.2.9", - "@angular/forms": "^16.2.9", - "@angular/localize": "^16.2.9", - "@angular/platform-browser": "^16.2.9", - "@angular/platform-browser-dynamic": "^16.2.9", - "@angular/router": "^16.2.9", + "@angular/animations": "^16.2.12", + "@angular/cdk": "^16.2.11", + "@angular/common": "^16.2.12", + "@angular/compiler": "^16.2.12", + "@angular/core": "^16.2.12", + "@angular/forms": "^16.2.12", + "@angular/localize": "^16.2.12", + "@angular/platform-browser": "^16.2.12", + "@angular/platform-browser-dynamic": "^16.2.12", + "@angular/router": "^16.2.12", "@fortawesome/fontawesome-free": "^6.4.2", "@iharbeck/ngx-virtual-scroller": "^16.0.0", "@iplab/ngx-file-upload": "^16.0.2", "@lithiumjs/angular": "^7.3.0", "@lithiumjs/ngx-virtual-scroll": "^0.3.0", "@microsoft/signalr": "^7.0.12", - "@ng-bootstrap/ng-bootstrap": "^15.1.1", + "@ng-bootstrap/ng-bootstrap": "^15.1.2", "@ngneat/transloco": "^6.0.0", "@ngneat/transloco-locale": "^5.1.1", "@ngneat/transloco-persist-lang": "^5.0.0", @@ -38,16 +38,18 @@ "@popperjs/core": "^2.11.7", "@swimlane/ngx-charts": "^20.1.2", "@tweenjs/tween.js": "^21.0.0", - "@types/file-saver": "^2.0.5", + "@types/file-saver": "^2.0.6", + "angular-animations": "^0.11.0", "bootstrap": "^5.3.1", + "charts.css": "^1.1.0", "eventsource": "^2.0.2", "file-saver": "^2.0.5", - "lazysizes": "^5.3.2", "luxon": "^3.4.3", "ng-circle-progress": "^1.7.1", + "ng-lazyload-image": "^9.1.3", "ng-select2-component": "^13.0.9", "ngx-color-picker": "^15.0.0", - "ngx-extended-pdf-viewer": "^18.0.2", + "ngx-extended-pdf-viewer": "^18.1.4", "ngx-file-drop": "^16.0.0", "ngx-slider-v2": "^16.0.2", "ngx-stars": "^1.6.5", @@ -59,20 +61,20 @@ "zone.js": "^0.13.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^16.2.6", + "@angular-devkit/build-angular": "^16.2.9", "@angular-eslint/builder": "^16.2.0", "@angular-eslint/eslint-plugin": "^16.2.0", "@angular-eslint/eslint-plugin-template": "^16.2.0", "@angular-eslint/schematics": "^16.2.0", "@angular-eslint/template-parser": "^16.2.0", - "@angular/cli": "^16.2.6", - "@angular/compiler-cli": "^16.2.9", - "@types/d3": "^7.4.1", - "@types/luxon": "^3.3.2", - "@types/node": "^20.8.6", + "@angular/cli": "^16.2.9", + "@angular/compiler-cli": "^16.2.12", + "@types/d3": "^7.4.2", + "@types/luxon": "^3.3.3", + "@types/node": "^20.8.10", "@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/parser": "^6.7.5", - "eslint": "^8.51.0", + "eslint": "^8.52.0", "jsonminify": "^0.4.2", "karma-coverage": "~2.2.0", "ts-node": "~10.9.1", diff --git a/UI/Web/src/app/_guards/admin.guard.ts b/UI/Web/src/app/_guards/admin.guard.ts index 9de2cfe44..9ab6dea95 100644 --- a/UI/Web/src/app/_guards/admin.guard.ts +++ b/UI/Web/src/app/_guards/admin.guard.ts @@ -22,7 +22,7 @@ export class AdminGuard implements CanActivate { } this.toastr.error(this.translocoService.translate('toasts.unauthorized-1')); - this.router.navigateByUrl('/libraries'); + this.router.navigateByUrl('/home'); return false; }) ); diff --git a/UI/Web/src/app/_models/auth/member.ts b/UI/Web/src/app/_models/auth/member.ts index a9b50d110..5c9002e9f 100644 --- a/UI/Web/src/app/_models/auth/member.ts +++ b/UI/Web/src/app/_models/auth/member.ts @@ -6,9 +6,11 @@ export interface Member { username: string; email: string; lastActive: string; // datetime + lastActiveUtc: string; // datetime created: string; // datetime + createdUtc: string; // datetime roles: string[]; libraries: Library[]; ageRestriction: AgeRestriction; isPending: boolean; -} \ No newline at end of file +} diff --git a/UI/Web/src/app/pipe/age-rating.pipe.ts b/UI/Web/src/app/_pipes/age-rating.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/age-rating.pipe.ts rename to UI/Web/src/app/_pipes/age-rating.pipe.ts diff --git a/UI/Web/src/app/pipe/bytes.pipe.ts b/UI/Web/src/app/_pipes/bytes.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/bytes.pipe.ts rename to UI/Web/src/app/_pipes/bytes.pipe.ts diff --git a/UI/Web/src/app/reading-list/_pipes/cbl-conflict-reason.pipe.ts b/UI/Web/src/app/_pipes/cbl-conflict-reason.pipe.ts similarity index 100% rename from UI/Web/src/app/reading-list/_pipes/cbl-conflict-reason.pipe.ts rename to UI/Web/src/app/_pipes/cbl-conflict-reason.pipe.ts diff --git a/UI/Web/src/app/reading-list/_pipes/cbl-import-result.pipe.ts b/UI/Web/src/app/_pipes/cbl-import-result.pipe.ts similarity index 100% rename from UI/Web/src/app/reading-list/_pipes/cbl-import-result.pipe.ts rename to UI/Web/src/app/_pipes/cbl-import-result.pipe.ts diff --git a/UI/Web/src/app/pipe/compact-number.pipe.ts b/UI/Web/src/app/_pipes/compact-number.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/compact-number.pipe.ts rename to UI/Web/src/app/_pipes/compact-number.pipe.ts diff --git a/UI/Web/src/app/statistics/_pipes/day-of-week.pipe.ts b/UI/Web/src/app/_pipes/day-of-week.pipe.ts similarity index 82% rename from UI/Web/src/app/statistics/_pipes/day-of-week.pipe.ts rename to UI/Web/src/app/_pipes/day-of-week.pipe.ts index f3387c107..f2fd9c0fe 100644 --- a/UI/Web/src/app/statistics/_pipes/day-of-week.pipe.ts +++ b/UI/Web/src/app/_pipes/day-of-week.pipe.ts @@ -1,10 +1,10 @@ -import {inject, Pipe, PipeTransform} from '@angular/core'; +import {Pipe, PipeTransform} from '@angular/core'; import { DayOfWeek } from 'src/app/_services/statistics.service'; -import {translate, TranslocoService} from "@ngneat/transloco"; +import {translate} from "@ngneat/transloco"; @Pipe({ - name: 'dayOfWeek', - standalone: true + name: 'dayOfWeek', + standalone: true }) export class DayOfWeekPipe implements PipeTransform { diff --git a/UI/Web/src/app/pipe/default-date.pipe.ts b/UI/Web/src/app/_pipes/default-date.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/default-date.pipe.ts rename to UI/Web/src/app/_pipes/default-date.pipe.ts diff --git a/UI/Web/src/app/pipe/default-value.pipe.ts b/UI/Web/src/app/_pipes/default-value.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/default-value.pipe.ts rename to UI/Web/src/app/_pipes/default-value.pipe.ts diff --git a/UI/Web/src/app/user-settings/_pipes/device-platform.pipe.ts b/UI/Web/src/app/_pipes/device-platform.pipe.ts similarity index 100% rename from UI/Web/src/app/user-settings/_pipes/device-platform.pipe.ts rename to UI/Web/src/app/_pipes/device-platform.pipe.ts diff --git a/UI/Web/src/app/metadata-filter/_pipes/filter-comparison.pipe.ts b/UI/Web/src/app/_pipes/filter-comparison.pipe.ts similarity index 100% rename from UI/Web/src/app/metadata-filter/_pipes/filter-comparison.pipe.ts rename to UI/Web/src/app/_pipes/filter-comparison.pipe.ts diff --git a/UI/Web/src/app/metadata-filter/_pipes/filter-field.pipe.ts b/UI/Web/src/app/_pipes/filter-field.pipe.ts similarity index 100% rename from UI/Web/src/app/metadata-filter/_pipes/filter-field.pipe.ts rename to UI/Web/src/app/_pipes/filter-field.pipe.ts diff --git a/UI/Web/src/app/pipe/filter.pipe.ts b/UI/Web/src/app/_pipes/filter.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/filter.pipe.ts rename to UI/Web/src/app/_pipes/filter.pipe.ts diff --git a/UI/Web/src/app/manga-reader/_pipes/fitting-icon.pipe.ts b/UI/Web/src/app/_pipes/fitting-icon.pipe.ts similarity index 86% rename from UI/Web/src/app/manga-reader/_pipes/fitting-icon.pipe.ts rename to UI/Web/src/app/_pipes/fitting-icon.pipe.ts index f0f2a3152..2123a910c 100644 --- a/UI/Web/src/app/manga-reader/_pipes/fitting-icon.pipe.ts +++ b/UI/Web/src/app/_pipes/fitting-icon.pipe.ts @@ -1,5 +1,5 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { FITTING_OPTION } from '../_models/reader-enums'; +import { FITTING_OPTION } from '../manga-reader/_models/reader-enums'; @Pipe({ name: 'fittingIcon', diff --git a/UI/Web/src/app/manga-reader/_pipes/fullscreen-icon.pipe.ts b/UI/Web/src/app/_pipes/fullscreen-icon.pipe.ts similarity index 100% rename from UI/Web/src/app/manga-reader/_pipes/fullscreen-icon.pipe.ts rename to UI/Web/src/app/_pipes/fullscreen-icon.pipe.ts diff --git a/UI/Web/src/app/pipe/language-name.pipe.ts b/UI/Web/src/app/_pipes/language-name.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/language-name.pipe.ts rename to UI/Web/src/app/_pipes/language-name.pipe.ts diff --git a/UI/Web/src/app/manga-reader/_pipes/layout-mode-icon.pipe.ts b/UI/Web/src/app/_pipes/layout-mode-icon.pipe.ts similarity index 88% rename from UI/Web/src/app/manga-reader/_pipes/layout-mode-icon.pipe.ts rename to UI/Web/src/app/_pipes/layout-mode-icon.pipe.ts index ce7abdf82..8c9c3951e 100644 --- a/UI/Web/src/app/manga-reader/_pipes/layout-mode-icon.pipe.ts +++ b/UI/Web/src/app/_pipes/layout-mode-icon.pipe.ts @@ -1,5 +1,5 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { LayoutMode } from '../_models/layout-mode'; +import { LayoutMode } from '../manga-reader/_models/layout-mode'; @Pipe({ name: 'layoutModeIcon', diff --git a/UI/Web/src/app/pipe/library-type.pipe.ts b/UI/Web/src/app/_pipes/library-type.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/library-type.pipe.ts rename to UI/Web/src/app/_pipes/library-type.pipe.ts diff --git a/UI/Web/src/app/pipe/manga-format-icon.pipe.ts b/UI/Web/src/app/_pipes/manga-format-icon.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/manga-format-icon.pipe.ts rename to UI/Web/src/app/_pipes/manga-format-icon.pipe.ts diff --git a/UI/Web/src/app/pipe/manga-format.pipe.ts b/UI/Web/src/app/_pipes/manga-format.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/manga-format.pipe.ts rename to UI/Web/src/app/_pipes/manga-format.pipe.ts diff --git a/UI/Web/src/app/pipe/person-role.pipe.ts b/UI/Web/src/app/_pipes/person-role.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/person-role.pipe.ts rename to UI/Web/src/app/_pipes/person-role.pipe.ts diff --git a/UI/Web/src/app/pipe/provider-image.pipe.ts b/UI/Web/src/app/_pipes/provider-image.pipe.ts similarity index 97% rename from UI/Web/src/app/pipe/provider-image.pipe.ts rename to UI/Web/src/app/_pipes/provider-image.pipe.ts index 0697e49e5..75e656651 100644 --- a/UI/Web/src/app/pipe/provider-image.pipe.ts +++ b/UI/Web/src/app/_pipes/provider-image.pipe.ts @@ -18,8 +18,6 @@ export class ProviderImagePipe implements PipeTransform { case ScrobbleProvider.Kavita: return 'assets/images/logo-32.png'; } - - return ''; } } diff --git a/UI/Web/src/app/pipe/provider-name.pipe.ts b/UI/Web/src/app/_pipes/provider-name.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/provider-name.pipe.ts rename to UI/Web/src/app/_pipes/provider-name.pipe.ts diff --git a/UI/Web/src/app/pipe/publication-status.pipe.ts b/UI/Web/src/app/_pipes/publication-status.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/publication-status.pipe.ts rename to UI/Web/src/app/_pipes/publication-status.pipe.ts diff --git a/UI/Web/src/app/manga-reader/_pipes/reader-mode-icon.pipe.ts b/UI/Web/src/app/_pipes/reader-mode-icon.pipe.ts similarity index 100% rename from UI/Web/src/app/manga-reader/_pipes/reader-mode-icon.pipe.ts rename to UI/Web/src/app/_pipes/reader-mode-icon.pipe.ts diff --git a/UI/Web/src/app/pipe/relationship.pipe.ts b/UI/Web/src/app/_pipes/relationship.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/relationship.pipe.ts rename to UI/Web/src/app/_pipes/relationship.pipe.ts diff --git a/UI/Web/src/app/pipe/safe-html.pipe.ts b/UI/Web/src/app/_pipes/safe-html.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/safe-html.pipe.ts rename to UI/Web/src/app/_pipes/safe-html.pipe.ts diff --git a/UI/Web/src/app/pipe/safe-style.pipe.ts b/UI/Web/src/app/_pipes/safe-style.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/safe-style.pipe.ts rename to UI/Web/src/app/_pipes/safe-style.pipe.ts diff --git a/UI/Web/src/app/pipe/sentence-case.pipe.ts b/UI/Web/src/app/_pipes/sentence-case.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/sentence-case.pipe.ts rename to UI/Web/src/app/_pipes/sentence-case.pipe.ts diff --git a/UI/Web/src/app/user-settings/_pipes/site-theme-provider.pipe.ts b/UI/Web/src/app/_pipes/site-theme-provider.pipe.ts similarity index 100% rename from UI/Web/src/app/user-settings/_pipes/site-theme-provider.pipe.ts rename to UI/Web/src/app/_pipes/site-theme-provider.pipe.ts diff --git a/UI/Web/src/app/pipe/sort-field.pipe.ts b/UI/Web/src/app/_pipes/sort-field.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/sort-field.pipe.ts rename to UI/Web/src/app/_pipes/sort-field.pipe.ts diff --git a/UI/Web/src/app/pipe/stream-name.pipe.ts b/UI/Web/src/app/_pipes/stream-name.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/stream-name.pipe.ts rename to UI/Web/src/app/_pipes/stream-name.pipe.ts diff --git a/UI/Web/src/app/pipe/time-ago.pipe.ts b/UI/Web/src/app/_pipes/time-ago.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/time-ago.pipe.ts rename to UI/Web/src/app/_pipes/time-ago.pipe.ts diff --git a/UI/Web/src/app/pipe/time-duration.pipe.ts b/UI/Web/src/app/_pipes/time-duration.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/time-duration.pipe.ts rename to UI/Web/src/app/_pipes/time-duration.pipe.ts diff --git a/UI/Web/src/app/pipe/utc-to-local-time.pipe.ts b/UI/Web/src/app/_pipes/utc-to-local-time.pipe.ts similarity index 100% rename from UI/Web/src/app/pipe/utc-to-local-time.pipe.ts rename to UI/Web/src/app/_pipes/utc-to-local-time.pipe.ts diff --git a/UI/Web/src/app/admin/admin-routing.module.ts b/UI/Web/src/app/_routes/admin-routing.module.ts similarity index 50% rename from UI/Web/src/app/admin/admin-routing.module.ts rename to UI/Web/src/app/_routes/admin-routing.module.ts index 11a2b6c7b..83918ccf2 100644 --- a/UI/Web/src/app/admin/admin-routing.module.ts +++ b/UI/Web/src/app/_routes/admin-routing.module.ts @@ -1,9 +1,8 @@ -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; +import { Routes } from '@angular/router'; import { AdminGuard } from '../_guards/admin.guard'; -import { DashboardComponent } from './dashboard/dashboard.component'; +import { DashboardComponent } from '../admin/dashboard/dashboard.component'; -const routes: Routes = [ +export const routes: Routes = [ {path: '**', component: DashboardComponent, pathMatch: 'full', canActivate: [AdminGuard]}, { path: '', @@ -15,9 +14,3 @@ const routes: Routes = [ } ]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class AdminRoutingModule { } diff --git a/UI/Web/src/app/_routes/all-series-routing.module.ts b/UI/Web/src/app/_routes/all-series-routing.module.ts new file mode 100644 index 000000000..eafdb03f5 --- /dev/null +++ b/UI/Web/src/app/_routes/all-series-routing.module.ts @@ -0,0 +1,14 @@ +import { Routes } from "@angular/router"; +import { AuthGuard } from "../_guards/auth.guard"; +import { AllSeriesComponent } from "../all-series/_components/all-series/all-series.component"; + + +export const routes: Routes = [ + {path: '**', component: AllSeriesComponent, pathMatch: 'full', canActivate: [AuthGuard]}, + { + path: '', + component: AllSeriesComponent, + runGuardsAndResolvers: 'always', + canActivate: [AuthGuard], + } +]; diff --git a/UI/Web/src/app/_routes/announcements-routing.module.ts b/UI/Web/src/app/_routes/announcements-routing.module.ts new file mode 100644 index 000000000..c5715ed74 --- /dev/null +++ b/UI/Web/src/app/_routes/announcements-routing.module.ts @@ -0,0 +1,16 @@ +import { Routes } from "@angular/router"; +import { AdminGuard } from "../_guards/admin.guard"; +import { AuthGuard } from "../_guards/auth.guard"; +import { AnnouncementsComponent } from "../announcements/_components/announcements/announcements.component"; + +export const routes: Routes = [ + {path: '**', component: AnnouncementsComponent, pathMatch: 'full', canActivate: [AuthGuard, AdminGuard]}, + { + path: '', + runGuardsAndResolvers: 'always', + canActivate: [AuthGuard, AdminGuard], + children: [ + {path: 'announcements', component: AnnouncementsComponent}, + ] + } +]; diff --git a/UI/Web/src/app/_routes/book-reader.router.module.ts b/UI/Web/src/app/_routes/book-reader.router.module.ts new file mode 100644 index 000000000..5083c2d4a --- /dev/null +++ b/UI/Web/src/app/_routes/book-reader.router.module.ts @@ -0,0 +1,10 @@ +import { Routes } from '@angular/router'; +import { BookReaderComponent } from '../book-reader/_components/book-reader/book-reader.component'; + +export const routes: Routes = [ + { + path: ':chapterId', + component: BookReaderComponent, + } +]; + diff --git a/UI/Web/src/app/_routes/bookmark-routing.module.ts b/UI/Web/src/app/_routes/bookmark-routing.module.ts new file mode 100644 index 000000000..d303173d8 --- /dev/null +++ b/UI/Web/src/app/_routes/bookmark-routing.module.ts @@ -0,0 +1,15 @@ +import { Routes } from "@angular/router"; +import { AuthGuard } from "../_guards/auth.guard"; +import { BookmarksComponent } from "../bookmark/_components/bookmarks/bookmarks.component"; + +export const routes: Routes = [ + {path: '**', component: BookmarksComponent, pathMatch: 'full', canActivate: [AuthGuard]}, + { + path: '', + runGuardsAndResolvers: 'always', + canActivate: [AuthGuard], + children: [ + {path: 'bookmarks', component: BookmarksComponent}, + ] + } +]; diff --git a/UI/Web/src/app/_routes/collections-routing.module.ts b/UI/Web/src/app/_routes/collections-routing.module.ts new file mode 100644 index 000000000..0227fda89 --- /dev/null +++ b/UI/Web/src/app/_routes/collections-routing.module.ts @@ -0,0 +1,17 @@ +import { Routes } from '@angular/router'; +import { AuthGuard } from '../_guards/auth.guard'; +import { AllCollectionsComponent } from '../collections/_components/all-collections/all-collections.component'; +import { CollectionDetailComponent } from '../collections/_components/collection-detail/collection-detail.component'; + +export const routes: Routes = [ + { + path: '', + runGuardsAndResolvers: 'always', + canActivate: [AuthGuard], + children: [ + {path: '', component: AllCollectionsComponent, pathMatch: 'full'}, + {path: ':id', component: CollectionDetailComponent}, + ] + } +]; + diff --git a/UI/Web/src/app/_routes/dashboard-routing.module.ts b/UI/Web/src/app/_routes/dashboard-routing.module.ts new file mode 100644 index 000000000..b08722fe0 --- /dev/null +++ b/UI/Web/src/app/_routes/dashboard-routing.module.ts @@ -0,0 +1,13 @@ +import { Routes } from '@angular/router'; +import { AuthGuard } from '../_guards/auth.guard'; +import { DashboardComponent } from '../dashboard/_components/dashboard.component'; + + +export const routes: Routes = [ + { + path: '', + runGuardsAndResolvers: 'always', + canActivate: [AuthGuard], + component: DashboardComponent, + } +]; diff --git a/UI/Web/src/app/_routes/library-detail-routing.module.ts b/UI/Web/src/app/_routes/library-detail-routing.module.ts new file mode 100644 index 000000000..04cb3c9dd --- /dev/null +++ b/UI/Web/src/app/_routes/library-detail-routing.module.ts @@ -0,0 +1,20 @@ +import { Routes } from '@angular/router'; +import { AuthGuard } from '../_guards/auth.guard'; +import { LibraryAccessGuard } from '../_guards/library-access.guard'; +import { LibraryDetailComponent } from '../library-detail/library-detail.component'; + + +export const routes: Routes = [ + { + path: ':libraryId', + runGuardsAndResolvers: 'always', + canActivate: [AuthGuard, LibraryAccessGuard], + component: LibraryDetailComponent + }, + { + path: '', + runGuardsAndResolvers: 'always', + canActivate: [AuthGuard, LibraryAccessGuard], + component: LibraryDetailComponent + } +]; diff --git a/UI/Web/src/app/_routes/manga-reader.router.module.ts b/UI/Web/src/app/_routes/manga-reader.router.module.ts new file mode 100644 index 000000000..04ff77b3c --- /dev/null +++ b/UI/Web/src/app/_routes/manga-reader.router.module.ts @@ -0,0 +1,15 @@ +import { Routes } from '@angular/router'; +import { MangaReaderComponent } from '../manga-reader/_components/manga-reader/manga-reader.component'; + +export const routes: Routes = [ + { + path: ':chapterId', + component: MangaReaderComponent + }, + { + // This will allow the MangaReader to have a list to use for next/prev chapters rather than natural sort order + path: ':chapterId/list/:listId', + component: MangaReaderComponent + } +]; + diff --git a/UI/Web/src/app/_routes/pdf-reader.router.module.ts b/UI/Web/src/app/_routes/pdf-reader.router.module.ts new file mode 100644 index 000000000..a55699280 --- /dev/null +++ b/UI/Web/src/app/_routes/pdf-reader.router.module.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { PdfReaderComponent } from '../pdf-reader/_components/pdf-reader/pdf-reader.component'; + +export const routes: Routes = [ + { + path: ':chapterId', + component: PdfReaderComponent, + } +]; diff --git a/UI/Web/src/app/_routes/reading-list-routing.module.ts b/UI/Web/src/app/_routes/reading-list-routing.module.ts new file mode 100644 index 000000000..e53e056c9 --- /dev/null +++ b/UI/Web/src/app/_routes/reading-list-routing.module.ts @@ -0,0 +1,18 @@ +import { Routes } from "@angular/router"; +import { AuthGuard } from "../_guards/auth.guard"; +import { ReadingListDetailComponent } from "../reading-list/_components/reading-list-detail/reading-list-detail.component"; +import { ReadingListsComponent } from "../reading-list/_components/reading-lists/reading-lists.component"; + + +export const routes: Routes = [ + { + path: '', + runGuardsAndResolvers: 'always', + canActivate: [AuthGuard], + children: [ + {path: '', component: ReadingListsComponent, pathMatch: 'full'}, + {path: ':id', component: ReadingListDetailComponent, pathMatch: 'full'}, + ] + }, + {path: '**', component: ReadingListsComponent, pathMatch: 'full', canActivate: [AuthGuard]}, +]; diff --git a/UI/Web/src/app/_routes/registration.router.module.ts b/UI/Web/src/app/_routes/registration.router.module.ts new file mode 100644 index 000000000..266c43187 --- /dev/null +++ b/UI/Web/src/app/_routes/registration.router.module.ts @@ -0,0 +1,43 @@ +import { Routes } from '@angular/router'; +import { UserLoginComponent } from '../registration/user-login/user-login.component'; +import { ConfirmEmailChangeComponent } from '../registration/_components/confirm-email-change/confirm-email-change.component'; +import { ConfirmEmailComponent } from '../registration/_components/confirm-email/confirm-email.component'; +import { ConfirmMigrationEmailComponent } from '../registration/_components/confirm-migration-email/confirm-migration-email.component'; +import { ConfirmResetPasswordComponent } from '../registration/_components/confirm-reset-password/confirm-reset-password.component'; +import { RegisterComponent } from '../registration/_components/register/register.component'; +import { ResetPasswordComponent } from '../registration/_components/reset-password/reset-password.component'; + +export const routes: Routes = [ + { + path: '', + component: UserLoginComponent + }, + { + path: 'login', + component: UserLoginComponent + }, + { + path: 'confirm-email', + component: ConfirmEmailComponent, + }, + { + path: 'confirm-migration-email', + component: ConfirmMigrationEmailComponent, + }, + { + path: 'confirm-email-update', + component: ConfirmEmailChangeComponent, + }, + { + path: 'register', + component: RegisterComponent, + }, + { + path: 'reset-password', + component: ResetPasswordComponent + }, + { + path: 'confirm-reset-password', + component: ConfirmResetPasswordComponent + } +]; diff --git a/UI/Web/src/app/_routes/user-settings-routing.module.ts b/UI/Web/src/app/_routes/user-settings-routing.module.ts new file mode 100644 index 000000000..4e02386ac --- /dev/null +++ b/UI/Web/src/app/_routes/user-settings-routing.module.ts @@ -0,0 +1,15 @@ +import { Routes } from '@angular/router'; +import { AuthGuard } from '../_guards/auth.guard'; +import { UserPreferencesComponent } from '../user-settings/user-preferences/user-preferences.component'; + +export const routes: Routes = [ + {path: '**', component: UserPreferencesComponent, pathMatch: 'full'}, + { + path: '', + runGuardsAndResolvers: 'always', + canActivate: [AuthGuard], + children: [ + {path: '', component: UserPreferencesComponent, pathMatch: 'full'}, + ] + } +]; diff --git a/UI/Web/src/app/_routes/want-to-read-routing.module.ts b/UI/Web/src/app/_routes/want-to-read-routing.module.ts new file mode 100644 index 000000000..ad0d58b05 --- /dev/null +++ b/UI/Web/src/app/_routes/want-to-read-routing.module.ts @@ -0,0 +1,15 @@ +import { Routes } from '@angular/router'; +import { AuthGuard } from '../_guards/auth.guard'; +import { WantToReadComponent } from '../want-to-read/_components/want-to-read/want-to-read.component'; + +export const routes: Routes = [ + {path: '**', component: WantToReadComponent, pathMatch: 'full'}, + { + path: '', + runGuardsAndResolvers: 'always', + canActivate: [AuthGuard], + children: [ + {path: '', component: WantToReadComponent, pathMatch: 'full'}, + ] + } +]; diff --git a/UI/Web/src/app/_services/image.service.ts b/UI/Web/src/app/_services/image.service.ts index 4aea05982..2c85f2605 100644 --- a/UI/Web/src/app/_services/image.service.ts +++ b/UI/Web/src/app/_services/image.service.ts @@ -95,10 +95,6 @@ export class ImageService { return `${this.baseUrl}image/cover-upload?filename=${encodeURIComponent(filename)}&apiKey=${this.encodedKey}`; } - updateErroredImage(event: any) { - event.target.src = this.placeholderImage; - } - updateErroredWebLinkImage(event: any) { event.target.src = this.errorWebLinkImage; } diff --git a/UI/Web/src/app/_services/statistics.service.ts b/UI/Web/src/app/_services/statistics.service.ts index ff87fa2c4..f30eb26aa 100644 --- a/UI/Web/src/app/_services/statistics.service.ts +++ b/UI/Web/src/app/_services/statistics.service.ts @@ -2,9 +2,9 @@ import { HttpClient } from '@angular/common/http'; import {inject, Injectable} from '@angular/core'; import { environment } from 'src/environments/environment'; import { UserReadStatistics } from '../statistics/_models/user-read-statistics'; -import { PublicationStatusPipe } from '../pipe/publication-status.pipe'; +import { PublicationStatusPipe } from '../_pipes/publication-status.pipe'; import { map } from 'rxjs'; -import { MangaFormatPipe } from '../pipe/manga-format.pipe'; +import { MangaFormatPipe } from '../_pipes/manga-format.pipe'; import { FileExtensionBreakdown } from '../statistics/_models/file-breakdown'; import { TopUserRead } from '../statistics/_models/top-reads'; import { ReadHistoryEvent } from '../statistics/_models/read-history-event'; diff --git a/UI/Web/src/app/_single-module/review-card-modal/review-card-modal.component.ts b/UI/Web/src/app/_single-module/review-card-modal/review-card-modal.component.ts index 1f0d521fb..24eb32d3f 100644 --- a/UI/Web/src/app/_single-module/review-card-modal/review-card-modal.component.ts +++ b/UI/Web/src/app/_single-module/review-card-modal/review-card-modal.component.ts @@ -12,7 +12,7 @@ import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; import {ReactiveFormsModule} from "@angular/forms"; import {UserReview} from "../review-card/user-review"; import {SpoilerComponent} from "../spoiler/spoiler.component"; -import {SafeHtmlPipe} from "../../pipe/safe-html.pipe"; +import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe"; import {TranslocoDirective} from "@ngneat/transloco"; @Component({ diff --git a/UI/Web/src/app/_single-module/review-card/review-card.component.ts b/UI/Web/src/app/_single-module/review-card/review-card.component.ts index ff9b03848..2f0d419a9 100644 --- a/UI/Web/src/app/_single-module/review-card/review-card.component.ts +++ b/UI/Web/src/app/_single-module/review-card/review-card.component.ts @@ -6,9 +6,9 @@ import {ReviewCardModalComponent} from "../review-card-modal/review-card-modal.c import {AccountService} from "../../_services/account.service"; import {ReviewSeriesModalComponent} from "../review-series-modal/review-series-modal.component"; import {ReadMoreComponent} from "../../shared/read-more/read-more.component"; -import {DefaultValuePipe} from "../../pipe/default-value.pipe"; +import {DefaultValuePipe} from "../../_pipes/default-value.pipe"; import {ImageComponent} from "../../shared/image/image.component"; -import {ProviderImagePipe} from "../../pipe/provider-image.pipe"; +import {ProviderImagePipe} from "../../_pipes/provider-image.pipe"; import {TranslocoDirective} from "@ngneat/transloco"; @Component({ diff --git a/UI/Web/src/app/_single-module/series-preview-drawer/series-preview-drawer.component.ts b/UI/Web/src/app/_single-module/series-preview-drawer/series-preview-drawer.component.ts index 1e6a420b0..b28fdf320 100644 --- a/UI/Web/src/app/_single-module/series-preview-drawer/series-preview-drawer.component.ts +++ b/UI/Web/src/app/_single-module/series-preview-drawer/series-preview-drawer.component.ts @@ -6,13 +6,13 @@ import {ExternalSeriesDetail, SeriesStaff} from "../../_models/series-detail/ext import {SeriesService} from "../../_services/series.service"; import {ImageComponent} from "../../shared/image/image.component"; import {LoadingComponent} from "../../shared/loading/loading.component"; -import {SafeHtmlPipe} from "../../pipe/safe-html.pipe"; +import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe"; import {A11yClickDirective} from "../../shared/a11y-click.directive"; import {MetadataDetailComponent} from "../../series-detail/_components/metadata-detail/metadata-detail.component"; import {PersonBadgeComponent} from "../../shared/person-badge/person-badge.component"; import {TagBadgeComponent} from "../../shared/tag-badge/tag-badge.component"; import {ImageService} from "../../_services/image.service"; -import {PublicationStatusPipe} from "../../pipe/publication-status.pipe"; +import {PublicationStatusPipe} from "../../_pipes/publication-status.pipe"; import {SeriesMetadata} from "../../_models/metadata/series-metadata"; import {ReadMoreComponent} from "../../shared/read-more/read-more.component"; import {ActionService} from "../../_services/action.service"; diff --git a/UI/Web/src/app/_single-module/spoiler/spoiler.component.ts b/UI/Web/src/app/_single-module/spoiler/spoiler.component.ts index 4dcbfc82d..6eefdcb70 100644 --- a/UI/Web/src/app/_single-module/spoiler/spoiler.component.ts +++ b/UI/Web/src/app/_single-module/spoiler/spoiler.component.ts @@ -8,7 +8,7 @@ import { ViewEncapsulation } from '@angular/core'; import {CommonModule} from '@angular/common'; -import {SafeHtmlPipe} from "../../pipe/safe-html.pipe"; +import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe"; import {TranslocoDirective} from "@ngneat/transloco"; @Component({ diff --git a/UI/Web/src/app/_single-module/user-scrobble-history/user-scrobble-history.component.ts b/UI/Web/src/app/_single-module/user-scrobble-history/user-scrobble-history.component.ts index 166e00254..0c52dabd9 100644 --- a/UI/Web/src/app/_single-module/user-scrobble-history/user-scrobble-history.component.ts +++ b/UI/Web/src/app/_single-module/user-scrobble-history/user-scrobble-history.component.ts @@ -12,9 +12,9 @@ import {PaginatedResult, Pagination} from "../../_models/pagination"; import {SortableHeader, SortEvent} from "../table/_directives/sortable-header.directive"; import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms"; import {TranslocoModule} from "@ngneat/transloco"; -import {DefaultValuePipe} from "../../pipe/default-value.pipe"; +import {DefaultValuePipe} from "../../_pipes/default-value.pipe"; import {TranslocoLocaleModule} from "@ngneat/transloco-locale"; -import {UtcToLocalTimePipe} from "../../pipe/utc-to-local-time.pipe"; +import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe"; @Component({ selector: 'app-user-scrobble-history', diff --git a/UI/Web/src/app/admin/_modals/reset-password-modal/reset-password-modal.component.ts b/UI/Web/src/app/admin/_modals/reset-password-modal/reset-password-modal.component.ts index 1df444819..b96619e4f 100644 --- a/UI/Web/src/app/admin/_modals/reset-password-modal/reset-password-modal.component.ts +++ b/UI/Web/src/app/admin/_modals/reset-password-modal/reset-password-modal.component.ts @@ -3,7 +3,7 @@ import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angula import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { Member } from 'src/app/_models/auth/member'; import { AccountService } from 'src/app/_services/account.service'; -import { SentenceCasePipe } from '../../../pipe/sentence-case.pipe'; +import { SentenceCasePipe } from '../../../_pipes/sentence-case.pipe'; import { NgIf } from '@angular/common'; import {TranslocoDirective} from "@ngneat/transloco"; diff --git a/UI/Web/src/app/admin/admin.module.ts b/UI/Web/src/app/admin/admin.module.ts deleted file mode 100644 index 92c2debfe..000000000 --- a/UI/Web/src/app/admin/admin.module.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { AdminRoutingModule } from './admin-routing.module'; -import { DashboardComponent } from './dashboard/dashboard.component'; -import { - NgbAccordionModule, - NgbCollapse, - NgbDropdownModule, - NgbNavModule, - NgbTooltipModule, - NgbTypeaheadModule -} from '@ng-bootstrap/ng-bootstrap'; -import { ManageLibraryComponent } from './manage-library/manage-library.component'; -import { ManageUsersComponent } from './manage-users/manage-users.component'; -import { LibraryAccessModalComponent } from './_modals/library-access-modal/library-access-modal.component'; -import { DirectoryPickerComponent } from './_modals/directory-picker/directory-picker.component'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { ResetPasswordModalComponent } from './_modals/reset-password-modal/reset-password-modal.component'; -import { ManageSettingsComponent } from './manage-settings/manage-settings.component'; -import { ManageSystemComponent } from './manage-system/manage-system.component'; -import { InviteUserComponent } from './invite-user/invite-user.component'; -import { RoleSelectorComponent } from './role-selector/role-selector.component'; -import { LibrarySelectorComponent } from './library-selector/library-selector.component'; -import { EditUserComponent } from './edit-user/edit-user.component'; -import { UserSettingsModule } from '../user-settings/user-settings.module'; -import { ManageMediaSettingsComponent } from './manage-media-settings/manage-media-settings.component'; -import { ManageEmailSettingsComponent } from './manage-email-settings/manage-email-settings.component'; -import { ManageTasksSettingsComponent } from './manage-tasks-settings/manage-tasks-settings.component'; -import { ManageLogsComponent } from './manage-logs/manage-logs.component'; -import { VirtualScrollerModule } from '@iharbeck/ngx-virtual-scroller'; - -import { ManageAlertsComponent } from './manage-alerts/manage-alerts.component'; -import {ManageScrobbleErrorsComponent} from "./manage-scrobble-errors/manage-scrobble-errors.component"; -import {DefaultValuePipe} from "../pipe/default-value.pipe"; -import {LibraryTypePipe} from "../pipe/library-type.pipe"; -import {TimeAgoPipe} from "../pipe/time-ago.pipe"; -import {SentenceCasePipe} from "../pipe/sentence-case.pipe"; -import {FilterPipe} from "../pipe/filter.pipe"; -import {TagBadgeComponent} from "../shared/tag-badge/tag-badge.component"; -import {LoadingComponent} from "../shared/loading/loading.component"; -import { - SideNavCompanionBarComponent -} from "../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component"; -import {RouterModule} from "@angular/router"; -import {LicenseComponent} from "./license/license.component"; - - - - -@NgModule({ - imports: [ - CommonModule, - AdminRoutingModule, - ReactiveFormsModule, - RouterModule, - FormsModule, - NgbNavModule, - NgbTooltipModule, - NgbTypeaheadModule, - NgbDropdownModule, - NgbAccordionModule, - UserSettingsModule, - VirtualScrollerModule, - ManageScrobbleErrorsComponent, - DefaultValuePipe, - LibraryTypePipe, - TimeAgoPipe, - SentenceCasePipe, - FilterPipe, - TagBadgeComponent, - LoadingComponent, - SideNavCompanionBarComponent, - NgbCollapse, - ManageUsersComponent, - DashboardComponent, - ManageLibraryComponent, - LibraryAccessModalComponent, - DirectoryPickerComponent, - ResetPasswordModalComponent, - ManageSettingsComponent, - ManageSystemComponent, - InviteUserComponent, - RoleSelectorComponent, - LibrarySelectorComponent, - EditUserComponent, - ManageMediaSettingsComponent, - ManageEmailSettingsComponent, - ManageTasksSettingsComponent, - ManageLogsComponent, - ManageAlertsComponent, - LicenseComponent -], - providers: [] -}) -export class AdminModule { } diff --git a/UI/Web/src/app/admin/dashboard/dashboard.component.ts b/UI/Web/src/app/admin/dashboard/dashboard.component.ts index 5dfd2c449..350d3d184 100644 --- a/UI/Web/src/app/admin/dashboard/dashboard.component.ts +++ b/UI/Web/src/app/admin/dashboard/dashboard.component.ts @@ -2,7 +2,7 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} f import {ActivatedRoute, RouterLink} from '@angular/router'; import {Title} from '@angular/platform-browser'; import {NavService} from '../../_services/nav.service'; -import {SentenceCasePipe} from '../../pipe/sentence-case.pipe'; +import {SentenceCasePipe} from '../../_pipes/sentence-case.pipe'; import {LicenseComponent} from '../license/license.component'; import {ManageTasksSettingsComponent} from '../manage-tasks-settings/manage-tasks-settings.component'; import {ServerStatsComponent} from '../../statistics/_components/server-stats/server-stats.component'; diff --git a/UI/Web/src/app/admin/edit-user/edit-user.component.ts b/UI/Web/src/app/admin/edit-user/edit-user.component.ts index a9eba589e..a0ed23cc1 100644 --- a/UI/Web/src/app/admin/edit-user/edit-user.component.ts +++ b/UI/Web/src/app/admin/edit-user/edit-user.component.ts @@ -5,7 +5,7 @@ import { AgeRestriction } from 'src/app/_models/metadata/age-restriction'; import { Library } from 'src/app/_models/library'; import { Member } from 'src/app/_models/auth/member'; import { AccountService } from 'src/app/_services/account.service'; -import { SentenceCasePipe } from '../../pipe/sentence-case.pipe'; +import { SentenceCasePipe } from '../../_pipes/sentence-case.pipe'; import { RestrictionSelectorComponent } from '../../user-settings/restriction-selector/restriction-selector.component'; import { LibrarySelectorComponent } from '../library-selector/library-selector.component'; import { RoleSelectorComponent } from '../role-selector/role-selector.component'; diff --git a/UI/Web/src/app/admin/invite-user/invite-user.component.html b/UI/Web/src/app/admin/invite-user/invite-user.component.html index e9b3d6af0..6d4a27607 100644 --- a/UI/Web/src/app/admin/invite-user/invite-user.component.html +++ b/UI/Web/src/app/admin/invite-user/invite-user.component.html @@ -47,10 +47,10 @@