diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6f77e6547..391776dd8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,11 +50,11 @@ jobs: - name: Install Swashbuckle CLI 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. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # 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). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # â„šī¸ 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 @@ -81,6 +81,6 @@ jobs: dotnet build Kavita.sln - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/API.Tests/API.Tests.csproj b/API.Tests/API.Tests.csproj index edf1af5eb..7f2a97e25 100644 --- a/API.Tests/API.Tests.csproj +++ b/API.Tests/API.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/API.Tests/Services/SeriesServiceTests.cs b/API.Tests/Services/SeriesServiceTests.cs index 0ef875e06..67a541b05 100644 --- a/API.Tests/Services/SeriesServiceTests.cs +++ b/API.Tests/Services/SeriesServiceTests.cs @@ -1141,6 +1141,41 @@ public class SeriesServiceTests : AbstractDbTest 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 + { + new AppUser + { + UserName = "majora2007" + } + }, + Name = "Test LIb", + Type = LibraryType.Book, + Series = new List + { + 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] public async Task UpdateRelatedSeries_DeleteAllRelations() { diff --git a/API/API.csproj b/API/API.csproj index c65c8ebcf..57097c64d 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -70,7 +70,7 @@ - + @@ -100,9 +100,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + diff --git a/API/Controllers/LocaleController.cs b/API/Controllers/LocaleController.cs index d96419b0f..9190c72cb 100644 --- a/API/Controllers/LocaleController.cs +++ b/API/Controllers/LocaleController.cs @@ -22,6 +22,11 @@ public class LocaleController : BaseApiController [HttpGet] public ActionResult> 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 => { try diff --git a/API/I18N/ar.json b/API/I18N/ar.json deleted file mode 100644 index 0967ef424..000000000 --- a/API/I18N/ar.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/API/I18N/kn.json b/API/I18N/kn.json deleted file mode 100644 index 0967ef424..000000000 --- a/API/I18N/kn.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/API/Services/SeriesService.cs b/API/Services/SeriesService.cs index e040d212b..4147ce641 100644 --- a/API/Services/SeriesService.cs +++ b/API/Services/SeriesService.cs @@ -625,7 +625,7 @@ public class SeriesService : ISeriesService } /// - /// 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. /// /// /// @@ -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.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.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.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; return await _unitOfWork.CommitAsync(); } + /// + /// Updates Prequel/Sequel relations and creates reciprocal relations on target series. + /// + /// List of target series IDs + /// The current series being updated + /// The relation kind (Prequel or Sequel) + private async Task UpdatePrequelSequelRelations(ICollection 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); + } + } + /// /// Applies the provided list to the series. Adds new relations and removes deleted relations. diff --git a/UI/Web/src/_card-item-common.scss b/UI/Web/src/_card-item-common.scss index 20dae728c..f418ed5bd 100644 --- a/UI/Web/src/_card-item-common.scss +++ b/UI/Web/src/_card-item-common.scss @@ -1,5 +1,3 @@ - - $image-height: 232.91px; $image-width: 160px; @@ -21,7 +19,6 @@ $image-width: 160px; outline: 2px solid var(--primary-color); } - .progress-banner { width: $image-width; height: 5px; @@ -63,7 +60,6 @@ $image-width: 160px; transform: rotate(45deg); } - .bulk-mode { position: absolute; top: 5px; @@ -90,7 +86,6 @@ $image-width: 160px; border-width: 0; } - .overlay { &:hover { .bulk-mode { @@ -102,18 +97,18 @@ $image-width: 160px; visibility: visible; .overlay-information { - visibility: visible; - display: block; + visibility: visible; + display: block; } & + .meta-title { - display: -webkit-box; - visibility: visible; - pointer-events: none; + display: -webkit-box; + visibility: visible; + pointer-events: none; } - } + } - .overlay-information { + .overlay-information { position: absolute; top: 0; left: 0; @@ -124,24 +119,24 @@ $image-width: 160px; border-top-right-radius: 4px; &:hover { - background-color: var(--card-overlay-hover-bg-color); - cursor: pointer; + background-color: var(--card-overlay-hover-bg-color); + cursor: pointer; } .overlay-information--centered { - position: absolute; - border-radius: 15px; - background-color: rgba(0, 0, 0, .7); - border-radius: 50px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 115; + position: absolute; + border-radius: 15px; + background-color: rgba(0, 0, 0, 0.7); + border-radius: 50px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 115; - &:hover { - background-color: var(--primary-color) !important; - cursor: pointer; - } + &:hover { + background-color: var(--primary-color) !important; + cursor: pointer; + } } } } diff --git a/UI/Web/src/_series-detail-common.scss b/UI/Web/src/_series-detail-common.scss index 5da0a9434..214047a93 100644 --- a/UI/Web/src/_series-detail-common.scss +++ b/UI/Web/src/_series-detail-common.scss @@ -20,6 +20,11 @@ .main-container { 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){ diff --git a/UI/Web/src/app/_services/colorscape.service.ts b/UI/Web/src/app/_services/colorscape.service.ts index c067d2c20..1d72cced8 100644 --- a/UI/Web/src/app/_services/colorscape.service.ts +++ b/UI/Web/src/app/_services/colorscape.service.ts @@ -245,46 +245,58 @@ export class ColorscapeService { const secondaryHSL = this.rgbToHsl(secondary); if (isDarkTheme) { - 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)) - }; + return this.calculateDarkThemeColors(secondaryHSL, primaryHSL, primary); } else { // NOTE: Light themes look bad in general with this system. - 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)) - }; + return this.calculateLightThemeDarkColors(primaryHSL, primary); } } + 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 { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { diff --git a/UI/Web/src/app/_single-module/cover-image/cover-image.component.scss b/UI/Web/src/app/_single-module/cover-image/cover-image.component.scss index d90b194ae..0c9c9d032 100644 --- a/UI/Web/src/app/_single-module/cover-image/cover-image.component.scss +++ b/UI/Web/src/app/_single-module/cover-image/cover-image.component.scss @@ -119,6 +119,13 @@ text-align: center; position: absolute; 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; } } diff --git a/UI/Web/src/app/_single-modules/related-tab/related-tab.component.html b/UI/Web/src/app/_single-modules/related-tab/related-tab.component.html index 5b6f1aae8..02c3d8584 100644 --- a/UI/Web/src/app/_single-modules/related-tab/related-tab.component.html +++ b/UI/Web/src/app/_single-modules/related-tab/related-tab.component.html @@ -13,7 +13,7 @@ + (clicked)="openCollection(item)" [linkUrl]="'/collections/' + item.id"> } @@ -24,7 +24,7 @@ + (clicked)="openReadingList(item)" [linkUrl]="'/lists/' + item.id"> } diff --git a/UI/Web/src/app/admin/manage-library/manage-library.component.html b/UI/Web/src/app/admin/manage-library/manage-library.component.html index cb4f0c3c5..323fd73f8 100644 --- a/UI/Web/src/app/admin/manage-library/manage-library.component.html +++ b/UI/Web/src/app/admin/manage-library/manage-library.component.html @@ -25,7 +25,7 @@ {{library.type | libraryType}} - {{t('folder-count', {num: library.folders.length})}} + {{library.folders.length}} {{library.lastScanned | timeAgo | defaultDate}} diff --git a/UI/Web/src/app/admin/manage-library/manage-library.component.scss b/UI/Web/src/app/admin/manage-library/manage-library.component.scss index a8e26e003..2e5048717 100644 --- a/UI/Web/src/app/admin/manage-library/manage-library.component.scss +++ b/UI/Web/src/app/admin/manage-library/manage-library.component.scss @@ -1,3 +1,5 @@ +@import "../../../theme/variables"; + .custom-position { right: 15px; top: -42px; @@ -11,3 +13,23 @@ .list-group-item:nth-child(even) { 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; + } + } +} diff --git a/UI/Web/src/app/admin/manage-media-issues/manage-media-issues.component.scss b/UI/Web/src/app/admin/manage-media-issues/manage-media-issues.component.scss index e69de29bb..a81f22a71 100644 --- a/UI/Web/src/app/admin/manage-media-issues/manage-media-issues.component.scss +++ b/UI/Web/src/app/admin/manage-media-issues/manage-media-issues.component.scss @@ -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; + } + } +} diff --git a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html index d94961fb7..b6707c890 100644 --- a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html +++ b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.html @@ -1,4 +1,11 @@ + +
+ +
+

