mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-31 20:24:27 -04:00
UX Pass 7 (#3135)
Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
parent
5bf5558212
commit
79eb98a3bb
8
.github/workflows/codeql.yml
vendored
8
.github/workflows/codeql.yml
vendored
@ -50,11 +50,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Install Swashbuckle CLI
|
- name: Install Swashbuckle CLI
|
||||||
shell: bash
|
shell: bash
|
||||||
run: dotnet tool install -g --version 6.5.0 Swashbuckle.AspNetCore.Cli
|
run: dotnet tool install -g Swashbuckle.AspNetCore.Cli
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@ -68,7 +68,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
@ -81,6 +81,6 @@ jobs:
|
|||||||
dotnet build Kavita.sln
|
dotnet build Kavita.sln
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.8" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
|
||||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="21.0.29" />
|
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="21.0.29" />
|
||||||
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="21.0.29" />
|
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="21.0.29" />
|
||||||
|
@ -1141,6 +1141,41 @@ public class SeriesServiceTests : AbstractDbTest
|
|||||||
Assert.Equal(3, series1.Relations.Single(s => s.TargetSeriesId == 3).TargetSeriesId);
|
Assert.Equal(3, series1.Relations.Single(s => s.TargetSeriesId == 3).TargetSeriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateRelatedSeries_ShouldAddPrequelWhenAddingSequel()
|
||||||
|
{
|
||||||
|
await ResetDb();
|
||||||
|
_context.Library.Add(new Library
|
||||||
|
{
|
||||||
|
AppUsers = new List<AppUser>
|
||||||
|
{
|
||||||
|
new AppUser
|
||||||
|
{
|
||||||
|
UserName = "majora2007"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Name = "Test LIb",
|
||||||
|
Type = LibraryType.Book,
|
||||||
|
Series = new List<Series>
|
||||||
|
{
|
||||||
|
new SeriesBuilder("Test Series").Build(),
|
||||||
|
new SeriesBuilder("Test Series Prequels").Build(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related);
|
||||||
|
var series2 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2, SeriesIncludes.Related);
|
||||||
|
// Add relations
|
||||||
|
var addRelationDto = CreateRelationsDto(series1);
|
||||||
|
addRelationDto.Sequels.Add(2);
|
||||||
|
await _seriesService.UpdateRelatedSeries(addRelationDto);
|
||||||
|
Assert.NotNull(series1);
|
||||||
|
Assert.Equal(2, series1.Relations.Single(s => s.TargetSeriesId == 2).TargetSeriesId);
|
||||||
|
Assert.Equal(1, series2.Relations.Single(s => s.TargetSeriesId == 1).TargetSeriesId);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task UpdateRelatedSeries_DeleteAllRelations()
|
public async Task UpdateRelatedSeries_DeleteAllRelations()
|
||||||
{
|
{
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<PackageReference Include="Hangfire.InMemory" Version="0.10.3" />
|
<PackageReference Include="Hangfire.InMemory" Version="0.10.3" />
|
||||||
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
||||||
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.4.2" />
|
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.4.2" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.63" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.64" />
|
||||||
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
||||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.14" />
|
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.14" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||||
@ -100,9 +100,9 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.2" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.2" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.2" />
|
||||||
<PackageReference Include="System.IO.Abstractions" Version="21.0.29" />
|
<PackageReference Include="System.IO.Abstractions" Version="21.0.29" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="8.0.8" />
|
<PackageReference Include="System.Drawing.Common" Version="8.0.8" />
|
||||||
<PackageReference Include="VersOne.Epub" Version="3.3.2" />
|
<PackageReference Include="VersOne.Epub" Version="3.3.2" />
|
||||||
|
@ -22,6 +22,11 @@ public class LocaleController : BaseApiController
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public ActionResult<IEnumerable<string>> GetAllLocales()
|
public ActionResult<IEnumerable<string>> GetAllLocales()
|
||||||
{
|
{
|
||||||
|
// Check if temp/locale_map.json exists
|
||||||
|
|
||||||
|
// If not, scan the 2 locale files and calculate empty keys or empty values
|
||||||
|
|
||||||
|
// Formulate the Locale object with Percentage
|
||||||
var languages = _localizationService.GetLocales().Select(c =>
|
var languages = _localizationService.GetLocales().Select(c =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -1 +0,0 @@
|
|||||||
{}
|
|
@ -1 +0,0 @@
|
|||||||
{}
|
|
@ -625,7 +625,7 @@ public class SeriesService : ISeriesService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update the relations attached to the Series. Does not generate associated Sequel/Prequel pairs on target series.
|
/// Update the relations attached to the Series. Generates associated Sequel/Prequel pairs on target series.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dto"></param>
|
/// <param name="dto"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
@ -643,15 +643,90 @@ public class SeriesService : ISeriesService
|
|||||||
UpdateRelationForKind(dto.AlternativeSettings, series.Relations.Where(r => r.RelationKind == RelationKind.AlternativeSetting).ToList(), series, RelationKind.AlternativeSetting);
|
UpdateRelationForKind(dto.AlternativeSettings, series.Relations.Where(r => r.RelationKind == RelationKind.AlternativeSetting).ToList(), series, RelationKind.AlternativeSetting);
|
||||||
UpdateRelationForKind(dto.AlternativeVersions, series.Relations.Where(r => r.RelationKind == RelationKind.AlternativeVersion).ToList(), series, RelationKind.AlternativeVersion);
|
UpdateRelationForKind(dto.AlternativeVersions, series.Relations.Where(r => r.RelationKind == RelationKind.AlternativeVersion).ToList(), series, RelationKind.AlternativeVersion);
|
||||||
UpdateRelationForKind(dto.Doujinshis, series.Relations.Where(r => r.RelationKind == RelationKind.Doujinshi).ToList(), series, RelationKind.Doujinshi);
|
UpdateRelationForKind(dto.Doujinshis, series.Relations.Where(r => r.RelationKind == RelationKind.Doujinshi).ToList(), series, RelationKind.Doujinshi);
|
||||||
UpdateRelationForKind(dto.Prequels, series.Relations.Where(r => r.RelationKind == RelationKind.Prequel).ToList(), series, RelationKind.Prequel);
|
|
||||||
UpdateRelationForKind(dto.Sequels, series.Relations.Where(r => r.RelationKind == RelationKind.Sequel).ToList(), series, RelationKind.Sequel);
|
|
||||||
UpdateRelationForKind(dto.Editions, series.Relations.Where(r => r.RelationKind == RelationKind.Edition).ToList(), series, RelationKind.Edition);
|
UpdateRelationForKind(dto.Editions, series.Relations.Where(r => r.RelationKind == RelationKind.Edition).ToList(), series, RelationKind.Edition);
|
||||||
UpdateRelationForKind(dto.Annuals, series.Relations.Where(r => r.RelationKind == RelationKind.Annual).ToList(), series, RelationKind.Annual);
|
UpdateRelationForKind(dto.Annuals, series.Relations.Where(r => r.RelationKind == RelationKind.Annual).ToList(), series, RelationKind.Annual);
|
||||||
|
|
||||||
|
await UpdatePrequelSequelRelations(dto.Prequels, series, RelationKind.Prequel);
|
||||||
|
await UpdatePrequelSequelRelations(dto.Sequels, series, RelationKind.Sequel);
|
||||||
|
|
||||||
if (!_unitOfWork.HasChanges()) return true;
|
if (!_unitOfWork.HasChanges()) return true;
|
||||||
return await _unitOfWork.CommitAsync();
|
return await _unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates Prequel/Sequel relations and creates reciprocal relations on target series.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="targetSeriesIds">List of target series IDs</param>
|
||||||
|
/// <param name="series">The current series being updated</param>
|
||||||
|
/// <param name="kind">The relation kind (Prequel or Sequel)</param>
|
||||||
|
private async Task UpdatePrequelSequelRelations(ICollection<int> targetSeriesIds, Series series, RelationKind kind)
|
||||||
|
{
|
||||||
|
var existingRelations = series.Relations.Where(r => r.RelationKind == kind).ToList();
|
||||||
|
|
||||||
|
// Remove relations that are not in the new list
|
||||||
|
foreach (var relation in existingRelations.Where(relation => !targetSeriesIds.Contains(relation.TargetSeriesId)))
|
||||||
|
{
|
||||||
|
series.Relations.Remove(relation);
|
||||||
|
await RemoveReciprocalRelation(series.Id, relation.TargetSeriesId, GetOppositeRelationKind(kind));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new relations
|
||||||
|
foreach (var targetSeriesId in targetSeriesIds)
|
||||||
|
{
|
||||||
|
if (series.Relations.Any(r => r.RelationKind == kind && r.TargetSeriesId == targetSeriesId))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
series.Relations.Add(new SeriesRelation
|
||||||
|
{
|
||||||
|
Series = series,
|
||||||
|
SeriesId = series.Id,
|
||||||
|
TargetSeriesId = targetSeriesId,
|
||||||
|
RelationKind = kind
|
||||||
|
});
|
||||||
|
|
||||||
|
await AddReciprocalRelation(series.Id, targetSeriesId, GetOppositeRelationKind(kind));
|
||||||
|
}
|
||||||
|
|
||||||
|
_unitOfWork.SeriesRepository.Update(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RelationKind GetOppositeRelationKind(RelationKind kind)
|
||||||
|
{
|
||||||
|
return kind == RelationKind.Prequel ? RelationKind.Sequel : RelationKind.Prequel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddReciprocalRelation(int sourceSeriesId, int targetSeriesId, RelationKind kind)
|
||||||
|
{
|
||||||
|
var targetSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(targetSeriesId, SeriesIncludes.Related);
|
||||||
|
if (targetSeries == null) return;
|
||||||
|
|
||||||
|
if (targetSeries.Relations.Any(r => r.RelationKind == kind && r.TargetSeriesId == sourceSeriesId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
targetSeries.Relations.Add(new SeriesRelation
|
||||||
|
{
|
||||||
|
Series = targetSeries,
|
||||||
|
SeriesId = targetSeriesId,
|
||||||
|
TargetSeriesId = sourceSeriesId,
|
||||||
|
RelationKind = kind
|
||||||
|
});
|
||||||
|
|
||||||
|
_unitOfWork.SeriesRepository.Update(targetSeries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveReciprocalRelation(int sourceSeriesId, int targetSeriesId, RelationKind kind)
|
||||||
|
{
|
||||||
|
var targetSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(targetSeriesId, SeriesIncludes.Related);
|
||||||
|
if (targetSeries == null) return;
|
||||||
|
|
||||||
|
var relationToRemove = targetSeries.Relations.FirstOrDefault(r => r.RelationKind == kind && r.TargetSeriesId == sourceSeriesId);
|
||||||
|
if (relationToRemove != null)
|
||||||
|
{
|
||||||
|
targetSeries.Relations.Remove(relationToRemove);
|
||||||
|
_unitOfWork.SeriesRepository.Update(targetSeries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies the provided list to the series. Adds new relations and removes deleted relations.
|
/// Applies the provided list to the series. Adds new relations and removes deleted relations.
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
$image-height: 232.91px;
|
$image-height: 232.91px;
|
||||||
$image-width: 160px;
|
$image-width: 160px;
|
||||||
|
|
||||||
@ -21,7 +19,6 @@ $image-width: 160px;
|
|||||||
outline: 2px solid var(--primary-color);
|
outline: 2px solid var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.progress-banner {
|
.progress-banner {
|
||||||
width: $image-width;
|
width: $image-width;
|
||||||
height: 5px;
|
height: 5px;
|
||||||
@ -63,7 +60,6 @@ $image-width: 160px;
|
|||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.bulk-mode {
|
.bulk-mode {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
@ -90,7 +86,6 @@ $image-width: 160px;
|
|||||||
border-width: 0;
|
border-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
&:hover {
|
&:hover {
|
||||||
.bulk-mode {
|
.bulk-mode {
|
||||||
@ -102,18 +97,18 @@ $image-width: 160px;
|
|||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
|
||||||
.overlay-information {
|
.overlay-information {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
& + .meta-title {
|
& + .meta-title {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-information {
|
.overlay-information {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -124,24 +119,24 @@ $image-width: 160px;
|
|||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--card-overlay-hover-bg-color);
|
background-color: var(--card-overlay-hover-bg-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-information--centered {
|
.overlay-information--centered {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
background-color: rgba(0, 0, 0, .7);
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
z-index: 115;
|
z-index: 115;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--primary-color) !important;
|
background-color: var(--primary-color) !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,11 @@
|
|||||||
|
|
||||||
.main-container {
|
.main-container {
|
||||||
overflow: unset !important;
|
overflow: unset !important;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .badge-expander .content a {
|
||||||
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-group > .btn.dropdown-toggle-split:not(first-child){
|
.btn-group > .btn.dropdown-toggle-split:not(first-child){
|
||||||
|
@ -245,46 +245,58 @@ export class ColorscapeService {
|
|||||||
const secondaryHSL = this.rgbToHsl(secondary);
|
const secondaryHSL = this.rgbToHsl(secondary);
|
||||||
|
|
||||||
if (isDarkTheme) {
|
if (isDarkTheme) {
|
||||||
const lighterHSL = this.adjustHue(secondaryHSL, 30);
|
return this.calculateDarkThemeColors(secondaryHSL, primaryHSL, primary);
|
||||||
lighterHSL.s = Math.min(lighterHSL.s + 0.2, 1);
|
|
||||||
lighterHSL.l = Math.min(lighterHSL.l + 0.1, 0.6);
|
|
||||||
|
|
||||||
const darkerHSL = { ...primaryHSL };
|
|
||||||
darkerHSL.l = Math.max(darkerHSL.l - 0.3, 0.1);
|
|
||||||
|
|
||||||
const complementaryHSL = this.adjustHue(primaryHSL, 180);
|
|
||||||
complementaryHSL.s = Math.min(complementaryHSL.s + 0.1, 1);
|
|
||||||
complementaryHSL.l = Math.max(complementaryHSL.l - 0.2, 0.2);
|
|
||||||
|
|
||||||
return {
|
|
||||||
primary: this.rgbToHex(primary),
|
|
||||||
lighter: this.rgbToHex(this.hslToRgb(lighterHSL)),
|
|
||||||
darker: this.rgbToHex(this.hslToRgb(darkerHSL)),
|
|
||||||
complementary: this.rgbToHex(this.hslToRgb(complementaryHSL))
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
// NOTE: Light themes look bad in general with this system.
|
// NOTE: Light themes look bad in general with this system.
|
||||||
const lighterHSL = { ...primaryHSL };
|
return this.calculateLightThemeDarkColors(primaryHSL, primary);
|
||||||
lighterHSL.s = Math.max(lighterHSL.s - 0.3, 0);
|
|
||||||
lighterHSL.l = Math.min(lighterHSL.l + 0.5, 0.95);
|
|
||||||
|
|
||||||
const darkerHSL = { ...primaryHSL };
|
|
||||||
darkerHSL.s = Math.max(darkerHSL.s - 0.1, 0);
|
|
||||||
darkerHSL.l = Math.min(darkerHSL.l + 0.3, 0.9);
|
|
||||||
|
|
||||||
const complementaryHSL = this.adjustHue(primaryHSL, 180);
|
|
||||||
complementaryHSL.s = Math.max(complementaryHSL.s - 0.2, 0);
|
|
||||||
complementaryHSL.l = Math.min(complementaryHSL.l + 0.4, 0.9);
|
|
||||||
|
|
||||||
return {
|
|
||||||
primary: this.rgbToHex(primary),
|
|
||||||
lighter: this.rgbToHex(this.hslToRgb(lighterHSL)),
|
|
||||||
darker: this.rgbToHex(this.hslToRgb(darkerHSL)),
|
|
||||||
complementary: this.rgbToHex(this.hslToRgb(complementaryHSL))
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private calculateLightThemeDarkColors(primaryHSL: { h: number; s: number; l: number }, primary: RGB) {
|
||||||
|
const lighterHSL = {...primaryHSL};
|
||||||
|
lighterHSL.s = Math.max(lighterHSL.s - 0.3, 0);
|
||||||
|
lighterHSL.l = Math.min(lighterHSL.l + 0.5, 0.95);
|
||||||
|
|
||||||
|
const darkerHSL = {...primaryHSL};
|
||||||
|
darkerHSL.s = Math.max(darkerHSL.s - 0.1, 0);
|
||||||
|
darkerHSL.l = Math.min(darkerHSL.l + 0.3, 0.9);
|
||||||
|
|
||||||
|
const complementaryHSL = this.adjustHue(primaryHSL, 180);
|
||||||
|
complementaryHSL.s = Math.max(complementaryHSL.s - 0.2, 0);
|
||||||
|
complementaryHSL.l = Math.min(complementaryHSL.l + 0.4, 0.9);
|
||||||
|
|
||||||
|
return {
|
||||||
|
primary: this.rgbToHex(primary),
|
||||||
|
lighter: this.rgbToHex(this.hslToRgb(lighterHSL)),
|
||||||
|
darker: this.rgbToHex(this.hslToRgb(darkerHSL)),
|
||||||
|
complementary: this.rgbToHex(this.hslToRgb(complementaryHSL))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateDarkThemeColors(secondaryHSL: { h: number; s: number; l: number }, primaryHSL: {
|
||||||
|
h: number;
|
||||||
|
s: number;
|
||||||
|
l: number
|
||||||
|
}, primary: RGB) {
|
||||||
|
const lighterHSL = this.adjustHue(secondaryHSL, 30);
|
||||||
|
lighterHSL.s = Math.min(lighterHSL.s + 0.2, 1);
|
||||||
|
lighterHSL.l = Math.min(lighterHSL.l + 0.1, 0.6);
|
||||||
|
|
||||||
|
const darkerHSL = {...primaryHSL};
|
||||||
|
darkerHSL.l = Math.max(darkerHSL.l - 0.3, 0.1);
|
||||||
|
|
||||||
|
const complementaryHSL = this.adjustHue(primaryHSL, 180);
|
||||||
|
complementaryHSL.s = Math.min(complementaryHSL.s + 0.1, 1);
|
||||||
|
complementaryHSL.l = Math.max(complementaryHSL.l - 0.2, 0.2);
|
||||||
|
|
||||||
|
return {
|
||||||
|
primary: this.rgbToHex(primary),
|
||||||
|
lighter: this.rgbToHex(this.hslToRgb(lighterHSL)),
|
||||||
|
darker: this.rgbToHex(this.hslToRgb(darkerHSL)),
|
||||||
|
complementary: this.rgbToHex(this.hslToRgb(complementaryHSL))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private hexToRgb(hex: string): RGB {
|
private hexToRgb(hex: string): RGB {
|
||||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||||
return result ? {
|
return result ? {
|
||||||
|
@ -119,6 +119,13 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
padding: 0 10px 0 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<ng-template #carouselItem let-item>
|
<ng-template #carouselItem let-item>
|
||||||
<app-card-item [title]="item.title" [entity]="item"
|
<app-card-item [title]="item.title" [entity]="item"
|
||||||
[suppressLibraryLink]="true" [imageUrl]="imageService.getCollectionCoverImage(item.id)"
|
[suppressLibraryLink]="true" [imageUrl]="imageService.getCollectionCoverImage(item.id)"
|
||||||
(clicked)="openCollection(item)"></app-card-item>
|
(clicked)="openCollection(item)" [linkUrl]="'/collections/' + item.id"></app-card-item>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-carousel-reel>
|
</app-carousel-reel>
|
||||||
}
|
}
|
||||||
@ -24,7 +24,7 @@
|
|||||||
<ng-template #carouselItem let-item>
|
<ng-template #carouselItem let-item>
|
||||||
<app-card-item [title]="item.title" [entity]="item"
|
<app-card-item [title]="item.title" [entity]="item"
|
||||||
[suppressLibraryLink]="true" [imageUrl]="imageService.getReadingListCoverImage(item.id)"
|
[suppressLibraryLink]="true" [imageUrl]="imageService.getReadingListCoverImage(item.id)"
|
||||||
(clicked)="openReadingList(item)"></app-card-item>
|
(clicked)="openReadingList(item)" [linkUrl]="'/lists/' + item.id"></app-card-item>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-carousel-reel>
|
</app-carousel-reel>
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
{{library.type | libraryType}}
|
{{library.type | libraryType}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{t('folder-count', {num: library.folders.length})}}
|
{{library.folders.length}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{library.lastScanned | timeAgo | defaultDate}}
|
{{library.lastScanned | timeAgo | defaultDate}}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@import "../../../theme/variables";
|
||||||
|
|
||||||
.custom-position {
|
.custom-position {
|
||||||
right: 15px;
|
right: 15px;
|
||||||
top: -42px;
|
top: -42px;
|
||||||
@ -11,3 +13,23 @@
|
|||||||
.list-group-item:nth-child(even) {
|
.list-group-item:nth-child(even) {
|
||||||
background-color: var(--elevation-layer1);
|
background-color: var(--elevation-layer1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
@media (max-width: $grid-breakpoints-sm) {
|
||||||
|
overflow-x: auto;
|
||||||
|
width: 100% !important;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.btn-container {
|
||||||
|
@media (max-width: $grid-breakpoints-lg) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
@import "../../../theme/variables";
|
||||||
|
|
||||||
|
.table {
|
||||||
|
@media (max-width: $grid-breakpoints-sm) {
|
||||||
|
overflow-x: auto;
|
||||||
|
width: 100% !important;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.btn-container {
|
||||||
|
@media (max-width: $grid-breakpoints-lg) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,11 @@
|
|||||||
<ng-container *transloco="let t; read: 'manage-media-settings'">
|
<ng-container *transloco="let t; read: 'manage-media-settings'">
|
||||||
|
|
||||||
|
<div class="position-relative">
|
||||||
|
<button class="btn btn-secondary-outline position-absolute custom-position" (click)="resetToDefaults()" [title]="t('reset-to-default')">
|
||||||
|
<span class="phone-hidden ms-1">{{t('reset-to-default')}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="settingsForm">
|
<form [formGroup]="settingsForm">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<p>
|
<p>
|
||||||
@ -64,10 +71,6 @@
|
|||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end">
|
|
||||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetToDefaults()">{{t('reset-to-default')}}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
.custom-position {
|
||||||
|
right: 5px;
|
||||||
|
top: -42px;
|
||||||
|
}
|
@ -67,26 +67,28 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if (canEditMember(member)) {
|
<div class="btn-container">
|
||||||
<button class="btn btn-danger btn-sm me-2" (click)="deleteUser(member)"
|
@if (canEditMember(member)) {
|
||||||
placement="top" [ngbTooltip]="t('delete-user-tooltip')" [attr.aria-label]="t('delete-user-alt', {user: member.username | titlecase})">
|
<button class="btn btn-danger btn-sm me-2 mb-2" (click)="deleteUser(member)"
|
||||||
<i class="fa fa-trash" aria-hidden="true"></i>
|
placement="top" [ngbTooltip]="t('delete-user-tooltip')" [attr.aria-label]="t('delete-user-alt', {user: member.username | titlecase})">
|
||||||
</button>
|
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||||
<button class="btn btn-primary btn-sm me-2" (click)="openEditUser(member)"
|
</button>
|
||||||
placement="top" [ngbTooltip]="t('edit-user-tooltip')" [attr.aria-label]="t('edit-user-alt', {user: member.username | titlecase})">
|
<button class="btn btn-primary btn-sm me-2 mb-2" (click)="openEditUser(member)"
|
||||||
<i class="fa fa-pen" aria-hidden="true"></i>
|
placement="top" [ngbTooltip]="t('edit-user-tooltip')" [attr.aria-label]="t('edit-user-alt', {user: member.username | titlecase})">
|
||||||
</button>
|
<i class="fa fa-pen" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
@if (member.isPending) {
|
@if (member.isPending) {
|
||||||
<button class="btn btn-secondary btn-sm me-2" (click)="resendEmail(member)"
|
<button class="btn btn-secondary btn-sm me-2 mb-2" (click)="resendEmail(member)"
|
||||||
placement="top" [ngbTooltip]="t('resend-invite-tooltip')" [attr.aria-label]="t('resend-invite-alt', {user: member.username | titlecase})"><i class="fa-solid fa-share-from-square" aria-hidden="true"></i></button>
|
placement="top" [ngbTooltip]="t('resend-invite-tooltip')" [attr.aria-label]="t('resend-invite-alt', {user: member.username | titlecase})"><i class="fa-solid fa-share-from-square" aria-hidden="true"></i></button>
|
||||||
<button class="btn btn-secondary btn-sm" (click)="setup(member)"
|
<button class="btn btn-secondary btn-sm me-2 mb-2" (click)="setup(member)"
|
||||||
placement="top" [ngbTooltip]="t('setup-user-tooltip')" [attr.aria-label]="t('setup-user-alt', {user: member.username | titlecase})"><i class="fa-solid fa-sliders" aria-hidden="true"></i></button>
|
placement="top" [ngbTooltip]="t('setup-user-tooltip')" [attr.aria-label]="t('setup-user-alt', {user: member.username | titlecase})"><i class="fa-solid fa-sliders" aria-hidden="true"></i></button>
|
||||||
} @else {
|
} @else {
|
||||||
<button class="btn btn-secondary btn-sm" (click)="updatePassword(member)"
|
<button class="btn btn-secondary btn-sm me-2 mb-2" (click)="updatePassword(member)"
|
||||||
placement="top" [ngbTooltip]="t('change-password-tooltip')" [attr.aria-label]="t('change-password-alt', {user: member.username | titlecase})"><i class="fa fa-key" aria-hidden="true"></i></button>
|
placement="top" [ngbTooltip]="t('change-password-tooltip')" [attr.aria-label]="t('change-password-alt', {user: member.username | titlecase})"><i class="fa fa-key" aria-hidden="true"></i></button>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@import '../../../theme/variables';
|
||||||
|
|
||||||
.presence {
|
.presence {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
@ -32,3 +34,24 @@
|
|||||||
.list-group-item:nth-child(even) {
|
.list-group-item:nth-child(even) {
|
||||||
background-color: var(--elevation-layer1);
|
background-color: var(--elevation-layer1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
@media (max-width: $grid-breakpoints-lg) {
|
||||||
|
overflow-x: auto;
|
||||||
|
width: 100% !important;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.btn-container {
|
||||||
|
@media (max-width: $grid-breakpoints-lg) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
<ng-container *transloco="let t; read: 'all-series'">
|
<div class="main-container container-fluid">
|
||||||
<app-side-nav-companion-bar [hasFilter]="true" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
|
<ng-container *transloco="let t; read: 'all-series'">
|
||||||
<h4 title>
|
<app-side-nav-companion-bar [hasFilter]="true" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
|
||||||
{{title}}
|
<h4 title>
|
||||||
</h4>
|
{{title}}
|
||||||
<h5 subtitle *ngIf="pagination">{{t('series-count', {num: pagination.totalItems | number})}}</h5>
|
</h4>
|
||||||
</app-side-nav-companion-bar>
|
<h5 subtitle *ngIf="pagination">{{t('series-count', {num: pagination.totalItems | number})}}</h5>
|
||||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
</app-side-nav-companion-bar>
|
||||||
<app-card-detail-layout *ngIf="filter"
|
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||||
[isLoading]="loadingSeries"
|
<app-card-detail-layout *ngIf="filter"
|
||||||
[items]="series"
|
[isLoading]="loadingSeries"
|
||||||
[trackByIdentity]="trackByIdentity"
|
[items]="series"
|
||||||
[filterSettings]="filterSettings"
|
[trackByIdentity]="trackByIdentity"
|
||||||
[filterOpen]="filterOpen"
|
[filterSettings]="filterSettings"
|
||||||
[jumpBarKeys]="jumpbarKeys"
|
[filterOpen]="filterOpen"
|
||||||
(applyFilter)="updateFilter($event)"
|
[jumpBarKeys]="jumpbarKeys"
|
||||||
>
|
(applyFilter)="updateFilter($event)"
|
||||||
<ng-template #cardItem let-item let-position="idx">
|
>
|
||||||
<app-series-card [series]="item" [libraryId]="item.libraryId" (reload)="loadPage()"
|
<ng-template #cardItem let-item let-position="idx">
|
||||||
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)"
|
<app-series-card [series]="item" [libraryId]="item.libraryId" (reload)="loadPage()"
|
||||||
[selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"></app-series-card>
|
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)"
|
||||||
</ng-template>
|
[selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"></app-series-card>
|
||||||
</app-card-detail-layout>
|
</ng-template>
|
||||||
|
</app-card-detail-layout>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<ng-container *transloco="let t; read: 'announcements'">
|
<div class="main-container container-fluid">
|
||||||
<app-side-nav-companion-bar>
|
<ng-container *transloco="let t; read: 'announcements'">
|
||||||
<h2 title>
|
<app-side-nav-companion-bar>
|
||||||
{{t('title')}}
|
<h2 title>
|
||||||
</h2>
|
{{t('title')}}
|
||||||
</app-side-nav-companion-bar>
|
</h2>
|
||||||
|
</app-side-nav-companion-bar>
|
||||||
|
|
||||||
<app-changelog></app-changelog>
|
<app-changelog></app-changelog>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
@ -1,42 +1,44 @@
|
|||||||
<ng-container *transloco="let t; read: 'changelog'">
|
<div class="main-container container-fluid">
|
||||||
<div class="changelog">
|
<ng-container *transloco="let t; read: 'changelog'">
|
||||||
<p class="pb-2">
|
<div class="changelog">
|
||||||
{{t('description', {installed: ''})}}
|
<p class="pb-2">
|
||||||
<span class="badge bg-secondary">{{t('installed')}}</span>
|
{{t('description', {installed: ''})}}
|
||||||
{{t('description-continued', {installed: ''})}}
|
<span class="badge bg-secondary">{{t('installed')}}</span>
|
||||||
</p>
|
{{t('description-continued', {installed: ''})}}
|
||||||
|
</p>
|
||||||
|
|
||||||
@for(update of updates; track update; let indx = $index) {
|
@for(update of updates; track update; let indx = $index) {
|
||||||
<div class="card w-100 mb-2" style="width: 18rem;">
|
<div class="card w-100 mb-2" style="width: 18rem;">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4 class="card-title">{{update.updateTitle}}
|
<h4 class="card-title">{{update.updateTitle}}
|
||||||
@if (update.isOnNightlyInRelease) {
|
@if (update.isOnNightlyInRelease) {
|
||||||
<span class="badge bg-secondary">{{t('nightly', {version: update.currentVersion})}}</span>
|
<span class="badge bg-secondary">{{t('nightly', {version: update.currentVersion})}}</span>
|
||||||
} @else if (update.isReleaseEqual) {
|
} @else if (update.isReleaseEqual) {
|
||||||
<span class="badge bg-secondary">{{t('installed')}}</span>
|
<span class="badge bg-secondary">{{t('installed')}}</span>
|
||||||
} @else if (update.isReleaseNewer && indx === 0) {
|
} @else if (update.isReleaseNewer && indx === 0) {
|
||||||
<span class="badge bg-secondary">{{t('available')}}</span>
|
<span class="badge bg-secondary">{{t('available')}}</span>
|
||||||
|
}
|
||||||
|
</h4>
|
||||||
|
<h6 class="card-subtitle mb-1 mt-1 text-muted">{{t('published-label')}}{{update.publishDate | date: 'short'}}</h6>
|
||||||
|
|
||||||
|
|
||||||
|
<pre class="card-text update-body">
|
||||||
|
<app-read-more [text]="update.updateBody" [maxLength]="500"></app-read-more>
|
||||||
|
</pre>
|
||||||
|
@if (!update.isDocker && (accountService.isAdmin$ | async)) {
|
||||||
|
@if (update.updateVersion === update.currentVersion) {
|
||||||
|
<a href="{{update.updateUrl}}" class="btn disabled btn-{{indx === 0 ? 'primary' : 'secondary'}} float-end" target="_blank" rel="noopener noreferrer">{{t('installed')}}</a>
|
||||||
|
} @else {
|
||||||
|
<a href="{{update.updateUrl}}" class="btn btn-{{indx === 0 ? 'primary' : 'secondary'}} float-end" target="_blank" rel="noopener noreferrer">{{t('download')}}</a>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</h4>
|
</div>
|
||||||
<h6 class="card-subtitle mb-1 mt-1 text-muted">{{t('published-label')}}{{update.publishDate | date: 'short'}}</h6>
|
|
||||||
|
|
||||||
|
|
||||||
<pre class="card-text update-body">
|
|
||||||
<app-read-more [text]="update.updateBody" [maxLength]="500"></app-read-more>
|
|
||||||
</pre>
|
|
||||||
@if (!update.isDocker && (accountService.isAdmin$ | async)) {
|
|
||||||
@if (update.updateVersion === update.currentVersion) {
|
|
||||||
<a href="{{update.updateUrl}}" class="btn disabled btn-{{indx === 0 ? 'primary' : 'secondary'}} float-end" target="_blank" rel="noopener noreferrer">{{t('installed')}}</a>
|
|
||||||
} @else {
|
|
||||||
<a href="{{update.updateUrl}}" class="btn btn-{{indx === 0 ? 'primary' : 'secondary'}} float-end" target="_blank" rel="noopener noreferrer">{{t('download')}}</a>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<app-loading [loading]="isLoading"></app-loading>
|
<app-loading [loading]="isLoading"></app-loading>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="container-fluid" [ngClass]="{'g-0': (navService.sideNavVisibility$ | async) === false}">
|
<div class="" [ngClass]="{'g-0': (navService.sideNavVisibility$ | async) === false}">
|
||||||
<a id="content"></a>
|
<a id="content"></a>
|
||||||
@if (navService.sideNavVisibility$ | async) {
|
@if (navService.sideNavVisibility$ | async) {
|
||||||
<div>
|
<div>
|
||||||
|
@ -4,15 +4,13 @@
|
|||||||
height: calc(var(--vh)* 100 - var(--nav-offset));
|
height: calc(var(--vh)* 100 - var(--nav-offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.companion-bar {
|
.companion-bar {
|
||||||
transition: all var(--side-nav-companion-bar-transistion);
|
transition: all var(--side-nav-companion-bar-transistion);
|
||||||
margin-left: 40px;
|
margin-left: 60px;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
height: calc(var(--vh)* 100 - var(--nav-mobile-offset));
|
height: calc(var(--vh)* 100 - var(--nav-mobile-offset));
|
||||||
padding-right: 10px;
|
scrollbar-gutter: stable;
|
||||||
scrollbar-gutter: stable both-edges;
|
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%);
|
mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%);
|
||||||
-webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%);
|
-webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%);
|
||||||
@ -58,7 +56,6 @@
|
|||||||
|
|
||||||
.companion-bar-content {
|
.companion-bar-content {
|
||||||
margin-left: 190px;
|
margin-left: 190px;
|
||||||
width: calc(100% - 180px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $grid-breakpoints-lg) {
|
@media (max-width: $grid-breakpoints-lg) {
|
||||||
@ -73,7 +70,7 @@
|
|||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: calc(var(--vh)* 100);
|
height: calc(var(--vh)* 100);
|
||||||
padding: 0 10px 0;
|
padding: 0;
|
||||||
|
|
||||||
&.closed {
|
&.closed {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@ -90,6 +87,7 @@
|
|||||||
scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0.1);
|
scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0.1);
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.companion-bar-content {
|
.companion-bar-content {
|
||||||
|
@ -1,31 +1,33 @@
|
|||||||
<ng-container *transloco="let t; read: 'bookmarks'">
|
<div class="main-container container-fluid">
|
||||||
<app-side-nav-companion-bar [hasFilter]="true" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
|
<ng-container *transloco="let t; read: 'bookmarks'">
|
||||||
<h4 title>
|
<app-side-nav-companion-bar [hasFilter]="true" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
|
||||||
{{t('title')}}
|
<h4 title>
|
||||||
</h4>
|
{{t('title')}}
|
||||||
<h5 subtitle>{{t('series-count', {num: series.length | number})}}</h5>
|
</h4>
|
||||||
</app-side-nav-companion-bar>
|
<h5 subtitle>{{t('series-count', {num: series.length | number})}}</h5>
|
||||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
</app-side-nav-companion-bar>
|
||||||
<app-card-detail-layout *ngIf="filter"
|
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||||
[isLoading]="loadingBookmarks"
|
<app-card-detail-layout *ngIf="filter"
|
||||||
[items]="series"
|
[isLoading]="loadingBookmarks"
|
||||||
[filterSettings]="filterSettings"
|
[items]="series"
|
||||||
[trackByIdentity]="trackByIdentity"
|
[filterSettings]="filterSettings"
|
||||||
[refresh]="refresh"
|
[trackByIdentity]="trackByIdentity"
|
||||||
[jumpBarKeys]="jumpbarKeys"
|
[refresh]="refresh"
|
||||||
(applyFilter)="updateFilter($event)"
|
[jumpBarKeys]="jumpbarKeys"
|
||||||
>
|
(applyFilter)="updateFilter($event)"
|
||||||
<ng-template #cardItem let-item let-position="idx">
|
>
|
||||||
<app-card-item [entity]="item" [title]="item.name" [imageUrl]="imageService.getSeriesCoverImage(item.id)"
|
<ng-template #cardItem let-item let-position="idx">
|
||||||
[suppressArchiveWarning]="true" (clicked)="viewBookmarks(item)" [count]="seriesIds[item.id]" [allowSelection]="true"
|
<app-card-item [entity]="item" [title]="item.name" [imageUrl]="imageService.getSeriesCoverImage(item.id)"
|
||||||
[actions]="actions"
|
[suppressArchiveWarning]="true" (clicked)="viewBookmarks(item)" [count]="seriesIds[item.id]" [allowSelection]="true"
|
||||||
[selected]="bulkSelectionService.isCardSelected('bookmark', position)"
|
[actions]="actions"
|
||||||
(selection)="bulkSelectionService.handleCardSelection('bookmark', position, series.length, $event)"
|
[selected]="bulkSelectionService.isCardSelected('bookmark', position)"
|
||||||
></app-card-item>
|
(selection)="bulkSelectionService.handleCardSelection('bookmark', position, series.length, $event)"
|
||||||
</ng-template>
|
></app-card-item>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #noData>
|
<ng-template #noData>
|
||||||
{{t('no-data')}} <a [href]="WikiLink.Bookmarks" rel="noopener noreferrer" target="_blank">{{t('no-data-2')}}<i class="fa fa-external-link-alt ms-1" aria-hidden="true"></i></a>
|
{{t('no-data')}} <a [href]="WikiLink.Bookmarks" rel="noopener noreferrer" target="_blank">{{t('no-data-2')}}<i class="fa fa-external-link-alt ms-1" aria-hidden="true"></i></a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-card-detail-layout>
|
</app-card-detail-layout>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
@ -1,33 +1,35 @@
|
|||||||
<ng-container *transloco="let t; read: 'bulk-operations'">
|
<ng-container *transloco="let t; read: 'bulk-operations'">
|
||||||
@if (bulkSelectionService.selections$ | async; as selectionCount) {
|
@if (bulkSelectionService.selections$ | async; as selectionCount) {
|
||||||
@if (selectionCount > 0) {
|
@if (selectionCount > 0) {
|
||||||
<div class="bulk-select mb-3 {{modalMode ? '' : 'fixed-top'}}" [ngStyle]="{'margin-top': topOffset + 'px'}">
|
<div class="bulk-select-container">
|
||||||
<div class="d-flex justify-content-around align-items-center">
|
<div class="bulk-select mb-3 {{modalMode ? '' : 'fixed-top'}}" [ngStyle]="{'margin-top': topOffset + 'px'}">
|
||||||
|
<div class="d-flex justify-content-around align-items-center">
|
||||||
|
|
||||||
<span class="highlight">
|
<span class="highlight">
|
||||||
<i class="fa fa-check me-1" aria-hidden="true"></i>
|
<i class="fa fa-check me-1" aria-hidden="true"></i>
|
||||||
{{t('items-selected',{num: selectionCount | number})}}
|
{{t('items-selected',{num: selectionCount | number})}}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
@if (hasMarkAsUnread) {
|
@if (hasMarkAsUnread) {
|
||||||
<button class="btn btn-icon" (click)="executeAction(Action.MarkAsUnread)" [ngbTooltip]="t('mark-as-unread')" placement="bottom">
|
<button class="btn btn-icon" (click)="executeAction(Action.MarkAsUnread)" [ngbTooltip]="t('mark-as-unread')" placement="bottom">
|
||||||
<i class="fa-regular fa-circle-check" aria-hidden="true"></i>
|
<i class="fa-regular fa-circle-check" aria-hidden="true"></i>
|
||||||
<span class="visually-hidden">{{t('mark-as-unread')}}</span>
|
<span class="visually-hidden">{{t('mark-as-unread')}}</span>
|
||||||
</button>
|
</button>
|
||||||
}
|
|
||||||
@if (hasMarkAsRead) {
|
|
||||||
<button class="btn btn-icon" (click)="executeAction(Action.MarkAsRead)" [ngbTooltip]="t('mark-as-read')" placement="bottom">
|
|
||||||
<i class="fa-solid fa-circle-check" aria-hidden="true"></i>
|
|
||||||
<span class="visually-hidden">{{t('mark-as-read')}}</span>
|
|
||||||
</button>
|
|
||||||
}
|
}
|
||||||
<app-card-actionables [actions]="actions" labelBy="bulk-actions-header" iconClass="fa-ellipsis-h" (actionHandler)="performAction($event)"></app-card-actionables>
|
@if (hasMarkAsRead) {
|
||||||
</span>
|
<button class="btn btn-icon" (click)="executeAction(Action.MarkAsRead)" [ngbTooltip]="t('mark-as-read')" placement="bottom">
|
||||||
|
<i class="fa-solid fa-circle-check" aria-hidden="true"></i>
|
||||||
|
<span class="visually-hidden">{{t('mark-as-read')}}</span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
<app-card-actionables [actions]="actions" labelBy="bulk-actions-header" iconClass="fa-ellipsis-h" (actionHandler)="performAction($event)"></app-card-actionables>
|
||||||
|
</span>
|
||||||
|
|
||||||
<span id="bulk-actions-header" class="visually-hidden">Bulk Actions</span>
|
<span id="bulk-actions-header" class="visually-hidden">Bulk Actions</span>
|
||||||
|
|
||||||
<button class="btn btn-icon" (click)="bulkSelectionService.deselectAll()"><i class="fa fa-times me-1" aria-hidden="true"></i>{{t('deselect-all')}}</button>
|
<button class="btn btn-icon" (click)="bulkSelectionService.deselectAll()"><i class="fa fa-times me-1" aria-hidden="true"></i>{{t('deselect-all')}}</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
.bulk-select {
|
.bulk-select-container {
|
||||||
background-color: var(--bulk-background-color);
|
position: absolute;
|
||||||
border-bottom: 2px solid var(--primary-color);
|
|
||||||
color: var(--bulk-selection-text-color) !important;
|
|
||||||
|
|
||||||
.btn-icon {
|
.bulk-select {
|
||||||
color: var(--bulk-selection-text-color);
|
background-color: var(--bulk-selection-bg-color);
|
||||||
|
border-bottom: 2px solid var(--primary-color);
|
||||||
|
color: var(--bulk-selection-text-color) !important;
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
color: var(--bulk-selection-text-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight {
|
.highlight {
|
||||||
color: var(--bulk-selection-highlight-text-color) !important;
|
color: var(--bulk-selection-highlight-text-color) !important;
|
||||||
}
|
}
|
||||||
|
@ -48,12 +48,12 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0 10px;
|
padding: 0 5px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
padding: 0 10px 0 5px;
|
padding: 0 5px 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
@ -133,7 +133,7 @@ h2 {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 40px;
|
width: 25px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
ContentChild,
|
ContentChild, DestroyRef,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
HostListener,
|
HostListener,
|
||||||
@ -17,7 +17,7 @@ import {
|
|||||||
TrackByFunction,
|
TrackByFunction,
|
||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {Router} from '@angular/router';
|
import {NavigationEnd, NavigationStart, Router} from '@angular/router';
|
||||||
import {VirtualScrollerComponent, VirtualScrollerModule} from '@iharbeck/ngx-virtual-scroller';
|
import {VirtualScrollerComponent, VirtualScrollerModule} from '@iharbeck/ngx-virtual-scroller';
|
||||||
import {FilterSettings} from 'src/app/metadata-filter/filter-settings';
|
import {FilterSettings} from 'src/app/metadata-filter/filter-settings';
|
||||||
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
|
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
|
||||||
@ -36,6 +36,9 @@ import {MetadataFilterComponent} from "../../metadata-filter/metadata-filter.com
|
|||||||
import {TranslocoDirective} from "@jsverse/transloco";
|
import {TranslocoDirective} from "@jsverse/transloco";
|
||||||
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
|
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
|
||||||
import {SeriesFilterV2} from "../../_models/metadata/v2/series-filter-v2";
|
import {SeriesFilterV2} from "../../_models/metadata/v2/series-filter-v2";
|
||||||
|
import {filter, map} from "rxjs/operators";
|
||||||
|
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||||
|
import {tap} from "rxjs";
|
||||||
|
|
||||||
|
|
||||||
const ANIMATION_TIME_MS = 0;
|
const ANIMATION_TIME_MS = 0;
|
||||||
@ -56,6 +59,7 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges {
|
|||||||
private readonly cdRef = inject(ChangeDetectorRef);
|
private readonly cdRef = inject(ChangeDetectorRef);
|
||||||
private readonly jumpbarService = inject(JumpbarService);
|
private readonly jumpbarService = inject(JumpbarService);
|
||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
protected readonly Breakpoint = Breakpoint;
|
protected readonly Breakpoint = Breakpoint;
|
||||||
|
|
||||||
@ -138,6 +142,14 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges {
|
|||||||
this.virtualScroller.refresh();
|
this.virtualScroller.refresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.router.events.pipe(
|
||||||
|
filter(event => event instanceof NavigationStart),
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
map(evt => evt as NavigationStart),
|
||||||
|
tap(_ => this.tryToSaveJumpKey()),
|
||||||
|
).subscribe();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,7 +77,11 @@
|
|||||||
<span class="card-title" placement="top" id="{{title}}_{{entity.id}}" [ngbTooltip]="tooltipTitle" (click)="handleClick($event)" tabindex="0">
|
<span class="card-title" placement="top" id="{{title}}_{{entity.id}}" [ngbTooltip]="tooltipTitle" (click)="handleClick($event)" tabindex="0">
|
||||||
<app-promoted-icon [promoted]="isPromoted()"></app-promoted-icon>
|
<app-promoted-icon [promoted]="isPromoted()"></app-promoted-icon>
|
||||||
<app-series-format [format]="format"></app-series-format>
|
<app-series-format [format]="format"></app-series-format>
|
||||||
{{title}}
|
@if (linkUrl) {
|
||||||
|
<a class="dark-exempt btn-icon" href="javascript:void(0);" [routerLink]="linkUrl">{{title}}</a>
|
||||||
|
} @else {
|
||||||
|
{{title}}
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
@if (actions && actions.length > 0) {
|
@if (actions && actions.length > 0) {
|
||||||
<span class="card-actions float-end">
|
<span class="card-actions float-end">
|
||||||
|
@ -145,6 +145,10 @@ export class CardItemComponent implements OnInit {
|
|||||||
* Will generate a button to instantly read
|
* Will generate a button to instantly read
|
||||||
*/
|
*/
|
||||||
@Input() hasReadButton = false;
|
@Input() hasReadButton = false;
|
||||||
|
/**
|
||||||
|
* A method that if defined will return the url
|
||||||
|
*/
|
||||||
|
@Input() linkUrl?: string;
|
||||||
/**
|
/**
|
||||||
* Event emitted when item is clicked
|
* Event emitted when item is clicked
|
||||||
*/
|
*/
|
||||||
|
@ -1,36 +1,39 @@
|
|||||||
<ng-container *transloco="let t; read: 'all-collections'">
|
<div class="main-container container-fluid">
|
||||||
<app-side-nav-companion-bar [hasFilter]="false" (filterOpen)="filterOpen.emit($event)">
|
<ng-container *transloco="let t; read: 'all-collections'">
|
||||||
<h4 title>{{t('title')}}</h4>
|
<app-side-nav-companion-bar [hasFilter]="false" (filterOpen)="filterOpen.emit($event)">
|
||||||
<h5 subtitle>{{t('item-count', {num: collections.length | number})}}</h5>
|
<h4 title>{{t('title')}}</h4>
|
||||||
</app-side-nav-companion-bar>
|
<h5 subtitle>{{t('item-count', {num: collections.length | number})}}</h5>
|
||||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
</app-side-nav-companion-bar>
|
||||||
|
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||||
|
|
||||||
<app-card-detail-layout
|
<app-card-detail-layout
|
||||||
[isLoading]="isLoading"
|
[isLoading]="isLoading"
|
||||||
[items]="collections"
|
[items]="collections"
|
||||||
[filterOpen]="filterOpen"
|
[filterOpen]="filterOpen"
|
||||||
[jumpBarKeys]="jumpbarKeys"
|
[jumpBarKeys]="jumpbarKeys"
|
||||||
[trackByIdentity]="trackByIdentity"
|
[trackByIdentity]="trackByIdentity"
|
||||||
>
|
>
|
||||||
<ng-template #cardItem let-item let-position="idx">
|
<ng-template #cardItem let-item let-position="idx">
|
||||||
<app-card-item [title]="item.title" [entity]="item" [actions]="collectionTagActions"
|
<app-card-item [title]="item.title" [entity]="item" [actions]="collectionTagActions"
|
||||||
[imageUrl]="imageService.getCollectionCoverImage(item.id)"
|
[imageUrl]="imageService.getCollectionCoverImage(item.id)"
|
||||||
(clicked)="loadCollection(item)"
|
[linkUrl]="'/collections/' + item.id"
|
||||||
(selection)="bulkSelectionService.handleCardSelection('collection', position, collections.length, $event)"
|
(clicked)="loadCollection(item)"
|
||||||
[selected]="bulkSelectionService.isCardSelected('collection', position)" [allowSelection]="true">
|
(selection)="bulkSelectionService.handleCardSelection('collection', position, collections.length, $event)"
|
||||||
|
[selected]="bulkSelectionService.isCardSelected('collection', position)" [allowSelection]="true">
|
||||||
|
|
||||||
<ng-template #subtitle>
|
<ng-template #subtitle>
|
||||||
<app-collection-owner [collection]="item"></app-collection-owner>
|
<app-collection-owner [collection]="item"></app-collection-owner>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-card-item>
|
</app-card-item>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #noData>
|
<ng-template #noData>
|
||||||
{{t('no-data')}}
|
{{t('no-data')}}
|
||||||
@if(accountService.isAdmin$ | async) {
|
@if(accountService.isAdmin$ | async) {
|
||||||
{{t('create-one-part-1')}} <a [href]="WikiLink.Collections" rel="noopener noreferrer" target="_blank">{{t('create-one-part-2')}}<i class="fa fa-external-link-alt ms-1" aria-hidden="true"></i></a>
|
{{t('create-one-part-1')}} <a [href]="WikiLink.Collections" rel="noopener noreferrer" target="_blank">{{t('create-one-part-2')}}<i class="fa fa-external-link-alt ms-1" aria-hidden="true"></i></a>
|
||||||
}
|
}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-card-detail-layout>
|
</app-card-detail-layout>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
.main-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
@ -1,74 +1,76 @@
|
|||||||
<ng-container *transloco="let t; read: 'collection-detail'">
|
<div class="main-container container-fluid">
|
||||||
<div #companionBar>
|
<ng-container *transloco="let t; read: 'collection-detail'">
|
||||||
<app-side-nav-companion-bar *ngIf="series !== undefined" [hasFilter]="true" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
|
<div #companionBar>
|
||||||
<ng-container title>
|
<app-side-nav-companion-bar *ngIf="series !== undefined" [hasFilter]="true" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
|
||||||
<h4 *ngIf="collectionTag !== undefined">
|
<ng-container title>
|
||||||
{{collectionTag.title}}<span class="ms-1" *ngIf="collectionTag.promoted">(<i aria-hidden="true" class="fa fa-angle-double-up"></i>)</span>
|
<h4 *ngIf="collectionTag !== undefined">
|
||||||
<app-card-actionables [disabled]="actionInProgress" (actionHandler)="performAction($event)" [actions]="collectionTagActions" [labelBy]="collectionTag.title" iconClass="fa-ellipsis-v"></app-card-actionables>
|
{{collectionTag.title}}<span class="ms-1" *ngIf="collectionTag.promoted">(<i aria-hidden="true" class="fa fa-angle-double-up"></i>)</span>
|
||||||
</h4>
|
<app-card-actionables [disabled]="actionInProgress" (actionHandler)="performAction($event)" [actions]="collectionTagActions" [labelBy]="collectionTag.title" iconClass="fa-ellipsis-v"></app-card-actionables>
|
||||||
</ng-container>
|
</h4>
|
||||||
</app-side-nav-companion-bar>
|
</ng-container>
|
||||||
</div>
|
</app-side-nav-companion-bar>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid pt-2" *ngIf="collectionTag !== undefined" #scrollingBlock>
|
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid pt-2" *ngIf="collectionTag !== undefined" #scrollingBlock>
|
||||||
@if (summary.length > 0 || collectionTag.source !== ScrobbleProvider.Kavita) {
|
@if (summary.length > 0 || collectionTag.source !== ScrobbleProvider.Kavita) {
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block">
|
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block">
|
||||||
<app-image [styles]="{'max-width': '481px'}" [imageUrl]="imageService.getCollectionCoverImage(collectionTag.id)"></app-image>
|
<app-image [styles]="{'max-width': '481px'}" [imageUrl]="imageService.getCollectionCoverImage(collectionTag.id)"></app-image>
|
||||||
@if (collectionTag.source !== ScrobbleProvider.Kavita && collectionTag.missingSeriesFromSource !== null
|
@if (collectionTag.source !== ScrobbleProvider.Kavita && collectionTag.missingSeriesFromSource !== null
|
||||||
&& series.length !== collectionTag.totalSourceCount && collectionTag.totalSourceCount > 0) {
|
&& series.length !== collectionTag.totalSourceCount && collectionTag.totalSourceCount > 0) {
|
||||||
<div class="under-image">
|
<div class="under-image">
|
||||||
<app-image [imageUrl]="collectionTag.source | providerImage"
|
<app-image [imageUrl]="collectionTag.source | providerImage"
|
||||||
width="16px" height="16px"
|
width="16px" height="16px"
|
||||||
[ngbTooltip]="collectionTag.source | providerName" tabindex="0"></app-image>
|
[ngbTooltip]="collectionTag.source | providerName" tabindex="0"></app-image>
|
||||||
<span class="ms-2 me-2">{{t('sync-progress', {title: series.length + ' / ' + collectionTag.totalSourceCount})}}</span>
|
<span class="ms-2 me-2">{{t('sync-progress', {title: series.length + ' / ' + collectionTag.totalSourceCount})}}</span>
|
||||||
<i class="fa-solid fa-question-circle" aria-hidden="true" [ngbTooltip]="t('last-sync', {date: collectionTag.lastSyncUtc | date: 'short' | defaultDate })"></i>
|
<i class="fa-solid fa-question-circle" aria-hidden="true" [ngbTooltip]="t('last-sync', {date: collectionTag.lastSyncUtc | date: 'short' | defaultDate })"></i>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-10 col-xs-8 col-sm-6 mt-2">
|
||||||
|
@if (summary.length > 0) {
|
||||||
|
<div class="mb-2">
|
||||||
|
<app-read-more [text]="summary" [maxLength]="(utilityService.activeBreakpoint$ | async)! >= Breakpoint.Desktop ? 585 : 200"></app-read-more>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-10 col-xs-8 col-sm-6 mt-2">
|
}
|
||||||
@if (summary.length > 0) {
|
|
||||||
<div class="mb-2">
|
|
||||||
<app-read-more [text]="summary" [maxLength]="utilityService.getActiveBreakpoint() < Breakpoint.Tablet ? 250 : 600"></app-read-more>
|
|
||||||
</div>
|
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||||
}
|
|
||||||
|
<app-card-detail-layout *ngIf="filter"
|
||||||
|
[header]="t('series-header')"
|
||||||
|
[isLoading]="isLoading"
|
||||||
|
[items]="series"
|
||||||
|
[pagination]="pagination"
|
||||||
|
[filterSettings]="filterSettings"
|
||||||
|
[filterOpen]="filterOpen"
|
||||||
|
[parentScroll]="scrollingBlock"
|
||||||
|
[trackByIdentity]="trackByIdentity"
|
||||||
|
[jumpBarKeys]="jumpbarKeys"
|
||||||
|
(applyFilter)="updateFilter($event)">
|
||||||
|
<ng-template #cardItem let-item let-position="idx">
|
||||||
|
<app-series-card [series]="item" [libraryId]="item.libraryId" (reload)="loadPage()"
|
||||||
|
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)" [selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"
|
||||||
|
></app-series-card>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<div *ngIf="!filterActive && series.length === 0">
|
||||||
|
<ng-template #noData>
|
||||||
|
{{t('no-data')}}
|
||||||
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
<div *ngIf="filterActive && series.length === 0">
|
||||||
|
<ng-template #noData>
|
||||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
{{t('no-data-filtered')}}
|
||||||
|
</ng-template>
|
||||||
<app-card-detail-layout *ngIf="filter"
|
</div>
|
||||||
[header]="t('series-header')"
|
</app-card-detail-layout>
|
||||||
[isLoading]="isLoading"
|
</div>
|
||||||
[items]="series"
|
</ng-container>
|
||||||
[pagination]="pagination"
|
</div>
|
||||||
[filterSettings]="filterSettings"
|
|
||||||
[filterOpen]="filterOpen"
|
|
||||||
[parentScroll]="scrollingBlock"
|
|
||||||
[trackByIdentity]="trackByIdentity"
|
|
||||||
[jumpBarKeys]="jumpbarKeys"
|
|
||||||
(applyFilter)="updateFilter($event)">
|
|
||||||
<ng-template #cardItem let-item let-position="idx">
|
|
||||||
<app-series-card [series]="item" [libraryId]="item.libraryId" (reload)="loadPage()"
|
|
||||||
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)" [selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"
|
|
||||||
></app-series-card>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<div *ngIf="!filterActive && series.length === 0">
|
|
||||||
<ng-template #noData>
|
|
||||||
{{t('no-data')}}
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="filterActive && series.length === 0">
|
|
||||||
<ng-template #noData>
|
|
||||||
{{t('no-data-filtered')}}
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
|
||||||
</app-card-detail-layout>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
@ -1,4 +1,4 @@
|
|||||||
import {DatePipe, DOCUMENT, NgIf, NgStyle} from '@angular/common';
|
import {AsyncPipe, DatePipe, DOCUMENT, NgIf, NgStyle} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
AfterContentChecked,
|
AfterContentChecked,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
@ -67,7 +67,7 @@ import {ProviderNamePipe} from "../../../_pipes/provider-name.pipe";
|
|||||||
styleUrls: ['./collection-detail.component.scss'],
|
styleUrls: ['./collection-detail.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgIf, SideNavCompanionBarComponent, CardActionablesComponent, NgStyle, ImageComponent, ReadMoreComponent, BulkOperationsComponent, CardDetailLayoutComponent, SeriesCardComponent, TranslocoDirective, NgbTooltip, SafeHtmlPipe, TranslocoDatePipe, DatePipe, DefaultDatePipe, ProviderImagePipe, ProviderNamePipe]
|
imports: [NgIf, SideNavCompanionBarComponent, CardActionablesComponent, NgStyle, ImageComponent, ReadMoreComponent, BulkOperationsComponent, CardDetailLayoutComponent, SeriesCardComponent, TranslocoDirective, NgbTooltip, SafeHtmlPipe, TranslocoDatePipe, DatePipe, DefaultDatePipe, ProviderImagePipe, ProviderNamePipe, AsyncPipe]
|
||||||
})
|
})
|
||||||
export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
export class CollectionDetailComponent implements OnInit, AfterContentChecked {
|
||||||
|
|
||||||
|
@ -1,109 +1,104 @@
|
|||||||
|
<div class="main-container">
|
||||||
<app-side-nav-companion-bar></app-side-nav-companion-bar>
|
<app-side-nav-companion-bar></app-side-nav-companion-bar>
|
||||||
|
|
||||||
<ng-container *transloco="let t; read: 'dashboard'">
|
<ng-container *transloco="let t; read: 'dashboard'">
|
||||||
@if (libraries$ | async; as libraries) {
|
@if (libraries$ | async; as libraries) {
|
||||||
@if (libraries.length === 0) {
|
@if (libraries.length === 0) {
|
||||||
@if (accountService.isAdmin$ | async; as isAdmin) {
|
@if (accountService.isAdmin$ | async; as isAdmin) {
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
@if (isAdmin) {
|
@if (isAdmin) {
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
<p>{{t('no-libraries')}} <a routerLink="/settings" [fragment]="SettingsTabId.Libraries">{{t('server-settings-link')}}</a>.</p>
|
<p>{{t('no-libraries')}} <a routerLink="/settings" [fragment]="SettingsTabId.Libraries">{{t('server-settings-link')}}</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
<p>{{t('not-granted')}}</p>
|
<p>{{t('not-granted')}}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@for(stream of streams; track stream.id) {
|
|
||||||
@switch (stream.streamType) {
|
|
||||||
@case (StreamType.OnDeck) {
|
|
||||||
<ng-container [ngTemplateOutlet]="onDeck" [ngTemplateOutletContext]="{ stream: stream }"></ng-container>
|
|
||||||
}
|
|
||||||
@case (StreamType.RecentlyUpdated) {
|
|
||||||
<ng-container [ngTemplateOutlet]="recentlyUpdated" [ngTemplateOutletContext]="{ stream: stream }"></ng-container>
|
|
||||||
}
|
|
||||||
@case (StreamType.NewlyAdded) {
|
|
||||||
<ng-container [ngTemplateOutlet]="newlyUpdated" [ngTemplateOutletContext]="{ stream: stream }"></ng-container>
|
|
||||||
}
|
|
||||||
@case (StreamType.SmartFilter) {
|
|
||||||
<ng-container [ngTemplateOutlet]="smartFilter" [ngTemplateOutletContext]="{ stream: stream }"></ng-container>
|
|
||||||
}
|
|
||||||
@case (StreamType.MoreInGenre) {
|
|
||||||
<ng-container [ngTemplateOutlet]="moreInGenre" [ngTemplateOutletContext]="{ stream: stream }"></ng-container>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@for(stream of streams; track stream.id) {
|
||||||
<ng-template #smartFilter let-stream: DashboardStream>
|
@switch (stream.streamType) {
|
||||||
@if(stream.api | async; as data) {
|
@case (StreamType.OnDeck) {
|
||||||
<app-carousel-reel [items]="data" [title]="stream.name" (sectionClick)="handleFilterSectionClick(stream)">
|
<ng-container [ngTemplateOutlet]="onDeck" [ngTemplateOutletContext]="{ stream: stream }"></ng-container>
|
||||||
<ng-template #carouselItem let-item>
|
}
|
||||||
<app-series-card [series]="item" [libraryId]="item.libraryId"
|
@case (StreamType.RecentlyUpdated) {
|
||||||
(reload)="reloadStream(item.id)" (dataChanged)="reloadStream(item.id)"></app-series-card>
|
<ng-container [ngTemplateOutlet]="recentlyUpdated" [ngTemplateOutletContext]="{ stream: stream }"></ng-container>
|
||||||
</ng-template>
|
}
|
||||||
</app-carousel-reel>
|
@case (StreamType.NewlyAdded) {
|
||||||
|
<ng-container [ngTemplateOutlet]="newlyUpdated" [ngTemplateOutletContext]="{ stream: stream }"></ng-container>
|
||||||
|
}
|
||||||
|
@case (StreamType.SmartFilter) {
|
||||||
|
<ng-container [ngTemplateOutlet]="smartFilter" [ngTemplateOutletContext]="{ stream: stream }"></ng-container>
|
||||||
|
}
|
||||||
|
@case (StreamType.MoreInGenre) {
|
||||||
|
<ng-container [ngTemplateOutlet]="moreInGenre" [ngTemplateOutletContext]="{ stream: stream }"></ng-container>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #onDeck let-stream: DashboardStream>
|
|
||||||
@if(stream.api | async; as data) {
|
|
||||||
<app-carousel-reel [items]="data" [title]="t('on-deck-title')" (sectionClick)="handleSectionClick(StreamId.OnDeck)">
|
|
||||||
<ng-template #carouselItem let-item>
|
|
||||||
<app-series-card [series]="item" [libraryId]="item.libraryId" [isOnDeck]="true"
|
|
||||||
(reload)="reloadStream(stream.id, true)" (dataChanged)="reloadStream(stream.id)"></app-series-card>
|
|
||||||
</ng-template>
|
|
||||||
</app-carousel-reel>
|
|
||||||
}
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #recentlyUpdated let-stream: DashboardStream>
|
<ng-template #smartFilter let-stream: DashboardStream>
|
||||||
@if(stream.api | async; as data) {
|
@if(stream.api | async; as data) {
|
||||||
<app-carousel-reel [items]="data" [title]="t('recently-updated-title')" (sectionClick)="handleSectionClick(StreamId.RecentlyUpdatedSeries)">
|
<app-carousel-reel [items]="data" [title]="stream.name" (sectionClick)="handleFilterSectionClick(stream)">
|
||||||
<ng-template #carouselItem let-item>
|
<ng-template #carouselItem let-item>
|
||||||
<app-card-item [entity]="item" [title]="item.seriesName" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"
|
<app-series-card [series]="item" [libraryId]="item.libraryId"
|
||||||
[suppressArchiveWarning]="true" (clicked)="handleRecentlyAddedChapterClick(item)" [count]="item.count"
|
(reload)="reloadStream(item.id)" (dataChanged)="reloadStream(item.id)"></app-series-card>
|
||||||
[showReadButton]="true" (readClicked)="handleRecentlyAddedChapterRead(item)">
|
</ng-template>
|
||||||
|
</app-carousel-reel>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
</app-card-item>
|
<ng-template #onDeck let-stream: DashboardStream>
|
||||||
</ng-template>
|
@if(stream.api | async; as data) {
|
||||||
</app-carousel-reel>
|
<app-carousel-reel [items]="data" [title]="t('on-deck-title')" (sectionClick)="handleSectionClick(StreamId.OnDeck)">
|
||||||
|
<ng-template #carouselItem let-item>
|
||||||
|
<app-series-card [series]="item" [libraryId]="item.libraryId" [isOnDeck]="true"
|
||||||
|
(reload)="reloadStream(stream.id, true)" (dataChanged)="reloadStream(stream.id)"></app-series-card>
|
||||||
|
</ng-template>
|
||||||
|
</app-carousel-reel>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #itemOverlay let-item="item">
|
<ng-template #recentlyUpdated let-stream: DashboardStream>
|
||||||
<span (click)="handleRecentlyAddedChapterClick(item)">
|
@if(stream.api | async; as data) {
|
||||||
<div>
|
<app-carousel-reel [items]="data" [title]="t('recently-updated-title')" (sectionClick)="handleSectionClick(StreamId.RecentlyUpdatedSeries)">
|
||||||
<i class="fa-solid fa-book" aria-hidden="true"></i>
|
<ng-template #carouselItem let-item>
|
||||||
</div>
|
<app-card-item [entity]="item" [title]="item.seriesName" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"
|
||||||
</span>
|
[suppressArchiveWarning]="true" [count]="item.count" (clicked)="handleRecentlyAddedChapterClick(item)"
|
||||||
</ng-template>
|
[showReadButton]="true" (readClicked)="handleRecentlyAddedChapterRead(item)"
|
||||||
}
|
[linkUrl]="'/library/' + item.libraryId + '/series/' + item.seriesId">
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #newlyUpdated let-stream: DashboardStream>
|
</app-card-item>
|
||||||
@if(stream.api | async; as data) {
|
</ng-template>
|
||||||
<app-carousel-reel [items]="data" [title]="t('recently-added-title')" (sectionClick)="handleSectionClick(StreamId.NewlyAddedSeries)">
|
</app-carousel-reel>
|
||||||
<ng-template #carouselItem let-item>
|
}
|
||||||
<app-series-card [series]="item" [libraryId]="item.libraryId" (dataChanged)="reloadStream(stream.id)"></app-series-card>
|
</ng-template>
|
||||||
</ng-template>
|
|
||||||
</app-carousel-reel>
|
|
||||||
}
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #moreInGenre let-stream: DashboardStream>
|
<ng-template #newlyUpdated let-stream: DashboardStream>
|
||||||
@if(stream.api | async; as data) {
|
@if(stream.api | async; as data) {
|
||||||
<app-carousel-reel [items]="data" [title]="t('more-in-genre-title', {genre: genre?.title})" (sectionClick)="handleSectionClick(StreamId.MoreInGenre)">
|
<app-carousel-reel [items]="data" [title]="t('recently-added-title')" (sectionClick)="handleSectionClick(StreamId.NewlyAddedSeries)">
|
||||||
<ng-template #carouselItem let-item>
|
<ng-template #carouselItem let-item>
|
||||||
<app-series-card [series]="item" [libraryId]="item.libraryId" (dataChanged)="reloadStream(stream.id)"></app-series-card>
|
<app-series-card [series]="item" [libraryId]="item.libraryId" (dataChanged)="reloadStream(stream.id)"></app-series-card>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-carousel-reel>
|
</app-carousel-reel>
|
||||||
}
|
}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
}
|
|
||||||
|
|
||||||
<app-loading [loading]="isLoadingDashboard || (streamCount !== streamsLoaded)"></app-loading>
|
<ng-template #moreInGenre let-stream: DashboardStream>
|
||||||
</ng-container>
|
@if(stream.api | async; as data) {
|
||||||
|
<app-carousel-reel [items]="data" [title]="t('more-in-genre-title', {genre: genre?.title})" (sectionClick)="handleSectionClick(StreamId.MoreInGenre)">
|
||||||
|
<ng-template #carouselItem let-item>
|
||||||
|
<app-series-card [series]="item" [libraryId]="item.libraryId" (dataChanged)="reloadStream(stream.id)"></app-series-card>
|
||||||
|
</ng-template>
|
||||||
|
</app-carousel-reel>
|
||||||
|
}
|
||||||
|
</ng-template>
|
||||||
|
}
|
||||||
|
|
||||||
|
<app-loading [loading]="isLoadingDashboard || (streamCount !== streamsLoaded)"></app-loading>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
.main-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 0 0 0 10px;
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core';
|
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core';
|
||||||
import {Title} from '@angular/platform-browser';
|
import {Title} from '@angular/platform-browser';
|
||||||
import {Router, RouterLink} from '@angular/router';
|
import {Router, RouterLink} from '@angular/router';
|
||||||
import {Observable, of, ReplaySubject, Subject, switchMap} from 'rxjs';
|
import {Observable, ReplaySubject, Subject, switchMap} from 'rxjs';
|
||||||
import {debounceTime, map, shareReplay, take, tap, throttleTime} from 'rxjs/operators';
|
import {debounceTime, map, shareReplay, take, tap, throttleTime} from 'rxjs/operators';
|
||||||
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
|
import {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service';
|
||||||
import {Library} from 'src/app/_models/library/library';
|
import {Library} from 'src/app/_models/library/library';
|
||||||
@ -32,7 +32,6 @@ import {StreamType} from "../../_models/dashboard/stream-type.enum";
|
|||||||
import {LoadingComponent} from "../../shared/loading/loading.component";
|
import {LoadingComponent} from "../../shared/loading/loading.component";
|
||||||
import {ScrobbleProvider, ScrobblingService} from "../../_services/scrobbling.service";
|
import {ScrobbleProvider, ScrobblingService} from "../../_services/scrobbling.service";
|
||||||
import {ToastrService} from "ngx-toastr";
|
import {ToastrService} from "ngx-toastr";
|
||||||
import {ServerService} from "../../_services/server.service";
|
|
||||||
import {SettingsTabId} from "../../sidenav/preference-nav/preference-nav.component";
|
import {SettingsTabId} from "../../sidenav/preference-nav/preference-nav.component";
|
||||||
import {ReaderService} from "../../_services/reader.service";
|
import {ReaderService} from "../../_services/reader.service";
|
||||||
|
|
||||||
|
@ -1,36 +1,38 @@
|
|||||||
<ng-container *transloco="let t">
|
<div class="main-container container-fluid">
|
||||||
<app-side-nav-companion-bar [hasFilter]="true" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
|
<ng-container *transloco="let t">
|
||||||
<h4 title>
|
<app-side-nav-companion-bar [hasFilter]="true" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
|
||||||
<span>{{libraryName}}</span>
|
<h4 title>
|
||||||
<app-card-actionables [actions]="actions" (actionHandler)="performAction($event)"></app-card-actionables>
|
<span>{{libraryName}}</span>
|
||||||
</h4>
|
<app-card-actionables [actions]="actions" (actionHandler)="performAction($event)"></app-card-actionables>
|
||||||
@if (active.fragment === '') {
|
</h4>
|
||||||
<h5 subtitle class="subtitle-with-actionables">{{t('common.series-count', {num: pagination.totalItems | number})}} </h5>
|
@if (active.fragment === '') {
|
||||||
|
<h5 subtitle class="subtitle-with-actionables">{{t('common.series-count', {num: pagination.totalItems | number})}} </h5>
|
||||||
|
}
|
||||||
|
|
||||||
|
</app-side-nav-companion-bar>
|
||||||
|
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||||
|
<app-loading [absolute]="true" [loading]="bulkLoader"></app-loading>
|
||||||
|
@if (filter) {
|
||||||
|
<app-card-detail-layout
|
||||||
|
[isLoading]="loadingSeries"
|
||||||
|
[items]="series"
|
||||||
|
[pagination]="pagination"
|
||||||
|
[filterSettings]="filterSettings"
|
||||||
|
[trackByIdentity]="trackByIdentity"
|
||||||
|
[filterOpen]="filterOpen"
|
||||||
|
[jumpBarKeys]="jumpKeys"
|
||||||
|
[refresh]="refresh"
|
||||||
|
(applyFilter)="updateFilter($event)"
|
||||||
|
>
|
||||||
|
<ng-template #cardItem let-item let-position="idx">
|
||||||
|
<app-series-card [series]="item" [libraryId]="libraryId" [suppressLibraryLink]="true" (reload)="loadPage()"
|
||||||
|
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)"
|
||||||
|
[selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"></app-series-card>
|
||||||
|
</ng-template>
|
||||||
|
</app-card-detail-layout>
|
||||||
}
|
}
|
||||||
|
|
||||||
</app-side-nav-companion-bar>
|
|
||||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
|
||||||
<app-loading [absolute]="true" [loading]="bulkLoader"></app-loading>
|
|
||||||
@if (filter) {
|
|
||||||
<app-card-detail-layout
|
|
||||||
[isLoading]="loadingSeries"
|
|
||||||
[items]="series"
|
|
||||||
[pagination]="pagination"
|
|
||||||
[filterSettings]="filterSettings"
|
|
||||||
[trackByIdentity]="trackByIdentity"
|
|
||||||
[filterOpen]="filterOpen"
|
|
||||||
[jumpBarKeys]="jumpKeys"
|
|
||||||
[refresh]="refresh"
|
|
||||||
(applyFilter)="updateFilter($event)"
|
|
||||||
>
|
|
||||||
<ng-template #cardItem let-item let-position="idx">
|
|
||||||
<app-series-card [series]="item" [libraryId]="libraryId" [suppressLibraryLink]="true" (reload)="loadPage()"
|
|
||||||
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)"
|
|
||||||
[selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"></app-series-card>
|
|
||||||
</ng-template>
|
|
||||||
</app-card-detail-layout>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</div>
|
||||||
|
@ -8,3 +8,8 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
@ -31,7 +31,7 @@
|
|||||||
>
|
>
|
||||||
|
|
||||||
<ng-template #libraryTemplate let-item>
|
<ng-template #libraryTemplate let-item>
|
||||||
<div style="display: flex;padding: 5px;" (click)="clickLibraryResult(item)">
|
<div class="clickable" style="display: flex;padding: 5px;" (click)="clickLibraryResult(item)">
|
||||||
<div class="ms-1">
|
<div class="ms-1">
|
||||||
<span>{{item.name}}</span>
|
<span>{{item.name}}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -39,7 +39,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #seriesTemplate let-item>
|
<ng-template #seriesTemplate let-item>
|
||||||
<div style="display: flex;padding: 5px;" (click)="clickSeriesSearchResult(item)">
|
<div class="clickable" style="display: flex;padding: 5px;" (click)="clickSeriesSearchResult(item)">
|
||||||
<div style="width: 24px" class="me-1">
|
<div style="width: 24px" class="me-1">
|
||||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"></app-image>
|
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"></app-image>
|
||||||
</div>
|
</div>
|
||||||
@ -58,7 +58,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #bookmarkTemplate let-item>
|
<ng-template #bookmarkTemplate let-item>
|
||||||
<div style="display: flex;padding: 5px;" (click)="clickBookmarkSearchResult(item)">
|
<div class="clickable" style="display: flex;padding: 5px;" (click)="clickBookmarkSearchResult(item)">
|
||||||
<div style="width: 24px" class="me-1">
|
<div style="width: 24px" class="me-1">
|
||||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"></app-image>
|
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"></app-image>
|
||||||
</div>
|
</div>
|
||||||
@ -77,7 +77,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #collectionTemplate let-item>
|
<ng-template #collectionTemplate let-item>
|
||||||
<div style="display: flex;padding: 5px;" (click)="clickCollectionSearchResult(item)">
|
<div class="clickable" style="display: flex;padding: 5px;" (click)="clickCollectionSearchResult(item)">
|
||||||
<div style="width: 24px" class="me-1">
|
<div style="width: 24px" class="me-1">
|
||||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getCollectionCoverImage(item.id)"></app-image>
|
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getCollectionCoverImage(item.id)"></app-image>
|
||||||
</div>
|
</div>
|
||||||
@ -92,7 +92,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #readingListTemplate let-item>
|
<ng-template #readingListTemplate let-item>
|
||||||
<div style="display: flex;padding: 5px;" (click)="clickReadingListSearchResult(item)">
|
<div class="clickable" style="display: flex;padding: 5px;" (click)="clickReadingListSearchResult(item)">
|
||||||
<div class="ms-1">
|
<div class="ms-1">
|
||||||
<span>{{item.title}}</span>
|
<span>{{item.title}}</span>
|
||||||
<app-promoted-icon [promoted]="item.promoted"></app-promoted-icon>
|
<app-promoted-icon [promoted]="item.promoted"></app-promoted-icon>
|
||||||
@ -101,7 +101,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #tagTemplate let-item>
|
<ng-template #tagTemplate let-item>
|
||||||
<div style="display: flex;padding: 5px;" (click)="goToOther(FilterField.Tags, item.id)">
|
<div class="clickable" style="display: flex;padding: 5px;" (click)="goToOther(FilterField.Tags, item.id)">
|
||||||
<div class="ms-1">
|
<div class="ms-1">
|
||||||
<span>{{item.title}}</span>
|
<span>{{item.title}}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -141,7 +141,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #fileTemplate let-item>
|
<ng-template #fileTemplate let-item>
|
||||||
<div style="display: flex;padding: 5px;" (click)="clickFileSearchResult(item)">
|
<div class="clickable" style="display: flex;padding: 5px;" (click)="clickFileSearchResult(item)">
|
||||||
<div class="ms-1">
|
<div class="ms-1">
|
||||||
<app-series-format [format]="item.format"></app-series-format>
|
<app-series-format [format]="item.format"></app-series-format>
|
||||||
<span>{{item.filePath}}</span>
|
<span>{{item.filePath}}</span>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Directive, ElementRef, EventEmitter, NgZone, OnDestroy, OnInit, Output } from '@angular/core';
|
import {Directive, ElementRef, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output} from '@angular/core';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { createSwipeSubscription, SwipeEvent } from './ag-swipe.core';
|
import {createSwipeSubscription, SwipeDirection, SwipeEvent, SwipeStartEvent} from './ag-swipe.core';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[ngSwipe]',
|
selector: '[ngSwipe]',
|
||||||
@ -9,8 +9,13 @@ import { createSwipeSubscription, SwipeEvent } from './ag-swipe.core';
|
|||||||
export class SwipeDirective implements OnInit, OnDestroy {
|
export class SwipeDirective implements OnInit, OnDestroy {
|
||||||
private swipeSubscription: Subscription | undefined;
|
private swipeSubscription: Subscription | undefined;
|
||||||
|
|
||||||
|
@Input() restrictSwipeToLeftSide: boolean = false;
|
||||||
@Output() swipeMove: EventEmitter<SwipeEvent> = new EventEmitter<SwipeEvent>();
|
@Output() swipeMove: EventEmitter<SwipeEvent> = new EventEmitter<SwipeEvent>();
|
||||||
@Output() swipeEnd: EventEmitter<SwipeEvent> = new EventEmitter<SwipeEvent>();
|
@Output() swipeEnd: EventEmitter<SwipeEvent> = new EventEmitter<SwipeEvent>();
|
||||||
|
@Output() swipeLeft: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
@Output() swipeRight: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
@Output() swipeUp: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
@Output() swipeDown: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private elementRef: ElementRef,
|
private elementRef: ElementRef,
|
||||||
@ -22,12 +27,49 @@ export class SwipeDirective implements OnInit, OnDestroy {
|
|||||||
this.swipeSubscription = createSwipeSubscription({
|
this.swipeSubscription = createSwipeSubscription({
|
||||||
domElement: this.elementRef.nativeElement,
|
domElement: this.elementRef.nativeElement,
|
||||||
onSwipeMove: (swipeMoveEvent: SwipeEvent) => this.swipeMove.emit(swipeMoveEvent),
|
onSwipeMove: (swipeMoveEvent: SwipeEvent) => this.swipeMove.emit(swipeMoveEvent),
|
||||||
onSwipeEnd: (swipeEndEvent: SwipeEvent) => this.swipeEnd.emit(swipeEndEvent)
|
onSwipeEnd: (swipeEndEvent: SwipeEvent) => {
|
||||||
|
if (this.isSwipeWithinRestrictedArea(swipeEndEvent)) {
|
||||||
|
this.swipeEnd.emit(swipeEndEvent);
|
||||||
|
this.detectSwipeDirection(swipeEndEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isSwipeWithinRestrictedArea(swipeEvent: SwipeEvent): boolean {
|
||||||
|
if (!this.restrictSwipeToLeftSide) return true; // If restriction is disabled, allow all swipes
|
||||||
|
|
||||||
|
const elementRect = this.elementRef.nativeElement.getBoundingClientRect();
|
||||||
|
const touchAreaWidth = elementRect.width * 0.3; // Define the left area (30% of the element's width)
|
||||||
|
|
||||||
|
// Assuming swipeEvent includes the starting coordinates; you may need to adjust this logic
|
||||||
|
if (swipeEvent.direction === SwipeDirection.X && Math.abs(swipeEvent.distance) < touchAreaWidth) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private detectSwipeDirection(swipeEvent: SwipeEvent) {
|
||||||
|
if (swipeEvent.direction === SwipeDirection.X) {
|
||||||
|
if (swipeEvent.distance > 0) {
|
||||||
|
this.swipeRight.emit();
|
||||||
|
} else {
|
||||||
|
this.swipeLeft.emit();
|
||||||
|
}
|
||||||
|
} else if (swipeEvent.direction === SwipeDirection.Y) {
|
||||||
|
if (swipeEvent.distance > 0) {
|
||||||
|
this.swipeDown.emit();
|
||||||
|
} else {
|
||||||
|
this.swipeUp.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.swipeSubscription?.unsubscribe?.();
|
this.swipeSubscription?.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,166 +1,168 @@
|
|||||||
<ng-container *transloco="let t; read: 'reading-list-detail'">
|
<div class="main-container container-fluid">
|
||||||
<app-side-nav-companion-bar [hasExtras]="readingList !== undefined" [extraDrawer]="extrasDrawer">
|
<ng-container *transloco="let t; read: 'reading-list-detail'">
|
||||||
<h4 title>
|
<app-side-nav-companion-bar [hasExtras]="readingList !== undefined" [extraDrawer]="extrasDrawer">
|
||||||
{{readingList?.title}}
|
<h4 title>
|
||||||
@if (readingList?.promoted) {
|
{{readingList?.title}}
|
||||||
<span class="ms-1">(<i class="fa fa-angle-double-up" aria-hidden="true"></i>)</span>
|
@if (readingList?.promoted) {
|
||||||
}
|
<span class="ms-1">(<i class="fa fa-angle-double-up" aria-hidden="true"></i>)</span>
|
||||||
@if (actions.length > 0) {
|
}
|
||||||
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [attr.aria-labelledby]="readingList?.title"></app-card-actionables>
|
@if (actions.length > 0) {
|
||||||
}
|
<app-card-actionables (actionHandler)="performAction($event)" [actions]="actions" [attr.aria-labelledby]="readingList?.title"></app-card-actionables>
|
||||||
</h4>
|
}
|
||||||
<h5 subtitle class="subtitle-with-actionables">{{t('item-count', {num: items.length | number})}}</h5>
|
</h4>
|
||||||
|
<h5 subtitle class="subtitle-with-actionables">{{t('item-count', {num: items.length | number})}}</h5>
|
||||||
|
|
||||||
<ng-template #extrasDrawer let-offcanvas>
|
<ng-template #extrasDrawer let-offcanvas>
|
||||||
@if (readingList) {
|
@if (readingList) {
|
||||||
<div>
|
<div>
|
||||||
<div class="offcanvas-header">
|
<div class="offcanvas-header">
|
||||||
<h4 class="offcanvas-title" id="offcanvas-basic-title">{{t('page-settings-title')}}</h4>
|
<h4 class="offcanvas-title" id="offcanvas-basic-title">{{t('page-settings-title')}}</h4>
|
||||||
<button type="button" class="btn-close" aria-label="Close" (click)="offcanvas.dismiss()"></button>
|
<button type="button" class="btn-close" aria-label="Close" (click)="offcanvas.dismiss()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="offcanvas-body">
|
||||||
|
<div class="row g-0">
|
||||||
|
<div class="col-md-12 col-sm-12 pe-2 mb-3">
|
||||||
|
<button class="btn btn-danger" (click)="removeRead()" [disabled]="readingList.promoted && !this.isAdmin">
|
||||||
|
<span>
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</span>
|
||||||
|
<span class="read-btn--text"> {{t('remove-read')}}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
@if (!(readingList.promoted && !this.isAdmin)) {
|
||||||
|
<div class="col-auto ms-2 mt-2">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" id="accessibility-mode" [disabled]="this.utilityService.getActiveBreakpoint() < Breakpoint.Tablet" [value]="accessibilityMode" (change)="updateAccessibilityMode()">
|
||||||
|
<label class="form-check-label" for="accessibility-mode">{{t('order-numbers-label')}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="offcanvas-body">
|
}
|
||||||
<div class="row g-0">
|
</ng-template>
|
||||||
<div class="col-md-12 col-sm-12 pe-2 mb-3">
|
</app-side-nav-companion-bar>
|
||||||
<button class="btn btn-danger" (click)="removeRead()" [disabled]="readingList.promoted && !this.isAdmin">
|
|
||||||
<span>
|
|
||||||
<i class="fa fa-check"></i>
|
|
||||||
</span>
|
|
||||||
<span class="read-btn--text"> {{t('remove-read')}}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
@if (!(readingList.promoted && !this.isAdmin)) {
|
@if (readingList) {
|
||||||
<div class="col-auto ms-2 mt-2">
|
<div class="container-fluid mt-2">
|
||||||
<div class="form-check form-check-inline">
|
|
||||||
<input class="form-check-input" type="checkbox" id="accessibility-mode" [disabled]="this.utilityService.getActiveBreakpoint() < Breakpoint.Tablet" [value]="accessibilityMode" (change)="updateAccessibilityMode()">
|
<div class="row mb-2">
|
||||||
<label class="form-check-label" for="accessibility-mode">{{t('order-numbers-label')}}</label>
|
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block">
|
||||||
|
<app-image [styles]="{'max-height': '400px', 'max-width': '300px'}" [imageUrl]="imageService.getReadingListCoverImage(readingList.id)"></app-image>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-10 col-xs-8 col-sm-6 mt-2">
|
||||||
|
<div class="row g-0 mb-3">
|
||||||
|
<div class="col-auto me-2">
|
||||||
|
<!-- Action row-->
|
||||||
|
<div class="btn-group me-3">
|
||||||
|
<button type="button" class="btn btn-primary" (click)="continue()">
|
||||||
|
<span>
|
||||||
|
<i class="fa fa-book-open me-1" aria-hidden="true"></i>
|
||||||
|
<span class="read-btn--text">{{t('continue')}}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div class="btn-group" ngbDropdown role="group" [attr.aria-label]="t('read-options-alt')">
|
||||||
|
<button type="button" class="btn btn-primary dropdown-toggle-split" ngbDropdownToggle></button>
|
||||||
|
<div class="dropdown-menu" ngbDropdownMenu>
|
||||||
|
<button ngbDropdownItem (click)="read()">
|
||||||
|
<span>
|
||||||
|
<i class="fa fa-book" aria-hidden="true"></i>
|
||||||
|
<span class="read-btn--text"> {{t('read')}}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button ngbDropdownItem (click)="continue(true)">
|
||||||
|
<span>
|
||||||
|
<i class="fa fa-book-open me-1" aria-hidden="true"></i>
|
||||||
|
<span class="read-btn--text">{{t('continue')}}</span>
|
||||||
|
(<i class="fa fa-glasses ms-1" aria-hidden="true"></i>)
|
||||||
|
<span class="visually-hidden">{{t('incognito-alt')}}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button ngbDropdownItem (click)="read(true)">
|
||||||
|
<span>
|
||||||
|
<i class="fa fa-book me-1" aria-hidden="true"></i>
|
||||||
|
<span class="read-btn--text"> {{t('read')}}</span>
|
||||||
|
(<i class="fa fa-glasses ms-1" aria-hidden="true"></i>)
|
||||||
|
<span class="visually-hidden">{{t('incognito-alt')}}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</ng-template>
|
|
||||||
</app-side-nav-companion-bar>
|
|
||||||
|
|
||||||
@if (readingList) {
|
|
||||||
<div class="container-fluid mt-2">
|
|
||||||
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="col-md-2 col-xs-4 col-sm-6 d-none d-sm-block">
|
|
||||||
<app-image [styles]="{'max-height': '400px', 'max-width': '300px'}" [imageUrl]="imageService.getReadingListCoverImage(readingList.id)"></app-image>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-10 col-xs-8 col-sm-6 mt-2">
|
|
||||||
<div class="row g-0 mb-3">
|
|
||||||
<div class="col-auto me-2">
|
|
||||||
<!-- Action row-->
|
|
||||||
<div class="btn-group me-3">
|
|
||||||
<button type="button" class="btn btn-primary" (click)="continue()">
|
|
||||||
<span>
|
|
||||||
<i class="fa fa-book-open me-1" aria-hidden="true"></i>
|
|
||||||
<span class="read-btn--text">{{t('continue')}}</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<div class="btn-group" ngbDropdown role="group" [attr.aria-label]="t('read-options-alt')">
|
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle-split" ngbDropdownToggle></button>
|
|
||||||
<div class="dropdown-menu" ngbDropdownMenu>
|
|
||||||
<button ngbDropdownItem (click)="read()">
|
|
||||||
<span>
|
|
||||||
<i class="fa fa-book" aria-hidden="true"></i>
|
|
||||||
<span class="read-btn--text"> {{t('read')}}</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button ngbDropdownItem (click)="continue(true)">
|
|
||||||
<span>
|
|
||||||
<i class="fa fa-book-open me-1" aria-hidden="true"></i>
|
|
||||||
<span class="read-btn--text">{{t('continue')}}</span>
|
|
||||||
(<i class="fa fa-glasses ms-1" aria-hidden="true"></i>)
|
|
||||||
<span class="visually-hidden">{{t('incognito-alt')}}</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button ngbDropdownItem (click)="read(true)">
|
|
||||||
<span>
|
|
||||||
<i class="fa fa-book me-1" aria-hidden="true"></i>
|
|
||||||
<span class="read-btn--text"> {{t('read')}}</span>
|
|
||||||
(<i class="fa fa-glasses ms-1" aria-hidden="true"></i>)
|
|
||||||
<span class="visually-hidden">{{t('incognito-alt')}}</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (readingList.startingYear !== 0) {
|
@if (readingList.startingYear !== 0) {
|
||||||
<div class="row g-0 mt-2">
|
<div class="row g-0 mt-2">
|
||||||
<h4 class="reading-list-years">
|
<h4 class="reading-list-years">
|
||||||
@if (readingList.startingMonth > 0) {
|
@if (readingList.startingMonth > 0) {
|
||||||
{{(readingList.startingMonth +'/01/2020')| date:'MMM'}}
|
{{(readingList.startingMonth +'/01/2020')| date:'MMM'}}
|
||||||
}
|
|
||||||
@if (readingList.startingMonth > 0 && readingList.startingYear > 0) {
|
|
||||||
,
|
|
||||||
}
|
|
||||||
@if (readingList.startingYear > 0) {
|
|
||||||
{{readingList.startingYear}}
|
|
||||||
}
|
|
||||||
—
|
|
||||||
@if (readingList.endingYear > 0) {
|
|
||||||
@if (readingList.endingMonth > 0) {
|
|
||||||
{{(readingList.endingMonth +'/01/2020')| date:'MMM'}}
|
|
||||||
}
|
}
|
||||||
@if (readingList.endingMonth > 0 && readingList.endingYear > 0) {
|
@if (readingList.startingMonth > 0 && readingList.startingYear > 0) {
|
||||||
,
|
,
|
||||||
}
|
}
|
||||||
@if (readingList.endingYear > 0) {
|
@if (readingList.startingYear > 0) {
|
||||||
{{readingList.endingYear}}
|
{{readingList.startingYear}}
|
||||||
}
|
}
|
||||||
}
|
—
|
||||||
</h4>
|
@if (readingList.endingYear > 0) {
|
||||||
</div>
|
@if (readingList.endingMonth > 0) {
|
||||||
}
|
{{(readingList.endingMonth +'/01/2020')| date:'MMM'}}
|
||||||
|
}
|
||||||
|
@if (readingList.endingMonth > 0 && readingList.endingYear > 0) {
|
||||||
<!-- Summary row-->
|
,
|
||||||
<div class="row g-0 mt-2">
|
}
|
||||||
<app-read-more [text]="readingListSummary" [maxLength]="250"></app-read-more>
|
@if (readingList.endingYear > 0) {
|
||||||
</div>
|
{{readingList.endingYear}}
|
||||||
|
}
|
||||||
@if (characters$ | async; as characters) {
|
}
|
||||||
@if (characters && characters.length > 0) {
|
</h4>
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="row">
|
|
||||||
<h5>{{t('characters-title')}}</h5>
|
|
||||||
<app-badge-expander [items]="characters">
|
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx">
|
|
||||||
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="goToCharacter(item)">{{item.name}}</a>
|
|
||||||
</ng-template>
|
|
||||||
</app-badge-expander>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Summary row-->
|
||||||
|
<div class="row g-0 mt-2">
|
||||||
|
<app-read-more [text]="readingListSummary" [maxLength]="(utilityService.activeBreakpoint$ | async)! >= Breakpoint.Desktop ? 585 : 200"></app-read-more>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (characters$ | async; as characters) {
|
||||||
|
@if (characters && characters.length > 0) {
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="row">
|
||||||
|
<h5>{{t('characters-title')}}</h5>
|
||||||
|
<app-badge-expander [items]="characters">
|
||||||
|
<ng-template #badgeExpanderItem let-item let-position="idx">
|
||||||
|
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="goToCharacter(item)">{{item.name}}</a>
|
||||||
|
</ng-template>
|
||||||
|
</app-badge-expander>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-1 scroll-container" #scrollingBlock>
|
||||||
|
@if (items.length === 0 && !isLoading) {
|
||||||
|
<div class="mx-auto" style="width: 200px;">
|
||||||
|
{{t('no-data')}}
|
||||||
|
</div>
|
||||||
|
} @else if(isLoading) {
|
||||||
|
<app-loading [loading]="isLoading"></app-loading>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<app-draggable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" [accessibilityMode]="accessibilityMode"
|
||||||
|
[showRemoveButton]="false">
|
||||||
|
<ng-template #draggableItem let-item let-position="idx">
|
||||||
|
<app-reading-list-item [ngClass]="{'content-container': items.length < 100, 'non-virtualized-container': items.length >= 100}" [item]="item" [position]="position" [libraryTypes]="libraryTypes"
|
||||||
|
[promoted]="item.promoted" (read)="readChapter($event)" (remove)="itemRemoved($event, position)"></app-reading-list-item>
|
||||||
|
</ng-template>
|
||||||
|
</app-draggable-ordered-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
<div class="row mb-1 scroll-container" #scrollingBlock>
|
</ng-container>
|
||||||
@if (items.length === 0 && !isLoading) {
|
</div>
|
||||||
<div class="mx-auto" style="width: 200px;">
|
|
||||||
{{t('no-data')}}
|
|
||||||
</div>
|
|
||||||
} @else if(isLoading) {
|
|
||||||
<app-loading [loading]="isLoading"></app-loading>
|
|
||||||
}
|
|
||||||
|
|
||||||
<app-draggable-ordered-list [items]="items" (orderUpdated)="orderUpdated($event)" [accessibilityMode]="accessibilityMode"
|
|
||||||
[showRemoveButton]="false">
|
|
||||||
<ng-template #draggableItem let-item let-position="idx">
|
|
||||||
<app-reading-list-item [ngClass]="{'content-container': items.length < 100, 'non-virtualized-container': items.length >= 100}" [item]="item" [position]="position" [libraryTypes]="libraryTypes"
|
|
||||||
[promoted]="item.promoted" (read)="readChapter($event)" (remove)="itemRemoved($event, position)"></app-reading-list-item>
|
|
||||||
</ng-template>
|
|
||||||
</app-draggable-ordered-list>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</ng-container>
|
|
||||||
|
@ -1,35 +1,38 @@
|
|||||||
<ng-container *transloco="let t; read: 'reading-lists'">
|
<div class="main-container container-fluid">
|
||||||
<app-side-nav-companion-bar>
|
<ng-container *transloco="let t; read: 'reading-lists'">
|
||||||
<h4 title>
|
<app-side-nav-companion-bar>
|
||||||
<span>{{t('title')}}</span>
|
<h4 title>
|
||||||
<app-card-actionables [actions]="globalActions" (actionHandler)="performGlobalAction($event)"></app-card-actionables>
|
<span>{{t('title')}}</span>
|
||||||
</h4>
|
<app-card-actionables [actions]="globalActions" (actionHandler)="performGlobalAction($event)"></app-card-actionables>
|
||||||
@if (pagination) {
|
</h4>
|
||||||
<h5 subtitle class="subtitle-with-actionables">{{t('item-count', {num: pagination.totalItems | number})}}</h5>
|
@if (pagination) {
|
||||||
}
|
<h5 subtitle class="subtitle-with-actionables">{{t('item-count', {num: pagination.totalItems | number})}}</h5>
|
||||||
|
}
|
||||||
|
|
||||||
</app-side-nav-companion-bar>
|
</app-side-nav-companion-bar>
|
||||||
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
<app-bulk-operations [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||||
|
|
||||||
<app-card-detail-layout
|
<app-card-detail-layout
|
||||||
[isLoading]="loadingLists"
|
[isLoading]="loadingLists"
|
||||||
[items]="lists"
|
[items]="lists"
|
||||||
[pagination]="pagination"
|
[pagination]="pagination"
|
||||||
[jumpBarKeys]="jumpbarKeys"
|
[jumpBarKeys]="jumpbarKeys"
|
||||||
[filteringDisabled]="true"
|
[filteringDisabled]="true"
|
||||||
[trackByIdentity]="trackByIdentity"
|
[trackByIdentity]="trackByIdentity"
|
||||||
>
|
>
|
||||||
<ng-template #cardItem let-item let-position="idx" >
|
<ng-template #cardItem let-item let-position="idx" >
|
||||||
<app-card-item [title]="item.title" [entity]="item" [actions]="actions[item.id]"
|
<app-card-item [title]="item.title" [entity]="item" [actions]="actions[item.id]"
|
||||||
[suppressLibraryLink]="true" [imageUrl]="imageService.getReadingListCoverImage(item.id)"
|
[suppressLibraryLink]="true" [imageUrl]="imageService.getReadingListCoverImage(item.id)"
|
||||||
(clicked)="handleClick(item)"
|
[linkUrl]="'/lists/' + item.id"
|
||||||
(selection)="bulkSelectionService.handleCardSelection('readingList', position, lists.length, $event)"
|
(clicked)="handleClick(item)"
|
||||||
[selected]="bulkSelectionService.isCardSelected('readingList', position)" [allowSelection]="true"></app-card-item>
|
(selection)="bulkSelectionService.handleCardSelection('readingList', position, lists.length, $event)"
|
||||||
</ng-template>
|
[selected]="bulkSelectionService.isCardSelected('readingList', position)" [allowSelection]="true"></app-card-item>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #noData>
|
<ng-template #noData>
|
||||||
{{t('no-data')}} {{t('create-one-part-1')}} <a [href]="WikiLink.ReadingLists" rel="noopener noreferrer" target="_blank">{{t('create-one-part-2')}}<i class="fa fa-external-link-alt ms-1" aria-hidden="true"></i></a>.
|
{{t('no-data')}} {{t('create-one-part-1')}} <a [href]="WikiLink.ReadingLists" rel="noopener noreferrer" target="_blank">{{t('create-one-part-2')}}<i class="fa fa-external-link-alt ms-1" aria-hidden="true"></i></a>.
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-card-detail-layout>
|
</app-card-detail-layout>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
@ -89,18 +89,16 @@ export class ReadingListsComponent implements OnInit {
|
|||||||
.filter(action => this.readingListService.actionListFilter(action, readingList, this.isAdmin || this.hasPromote));
|
.filter(action => this.readingListService.actionListFilter(action, readingList, this.isAdmin || this.hasPromote));
|
||||||
}
|
}
|
||||||
|
|
||||||
performAction(action: ActionItem<ReadingList>, readingList: ReadingList) {
|
|
||||||
if (typeof action.callback === 'function') {
|
|
||||||
action.callback(action, readingList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
performGlobalAction(action: ActionItem<any>) {
|
performGlobalAction(action: ActionItem<any>) {
|
||||||
if (typeof action.callback === 'function') {
|
if (typeof action.callback === 'function') {
|
||||||
action.callback(action, undefined);
|
action.callback(action, undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleClick(list: ReadingList) {
|
||||||
|
this.router.navigateByUrl('lists/' + list.id);
|
||||||
|
}
|
||||||
|
|
||||||
handleReadingListActionCallback(action: ActionItem<ReadingList>, readingList: ReadingList) {
|
handleReadingListActionCallback(action: ActionItem<ReadingList>, readingList: ReadingList) {
|
||||||
switch(action.action) {
|
switch(action.action) {
|
||||||
case Action.Delete:
|
case Action.Delete:
|
||||||
@ -159,10 +157,6 @@ export class ReadingListsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick(list: ReadingList) {
|
|
||||||
this.router.navigateByUrl('lists/' + list.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
bulkActionCallback = (action: ActionItem<any>, data: any) => {
|
bulkActionCallback = (action: ActionItem<any>, data: any) => {
|
||||||
const selectedReadingListIndexies = this.bulkSelectionService.getSelectedCardsForSource('readingList');
|
const selectedReadingListIndexies = this.bulkSelectionService.getSelectedCardsForSource('readingList');
|
||||||
const selectedReadingLists = this.lists.filter((col, index: number) => selectedReadingListIndexies.includes(index + ''));
|
const selectedReadingLists = this.lists.filter((col, index: number) => selectedReadingListIndexies.includes(index + ''));
|
||||||
|
@ -334,7 +334,7 @@
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
@if (seriesMetadata && showDetailsTab) {
|
@if (seriesMetadata && showDetailsTab) {
|
||||||
<li [ngbNavItem]="TabID.Details">
|
<li [ngbNavItem]="TabID.Details" id="details-tab">
|
||||||
<a ngbNavLink>{{t(TabID.Details)}}</a>
|
<a ngbNavLink>{{t(TabID.Details)}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
@defer (when activeTabId === TabID.Details; prefetch on idle) {
|
@defer (when activeTabId === TabID.Details; prefetch on idle) {
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
@use '../../../../series-detail-common';
|
@use '../../../../series-detail-common';
|
||||||
|
|
||||||
|
|
||||||
.to-read-counter {
|
.to-read-counter {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 15px;
|
top: 15px;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.card-container{
|
.card-container{
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, 160px);
|
grid-template-columns: repeat(auto-fill, 160px);
|
||||||
|
@ -1174,5 +1174,11 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||||||
switchTabsToDetail() {
|
switchTabsToDetail() {
|
||||||
this.activeTabId = TabID.Details;
|
this.activeTabId = TabID.Details;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
|
setTimeout(() => {
|
||||||
|
const tabElem = this.document.querySelector('#details-tab');
|
||||||
|
if (tabElem) {
|
||||||
|
(tabElem as HTMLLIElement).scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,180 +1,183 @@
|
|||||||
<ng-container *transloco="let t; read:'settings'">
|
<div class="main-container container-fluid">
|
||||||
<app-side-nav-companion-bar>
|
<ng-container *transloco="let t; read:'settings'">
|
||||||
<h2 title>
|
<app-side-nav-companion-bar>
|
||||||
{{fragment | settingFragment}}
|
<h2 title>
|
||||||
</h2>
|
{{fragment | settingFragment}}
|
||||||
</app-side-nav-companion-bar>
|
</h2>
|
||||||
<div class="row col-me-4 pb-3">
|
</app-side-nav-companion-bar>
|
||||||
|
<div class="row col-me-4 pb-3">
|
||||||
@if (accountService.currentUser$ | async; as user) {
|
|
||||||
@if (accountService.hasAdminRole(user)) {
|
@if (accountService.currentUser$ | async; as user) {
|
||||||
@defer (when fragment === SettingsTabId.General; prefetch on idle) {
|
@if (accountService.hasAdminRole(user)) {
|
||||||
@if (fragment === SettingsTabId.General) {
|
@defer (when fragment === SettingsTabId.General; prefetch on idle) {
|
||||||
|
@if (fragment === SettingsTabId.General) {
|
||||||
|
<div class="col-xxl-6 col-12">
|
||||||
|
<app-manage-settings></app-manage-settings>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@defer (when fragment === SettingsTabId.Email; prefetch on idle) {
|
||||||
|
@if (fragment === SettingsTabId.Email) {
|
||||||
|
<div class="col-xxl-6 col-12">
|
||||||
|
<app-manage-email-settings></app-manage-email-settings>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@defer (when fragment === SettingsTabId.Media; prefetch on idle) {
|
||||||
|
@if (fragment === SettingsTabId.Media) {
|
||||||
|
<div class="col-xxl-6 col-12">
|
||||||
|
<app-manage-media-settings></app-manage-media-settings>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@defer (when fragment === SettingsTabId.Users; prefetch on idle) {
|
||||||
|
@if (fragment === SettingsTabId.Users) {
|
||||||
|
<div class="scale col-md-12">
|
||||||
|
<app-manage-users></app-manage-users>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@defer (when fragment === SettingsTabId.Libraries; prefetch on idle) {
|
||||||
|
@if (fragment === SettingsTabId.Libraries) {
|
||||||
|
<div class="scale col-md-12">
|
||||||
|
<app-manage-library></app-manage-library>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@defer (when fragment === SettingsTabId.MediaIssues; prefetch on idle) {
|
||||||
|
@if (fragment === SettingsTabId.MediaIssues) {
|
||||||
|
<div class="scale col-md-12">
|
||||||
|
<app-manage-media-issues></app-manage-media-issues>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@defer (when fragment === SettingsTabId.System; prefetch on idle) {
|
||||||
|
@if (fragment === SettingsTabId.System) {
|
||||||
|
<div class="scale col-md-12">
|
||||||
|
<app-manage-system></app-manage-system>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@defer (when fragment === SettingsTabId.Statistics; prefetch on idle) {
|
||||||
|
@if (fragment === SettingsTabId.Statistics) {
|
||||||
|
<div class="scale col-md-12">
|
||||||
|
<app-server-stats></app-server-stats>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@defer (when fragment === SettingsTabId.Tasks; prefetch on idle) {
|
||||||
|
@if (fragment === SettingsTabId.Tasks) {
|
||||||
|
<div class="scale col-md-12">
|
||||||
|
<app-manage-tasks-settings></app-manage-tasks-settings>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@defer (when fragment === SettingsTabId.KavitaPlus; prefetch on idle) {
|
||||||
|
@if (fragment === SettingsTabId.KavitaPlus) {
|
||||||
|
<div class="scale col-md-12">
|
||||||
|
<app-manage-kavitaplus></app-manage-kavitaplus>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@defer (when fragment === SettingsTabId.Account; prefetch on idle) {
|
||||||
|
@if (fragment === SettingsTabId.Account) {
|
||||||
<div class="col-xxl-6 col-12">
|
<div class="col-xxl-6 col-12">
|
||||||
<app-manage-settings></app-manage-settings>
|
<app-change-email></app-change-email>
|
||||||
|
<div class="setting-section-break"></div>
|
||||||
|
<app-change-password></app-change-password>
|
||||||
|
<div class="setting-section-break"></div>
|
||||||
|
<app-change-age-restriction></app-change-age-restriction>
|
||||||
|
<div class="setting-section-break"></div>
|
||||||
|
<app-manage-scrobbling-providers></app-manage-scrobbling-providers>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Email; prefetch on idle) {
|
@defer (when fragment === SettingsTabId.Preferences; prefetch on idle) {
|
||||||
@if (fragment === SettingsTabId.Email) {
|
@if (fragment === SettingsTabId.Preferences) {
|
||||||
<div class="col-xxl-6 col-12">
|
<div class="col-xxl-6 col-12">
|
||||||
<app-manage-email-settings></app-manage-email-settings>
|
<app-manga-user-preferences></app-manga-user-preferences>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Media; prefetch on idle) {
|
@defer (when fragment === SettingsTabId.Customize; prefetch on idle) {
|
||||||
@if (fragment === SettingsTabId.Media) {
|
@if (fragment === SettingsTabId.Customize) {
|
||||||
|
<div class="scale col-md-12">
|
||||||
|
<app-manage-customization></app-manage-customization>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@defer (when fragment === SettingsTabId.Clients; prefetch on idle) {
|
||||||
|
@if (fragment === SettingsTabId.Clients) {
|
||||||
<div class="col-xxl-6 col-12">
|
<div class="col-xxl-6 col-12">
|
||||||
<app-manage-media-settings></app-manage-media-settings>
|
<app-manage-opds></app-manage-opds>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Users; prefetch on idle) {
|
@defer (when fragment === SettingsTabId.Theme; prefetch on idle) {
|
||||||
@if (fragment === SettingsTabId.Users) {
|
@if (fragment === SettingsTabId.Theme) {
|
||||||
<div class="scale col-md-12">
|
<div class="scale col-md-12">
|
||||||
<app-manage-users></app-manage-users>
|
<app-theme-manager></app-theme-manager>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Libraries; prefetch on idle) {
|
@defer (when fragment === SettingsTabId.Devices; prefetch on idle) {
|
||||||
@if (fragment === SettingsTabId.Libraries) {
|
@if (fragment === SettingsTabId.Devices) {
|
||||||
<div class="scale col-md-12">
|
<div class="scale col-md-12">
|
||||||
<app-manage-library></app-manage-library>
|
<app-manage-devices></app-manage-devices>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.MediaIssues; prefetch on idle) {
|
@defer (when fragment === SettingsTabId.UserStats; prefetch on idle) {
|
||||||
@if (fragment === SettingsTabId.MediaIssues) {
|
@if (fragment === SettingsTabId.UserStats) {
|
||||||
<div class="scale col-md-12">
|
<div class="scale col-md-12">
|
||||||
<app-manage-media-issues></app-manage-media-issues>
|
<app-user-stats></app-user-stats>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.System; prefetch on idle) {
|
@defer (when fragment === SettingsTabId.CBLImport; prefetch on idle) {
|
||||||
@if (fragment === SettingsTabId.System) {
|
@if (fragment === SettingsTabId.CBLImport) {
|
||||||
<div class="scale col-md-12">
|
<div class="scale col-md-12">
|
||||||
<app-manage-system></app-manage-system>
|
<app-import-cbl></app-import-cbl>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Statistics; prefetch on idle) {
|
@defer (when fragment === SettingsTabId.Scrobbling; prefetch on idle) {
|
||||||
@if (fragment === SettingsTabId.Statistics) {
|
@if(hasActiveLicense && fragment === SettingsTabId.Scrobbling) {
|
||||||
<div class="scale col-md-12">
|
<div class="scale col-md-12">
|
||||||
<app-server-stats></app-server-stats>
|
<app-manage-scrobling></app-manage-scrobling>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Tasks; prefetch on idle) {
|
@defer (when fragment === SettingsTabId.MALStackImport; prefetch on idle) {
|
||||||
@if (fragment === SettingsTabId.Tasks) {
|
@if(hasActiveLicense && fragment === SettingsTabId.MALStackImport) {
|
||||||
<div class="scale col-md-12">
|
<div class="scale col-md-12">
|
||||||
<app-manage-tasks-settings></app-manage-tasks-settings>
|
<app-import-mal-collection></app-import-mal-collection>
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.KavitaPlus; prefetch on idle) {
|
|
||||||
@if (fragment === SettingsTabId.KavitaPlus) {
|
|
||||||
<div class="scale col-md-12">
|
|
||||||
<app-manage-kavitaplus></app-manage-kavitaplus>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Account; prefetch on idle) {
|
|
||||||
@if (fragment === SettingsTabId.Account) {
|
|
||||||
<div class="col-xxl-6 col-12">
|
|
||||||
<app-change-email></app-change-email>
|
|
||||||
<div class="setting-section-break"></div>
|
|
||||||
<app-change-password></app-change-password>
|
|
||||||
<div class="setting-section-break"></div>
|
|
||||||
<app-change-age-restriction></app-change-age-restriction>
|
|
||||||
<div class="setting-section-break"></div>
|
|
||||||
<app-manage-scrobbling-providers></app-manage-scrobbling-providers>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Preferences; prefetch on idle) {
|
|
||||||
@if (fragment === SettingsTabId.Preferences) {
|
|
||||||
<div class="col-xxl-6 col-12">
|
|
||||||
<app-manga-user-preferences></app-manga-user-preferences>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Customize; prefetch on idle) {
|
|
||||||
@if (fragment === SettingsTabId.Customize) {
|
|
||||||
<div class="scale col-md-12">
|
|
||||||
<app-manage-customization></app-manage-customization>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Clients; prefetch on idle) {
|
|
||||||
@if (fragment === SettingsTabId.Clients) {
|
|
||||||
<div class="col-xxl-6 col-12">
|
|
||||||
<app-manage-opds></app-manage-opds>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Theme; prefetch on idle) {
|
|
||||||
@if (fragment === SettingsTabId.Theme) {
|
|
||||||
<div class="scale col-md-12">
|
|
||||||
<app-theme-manager></app-theme-manager>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Devices; prefetch on idle) {
|
|
||||||
@if (fragment === SettingsTabId.Devices) {
|
|
||||||
<div class="scale col-md-12">
|
|
||||||
<app-manage-devices></app-manage-devices>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.UserStats; prefetch on idle) {
|
|
||||||
@if (fragment === SettingsTabId.UserStats) {
|
|
||||||
<div class="scale col-md-12">
|
|
||||||
<app-user-stats></app-user-stats>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.CBLImport; prefetch on idle) {
|
|
||||||
@if (fragment === SettingsTabId.CBLImport) {
|
|
||||||
<div class="scale col-md-12">
|
|
||||||
<app-import-cbl></app-import-cbl>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.Scrobbling; prefetch on idle) {
|
|
||||||
@if(hasActiveLicense && fragment === SettingsTabId.Scrobbling) {
|
|
||||||
<div class="scale col-md-12">
|
|
||||||
<app-manage-scrobling></app-manage-scrobling>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@defer (when fragment === SettingsTabId.MALStackImport; prefetch on idle) {
|
|
||||||
@if(hasActiveLicense && fragment === SettingsTabId.MALStackImport) {
|
|
||||||
<div class="scale col-md-12">
|
|
||||||
<app-import-mal-collection></app-import-mal-collection>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
|
@ -1,10 +1,28 @@
|
|||||||
|
@import '../../../../theme/variables';
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
::ng-deep .content-wrapper:not(.closed) {
|
::ng-deep .content-wrapper:not(.closed) {
|
||||||
.scale {
|
.scale {
|
||||||
width: calc(100dvw - 200px) !important;
|
width: calc(100dvw - 200px) !important;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width: $grid-breakpoints-lg) {
|
||||||
|
::ng-deep .content-wrapper:not(.closed) {
|
||||||
|
.scale {
|
||||||
|
width: 100% !important;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,31 @@
|
|||||||
<ng-container *transloco="let t; read: 'customize-sidenav-streams'">
|
<ng-container *transloco="let t; read: 'customize-sidenav-streams'">
|
||||||
<form [formGroup]="listForm">
|
<form [formGroup]="listForm">
|
||||||
<div class="row g-0 mb-3 justify-content-between">
|
@if (items.length > 3) {
|
||||||
@if (items.length > 3) {
|
<div class="row g-0 mb-2">
|
||||||
<div class="col-9">
|
<label for="sidenav-stream-filter" class="form-label">{{t('filter')}}</label>
|
||||||
<label for="sidenav-stream-filter" class="form-label">{{t('filter')}}</label>
|
<div class="input-group">
|
||||||
<div class="input-group">
|
<input id="sidenav-stream-filter" autocomplete="off" class="form-control" formControlName="filterSideNavStream" type="text" aria-describedby="reset-sidenav-stream-input">
|
||||||
<input id="sidenav-stream-filter" autocomplete="off" class="form-control" formControlName="filterSideNavStream" type="text" aria-describedby="reset-sidenav-stream-input">
|
<button class="btn btn-outline-secondary" type="button" id="reset-sidenav-stream-input" (click)="resetSideNavFilter()">{{t('clear')}}</button>
|
||||||
<button class="btn btn-outline-secondary" type="button" id="reset-sidenav-stream-input" (click)="resetSideNavFilter()">{{t('clear')}}</button>
|
|
||||||
</div>
|
|
||||||
@if (listForm.get('filterSideNavStream')?.value) {
|
|
||||||
<p role="alert" class="mt-2">{{t('reorder-when-filter-present')}}</p>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
@if (listForm.get('filterSideNavStream')?.value) {
|
||||||
|
<p role="alert" class="mt-2">{{t('reorder-when-filter-present')}}</p>
|
||||||
<div class="col-3">
|
}
|
||||||
<form [formGroup]="pageOperationsForm">
|
|
||||||
<div class="form-check form-check-inline" style="margin-top: 40px; margin-left: 10px">
|
|
||||||
<input class="form-check-input" type="checkbox" id="accessibility-mode" formControlName="accessibilityMode">
|
|
||||||
<label class="form-check-label" for="accessibility-mode">{{t('order-numbers-label')}}</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check form-check-inline" style="margin-left: 10px">
|
|
||||||
<input class="form-check-input" type="checkbox" id="bulk-mode" formControlName="bulkMode" >
|
|
||||||
<label class="form-check-label" for="bulk-mode">{{t('bulk-mode-label')}}</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="row g-0 mb-3 ">
|
||||||
|
<form [formGroup]="pageOperationsForm">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" id="accessibility-mode" formControlName="accessibilityMode">
|
||||||
|
<label class="form-check-label" for="accessibility-mode">{{t('order-numbers-label')}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline ms-2">
|
||||||
|
<input class="form-check-input" type="checkbox" id="bulk-mode" formControlName="bulkMode" >
|
||||||
|
<label class="form-check-label" for="bulk-mode">{{t('bulk-mode-label')}}</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-bulk-operations [modalMode]="true" [topOffset]="0" [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
<app-bulk-operations [modalMode]="true" [topOffset]="0" [actionCallback]="bulkActionCallback"></app-bulk-operations>
|
||||||
<div style="max-height: 500px; overflow-y: auto">
|
<div style="max-height: 500px; overflow-y: auto">
|
||||||
<app-draggable-ordered-list [items]="items | filter: filterSideNavStreams" (orderUpdated)="orderUpdated($event)"
|
<app-draggable-ordered-list [items]="items | filter: filterSideNavStreams" (orderUpdated)="orderUpdated($event)"
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@import "../../../../theme/variables";
|
||||||
|
|
||||||
.list-item {
|
.list-item {
|
||||||
height: 60px;
|
height: 60px;
|
||||||
max-height: 60px;
|
max-height: 60px;
|
||||||
@ -5,4 +7,10 @@
|
|||||||
|
|
||||||
.meta {
|
.meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $grid-breakpoints-sm) {
|
||||||
|
.list-item div h5 span:first-of-type {
|
||||||
|
font-size: 1rem !important;
|
||||||
|
}
|
||||||
}
|
}
|
@ -22,16 +22,23 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 10px;
|
padding: 0 0 0 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
|
||||||
.side-nav-text {
|
.side-nav-text {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
|
word-break: break-all;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box !important;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
min-width: 102px;
|
min-width: 102px;
|
||||||
@ -49,7 +56,6 @@
|
|||||||
|
|
||||||
div {
|
div {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
justify-content: inherit;
|
justify-content: inherit;
|
||||||
min-width: 30px;
|
min-width: 30px;
|
||||||
|
@ -72,10 +72,12 @@ export class SideNavItemComponent implements OnInit {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.router.events
|
this.router.events
|
||||||
.pipe(filter(event => event instanceof NavigationEnd),
|
.pipe(
|
||||||
takeUntilDestroyed(this.destroyRef),
|
filter(event => event instanceof NavigationEnd),
|
||||||
map(evt => evt as NavigationEnd),
|
takeUntilDestroyed(this.destroyRef),
|
||||||
tap((evt: NavigationEnd) => this.triggerHighlightCheck(evt.url))
|
map(evt => evt as NavigationEnd),
|
||||||
|
tap((evt: NavigationEnd) => this.triggerHighlightCheck(evt.url)),
|
||||||
|
tap(_ => this.collapseNavIfApplicable())
|
||||||
).subscribe();
|
).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +155,6 @@ export class SideNavItemComponent implements OnInit {
|
|||||||
// If on mobile, automatically collapse the side nav after making a selection
|
// If on mobile, automatically collapse the side nav after making a selection
|
||||||
collapseNavIfApplicable() {
|
collapseNavIfApplicable() {
|
||||||
if (this.utilityService.getActiveBreakpoint() < Breakpoint.Tablet) {
|
if (this.utilityService.getActiveBreakpoint() < Breakpoint.Tablet) {
|
||||||
console.log('collapsing side nav');
|
|
||||||
this.navService.collapseSideNav(true);
|
this.navService.collapseSideNav(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::ng-deep .side-nav-text {
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: $grid-breakpoints-lg) {
|
@media (max-width: $grid-breakpoints-lg) {
|
||||||
.side-nav {
|
.side-nav {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
|
@ -199,8 +199,9 @@ export class PreferenceNavComponent implements AfterViewInit {
|
|||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.sections[3].children.length === 1) {
|
|
||||||
this.sections[3].children.push(new SideNavItem(SettingsTabId.MALStackImport, []));
|
if (this.sections[2].children.length === 1) {
|
||||||
|
this.sections[2].children.push(new SideNavItem(SettingsTabId.MALStackImport, []));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,9 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{{t('platform-label')}}
|
{{t('platform-label')}}
|
||||||
</th>
|
</th>
|
||||||
<th scope="col"></th>
|
<th scope="col">
|
||||||
|
{{t('actions-header')}}
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -1,4 +1,26 @@
|
|||||||
|
@import '../../../theme/variables';
|
||||||
|
|
||||||
.custom-position {
|
.custom-position {
|
||||||
right: 15px;
|
right: 15px;
|
||||||
top: -42px;
|
top: -42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
@media (max-width: $grid-breakpoints-sm) {
|
||||||
|
overflow-x: auto;
|
||||||
|
width: 100% !important;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.btn-container {
|
||||||
|
@media (max-width: $grid-breakpoints-lg) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<ng-container *transloco="let t; read: 'series-detail'">
|
<ng-container *transloco="let t; read: 'series-detail'">
|
||||||
|
|
||||||
<app-bulk-operations [actionCallback]="bulkActionCallback" [topOffset]="56"></app-bulk-operations>
|
<app-bulk-operations [actionCallback]="bulkActionCallback" [topOffset]="55"></app-bulk-operations>
|
||||||
|
|
||||||
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid" #scrollingBlock>
|
<div [ngStyle]="{'height': ScrollingBlockHeight}" class="main-container container-fluid" #scrollingBlock>
|
||||||
|
|
||||||
@ -95,7 +95,7 @@
|
|||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<span>{{t('writers-title')}}</span>
|
<span class="fw-bold">{{t('writers-title')}}</span>
|
||||||
<div>
|
<div>
|
||||||
<app-badge-expander [items]="volumeCast.writers" [allowToggle]="false" (toggle)="switchTabsToDetail()">
|
<app-badge-expander [items]="volumeCast.writers" [allowToggle]="false" (toggle)="switchTabsToDetail()">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||||
@ -105,7 +105,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<span>{{t('cover-artists-title')}}</span>
|
<span class="fw-bold">{{t('cover-artists-title')}}</span>
|
||||||
<div>
|
<div>
|
||||||
<app-badge-expander [items]="volumeCast.coverArtists" [allowToggle]="false" (toggle)="switchTabsToDetail()">
|
<app-badge-expander [items]="volumeCast.coverArtists" [allowToggle]="false" (toggle)="switchTabsToDetail()">
|
||||||
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
|
||||||
@ -155,7 +155,7 @@
|
|||||||
|
|
||||||
<div class="carousel-tabs-container mb-2">
|
<div class="carousel-tabs-container mb-2">
|
||||||
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="nav nav-tabs" (navChange)="onNavChange($event)">
|
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTabId" class="nav nav-tabs" (navChange)="onNavChange($event)">
|
||||||
<li [ngbNavItem]="TabID.Chapters">
|
<li [ngbNavItem]="TabID.Chapters">
|
||||||
<a ngbNavLink>
|
<a ngbNavLink>
|
||||||
{{utilityService.formatChapterName(libraryType!, false, false, true)}}
|
{{utilityService.formatChapterName(libraryType!, false, false, true)}}
|
||||||
<span class="badge rounded-pill text-bg-secondary">{{volume.chapters.length}}</span>
|
<span class="badge rounded-pill text-bg-secondary">{{volume.chapters.length}}</span>
|
||||||
@ -180,28 +180,28 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@if (volume.chapters.length === 1 && readingLists.length > 0) {
|
@if (volume.chapters.length === 1 && readingLists.length > 0) {
|
||||||
<li [ngbNavItem]="TabID.Related">
|
<li [ngbNavItem]="TabID.Related">
|
||||||
<a ngbNavLink>{{t('related-tab')}}</a>
|
<a ngbNavLink>{{t('related-tab')}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
@defer (when activeTabId === TabID.Related; prefetch on idle) {
|
@defer (when activeTabId === TabID.Related; prefetch on idle) {
|
||||||
<app-related-tab [readingLists]="readingLists"></app-related-tab>
|
<app-related-tab [readingLists]="readingLists"></app-related-tab>
|
||||||
}
|
}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (showDetailsTab) {
|
@if (showDetailsTab) {
|
||||||
<li [ngbNavItem]="TabID.Details">
|
<li [ngbNavItem]="TabID.Details" id="details-tab">
|
||||||
<a ngbNavLink>{{t('details-tab')}}</a>
|
<a ngbNavLink>{{t('details-tab')}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
@defer (when activeTabId === TabID.Details; prefetch on idle) {
|
@defer (when activeTabId === TabID.Details; prefetch on idle) {
|
||||||
<app-details-tab [metadata]="volumeCast" [genres]="genres" [tags]="tags"></app-details-tab>
|
<app-details-tab [metadata]="volumeCast" [genres]="genres" [tags]="tags"></app-details-tab>
|
||||||
}
|
}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- Min height helps with scroll jerking when switching from chapter -> related/details -->
|
<!-- Min height helps with scroll jerking when switching from chapter -> related/details -->
|
||||||
<div [ngbNavOutlet]="nav" style="min-height: 300px"></div>
|
<div [ngbNavOutlet]="nav" style="min-height: 300px"></div>
|
||||||
|
@ -646,6 +646,12 @@ export class VolumeDetailComponent implements OnInit {
|
|||||||
switchTabsToDetail() {
|
switchTabsToDetail() {
|
||||||
this.activeTabId = TabID.Details;
|
this.activeTabId = TabID.Details;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
|
setTimeout(() => {
|
||||||
|
const tabElem = this.document.querySelector('#details-tab');
|
||||||
|
if (tabElem) {
|
||||||
|
(tabElem as HTMLLIElement).scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateToSeries() {
|
navigateToSeries() {
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
// This is responsible for ensuring we scroll down and only tabs and companion bar is visible
|
// This is responsible for ensuring we scroll down and only tabs and companion bar is visible
|
||||||
.main-container {
|
.main-container {
|
||||||
// Height set dynamically by get ScrollingBlockHeight()
|
// Height set dynamically by get ScrollingBlockHeight()
|
||||||
overflow-y: auto;
|
overflow: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
overscroll-behavior-y: none;
|
overscroll-behavior-y: none;
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
|
@ -250,13 +250,15 @@
|
|||||||
"add": "{{common.add}}",
|
"add": "{{common.add}}",
|
||||||
"delete": "{{common.delete}}",
|
"delete": "{{common.delete}}",
|
||||||
"edit": "{{common.edit}}",
|
"edit": "{{common.edit}}",
|
||||||
"no-data": "{{typeahead.no-data}}"
|
"no-data": "{{typeahead.no-data}}",
|
||||||
|
"actions-header": "{{manage-users.actions-header}}"
|
||||||
},
|
},
|
||||||
|
|
||||||
"edit-device-modal": {
|
"edit-device-modal": {
|
||||||
"title": "Edit Device",
|
"title": "Edit Device",
|
||||||
"device-name-label": "{{manage-devices.name-label}}",
|
"device-name-label": "{{manage-devices.name-label}}",
|
||||||
"platform-label": "{{manage-devices.platform-label}}",
|
"platform-label": "{{manage-devices.platform-label}}",
|
||||||
|
|
||||||
"email-label": "{{common.email}}",
|
"email-label": "{{common.email}}",
|
||||||
"email-tooltip": "This email will be used to accept the file via Send To",
|
"email-tooltip": "This email will be used to accept the file via Send To",
|
||||||
"device-platform-label": "Device Platform",
|
"device-platform-label": "Device Platform",
|
||||||
@ -672,7 +674,7 @@
|
|||||||
"discord-validation": "This is not a valid Discord User Id. Your user id is not your discord username.",
|
"discord-validation": "This is not a valid Discord User Id. Your user id is not your discord username.",
|
||||||
"activate-delete": "{{common.delete}}",
|
"activate-delete": "{{common.delete}}",
|
||||||
"activate-reset": "{{common.reset}}",
|
"activate-reset": "{{common.reset}}",
|
||||||
"activate-reset-tooltip": "Invalidate an previous registration using your license. Requires both License and Email",
|
"activate-reset-tooltip": "Invalidate a previous registration using your license. Requires both License and Email",
|
||||||
"activate-save": "{{common.save}}",
|
"activate-save": "{{common.save}}",
|
||||||
|
|
||||||
"kavita+-desc-part-1": "Kavita+ is a premium subscription service which unlocks features for all users on this Kavita instance. Buy a subscription to unlock ",
|
"kavita+-desc-part-1": "Kavita+ is a premium subscription service which unlocks features for all users on this Kavita instance. Buy a subscription to unlock ",
|
||||||
|
@ -101,15 +101,17 @@ app-root {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
scrollbar-gutter: stable both-edges;
|
|
||||||
font-family: 'Poppins', sans-serif;
|
font-family: 'Poppins', sans-serif;
|
||||||
overflow: hidden; // When this is enabled, it will break the webtoon reader. The nav.service will automatically remove/apply on toggling them
|
overflow: hidden; // When this is enabled, it will break the webtoon reader. The nav.service will automatically remove/apply on toggling them
|
||||||
scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0.1);
|
scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0.1);
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
@media (max-width: $grid-breakpoints-lg) {
|
@media (max-width: $grid-breakpoints-lg) {
|
||||||
margin-top: var(--nav-mobile-offset) !important;
|
/* Setting this break the readers */
|
||||||
height: calc(var(--vh)* 100 - var(--nav-mobile-offset)) !important;
|
//margin-top: var(--nav-mobile-offset) !important;
|
||||||
|
//height: calc(var(--vh)* 100 - var(--nav-mobile-offset)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -118,4 +120,4 @@ body {
|
|||||||
height: 1px;
|
height: 1px;
|
||||||
background-color: var(--setting-break-color);
|
background-color: var(--setting-break-color);
|
||||||
margin: 30px 0;
|
margin: 30px 0;
|
||||||
}
|
}
|
@ -1,88 +1,19 @@
|
|||||||
|
@import "../variables";
|
||||||
@import '../variables';
|
|
||||||
|
|
||||||
.sidenav-bottom {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 190px;
|
|
||||||
font-size: 12px;
|
|
||||||
transition: width var(--side-nav-openclose-transition);
|
|
||||||
z-index: 999;
|
|
||||||
left: 10px;
|
|
||||||
|
|
||||||
.donate {
|
|
||||||
.side-nav-item {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 80px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.closed {
|
|
||||||
width: 45px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
left: -50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep .sidenav-bottom .donate .side-nav-item {
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 25px;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
:hover {
|
|
||||||
background-color: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep .sidenav-bottom .donate .side-nav-item span {
|
|
||||||
flex-grow: unset !important;
|
|
||||||
min-width: unset !important;;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep .sidenav-bottom .donate .side-nav-item span div {
|
|
||||||
min-width: unset !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep .sidenav-bottom .donate .side-nav-item span div i{
|
|
||||||
font-size: 12px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
:host ::ng-deep .sidenav-bottom .donate .side-nav-item.closed span.phone-hidden div {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep .sidenav-bottom .donate .side-nav-item.closed span.side-nav-text div {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $grid-breakpoints-lg) {
|
|
||||||
:host ::ng-deep .sidenav-bottom .donate .side-nav-item.closed {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::ng-deep .sidenav-bottom .donate .side-nav-item span.phone-hidden {
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-nav-container {
|
.side-nav-container {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
width: 190px;
|
width: 190px;
|
||||||
background-color: var(--side-nav-bg-color);
|
background-color: var(--side-nav-bg-color);
|
||||||
height: calc((var(--vh)*100) - 115px);
|
height: calc((var(--vh) * 100) - 115px);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
top: 73px;
|
top: 73px;
|
||||||
border-radius: var(--side-nav-border-radius);
|
border-radius: var(--side-nav-border-radius);
|
||||||
transition: width var(--side-nav-openclose-transition), background-color var(--side-nav-bg-color-transition), border-color var(--side-nav-border-transition);
|
transition:
|
||||||
|
width var(--side-nav-openclose-transition),
|
||||||
|
background-color var(--side-nav-bg-color-transition),
|
||||||
|
border-color var(--side-nav-border-transition);
|
||||||
border: var(--side-nav-border);
|
border: var(--side-nav-border);
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
@ -90,11 +21,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.preference {
|
&.preference {
|
||||||
height: calc((var(--vh)*100));
|
height: calc((var(--vh) * 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
&.no-donate {
|
&.no-donate {
|
||||||
height: calc((var(--vh)*100) - 82px);
|
height: calc((var(--vh) * 100) - 82px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
@ -117,39 +48,39 @@
|
|||||||
-webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 97%, transparent 100%);
|
-webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 97%, transparent 100%);
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
|
|
||||||
// For firefox
|
|
||||||
@supports (-moz-appearance:none) {
|
|
||||||
scrollbar-color: transparent transparent;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
background-color: transparent; /*make scrollbar space invisible */
|
|
||||||
width: inherit;
|
|
||||||
display: none;
|
|
||||||
visibility: hidden;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: transparent; /*makes it invisible when not hovering*/
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
scrollbar-width: thin;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
// For firefox
|
// For firefox
|
||||||
@supports (-moz-appearance:none) {
|
@supports (-moz-appearance: none) {
|
||||||
scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0);
|
scrollbar-color: transparent transparent;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
background-color: transparent; /*make scrollbar space invisible */
|
||||||
|
width: inherit;
|
||||||
|
display: none;
|
||||||
|
visibility: hidden;
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
visibility: visible;
|
background: transparent; /*makes it invisible when not hovering*/
|
||||||
background-color: rgba(255,255,255,0.3); /*On hover, it will turn grey*/
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
// For firefox
|
||||||
|
@supports (-moz-appearance: none) {
|
||||||
|
scrollbar-color: rgba(255, 255, 255, 0.3) rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
visibility: visible;
|
||||||
|
background-color: rgba(255, 255, 255, 0.3); /*On hover, it will turn grey*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.side-nav-item:first {
|
.side-nav-item:first {
|
||||||
border-top-left-radius: var(--side-nav-border-radius);
|
border-top-left-radius: var(--side-nav-border-radius);
|
||||||
@ -158,6 +89,85 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidenav-bottom {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 190px;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: width var(--side-nav-openclose-transition);
|
||||||
|
z-index: 999;
|
||||||
|
left: 10px;
|
||||||
|
|
||||||
|
.donate {
|
||||||
|
.side-nav-item {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 80px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: unset !important;
|
||||||
|
color: white !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--side-nav-item-closed-color) !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
flex-grow: unset !important;
|
||||||
|
min-width: unset !important;
|
||||||
|
|
||||||
|
div {
|
||||||
|
min-width: unset !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 1rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.closed {
|
||||||
|
span {
|
||||||
|
&.phone-hidden {
|
||||||
|
div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-nav-text {
|
||||||
|
div {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.closed {
|
||||||
|
width: 45px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.side-nav-item {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
display: block;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: $grid-breakpoints-lg) {
|
@media (max-width: $grid-breakpoints-lg) {
|
||||||
.side-nav-container {
|
.side-nav-container {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
@ -170,11 +180,9 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
transition: width var(--side-nav-openclose-transition);
|
transition: width var(--side-nav-openclose-transition);
|
||||||
z-index: 1050;
|
z-index: 1050;
|
||||||
overflow: auto;
|
overflow-y: auto;
|
||||||
border: var(--side-nav-mobile-border);
|
border: var(--side-nav-mobile-border);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
&.no-donate {
|
&.no-donate {
|
||||||
height: 100dvh;
|
height: 100dvh;
|
||||||
}
|
}
|
||||||
@ -185,6 +193,14 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.side-nav {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-nav-item {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.side-nav-item:first {
|
.side-nav-item:first {
|
||||||
border-top-left-radius: var(--side-nav-border-radius);
|
border-top-left-radius: var(--side-nav-border-radius);
|
||||||
border-top-right-radius: var(--side-nav-border-radius);
|
border-top-right-radius: var(--side-nav-border-radius);
|
||||||
@ -192,14 +208,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidenav-bottom {
|
.sidenav-bottom {
|
||||||
display:none;
|
display: none;
|
||||||
|
&.closed {
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-nav-overlay {
|
.side-nav-overlay {
|
||||||
background-color: var(--side-nav-overlay-color);
|
background-color: var(--side-nav-overlay-color);
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: calc((var(--vh)*100) - var(--nav-mobile-offset));
|
height: calc((var(--vh) * 100) - var(--nav-mobile-offset));
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: var(--nav-mobile-offset);
|
top: var(--nav-mobile-offset);
|
||||||
|
@ -53,8 +53,6 @@
|
|||||||
--default-state-scrollbar: transparent;
|
--default-state-scrollbar: transparent;
|
||||||
--text-muted-color: hsla(0,0%,100%,.45);
|
--text-muted-color: hsla(0,0%,100%,.45);
|
||||||
|
|
||||||
/* New Color scheme */
|
|
||||||
--bulk-background-color: rgba(39,39,39,1);
|
|
||||||
|
|
||||||
/* Theming colors that performs a gradient for background. Can be disabled else automatically applied based on cover image colors.
|
/* Theming colors that performs a gradient for background. Can be disabled else automatically applied based on cover image colors.
|
||||||
* --colorscape-primary-color and the alpha variants will be updated in real time. the default variant is fixed and represents the default state and should
|
* --colorscape-primary-color and the alpha variants will be updated in real time. the default variant is fixed and represents the default state and should
|
||||||
@ -82,14 +80,15 @@
|
|||||||
--theme-color: #000000;
|
--theme-color: #000000;
|
||||||
--color-scheme: dark;
|
--color-scheme: dark;
|
||||||
--tile-color: var(--primary-color);
|
--tile-color: var(--primary-color);
|
||||||
--nav-offset: 70px;
|
--nav-offset: 60px;
|
||||||
--nav-mobile-offset: 55px;
|
--nav-mobile-offset: 55px;
|
||||||
|
/* Should we render the series cover as background on mobile */
|
||||||
|
--mobile-series-img-background: true;
|
||||||
|
|
||||||
/* Setting Item */
|
/* Setting Item */
|
||||||
// TODO: Robbie let's refactor this so all setting classes inherit from this area
|
--setting-header-text-color: #d5d5d5;
|
||||||
--h6-text-color: #d5d5d5;
|
--setting-header-font-size: 1.2rem;
|
||||||
--h6-font-size: 1.2rem;
|
--setting-header-font-weight: bold;
|
||||||
--h6-font-weight: bold;
|
|
||||||
--setting-break-color: rgba(255, 255, 255, 0.2);
|
--setting-break-color: rgba(255, 255, 255, 0.2);
|
||||||
|
|
||||||
|
|
||||||
@ -100,6 +99,7 @@
|
|||||||
--table-body-text-color: hsla(0,0%,100%,.85);
|
--table-body-text-color: hsla(0,0%,100%,.85);
|
||||||
--table-body-striped-bg-color: hsla(0,0%,100%,.25);
|
--table-body-striped-bg-color: hsla(0,0%,100%,.25);
|
||||||
--table-body-border: hidden;
|
--table-body-border: hidden;
|
||||||
|
--table-body-striped-bg-color: var(--elevation-layer2);
|
||||||
|
|
||||||
|
|
||||||
/* Navbar */
|
/* Navbar */
|
||||||
@ -383,6 +383,7 @@
|
|||||||
/* Bulk Selection */
|
/* Bulk Selection */
|
||||||
--bulk-selection-text-color: var(--navbar-text-color);
|
--bulk-selection-text-color: var(--navbar-text-color);
|
||||||
--bulk-selection-highlight-text-color: var(--primary-color);
|
--bulk-selection-highlight-text-color: var(--primary-color);
|
||||||
|
--bulk-selection-bg-color: rgba(39,39,39,1);
|
||||||
|
|
||||||
/* List Card Item */
|
/* List Card Item */
|
||||||
--card-list-item-bg-color: linear-gradient(180deg, rgba(0,0,0,0.15) 0%, rgba(0,0,0,0.15) 1%, rgba(0,0,0,0) 100%);
|
--card-list-item-bg-color: linear-gradient(180deg, rgba(0,0,0,0.15) 0%, rgba(0,0,0,0.15) 1%, rgba(0,0,0,0) 100%);
|
||||||
@ -410,7 +411,6 @@
|
|||||||
--login-input-box-shadow-focus: 0 0 0 1px rgba(74, 198, 148, 0.8);
|
--login-input-box-shadow-focus: 0 0 0 1px rgba(74, 198, 148, 0.8);
|
||||||
--login-input-background-color: #353535;
|
--login-input-background-color: #353535;
|
||||||
--login-input-color: #fff;
|
--login-input-color: #fff;
|
||||||
--login-input-placeholder-color: #cecece;
|
|
||||||
--login-forgot-password-color: var(--primary-color);
|
--login-forgot-password-color: var(--primary-color);
|
||||||
--login-background-url: url('../../assets/images/login-bg.jpg');
|
--login-background-url: url('../../assets/images/login-bg.jpg');
|
||||||
--login-background-size: cover;
|
--login-background-size: cover;
|
||||||
@ -419,6 +419,4 @@
|
|||||||
--login-input-font-family: 'League Spartan', sans-serif;
|
--login-input-font-family: 'League Spartan', sans-serif;
|
||||||
--login-input-placeholder-opacity: 0.5;
|
--login-input-placeholder-opacity: 0.5;
|
||||||
--login-input-placeholder-color: #fff;
|
--login-input-placeholder-color: #fff;
|
||||||
|
|
||||||
--mobile-series-img-background: true;
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
h6.section-title {
|
h6.section-title {
|
||||||
color: var(--h6-text-color);
|
color: var(--setting-header-text-color);
|
||||||
font-weight: var(--h6-font-weight);
|
font-weight: var(--setting-header-font-weight);
|
||||||
font-size: var(--h6-font-size);
|
font-size: var(--setting-header-font-size);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user