@@ -64,10 +71,6 @@ - -

- -
diff --git a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.scss b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.scss index e69de29bb..30e2c0819 100644 --- a/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.scss +++ b/UI/Web/src/app/admin/manage-media-settings/manage-media-settings.component.scss @@ -0,0 +1,4 @@ +.custom-position { + right: 5px; + top: -42px; +} diff --git a/UI/Web/src/app/admin/manage-users/manage-users.component.html b/UI/Web/src/app/admin/manage-users/manage-users.component.html index 64031f4ff..56cf5e20a 100644 --- a/UI/Web/src/app/admin/manage-users/manage-users.component.html +++ b/UI/Web/src/app/admin/manage-users/manage-users.component.html @@ -67,26 +67,28 @@ } - @if (canEditMember(member)) { - - +
+ @if (canEditMember(member)) { + + - @if (member.isPending) { - - - } @else { - + @if (member.isPending) { + + + } @else { + + } } - } +
} diff --git a/UI/Web/src/app/admin/manage-users/manage-users.component.scss b/UI/Web/src/app/admin/manage-users/manage-users.component.scss index 97014e77c..07014a105 100644 --- a/UI/Web/src/app/admin/manage-users/manage-users.component.scss +++ b/UI/Web/src/app/admin/manage-users/manage-users.component.scss @@ -1,3 +1,5 @@ +@import '../../../theme/variables'; + .presence { font-size: 12px; color: var(--primary-color); @@ -32,3 +34,24 @@ .list-group-item:nth-child(even) { 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; + } + } +} + diff --git a/UI/Web/src/app/all-series/_components/all-series/all-series.component.html b/UI/Web/src/app/all-series/_components/all-series/all-series.component.html index 0dfa95b41..e1bdeb1ef 100644 --- a/UI/Web/src/app/all-series/_components/all-series/all-series.component.html +++ b/UI/Web/src/app/all-series/_components/all-series/all-series.component.html @@ -1,25 +1,27 @@ - - -

- {{title}} -

-
{{t('series-count', {num: pagination.totalItems | number})}}
-
- - - - - - +
+ + +

+ {{title}} +

+
{{t('series-count', {num: pagination.totalItems | number})}}
+
+ + + + + + -
+ +
diff --git a/UI/Web/src/app/announcements/_components/announcements/announcements.component.html b/UI/Web/src/app/announcements/_components/announcements/announcements.component.html index 7ec015f5e..f390304f6 100644 --- a/UI/Web/src/app/announcements/_components/announcements/announcements.component.html +++ b/UI/Web/src/app/announcements/_components/announcements/announcements.component.html @@ -1,9 +1,11 @@ - - -

- {{t('title')}} -

-
+
+ + +

+ {{t('title')}} +

+
- -
+ + +
diff --git a/UI/Web/src/app/announcements/_components/changelog/changelog.component.html b/UI/Web/src/app/announcements/_components/changelog/changelog.component.html index 7d082cd57..6a526a46d 100644 --- a/UI/Web/src/app/announcements/_components/changelog/changelog.component.html +++ b/UI/Web/src/app/announcements/_components/changelog/changelog.component.html @@ -1,42 +1,44 @@ - -
-

- {{t('description', {installed: ''})}} - {{t('installed')}} - {{t('description-continued', {installed: ''})}} -

+
+ +
+

+ {{t('description', {installed: ''})}} + {{t('installed')}} + {{t('description-continued', {installed: ''})}} +

- @for(update of updates; track update; let indx = $index) { -
-
-

{{update.updateTitle}}  - @if (update.isOnNightlyInRelease) { - {{t('nightly', {version: update.currentVersion})}} - } @else if (update.isReleaseEqual) { - {{t('installed')}} - } @else if (update.isReleaseNewer && indx === 0) { - {{t('available')}} + @for(update of updates; track update; let indx = $index) { +
+
+

{{update.updateTitle}}  + @if (update.isOnNightlyInRelease) { + {{t('nightly', {version: update.currentVersion})}} + } @else if (update.isReleaseEqual) { + {{t('installed')}} + } @else if (update.isReleaseNewer && indx === 0) { + {{t('available')}} + } +

+
{{t('published-label')}}{{update.publishDate | date: 'short'}}
+ + +
+              
+            
+ @if (!update.isDocker && (accountService.isAdmin$ | async)) { + @if (update.updateVersion === update.currentVersion) { + {{t('installed')}} + } @else { + {{t('download')}} + } } -

-
{{t('published-label')}}{{update.publishDate | date: 'short'}}
- - -
-            
-          
- @if (!update.isDocker && (accountService.isAdmin$ | async)) { - @if (update.updateVersion === update.currentVersion) { - {{t('installed')}} - } @else { - {{t('download')}} - } - } +
-
- } -
+ } +
- + -
+
+
diff --git a/UI/Web/src/app/app.component.html b/UI/Web/src/app/app.component.html index f346b5602..16bb8f05d 100644 --- a/UI/Web/src/app/app.component.html +++ b/UI/Web/src/app/app.component.html @@ -20,7 +20,7 @@ } } -
+
@if (navService.sideNavVisibility$ | async) {
diff --git a/UI/Web/src/app/app.component.scss b/UI/Web/src/app/app.component.scss index 5bd3746a9..cc86b965f 100644 --- a/UI/Web/src/app/app.component.scss +++ b/UI/Web/src/app/app.component.scss @@ -4,15 +4,13 @@ height: calc(var(--vh)* 100 - var(--nav-offset)); } - .companion-bar { transition: all var(--side-nav-companion-bar-transistion); - margin-left: 40px; + margin-left: 60px; overflow-y: hidden; overflow-x: hidden; height: calc(var(--vh)* 100 - var(--nav-mobile-offset)); - padding-right: 10px; - scrollbar-gutter: stable both-edges; + scrollbar-gutter: stable; scrollbar-width: thin; 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 { margin-left: 190px; - width: calc(100% - 180px); } @media (max-width: $grid-breakpoints-lg) { @@ -73,7 +70,7 @@ .content-wrapper { overflow: hidden; height: calc(var(--vh)* 100); - padding: 0 10px 0; + padding: 0; &.closed { overflow: auto; @@ -90,6 +87,7 @@ scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0.1); scrollbar-width: thin; margin-bottom: 20px; + overflow-y: auto; } .companion-bar-content { diff --git a/UI/Web/src/app/bookmark/_components/bookmarks/bookmarks.component.html b/UI/Web/src/app/bookmark/_components/bookmarks/bookmarks.component.html index e9b3ffd28..cb4e9c890 100644 --- a/UI/Web/src/app/bookmark/_components/bookmarks/bookmarks.component.html +++ b/UI/Web/src/app/bookmark/_components/bookmarks/bookmarks.component.html @@ -1,31 +1,33 @@ - - -

- {{t('title')}} -

-
{{t('series-count', {num: series.length | number})}}
-
- - - - - +
+ + +

+ {{t('title')}} +

+
{{t('series-count', {num: series.length | number})}}
+
+ + + + + - - {{t('no-data')}} {{t('no-data-2')}} - - -
+ + {{t('no-data')}} {{t('no-data-2')}} + + + +
diff --git a/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.html b/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.html index 0eae4f356..20a4491b0 100644 --- a/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.html +++ b/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.html @@ -1,33 +1,35 @@ @if (bulkSelectionService.selections$ | async; as selectionCount) { @if (selectionCount > 0) { -
-
+
+
+
- - - {{t('items-selected',{num: selectionCount | number})}} - + + + {{t('items-selected',{num: selectionCount | number})}} + - - @if (hasMarkAsUnread) { - - } - @if (hasMarkAsRead) { - + + @if (hasMarkAsUnread) { + } - - + @if (hasMarkAsRead) { + + } + + - Bulk Actions + Bulk Actions - + +
} diff --git a/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.scss b/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.scss index 57802adaa..4bbd1f73e 100644 --- a/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.scss +++ b/UI/Web/src/app/cards/bulk-operations/bulk-operations.component.scss @@ -1,13 +1,17 @@ -.bulk-select { - background-color: var(--bulk-background-color); - border-bottom: 2px solid var(--primary-color); - color: var(--bulk-selection-text-color) !important; +.bulk-select-container { + position: absolute; - .btn-icon { - color: var(--bulk-selection-text-color); + .bulk-select { + 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 { color: var(--bulk-selection-highlight-text-color) !important; -} \ No newline at end of file +} diff --git a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss index 392ec4e83..c4aacb7b8 100644 --- a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss +++ b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss @@ -48,12 +48,12 @@ flex-shrink: 0; font-size: 13px; overflow: hidden; - padding: 0 10px; + padding: 0 5px; align-items: center; justify-content: space-around; @media (max-width: 576px) { - padding: 0 10px 0 5px; + padding: 0 5px 0 5px; } .btn { @@ -133,7 +133,7 @@ h2 { cursor: pointer; padding: 0; margin: 0; - width: 40px; + width: 25px; height: 25px; position: relative; overflow: hidden; diff --git a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.ts b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.ts index d6b719d73..0ce3179ed 100644 --- a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.ts +++ b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.ts @@ -3,7 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - ContentChild, + ContentChild, DestroyRef, ElementRef, EventEmitter, HostListener, @@ -17,7 +17,7 @@ import { TrackByFunction, ViewChild } from '@angular/core'; -import {Router} from '@angular/router'; +import {NavigationEnd, NavigationStart, Router} from '@angular/router'; import {VirtualScrollerComponent, VirtualScrollerModule} from '@iharbeck/ngx-virtual-scroller'; import {FilterSettings} from 'src/app/metadata-filter/filter-settings'; 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 {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component"; 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; @@ -56,6 +59,7 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges { private readonly cdRef = inject(ChangeDetectorRef); private readonly jumpbarService = inject(JumpbarService); private readonly router = inject(Router); + private readonly destroyRef = inject(DestroyRef); protected readonly Breakpoint = Breakpoint; @@ -138,6 +142,14 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges { this.virtualScroller.refresh(); }); } + + this.router.events.pipe( + filter(event => event instanceof NavigationStart), + takeUntilDestroyed(this.destroyRef), + map(evt => evt as NavigationStart), + tap(_ => this.tryToSaveJumpKey()), + ).subscribe(); + } diff --git a/UI/Web/src/app/cards/card-item/card-item.component.html b/UI/Web/src/app/cards/card-item/card-item.component.html index 2a106e9b8..d24017a48 100644 --- a/UI/Web/src/app/cards/card-item/card-item.component.html +++ b/UI/Web/src/app/cards/card-item/card-item.component.html @@ -77,7 +77,11 @@ - {{title}} + @if (linkUrl) { + {{title}} + } @else { + {{title}} + } @if (actions && actions.length > 0) { diff --git a/UI/Web/src/app/cards/card-item/card-item.component.ts b/UI/Web/src/app/cards/card-item/card-item.component.ts index a4f49bd66..d39aa8c0a 100644 --- a/UI/Web/src/app/cards/card-item/card-item.component.ts +++ b/UI/Web/src/app/cards/card-item/card-item.component.ts @@ -145,6 +145,10 @@ export class CardItemComponent implements OnInit { * Will generate a button to instantly read */ @Input() hasReadButton = false; + /** + * A method that if defined will return the url + */ + @Input() linkUrl?: string; /** * Event emitted when item is clicked */ diff --git a/UI/Web/src/app/collections/_components/all-collections/all-collections.component.html b/UI/Web/src/app/collections/_components/all-collections/all-collections.component.html index 9fe675eb9..962eaa90c 100644 --- a/UI/Web/src/app/collections/_components/all-collections/all-collections.component.html +++ b/UI/Web/src/app/collections/_components/all-collections/all-collections.component.html @@ -1,36 +1,39 @@ - - -

{{t('title')}}

-
{{t('item-count', {num: collections.length | number})}}
-
- +
+ + +

{{t('title')}}

+
{{t('item-count', {num: collections.length | number})}}
+
+ - - - + + + - - - - - + + + + + - - {{t('no-data')}} - @if(accountService.isAdmin$ | async) { - {{t('create-one-part-1')}} {{t('create-one-part-2')}} - } - - + + {{t('no-data')}} + @if(accountService.isAdmin$ | async) { + {{t('create-one-part-1')}} {{t('create-one-part-2')}} + } + + -
+ +
diff --git a/UI/Web/src/app/collections/_components/all-collections/all-collections.component.scss b/UI/Web/src/app/collections/_components/all-collections/all-collections.component.scss index e69de29bb..b310ff108 100644 --- a/UI/Web/src/app/collections/_components/all-collections/all-collections.component.scss +++ b/UI/Web/src/app/collections/_components/all-collections/all-collections.component.scss @@ -0,0 +1,3 @@ +.main-container { + margin-top: 10px; +} diff --git a/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.html b/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.html index 43eec6e84..eb6dd5831 100644 --- a/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.html +++ b/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.html @@ -1,74 +1,76 @@ - -
- - -

- {{collectionTag.title}}() - -

-
-
-
+
+ +
+ + +

+ {{collectionTag.title}}() + +

+
+
+
-
- @if (summary.length > 0 || collectionTag.source !== ScrobbleProvider.Kavita) { -
-
- - @if (collectionTag.source !== ScrobbleProvider.Kavita && collectionTag.missingSeriesFromSource !== null - && series.length !== collectionTag.totalSourceCount && collectionTag.totalSourceCount > 0) { -
- - {{t('sync-progress', {title: series.length + ' / ' + collectionTag.totalSourceCount})}} - -
- } +
+ @if (summary.length > 0 || collectionTag.source !== ScrobbleProvider.Kavita) { +
+
+ + @if (collectionTag.source !== ScrobbleProvider.Kavita && collectionTag.missingSeriesFromSource !== null + && series.length !== collectionTag.totalSourceCount && collectionTag.totalSourceCount > 0) { +
+ + {{t('sync-progress', {title: series.length + ' / ' + collectionTag.totalSourceCount})}} + +
+ } +
+
+ @if (summary.length > 0) { +
+ +
+ } +
+
-
- @if (summary.length > 0) { -
- -
- } + } + + + + + + + + + + +
+ + {{t('no-data')}} +
-
-
- } - - - - - - - - - -
- - {{t('no-data')}} - -
- -
- - {{t('no-data-filtered')}} - -
-
-
- +
+ + {{t('no-data-filtered')}} + +
+ +
+ +
\ No newline at end of file diff --git a/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.ts b/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.ts index 667264f8d..5250d2d12 100644 --- a/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.ts +++ b/UI/Web/src/app/collections/_components/collection-detail/collection-detail.component.ts @@ -1,4 +1,4 @@ -import {DatePipe, DOCUMENT, NgIf, NgStyle} from '@angular/common'; +import {AsyncPipe, DatePipe, DOCUMENT, NgIf, NgStyle} from '@angular/common'; import { AfterContentChecked, ChangeDetectionStrategy, @@ -67,7 +67,7 @@ import {ProviderNamePipe} from "../../../_pipes/provider-name.pipe"; styleUrls: ['./collection-detail.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, 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 { diff --git a/UI/Web/src/app/dashboard/_components/dashboard.component.html b/UI/Web/src/app/dashboard/_components/dashboard.component.html index 5bb41449f..85cfd10ec 100644 --- a/UI/Web/src/app/dashboard/_components/dashboard.component.html +++ b/UI/Web/src/app/dashboard/_components/dashboard.component.html @@ -1,109 +1,104 @@ +
- - @if (libraries$ | async; as libraries) { - @if (libraries.length === 0) { - @if (accountService.isAdmin$ | async; as isAdmin) { -
- @if (isAdmin) { -
-

{{t('no-libraries')}} {{t('server-settings-link')}}.

-
- } @else { -
-

{{t('not-granted')}}

-
- } -
- } - } - } - - @for(stream of streams; track stream.id) { - @switch (stream.streamType) { - @case (StreamType.OnDeck) { - - } - @case (StreamType.RecentlyUpdated) { - - } - @case (StreamType.NewlyAdded) { - - } - @case (StreamType.SmartFilter) { - - } - @case (StreamType.MoreInGenre) { - + + @if (libraries$ | async; as libraries) { + @if (libraries.length === 0) { + @if (accountService.isAdmin$ | async; as isAdmin) { +
+ @if (isAdmin) { +
+

{{t('no-libraries')}} {{t('server-settings-link')}}.

+
+ } @else { +
+

{{t('not-granted')}}

+
+ } +
+ } } } - - - @if(stream.api | async; as data) { - - - - - + @for(stream of streams; track stream.id) { + @switch (stream.streamType) { + @case (StreamType.OnDeck) { + + } + @case (StreamType.RecentlyUpdated) { + + } + @case (StreamType.NewlyAdded) { + + } + @case (StreamType.SmartFilter) { + + } + @case (StreamType.MoreInGenre) { + + } } - - - @if(stream.api | async; as data) { - - - - - - } - - - @if(stream.api | async; as data) { - - - + + @if(stream.api | async; as data) { + + + + + + } + - - - + + @if(stream.api | async; as data) { + + + + + + } + - - -
- -
-
-
- } -
+ + @if(stream.api | async; as data) { + + + - - @if(stream.api | async; as data) { - - - - - - } - + + + + } + - - @if(stream.api | async; as data) { - - - - - - } - - } + + @if(stream.api | async; as data) { + + + + + + } + - -
+ + @if(stream.api | async; as data) { + + + + + + } + + } + + +
+
diff --git a/UI/Web/src/app/dashboard/_components/dashboard.component.scss b/UI/Web/src/app/dashboard/_components/dashboard.component.scss index e69de29bb..8e2c02603 100644 --- a/UI/Web/src/app/dashboard/_components/dashboard.component.scss +++ b/UI/Web/src/app/dashboard/_components/dashboard.component.scss @@ -0,0 +1,4 @@ +.main-container { + margin-top: 10px; + padding: 0 0 0 10px; +} \ No newline at end of file diff --git a/UI/Web/src/app/dashboard/_components/dashboard.component.ts b/UI/Web/src/app/dashboard/_components/dashboard.component.ts index be0ef8332..4d32cbe31 100644 --- a/UI/Web/src/app/dashboard/_components/dashboard.component.ts +++ b/UI/Web/src/app/dashboard/_components/dashboard.component.ts @@ -1,7 +1,7 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core'; import {Title} from '@angular/platform-browser'; 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 {FilterUtilitiesService} from 'src/app/shared/_services/filter-utilities.service'; 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 {ScrobbleProvider, ScrobblingService} from "../../_services/scrobbling.service"; import {ToastrService} from "ngx-toastr"; -import {ServerService} from "../../_services/server.service"; import {SettingsTabId} from "../../sidenav/preference-nav/preference-nav.component"; import {ReaderService} from "../../_services/reader.service"; diff --git a/UI/Web/src/app/library-detail/library-detail.component.html b/UI/Web/src/app/library-detail/library-detail.component.html index 0a360eaf8..884d557cc 100644 --- a/UI/Web/src/app/library-detail/library-detail.component.html +++ b/UI/Web/src/app/library-detail/library-detail.component.html @@ -1,36 +1,38 @@ - - -

- {{libraryName}} - -

- @if (active.fragment === '') { -
{{t('common.series-count', {num: pagination.totalItems | number})}}
+
+ + +

+ {{libraryName}} + +

+ @if (active.fragment === '') { +
{{t('common.series-count', {num: pagination.totalItems | number})}}
+ } + +
+ + + @if (filter) { + + + + + } - - - - @if (filter) { - - - - - - } - -
+ +
diff --git a/UI/Web/src/app/library-detail/library-detail.component.scss b/UI/Web/src/app/library-detail/library-detail.component.scss index 3343a96e5..308fd24f6 100644 --- a/UI/Web/src/app/library-detail/library-detail.component.scss +++ b/UI/Web/src/app/library-detail/library-detail.component.scss @@ -8,3 +8,8 @@ height: 100%; overflow-y: auto; } + + +.main-container { + margin-top: 10px; +} \ No newline at end of file diff --git a/UI/Web/src/app/nav/_components/nav-header/nav-header.component.html b/UI/Web/src/app/nav/_components/nav-header/nav-header.component.html index d06cf725b..8a1feae7a 100644 --- a/UI/Web/src/app/nav/_components/nav-header/nav-header.component.html +++ b/UI/Web/src/app/nav/_components/nav-header/nav-header.component.html @@ -31,7 +31,7 @@ > -
+
{{item.name}}
@@ -39,7 +39,7 @@ -
+
@@ -58,7 +58,7 @@ -
+
@@ -77,7 +77,7 @@ -
+
@@ -92,7 +92,7 @@ -
+
{{item.title}} @@ -101,7 +101,7 @@ -
+
{{item.title}}
@@ -141,7 +141,7 @@ -
+
{{item.filePath}} diff --git a/UI/Web/src/app/ng-swipe/ng-swipe.directive.ts b/UI/Web/src/app/ng-swipe/ng-swipe.directive.ts index 94f2bcca2..41a8d164b 100644 --- a/UI/Web/src/app/ng-swipe/ng-swipe.directive.ts +++ b/UI/Web/src/app/ng-swipe/ng-swipe.directive.ts @@ -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 { createSwipeSubscription, SwipeEvent } from './ag-swipe.core'; +import {createSwipeSubscription, SwipeDirection, SwipeEvent, SwipeStartEvent} from './ag-swipe.core'; @Directive({ selector: '[ngSwipe]', @@ -9,8 +9,13 @@ import { createSwipeSubscription, SwipeEvent } from './ag-swipe.core'; export class SwipeDirective implements OnInit, OnDestroy { private swipeSubscription: Subscription | undefined; + @Input() restrictSwipeToLeftSide: boolean = false; @Output() swipeMove: EventEmitter = new EventEmitter(); @Output() swipeEnd: EventEmitter = new EventEmitter(); + @Output() swipeLeft: EventEmitter = new EventEmitter(); + @Output() swipeRight: EventEmitter = new EventEmitter(); + @Output() swipeUp: EventEmitter = new EventEmitter(); + @Output() swipeDown: EventEmitter = new EventEmitter(); constructor( private elementRef: ElementRef, @@ -22,12 +27,49 @@ export class SwipeDirective implements OnInit, OnDestroy { this.swipeSubscription = createSwipeSubscription({ domElement: this.elementRef.nativeElement, 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() { - this.swipeSubscription?.unsubscribe?.(); + this.swipeSubscription?.unsubscribe(); } } diff --git a/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.html b/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.html index bacaab702..90f8f1de8 100644 --- a/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.html +++ b/UI/Web/src/app/reading-list/_components/reading-list-detail/reading-list-detail.component.html @@ -1,166 +1,168 @@ - - -

- {{readingList?.title}} - @if (readingList?.promoted) { - () - } - @if (actions.length > 0) { - - } -

-
{{t('item-count', {num: items.length | number})}}
+
+ + +

+ {{readingList?.title}} + @if (readingList?.promoted) { + () + } + @if (actions.length > 0) { + + } +

+
{{t('item-count', {num: items.length | number})}}
- - @if (readingList) { -
-
-

{{t('page-settings-title')}}

- + + @if (readingList) { +
+
+

{{t('page-settings-title')}}

+ +
+
+
+
+ + + @if (!(readingList.promoted && !this.isAdmin)) { +
+
+ + +
+
+ } +
+
+
-
-
-
- + } + + - @if (!(readingList.promoted && !this.isAdmin)) { -
-
- - + @if (readingList) { +
+ +
+
+ +
+
+
+
+ +
+ +
+ +
- } -
-
-
-
- } - - - - @if (readingList) { -
- -
-
- -
-
-
-
- -
- -
- -
-
- @if (readingList.startingYear !== 0) { -
-

- @if (readingList.startingMonth > 0) { - {{(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.startingYear !== 0) { +
+

+ @if (readingList.startingMonth > 0) { + {{(readingList.startingMonth +'/01/2020')| date:'MMM'}} } - @if (readingList.endingMonth > 0 && readingList.endingYear > 0) { + @if (readingList.startingMonth > 0 && readingList.startingYear > 0) { , } - @if (readingList.endingYear > 0) { - {{readingList.endingYear}} + @if (readingList.startingYear > 0) { + {{readingList.startingYear}} } - } -

-
- } - - - -
- -
- - @if (characters$ | async; as characters) { - @if (characters && characters.length > 0) { -
-
-
{{t('characters-title')}}
- - - {{item.name}} - - -
+ — + @if (readingList.endingYear > 0) { + @if (readingList.endingMonth > 0) { + {{(readingList.endingMonth +'/01/2020')| date:'MMM'}} + } + @if (readingList.endingMonth > 0 && readingList.endingYear > 0) { + , + } + @if (readingList.endingYear > 0) { + {{readingList.endingYear}} + } + } +

} + + + +
+ +
+ + @if (characters$ | async; as characters) { + @if (characters && characters.length > 0) { +
+
+
{{t('characters-title')}}
+ + + {{item.name}} + + +
+
+ } + } +
+
+ +
+ @if (items.length === 0 && !isLoading) { +
+ {{t('no-data')}} +
+ } @else if(isLoading) { + } + + + + + +
- -
- @if (items.length === 0 && !isLoading) { -
- {{t('no-data')}} -
- } @else if(isLoading) { - - } - - - - - - -
-
- } - + } + +
diff --git a/UI/Web/src/app/reading-list/_components/reading-lists/reading-lists.component.html b/UI/Web/src/app/reading-list/_components/reading-lists/reading-lists.component.html index 8f7119869..5c49198cd 100644 --- a/UI/Web/src/app/reading-list/_components/reading-lists/reading-lists.component.html +++ b/UI/Web/src/app/reading-list/_components/reading-lists/reading-lists.component.html @@ -1,35 +1,38 @@ - - -

- {{t('title')}} - -

- @if (pagination) { -
{{t('item-count', {num: pagination.totalItems | number})}}
- } +
+ + +

+ {{t('title')}} + +

+ @if (pagination) { +
{{t('item-count', {num: pagination.totalItems | number})}}
+ } -
- + + - - - - + + + + - - {{t('no-data')}} {{t('create-one-part-1')}} {{t('create-one-part-2')}}. - - + + {{t('no-data')}} {{t('create-one-part-1')}} {{t('create-one-part-2')}}. + + -
+ +
diff --git a/UI/Web/src/app/reading-list/_components/reading-lists/reading-lists.component.ts b/UI/Web/src/app/reading-list/_components/reading-lists/reading-lists.component.ts index 7a82dcd62..2f430ee57 100644 --- a/UI/Web/src/app/reading-list/_components/reading-lists/reading-lists.component.ts +++ b/UI/Web/src/app/reading-list/_components/reading-lists/reading-lists.component.ts @@ -89,18 +89,16 @@ export class ReadingListsComponent implements OnInit { .filter(action => this.readingListService.actionListFilter(action, readingList, this.isAdmin || this.hasPromote)); } - performAction(action: ActionItem, readingList: ReadingList) { - if (typeof action.callback === 'function') { - action.callback(action, readingList); - } - } - performGlobalAction(action: ActionItem) { if (typeof action.callback === 'function') { action.callback(action, undefined); } } + handleClick(list: ReadingList) { + this.router.navigateByUrl('lists/' + list.id); + } + handleReadingListActionCallback(action: ActionItem, readingList: ReadingList) { switch(action.action) { case Action.Delete: @@ -159,10 +157,6 @@ export class ReadingListsComponent implements OnInit { }); } - handleClick(list: ReadingList) { - this.router.navigateByUrl('lists/' + list.id); - } - bulkActionCallback = (action: ActionItem, data: any) => { const selectedReadingListIndexies = this.bulkSelectionService.getSelectedCardsForSource('readingList'); const selectedReadingLists = this.lists.filter((col, index: number) => selectedReadingListIndexies.includes(index + '')); diff --git a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.html b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.html index e882a0f65..d766fd1f1 100644 --- a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.html +++ b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.html @@ -334,7 +334,7 @@ @if (seriesMetadata && showDetailsTab) { -
  • +
  • {{t(TabID.Details)}} @defer (when activeTabId === TabID.Details; prefetch on idle) { diff --git a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.scss b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.scss index 5d2a0e6c3..a8586fbbb 100644 --- a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.scss +++ b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.scss @@ -1,13 +1,11 @@ @use '../../../../series-detail-common'; - .to-read-counter { position: absolute; top: 15px; left: 20px; } - .card-container{ display: grid; grid-template-columns: repeat(auto-fill, 160px); diff --git a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts index 1fce88107..a7815f72a 100644 --- a/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts +++ b/UI/Web/src/app/series-detail/_components/series-detail/series-detail.component.ts @@ -1174,5 +1174,11 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked { switchTabsToDetail() { this.activeTabId = TabID.Details; this.cdRef.markForCheck(); + setTimeout(() => { + const tabElem = this.document.querySelector('#details-tab'); + if (tabElem) { + (tabElem as HTMLLIElement).scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); + } + }, 10); } } diff --git a/UI/Web/src/app/settings/_components/settings/settings.component.html b/UI/Web/src/app/settings/_components/settings/settings.component.html index 54c36428e..83dc23817 100644 --- a/UI/Web/src/app/settings/_components/settings/settings.component.html +++ b/UI/Web/src/app/settings/_components/settings/settings.component.html @@ -1,180 +1,183 @@ - - -

    - {{fragment | settingFragment}} -

    -
    -
    - - @if (accountService.currentUser$ | async; as user) { - @if (accountService.hasAdminRole(user)) { - @defer (when fragment === SettingsTabId.General; prefetch on idle) { - @if (fragment === SettingsTabId.General) { +
    + + +

    + {{fragment | settingFragment}} +

    +
    +
    + + @if (accountService.currentUser$ | async; as user) { + @if (accountService.hasAdminRole(user)) { + @defer (when fragment === SettingsTabId.General; prefetch on idle) { + @if (fragment === SettingsTabId.General) { +
    + +
    + } + } + + @defer (when fragment === SettingsTabId.Email; prefetch on idle) { + @if (fragment === SettingsTabId.Email) { +
    + +
    + } + } + + @defer (when fragment === SettingsTabId.Media; prefetch on idle) { + @if (fragment === SettingsTabId.Media) { +
    + +
    + } + } + + @defer (when fragment === SettingsTabId.Users; prefetch on idle) { + @if (fragment === SettingsTabId.Users) { +
    + +
    + } + } + + @defer (when fragment === SettingsTabId.Libraries; prefetch on idle) { + @if (fragment === SettingsTabId.Libraries) { +
    + +
    + } + } + + @defer (when fragment === SettingsTabId.MediaIssues; prefetch on idle) { + @if (fragment === SettingsTabId.MediaIssues) { +
    + +
    + } + } + + @defer (when fragment === SettingsTabId.System; prefetch on idle) { + @if (fragment === SettingsTabId.System) { +
    + +
    + } + } + + @defer (when fragment === SettingsTabId.Statistics; prefetch on idle) { + @if (fragment === SettingsTabId.Statistics) { +
    + +
    + } + } + + @defer (when fragment === SettingsTabId.Tasks; prefetch on idle) { + @if (fragment === SettingsTabId.Tasks) { +
    + +
    + } + } + + @defer (when fragment === SettingsTabId.KavitaPlus; prefetch on idle) { + @if (fragment === SettingsTabId.KavitaPlus) { +
    + +
    + } + } + } + + + @defer (when fragment === SettingsTabId.Account; prefetch on idle) { + @if (fragment === SettingsTabId.Account) {
    - + +
    + +
    + +
    +
    } } - - @defer (when fragment === SettingsTabId.Email; prefetch on idle) { - @if (fragment === SettingsTabId.Email) { + + @defer (when fragment === SettingsTabId.Preferences; prefetch on idle) { + @if (fragment === SettingsTabId.Preferences) {
    - +
    } } - - @defer (when fragment === SettingsTabId.Media; prefetch on idle) { - @if (fragment === SettingsTabId.Media) { + + @defer (when fragment === SettingsTabId.Customize; prefetch on idle) { + @if (fragment === SettingsTabId.Customize) { +
    + +
    + } + } + + @defer (when fragment === SettingsTabId.Clients; prefetch on idle) { + @if (fragment === SettingsTabId.Clients) {
    - +
    } } - - @defer (when fragment === SettingsTabId.Users; prefetch on idle) { - @if (fragment === SettingsTabId.Users) { + + @defer (when fragment === SettingsTabId.Theme; prefetch on idle) { + @if (fragment === SettingsTabId.Theme) {
    - +
    } } - - @defer (when fragment === SettingsTabId.Libraries; prefetch on idle) { - @if (fragment === SettingsTabId.Libraries) { + + @defer (when fragment === SettingsTabId.Devices; prefetch on idle) { + @if (fragment === SettingsTabId.Devices) {
    - +
    } } - - @defer (when fragment === SettingsTabId.MediaIssues; prefetch on idle) { - @if (fragment === SettingsTabId.MediaIssues) { + + @defer (when fragment === SettingsTabId.UserStats; prefetch on idle) { + @if (fragment === SettingsTabId.UserStats) {
    - +
    } } - - @defer (when fragment === SettingsTabId.System; prefetch on idle) { - @if (fragment === SettingsTabId.System) { + + @defer (when fragment === SettingsTabId.CBLImport; prefetch on idle) { + @if (fragment === SettingsTabId.CBLImport) {
    - +
    } } - - @defer (when fragment === SettingsTabId.Statistics; prefetch on idle) { - @if (fragment === SettingsTabId.Statistics) { + + @defer (when fragment === SettingsTabId.Scrobbling; prefetch on idle) { + @if(hasActiveLicense && fragment === SettingsTabId.Scrobbling) {
    - +
    } } - - @defer (when fragment === SettingsTabId.Tasks; prefetch on idle) { - @if (fragment === SettingsTabId.Tasks) { + + @defer (when fragment === SettingsTabId.MALStackImport; prefetch on idle) { + @if(hasActiveLicense && fragment === SettingsTabId.MALStackImport) {
    - -
    - } - } - - @defer (when fragment === SettingsTabId.KavitaPlus; prefetch on idle) { - @if (fragment === SettingsTabId.KavitaPlus) { -
    - +
    } } } +
    +
    +
    - - @defer (when fragment === SettingsTabId.Account; prefetch on idle) { - @if (fragment === SettingsTabId.Account) { -
    - -
    - -
    - -
    - -
    - } - } - - @defer (when fragment === SettingsTabId.Preferences; prefetch on idle) { - @if (fragment === SettingsTabId.Preferences) { -
    - -
    - } - } - - @defer (when fragment === SettingsTabId.Customize; prefetch on idle) { - @if (fragment === SettingsTabId.Customize) { -
    - -
    - } - } - - @defer (when fragment === SettingsTabId.Clients; prefetch on idle) { - @if (fragment === SettingsTabId.Clients) { -
    - -
    - } - } - - @defer (when fragment === SettingsTabId.Theme; prefetch on idle) { - @if (fragment === SettingsTabId.Theme) { -
    - -
    - } - } - - @defer (when fragment === SettingsTabId.Devices; prefetch on idle) { - @if (fragment === SettingsTabId.Devices) { -
    - -
    - } - } - - @defer (when fragment === SettingsTabId.UserStats; prefetch on idle) { - @if (fragment === SettingsTabId.UserStats) { -
    - -
    - } - } - - @defer (when fragment === SettingsTabId.CBLImport; prefetch on idle) { - @if (fragment === SettingsTabId.CBLImport) { -
    - -
    - } - } - - @defer (when fragment === SettingsTabId.Scrobbling; prefetch on idle) { - @if(hasActiveLicense && fragment === SettingsTabId.Scrobbling) { -
    - -
    - } - } - - @defer (when fragment === SettingsTabId.MALStackImport; prefetch on idle) { - @if(hasActiveLicense && fragment === SettingsTabId.MALStackImport) { -
    - -
    - } - } - } -
    -
    diff --git a/UI/Web/src/app/settings/_components/settings/settings.component.scss b/UI/Web/src/app/settings/_components/settings/settings.component.scss index a92da0bde..cf18f3714 100644 --- a/UI/Web/src/app/settings/_components/settings/settings.component.scss +++ b/UI/Web/src/app/settings/_components/settings/settings.component.scss @@ -1,10 +1,28 @@ +@import '../../../../theme/variables'; + h2 { color: white; font-weight: bold; } +.main-container { + margin-top: 10px; +} + ::ng-deep .content-wrapper:not(.closed) { .scale { width: calc(100dvw - 200px) !important; + + + } +} + + +@media (max-width: $grid-breakpoints-lg) { + ::ng-deep .content-wrapper:not(.closed) { + .scale { + width: 100% !important; + overflow-x: auto; + } } } diff --git a/UI/Web/src/app/sidenav/_components/customize-sidenav-streams/customize-sidenav-streams.component.html b/UI/Web/src/app/sidenav/_components/customize-sidenav-streams/customize-sidenav-streams.component.html index 5642cf404..7a497f212 100644 --- a/UI/Web/src/app/sidenav/_components/customize-sidenav-streams/customize-sidenav-streams.component.html +++ b/UI/Web/src/app/sidenav/_components/customize-sidenav-streams/customize-sidenav-streams.component.html @@ -1,32 +1,31 @@
    -
    - @if (items.length > 3) { -
    - -
    - - -
    - @if (listForm.get('filterSideNavStream')?.value) { - - } + @if (items.length > 3) { +
    + +
    + +
    - } - -
    - -
    - - -
    -
    - - -
    - + @if (listForm.get('filterSideNavStream')?.value) { + + }
    + } + +
    +
    +
    + + +
    +
    + + +
    +
    +
    event instanceof NavigationEnd), - takeUntilDestroyed(this.destroyRef), - map(evt => evt as NavigationEnd), - tap((evt: NavigationEnd) => this.triggerHighlightCheck(evt.url)) + .pipe( + filter(event => event instanceof NavigationEnd), + takeUntilDestroyed(this.destroyRef), + map(evt => evt as NavigationEnd), + tap((evt: NavigationEnd) => this.triggerHighlightCheck(evt.url)), + tap(_ => this.collapseNavIfApplicable()) ).subscribe(); } @@ -153,7 +155,6 @@ export class SideNavItemComponent implements OnInit { // If on mobile, automatically collapse the side nav after making a selection collapseNavIfApplicable() { if (this.utilityService.getActiveBreakpoint() < Breakpoint.Tablet) { - console.log('collapsing side nav'); this.navService.collapseSideNav(true); } } diff --git a/UI/Web/src/app/sidenav/preference-nav/preference-nav.component.scss b/UI/Web/src/app/sidenav/preference-nav/preference-nav.component.scss index d74d7858c..2515ad5fd 100644 --- a/UI/Web/src/app/sidenav/preference-nav/preference-nav.component.scss +++ b/UI/Web/src/app/sidenav/preference-nav/preference-nav.component.scss @@ -73,6 +73,15 @@ } } +::ng-deep .side-nav-text { + div { + display: flex; + } + span { + font-size: 0.6rem; + } +} + @media (max-width: $grid-breakpoints-lg) { .side-nav { padding: 10px 0; diff --git a/UI/Web/src/app/sidenav/preference-nav/preference-nav.component.ts b/UI/Web/src/app/sidenav/preference-nav/preference-nav.component.ts index a1e4eb440..738178931 100644 --- a/UI/Web/src/app/sidenav/preference-nav/preference-nav.component.ts +++ b/UI/Web/src/app/sidenav/preference-nav/preference-nav.component.ts @@ -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, [])); } } diff --git a/UI/Web/src/app/user-settings/manage-devices/manage-devices.component.html b/UI/Web/src/app/user-settings/manage-devices/manage-devices.component.html index 32f4f64f4..6ec0fecc1 100644 --- a/UI/Web/src/app/user-settings/manage-devices/manage-devices.component.html +++ b/UI/Web/src/app/user-settings/manage-devices/manage-devices.component.html @@ -27,7 +27,9 @@ {{t('platform-label')}} - + + {{t('actions-header')}} + diff --git a/UI/Web/src/app/user-settings/manage-devices/manage-devices.component.scss b/UI/Web/src/app/user-settings/manage-devices/manage-devices.component.scss index 4b988b958..14218074a 100644 --- a/UI/Web/src/app/user-settings/manage-devices/manage-devices.component.scss +++ b/UI/Web/src/app/user-settings/manage-devices/manage-devices.component.scss @@ -1,4 +1,26 @@ +@import '../../../theme/variables'; + .custom-position { right: 15px; 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; + } + } +} \ No newline at end of file diff --git a/UI/Web/src/app/volume-detail/volume-detail.component.html b/UI/Web/src/app/volume-detail/volume-detail.component.html index 146102f52..7f3cb442e 100644 --- a/UI/Web/src/app/volume-detail/volume-detail.component.html +++ b/UI/Web/src/app/volume-detail/volume-detail.component.html @@ -1,6 +1,6 @@ - +
    @@ -95,7 +95,7 @@
    - {{t('writers-title')}} + {{t('writers-title')}}
    @@ -105,7 +105,7 @@
    - {{t('cover-artists-title')}} + {{t('cover-artists-title')}}
    @@ -155,7 +155,7 @@
  • + {{t('details-tab')}} + + @defer (when activeTabId === TabID.Details; prefetch on idle) { + + } + +
  • + } +
    diff --git a/UI/Web/src/app/volume-detail/volume-detail.component.ts b/UI/Web/src/app/volume-detail/volume-detail.component.ts index 3f090aafb..b77fd36b2 100644 --- a/UI/Web/src/app/volume-detail/volume-detail.component.ts +++ b/UI/Web/src/app/volume-detail/volume-detail.component.ts @@ -646,6 +646,12 @@ export class VolumeDetailComponent implements OnInit { switchTabsToDetail() { this.activeTabId = TabID.Details; 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() { diff --git a/UI/Web/src/app/want-to-read/_components/want-to-read/want-to-read.component.scss b/UI/Web/src/app/want-to-read/_components/want-to-read/want-to-read.component.scss index e0c5328d9..0634ecd2d 100644 --- a/UI/Web/src/app/want-to-read/_components/want-to-read/want-to-read.component.scss +++ b/UI/Web/src/app/want-to-read/_components/want-to-read/want-to-read.component.scss @@ -7,7 +7,7 @@ // This is responsible for ensuring we scroll down and only tabs and companion bar is visible .main-container { // Height set dynamically by get ScrollingBlockHeight() - overflow-y: auto; + overflow: auto; position: relative; overscroll-behavior-y: none; scrollbar-gutter: stable; diff --git a/UI/Web/src/assets/langs/en.json b/UI/Web/src/assets/langs/en.json index 923511cfe..b0419343c 100644 --- a/UI/Web/src/assets/langs/en.json +++ b/UI/Web/src/assets/langs/en.json @@ -250,13 +250,15 @@ "add": "{{common.add}}", "delete": "{{common.delete}}", "edit": "{{common.edit}}", - "no-data": "{{typeahead.no-data}}" + "no-data": "{{typeahead.no-data}}", + "actions-header": "{{manage-users.actions-header}}" }, "edit-device-modal": { "title": "Edit Device", "device-name-label": "{{manage-devices.name-label}}", "platform-label": "{{manage-devices.platform-label}}", + "email-label": "{{common.email}}", "email-tooltip": "This email will be used to accept the file via Send To", "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.", "activate-delete": "{{common.delete}}", "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}}", "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 ", diff --git a/UI/Web/src/styles.scss b/UI/Web/src/styles.scss index 332cbf0c4..889a89642 100644 --- a/UI/Web/src/styles.scss +++ b/UI/Web/src/styles.scss @@ -101,15 +101,17 @@ app-root { } body { - scrollbar-gutter: stable both-edges; 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 scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0.1); scrollbar-width: thin; + padding: 0; + margin: 0; @media (max-width: $grid-breakpoints-lg) { - margin-top: var(--nav-mobile-offset) !important; - height: calc(var(--vh)* 100 - var(--nav-mobile-offset)) !important; + /* Setting this break the readers */ + //margin-top: var(--nav-mobile-offset) !important; + //height: calc(var(--vh)* 100 - var(--nav-mobile-offset)) !important; } } @@ -118,4 +120,4 @@ body { height: 1px; background-color: var(--setting-break-color); margin: 30px 0; -} +} \ No newline at end of file diff --git a/UI/Web/src/theme/components/_sidenav.scss b/UI/Web/src/theme/components/_sidenav.scss index 36558c05c..390dedbdb 100644 --- a/UI/Web/src/theme/components/_sidenav.scss +++ b/UI/Web/src/theme/components/_sidenav.scss @@ -1,88 +1,19 @@ - -@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; - } -} +@import "../variables"; .side-nav-container { padding-bottom: 10px; width: 190px; background-color: var(--side-nav-bg-color); - height: calc((var(--vh)*100) - 115px); + height: calc((var(--vh) * 100) - 115px); position: fixed; margin: 0; left: 10px; top: 73px; 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); &::-webkit-scrollbar { @@ -90,11 +21,11 @@ } &.preference { - height: calc((var(--vh)*100)); + height: calc((var(--vh) * 100)); } &.no-donate { - height: calc((var(--vh)*100) - 82px); + height: calc((var(--vh) * 100) - 82px); } &.hidden { @@ -117,39 +48,39 @@ -webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 97%, transparent 100%); scrollbar-gutter: stable; 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 - @supports (-moz-appearance:none) { - scrollbar-color: rgba(255,255,255,0.3) rgba(0, 0, 0, 0); + @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 { - visibility: visible; - background-color: rgba(255,255,255,0.3); /*On hover, it will turn grey*/ + background: transparent; /*makes it invisible when not hovering*/ + } + + &: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 { 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) { .side-nav-container { padding: 10px 0; @@ -170,11 +180,9 @@ top: 0; transition: width var(--side-nav-openclose-transition); z-index: 1050; - overflow: auto; + overflow-y: auto; border: var(--side-nav-mobile-border); - - &.no-donate { height: 100dvh; } @@ -185,6 +193,14 @@ box-shadow: none; } + .side-nav { + overflow: auto; + } + + .side-nav-item { + padding: 0; + } + .side-nav-item:first { border-top-left-radius: var(--side-nav-border-radius); border-top-right-radius: var(--side-nav-border-radius); @@ -192,14 +208,16 @@ } .sidenav-bottom { - display:none; - + display: none; + &.closed { + left: 10px; + } } .side-nav-overlay { background-color: var(--side-nav-overlay-color); width: 100vw; - height: calc((var(--vh)*100) - var(--nav-mobile-offset)); + height: calc((var(--vh) * 100) - var(--nav-mobile-offset)); position: absolute; left: 0; top: var(--nav-mobile-offset); diff --git a/UI/Web/src/theme/themes/dark.scss b/UI/Web/src/theme/themes/dark.scss index 53a3d16f5..e92bb0468 100644 --- a/UI/Web/src/theme/themes/dark.scss +++ b/UI/Web/src/theme/themes/dark.scss @@ -53,8 +53,6 @@ --default-state-scrollbar: transparent; --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. * --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; --color-scheme: dark; --tile-color: var(--primary-color); - --nav-offset: 70px; + --nav-offset: 60px; --nav-mobile-offset: 55px; + /* Should we render the series cover as background on mobile */ + --mobile-series-img-background: true; /* Setting Item */ - // TODO: Robbie let's refactor this so all setting classes inherit from this area - --h6-text-color: #d5d5d5; - --h6-font-size: 1.2rem; - --h6-font-weight: bold; + --setting-header-text-color: #d5d5d5; + --setting-header-font-size: 1.2rem; + --setting-header-font-weight: bold; --setting-break-color: rgba(255, 255, 255, 0.2); @@ -100,6 +99,7 @@ --table-body-text-color: hsla(0,0%,100%,.85); --table-body-striped-bg-color: hsla(0,0%,100%,.25); --table-body-border: hidden; + --table-body-striped-bg-color: var(--elevation-layer2); /* Navbar */ @@ -383,6 +383,7 @@ /* Bulk Selection */ --bulk-selection-text-color: var(--navbar-text-color); --bulk-selection-highlight-text-color: var(--primary-color); + --bulk-selection-bg-color: rgba(39,39,39,1); /* 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%); @@ -410,7 +411,6 @@ --login-input-box-shadow-focus: 0 0 0 1px rgba(74, 198, 148, 0.8); --login-input-background-color: #353535; --login-input-color: #fff; - --login-input-placeholder-color: #cecece; --login-forgot-password-color: var(--primary-color); --login-background-url: url('../../assets/images/login-bg.jpg'); --login-background-size: cover; @@ -419,6 +419,4 @@ --login-input-font-family: 'League Spartan', sans-serif; --login-input-placeholder-opacity: 0.5; --login-input-placeholder-color: #fff; - - --mobile-series-img-background: true; } diff --git a/UI/Web/src/theme/utilities/_headings.scss b/UI/Web/src/theme/utilities/_headings.scss index 11460676b..a64ac9432 100644 --- a/UI/Web/src/theme/utilities/_headings.scss +++ b/UI/Web/src/theme/utilities/_headings.scss @@ -1,5 +1,5 @@ h6.section-title { - color: var(--h6-text-color); - font-weight: var(--h6-font-weight); - font-size: var(--h6-font-size); + color: var(--setting-header-text-color); + font-weight: var(--setting-header-font-weight); + font-size: var(--setting-header-font-size); }