mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Smart Filter Polish & New Filters (#2283)
This commit is contained in:
parent
0d8c081093
commit
45f6fb67d4
@ -40,6 +40,10 @@ public enum FilterField
|
||||
/// <summary>
|
||||
/// On Want To Read or Not
|
||||
/// </summary>
|
||||
WantToRead = 26
|
||||
WantToRead = 26,
|
||||
/// <summary>
|
||||
/// Last time User Read
|
||||
/// </summary>
|
||||
ReadingDate = 27
|
||||
|
||||
}
|
||||
|
@ -868,8 +868,6 @@ public class SeriesRepository : ISeriesRepository
|
||||
.HasGenre(hasGenresFilter, FilterComparison.Contains, filter.Genres)
|
||||
.HasFormat(filter.Formats != null && filter.Formats.Count > 0, FilterComparison.Contains, filter.Formats!)
|
||||
.HasAverageReadTime(true, FilterComparison.GreaterThanEqual, 0)
|
||||
|
||||
// TODO: This needs different treatment
|
||||
.HasPeople(hasPeopleFilter, FilterComparison.Contains, allPeopleIds)
|
||||
|
||||
.WhereIf(onlyParentSeries,
|
||||
@ -917,6 +915,7 @@ public class SeriesRepository : ISeriesRepository
|
||||
SortField.LastChapterAdded => query.OrderBy(s => s.LastChapterAdded),
|
||||
SortField.TimeToRead => query.OrderBy(s => s.AvgHoursToRead),
|
||||
SortField.ReleaseYear => query.OrderBy(s => s.Metadata.ReleaseYear),
|
||||
SortField.ReadProgress => query.OrderBy(s => s.Progress.Where(p => p.SeriesId == s.Id).Select(p => p.LastModified).Max()),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
@ -930,6 +929,7 @@ public class SeriesRepository : ISeriesRepository
|
||||
SortField.LastChapterAdded => query.OrderByDescending(s => s.LastChapterAdded),
|
||||
SortField.TimeToRead => query.OrderByDescending(s => s.AvgHoursToRead),
|
||||
SortField.ReleaseYear => query.OrderByDescending(s => s.Metadata.ReleaseYear),
|
||||
SortField.ReadProgress => query.OrderByDescending(s => s.Progress.Where(p => p.SeriesId == s.Id).Select(p => p.LastModified).Max()),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
@ -1089,6 +1089,7 @@ public class SeriesRepository : ISeriesRepository
|
||||
FilterField.Formats => query.HasFormat(true, statement.Comparison, (IList<MangaFormat>) value),
|
||||
FilterField.ReleaseYear => query.HasReleaseYear(true, statement.Comparison, (int) value),
|
||||
FilterField.ReadTime => query.HasAverageReadTime(true, statement.Comparison, (int) value),
|
||||
FilterField.ReadingDate => query.HasReadingDate(true, statement.Comparison, (DateTime) value, userId),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
@ -439,8 +439,8 @@ public class UserRepository : IUserRepository
|
||||
var filterSeriesQuery = query.Join(_context.Series, b => b.SeriesId, s => s.Id,
|
||||
(bookmark, series) => new BookmarkSeriesPair()
|
||||
{
|
||||
bookmark = bookmark,
|
||||
series = series
|
||||
Bookmark = bookmark,
|
||||
Series = series
|
||||
});
|
||||
|
||||
var filterStatement = filter.Statements.FirstOrDefault(f => f.Field == FilterField.SeriesName);
|
||||
@ -457,34 +457,34 @@ public class UserRepository : IUserRepository
|
||||
switch (filterStatement.Comparison)
|
||||
{
|
||||
case FilterComparison.Equal:
|
||||
filterSeriesQuery = filterSeriesQuery.Where(s => s.series.Name.Equals(queryString)
|
||||
|| s.series.OriginalName.Equals(queryString)
|
||||
|| s.series.LocalizedName.Equals(queryString)
|
||||
|| s.series.SortName.Equals(queryString));
|
||||
filterSeriesQuery = filterSeriesQuery.Where(s => s.Series.Name.Equals(queryString)
|
||||
|| s.Series.OriginalName.Equals(queryString)
|
||||
|| s.Series.LocalizedName.Equals(queryString)
|
||||
|| s.Series.SortName.Equals(queryString));
|
||||
break;
|
||||
case FilterComparison.BeginsWith:
|
||||
filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.series.Name, $"{queryString}%")
|
||||
||EF.Functions.Like(s.series.OriginalName, $"{queryString}%")
|
||||
|| EF.Functions.Like(s.series.LocalizedName, $"{queryString}%")
|
||||
|| EF.Functions.Like(s.series.SortName, $"{queryString}%"));
|
||||
filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.Series.Name, $"{queryString}%")
|
||||
||EF.Functions.Like(s.Series.OriginalName, $"{queryString}%")
|
||||
|| EF.Functions.Like(s.Series.LocalizedName, $"{queryString}%")
|
||||
|| EF.Functions.Like(s.Series.SortName, $"{queryString}%"));
|
||||
break;
|
||||
case FilterComparison.EndsWith:
|
||||
filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.series.Name, $"%{queryString}")
|
||||
||EF.Functions.Like(s.series.OriginalName, $"%{queryString}")
|
||||
|| EF.Functions.Like(s.series.LocalizedName, $"%{queryString}")
|
||||
|| EF.Functions.Like(s.series.SortName, $"%{queryString}"));
|
||||
filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.Series.Name, $"%{queryString}")
|
||||
||EF.Functions.Like(s.Series.OriginalName, $"%{queryString}")
|
||||
|| EF.Functions.Like(s.Series.LocalizedName, $"%{queryString}")
|
||||
|| EF.Functions.Like(s.Series.SortName, $"%{queryString}"));
|
||||
break;
|
||||
case FilterComparison.Matches:
|
||||
filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.series.Name, $"%{queryString}%")
|
||||
||EF.Functions.Like(s.series.OriginalName, $"%{queryString}%")
|
||||
|| EF.Functions.Like(s.series.LocalizedName, $"%{queryString}%")
|
||||
|| EF.Functions.Like(s.series.SortName, $"%{queryString}%"));
|
||||
filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.Series.Name, $"%{queryString}%")
|
||||
||EF.Functions.Like(s.Series.OriginalName, $"%{queryString}%")
|
||||
|| EF.Functions.Like(s.Series.LocalizedName, $"%{queryString}%")
|
||||
|| EF.Functions.Like(s.Series.SortName, $"%{queryString}%"));
|
||||
break;
|
||||
case FilterComparison.NotEqual:
|
||||
filterSeriesQuery = filterSeriesQuery.Where(s => s.series.Name != queryString
|
||||
|| s.series.OriginalName != queryString
|
||||
|| s.series.LocalizedName != queryString
|
||||
|| s.series.SortName != queryString);
|
||||
filterSeriesQuery = filterSeriesQuery.Where(s => s.Series.Name != queryString
|
||||
|| s.Series.OriginalName != queryString
|
||||
|| s.Series.LocalizedName != queryString
|
||||
|| s.Series.SortName != queryString);
|
||||
break;
|
||||
case FilterComparison.MustContains:
|
||||
case FilterComparison.NotContains:
|
||||
@ -504,7 +504,7 @@ public class UserRepository : IUserRepository
|
||||
return await ApplyLimit(filterSeriesQuery
|
||||
.Sort(filter.SortOptions)
|
||||
.AsSplitQuery(), filter.LimitTo)
|
||||
.Select(o => o.bookmark)
|
||||
.Select(o => o.Bookmark)
|
||||
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ namespace API.Extensions.QueryExtensions.Filtering;
|
||||
|
||||
public class BookmarkSeriesPair
|
||||
{
|
||||
public AppUserBookmark bookmark { get; set; }
|
||||
public Series series { get; set; }
|
||||
public AppUserBookmark Bookmark { get; set; }
|
||||
public Series Series { get; set; }
|
||||
}
|
||||
|
||||
public static class BookmarkSort
|
||||
@ -31,12 +31,13 @@ public static class BookmarkSort
|
||||
{
|
||||
query = sortOptions.SortField switch
|
||||
{
|
||||
SortField.SortName => query.OrderBy(s => s.series.SortName.ToLower()),
|
||||
SortField.CreatedDate => query.OrderBy(s => s.series.Created),
|
||||
SortField.LastModifiedDate => query.OrderBy(s => s.series.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderBy(s => s.series.LastChapterAdded),
|
||||
SortField.TimeToRead => query.OrderBy(s => s.series.AvgHoursToRead),
|
||||
SortField.ReleaseYear => query.OrderBy(s => s.series.Metadata.ReleaseYear),
|
||||
SortField.SortName => query.OrderBy(s => s.Series.SortName.ToLower()),
|
||||
SortField.CreatedDate => query.OrderBy(s => s.Series.Created),
|
||||
SortField.LastModifiedDate => query.OrderBy(s => s.Series.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderBy(s => s.Series.LastChapterAdded),
|
||||
SortField.TimeToRead => query.OrderBy(s => s.Series.AvgHoursToRead),
|
||||
SortField.ReleaseYear => query.OrderBy(s => s.Series.Metadata.ReleaseYear),
|
||||
SortField.ReadProgress => query.OrderBy(s => s.Series.Progress.Where(p => p.SeriesId == s.Series.Id).Select(p => p.LastModified).Max()),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
@ -44,12 +45,13 @@ public static class BookmarkSort
|
||||
{
|
||||
query = sortOptions.SortField switch
|
||||
{
|
||||
SortField.SortName => query.OrderByDescending(s => s.series.SortName.ToLower()),
|
||||
SortField.CreatedDate => query.OrderByDescending(s => s.series.Created),
|
||||
SortField.LastModifiedDate => query.OrderByDescending(s => s.series.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderByDescending(s => s.series.LastChapterAdded),
|
||||
SortField.TimeToRead => query.OrderByDescending(s => s.series.AvgHoursToRead),
|
||||
SortField.ReleaseYear => query.OrderByDescending(s => s.series.Metadata.ReleaseYear),
|
||||
SortField.SortName => query.OrderByDescending(s => s.Series.SortName.ToLower()),
|
||||
SortField.CreatedDate => query.OrderByDescending(s => s.Series.Created),
|
||||
SortField.LastModifiedDate => query.OrderByDescending(s => s.Series.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderByDescending(s => s.Series.LastChapterAdded),
|
||||
SortField.TimeToRead => query.OrderByDescending(s => s.Series.AvgHoursToRead),
|
||||
SortField.ReleaseYear => query.OrderByDescending(s => s.Series.Metadata.ReleaseYear),
|
||||
SortField.ReadProgress => query.OrderByDescending(s => s.Series.Progress.Where(p => p.SeriesId == s.Series.Id).Select(p => p.LastModified).Max()),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
|
@ -288,6 +288,64 @@ public static class SeriesFilter
|
||||
return queryable.Where(s => ids.Contains(s.Id));
|
||||
}
|
||||
|
||||
public static IQueryable<Series> HasReadingDate(this IQueryable<Series> queryable, bool condition,
|
||||
FilterComparison comparison, DateTime? date, int userId)
|
||||
{
|
||||
if (!condition || !date.HasValue) return queryable;
|
||||
|
||||
var subQuery = queryable
|
||||
.Include(s => s.Progress)
|
||||
.Where(s => s.Progress != null)
|
||||
.Select(s => new
|
||||
{
|
||||
Series = s,
|
||||
MaxDate = s.Progress.Where(p => p != null && p.AppUserId == userId)
|
||||
.Select(p => (DateTime?) p.LastModified)
|
||||
.DefaultIfEmpty()
|
||||
.Max()
|
||||
})
|
||||
.Where(s => s.MaxDate != null)
|
||||
.AsEnumerable();
|
||||
|
||||
switch (comparison)
|
||||
{
|
||||
case FilterComparison.Equal:
|
||||
subQuery = subQuery.Where(s => s.MaxDate != null && s.MaxDate.Equals(date));
|
||||
break;
|
||||
case FilterComparison.IsAfter:
|
||||
case FilterComparison.GreaterThan:
|
||||
subQuery = subQuery.Where(s => s.MaxDate != null && s.MaxDate > date);
|
||||
break;
|
||||
case FilterComparison.GreaterThanEqual:
|
||||
subQuery = subQuery.Where(s => s.MaxDate != null && s.MaxDate >= date);
|
||||
break;
|
||||
case FilterComparison.IsBefore:
|
||||
case FilterComparison.LessThan:
|
||||
subQuery = subQuery.Where(s => s.MaxDate != null && s.MaxDate < date);
|
||||
break;
|
||||
case FilterComparison.LessThanEqual:
|
||||
subQuery = subQuery.Where(s => s.MaxDate != null && s.MaxDate <= date);
|
||||
break;
|
||||
case FilterComparison.NotEqual:
|
||||
subQuery = subQuery.Where(s => s.MaxDate != null && !s.MaxDate.Equals(date));
|
||||
break;
|
||||
case FilterComparison.Matches:
|
||||
case FilterComparison.Contains:
|
||||
case FilterComparison.NotContains:
|
||||
case FilterComparison.BeginsWith:
|
||||
case FilterComparison.EndsWith:
|
||||
case FilterComparison.IsInLast:
|
||||
case FilterComparison.IsNotInLast:
|
||||
case FilterComparison.MustContains:
|
||||
throw new KavitaException($"{comparison} not applicable for Series.ReadProgress");
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null);
|
||||
}
|
||||
|
||||
var ids = subQuery.Select(s => s.Series.Id).ToList();
|
||||
return queryable.Where(s => ids.Contains(s.Id));
|
||||
}
|
||||
|
||||
public static IQueryable<Series> HasTags(this IQueryable<Series> queryable, bool condition,
|
||||
FilterComparison comparison, IList<int> tags)
|
||||
{
|
||||
|
@ -31,7 +31,7 @@ public static class SeriesSort
|
||||
SortField.LastChapterAdded => query.OrderBy(s => s.LastChapterAdded),
|
||||
SortField.TimeToRead => query.OrderBy(s => s.AvgHoursToRead),
|
||||
SortField.ReleaseYear => query.OrderBy(s => s.Metadata.ReleaseYear),
|
||||
//SortField.ReadProgress => query.OrderBy()
|
||||
SortField.ReadProgress => query.OrderBy(s => s.Progress.Where(p => p.SeriesId == s.Id).Select(p => p.LastModified).Max()),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
@ -45,6 +45,7 @@ public static class SeriesSort
|
||||
SortField.LastChapterAdded => query.OrderByDescending(s => s.LastChapterAdded),
|
||||
SortField.TimeToRead => query.OrderByDescending(s => s.AvgHoursToRead),
|
||||
SortField.ReleaseYear => query.OrderByDescending(s => s.Metadata.ReleaseYear),
|
||||
SortField.ReadProgress => query.OrderByDescending(s => s.Progress.Where(p => p.SeriesId == s.Id).Select(p => p.LastModified).Max()),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
|
@ -36,12 +36,12 @@ public class AutoMapperProfiles : Profile
|
||||
public AutoMapperProfiles()
|
||||
{
|
||||
CreateMap<BookmarkSeriesPair, BookmarkDto>()
|
||||
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.bookmark.Id))
|
||||
.ForMember(dest => dest.Page, opt => opt.MapFrom(src => src.bookmark.Page))
|
||||
.ForMember(dest => dest.VolumeId, opt => opt.MapFrom(src => src.bookmark.VolumeId))
|
||||
.ForMember(dest => dest.SeriesId, opt => opt.MapFrom(src => src.bookmark.SeriesId))
|
||||
.ForMember(dest => dest.ChapterId, opt => opt.MapFrom(src => src.bookmark.ChapterId))
|
||||
.ForMember(dest => dest.Series, opt => opt.MapFrom(src => src.series));
|
||||
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Bookmark.Id))
|
||||
.ForMember(dest => dest.Page, opt => opt.MapFrom(src => src.Bookmark.Page))
|
||||
.ForMember(dest => dest.VolumeId, opt => opt.MapFrom(src => src.Bookmark.VolumeId))
|
||||
.ForMember(dest => dest.SeriesId, opt => opt.MapFrom(src => src.Bookmark.SeriesId))
|
||||
.ForMember(dest => dest.ChapterId, opt => opt.MapFrom(src => src.Bookmark.ChapterId))
|
||||
.ForMember(dest => dest.Series, opt => opt.MapFrom(src => src.Series));
|
||||
CreateMap<LibraryDto, Library>();
|
||||
CreateMap<Volume, VolumeDto>();
|
||||
CreateMap<MangaFile, MangaFileDto>();
|
||||
|
@ -69,6 +69,7 @@ public static class FilterFieldValueConverter
|
||||
.ToList(), typeof(IList<int>)),
|
||||
FilterField.WantToRead => (bool.Parse(value), typeof(bool)),
|
||||
FilterField.ReadProgress => (int.Parse(value), typeof(int)),
|
||||
FilterField.ReadingDate => (DateTime.Parse(value), typeof(DateTime?)),
|
||||
FilterField.Formats => (value.Split(',')
|
||||
.Select(x => (MangaFormat) Enum.Parse(typeof(MangaFormat), x))
|
||||
.ToList(), typeof(IList<MangaFormat>)),
|
||||
|
@ -20,6 +20,7 @@ export enum SortField {
|
||||
LastChapterAdded = 4,
|
||||
TimeToRead = 5,
|
||||
ReleaseYear = 6,
|
||||
ReadProgress = 7,
|
||||
}
|
||||
|
||||
export const allSortFields = Object.keys(SortField)
|
||||
|
@ -27,7 +27,8 @@ export enum FilterField
|
||||
ReadTime = 23,
|
||||
Path = 24,
|
||||
FilePath = 25,
|
||||
WantToRead = 26
|
||||
WantToRead = 26,
|
||||
ReadingDate = 27
|
||||
}
|
||||
|
||||
export const allFields = Object.keys(FilterField)
|
||||
|
@ -205,7 +205,7 @@ export class ActionFactoryService {
|
||||
action: Action.Scan,
|
||||
title: 'scan-library',
|
||||
callback: this.dummyCallback,
|
||||
requiresAdmin: false,
|
||||
requiresAdmin: true,
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
|
@ -1,47 +1,73 @@
|
||||
<ng-container *transloco="let t; read: 'metadata-filter-row'">
|
||||
<form [formGroup]="formGroup">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-3 me-2 col-10 mb-2">
|
||||
<select class="form-select me-2" formControlName="input">
|
||||
<option *ngFor="let field of availableFields" [value]="field">{{field | filterField}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<form [formGroup]="formGroup">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-3 me-2 col-10 mb-2">
|
||||
<select class="form-select me-2" formControlName="input">
|
||||
<option *ngFor="let field of availableFields" [value]="field">{{field | filterField}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2 me-2 col-10 mb-2">
|
||||
<select class="col-auto form-select" formControlName="comparison">
|
||||
<option *ngFor="let comparison of validComparisons$ | async" [value]="comparison">{{comparison | filterComparison}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 me-2 col-10 mb-2">
|
||||
<select class="col-auto form-select" formControlName="comparison">
|
||||
<option *ngFor="let comparison of validComparisons$ | async" [value]="comparison">{{comparison | filterComparison}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4 col-10 mb-2">
|
||||
<ng-container *ngIf="predicateType$ | async as predicateType">
|
||||
<ng-container [ngSwitch]="predicateType">
|
||||
<ng-container *ngSwitchCase="PredicateType.Text">
|
||||
<input type="text" class="form-control me-2" autocomplete="true" formControlName="filterValue">
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PredicateType.Number">
|
||||
<input type="number" inputmode="numeric" class="form-control me-2" formControlName="filterValue" min="0">
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PredicateType.Boolean">
|
||||
<input type="checkbox" class="form-check-input mt-2 me-2" style="font-size: 1.5rem" formControlName="filterValue">
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PredicateType.Date">
|
||||
<div class="input-group">
|
||||
<input
|
||||
class="form-control"
|
||||
placeholder="yyyy-mm-dd"
|
||||
name="dp"
|
||||
formControlName="filterValue"
|
||||
(dateSelect)="onDateSelect($event)"
|
||||
(blur)="updateIfDateFilled()"
|
||||
ngbDatepicker
|
||||
#d="ngbDatepicker"
|
||||
/>
|
||||
<button class="btn btn-outline-secondary fa-solid fa-calendar-days" (click)="d.toggle()" type="button"></button>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 col-10 mb-2">
|
||||
<ng-container *ngIf="predicateType$ | async as predicateType">
|
||||
<ng-container [ngSwitch]="predicateType">
|
||||
<ng-container *ngSwitchCase="PredicateType.Text">
|
||||
<input type="text" class="form-control me-2" autocomplete="true" formControlName="filterValue">
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PredicateType.Number">
|
||||
<input type="number" inputmode="numeric" class="form-control me-2" formControlName="filterValue" min="0">
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PredicateType.Boolean">
|
||||
<input type="checkbox" class="form-check-input mt-2 me-2" style="font-size: 1.5rem" formControlName="filterValue">
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PredicateType.Dropdown">
|
||||
<ng-container *ngIf="dropdownOptions$ | async as opts">
|
||||
<ng-container *ngTemplateOutlet="dropdown; context: { options: opts, multipleAllowed: MultipleDropdownAllowed }"></ng-container>
|
||||
<ng-template #dropdown let-options="options" let-multipleAllowed="multipleAllowed">
|
||||
<select2 [data]="options"
|
||||
formControlName="filterValue"
|
||||
[multiple]="multipleAllowed"
|
||||
[infiniteScroll]="true"
|
||||
[resettable]="true">
|
||||
</select2>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PredicateType.Dropdown">
|
||||
<ng-container *ngIf="dropdownOptions$ | async as opts">
|
||||
<ng-container *ngTemplateOutlet="dropdown; context: { options: opts, multipleAllowed: MultipleDropdownAllowed }"></ng-container>
|
||||
<ng-template #dropdown let-options="options" let-multipleAllowed="multipleAllowed">
|
||||
<select2 [data]="options"
|
||||
formControlName="filterValue"
|
||||
[hideSelectedItems]="true"
|
||||
[multiple]="multipleAllowed"
|
||||
[infiniteScroll]="true"
|
||||
[resettable]="true">
|
||||
</select2>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-content #removeBtn></ng-content>
|
||||
</div>
|
||||
</form>
|
||||
<div class="col pt-2 ms-2">
|
||||
<ng-container *ngIf="UiLabel !== null">
|
||||
<span class="text-muted">{{t(UiLabel.unit)}}</span>
|
||||
<i *ngIf="UiLabel.tooltip" class="fa fa-info-circle ms-1" aria-hidden="true" [ngbTooltip]="t(UiLabel.tooltip)"></i>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<ng-content #removeBtn></ng-content>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
|
@ -1,3 +1,23 @@
|
||||
::ng-deep .select2-selection__rendered {
|
||||
padding-top: 4px !important;
|
||||
}
|
||||
|
||||
|
||||
::ng-deep .ngb-dp-content, ::ng-deep .ngb-dp-header, ::ng-deep .dropdown-menu{
|
||||
background: var(--bs-body-bg);
|
||||
color: var(--body-text-color);
|
||||
}
|
||||
|
||||
::ng-deep .ngb-dp-header, ::ng-deep .ngb-dp-weekdays {
|
||||
background-color: var(--bs-body-bg) !important;
|
||||
}
|
||||
|
||||
::ng-deep .ngb-dp-day .btn-light, ::ng-deep .ngb-dp-weekday {
|
||||
background: var(--bs-body-bg);
|
||||
color: var(--body-text-color);
|
||||
}
|
||||
|
||||
::ng-deep [ngbDatepickerDayView]:hover:not(.bg-primary), [ngbDatepickerDayView].active:not(.bg-primary) {
|
||||
background: var(--primary-color-dark-shade) !important;
|
||||
outline: 1px solid var(--primary-color-dark-shade) !important;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
inject,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
Output, ViewChild,
|
||||
} from '@angular/core';
|
||||
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||
import {FilterStatement} from '../../../_models/metadata/v2/filter-statement';
|
||||
@ -25,14 +25,38 @@ import {FilterComparisonPipe} from "../../_pipes/filter-comparison.pipe";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {Select2Module, Select2Option} from "ng-select2-component";
|
||||
import {TagBadgeComponent} from "../../../shared/tag-badge/tag-badge.component";
|
||||
import {
|
||||
NgbDate,
|
||||
NgbDateParserFormatter,
|
||||
NgbDatepicker,
|
||||
NgbDateStruct,
|
||||
NgbInputDatepicker,
|
||||
NgbTooltip
|
||||
} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {TranslocoDirective} from "@ngneat/transloco";
|
||||
|
||||
enum PredicateType {
|
||||
Text = 1,
|
||||
Number = 2,
|
||||
Dropdown = 3,
|
||||
Boolean = 4
|
||||
Boolean = 4,
|
||||
Date = 5
|
||||
}
|
||||
|
||||
class FilterRowUi {
|
||||
unit = '';
|
||||
tooltip = ''
|
||||
constructor(unit: string = '', tooltip: string = '') {
|
||||
this.unit = unit;
|
||||
this.tooltip = tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
const unitLabels: Map<FilterField, FilterRowUi> = new Map([
|
||||
[FilterField.ReadingDate, new FilterRowUi('unit-reading-date')],
|
||||
[FilterField.ReadProgress, new FilterRowUi('unit-reading-progress')],
|
||||
]);
|
||||
|
||||
const StringFields = [FilterField.SeriesName, FilterField.Summary, FilterField.Path, FilterField.FilePath];
|
||||
const NumberFields = [FilterField.ReadTime, FilterField.ReleaseYear, FilterField.ReadProgress, FilterField.UserRating];
|
||||
const DropdownFields = [FilterField.PublicationStatus, FilterField.Languages, FilterField.AgeRating,
|
||||
@ -42,7 +66,8 @@ const DropdownFields = [FilterField.PublicationStatus, FilterField.Languages, Fi
|
||||
FilterField.Writers, FilterField.Genres, FilterField.Libraries,
|
||||
FilterField.Formats, FilterField.CollectionTags, FilterField.Tags
|
||||
];
|
||||
const BooleanFields = [FilterField.WantToRead]
|
||||
const BooleanFields = [FilterField.WantToRead];
|
||||
const DateFields = [FilterField.ReadingDate];
|
||||
|
||||
const DropdownFieldsWithoutMustContains = [
|
||||
FilterField.Libraries, FilterField.Formats, FilterField.AgeRating, FilterField.PublicationStatus
|
||||
@ -59,7 +84,8 @@ const StringComparisons = [FilterComparison.Equal,
|
||||
FilterComparison.BeginsWith,
|
||||
FilterComparison.EndsWith,
|
||||
FilterComparison.Matches];
|
||||
const DateComparisons = [FilterComparison.IsBefore, FilterComparison.IsAfter, FilterComparison.IsInLast, FilterComparison.IsNotInLast];
|
||||
const DateComparisons = [FilterComparison.IsBefore, FilterComparison.IsAfter, FilterComparison.Equal,
|
||||
FilterComparison.NotEqual,];
|
||||
const NumberComparisons = [FilterComparison.Equal,
|
||||
FilterComparison.NotEqual,
|
||||
FilterComparison.LessThan,
|
||||
@ -91,7 +117,11 @@ const BooleanComparisons = [
|
||||
NgIf,
|
||||
Select2Module,
|
||||
NgTemplateOutlet,
|
||||
TagBadgeComponent
|
||||
TagBadgeComponent,
|
||||
NgbTooltip,
|
||||
TranslocoDirective,
|
||||
NgbDatepicker,
|
||||
NgbInputDatepicker
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
@ -105,8 +135,10 @@ export class MetadataFilterRowComponent implements OnInit {
|
||||
@Input() availableFields: Array<FilterField> = allFields;
|
||||
@Output() filterStatement = new EventEmitter<FilterStatement>();
|
||||
|
||||
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly dateParser = inject(NgbDateParserFormatter);
|
||||
|
||||
formGroup: FormGroup = new FormGroup({
|
||||
'comparison': new FormControl<FilterComparison>(FilterComparison.Equal, []),
|
||||
@ -119,6 +151,12 @@ export class MetadataFilterRowComponent implements OnInit {
|
||||
loaded: boolean = false;
|
||||
protected readonly PredicateType = PredicateType;
|
||||
|
||||
get UiLabel(): FilterRowUi | null {
|
||||
const field = parseInt(this.formGroup.get('input')!.value, 10) as FilterField;
|
||||
if (!unitLabels.has(field)) return null;
|
||||
return unitLabels.get(field) as FilterRowUi;
|
||||
}
|
||||
|
||||
get MultipleDropdownAllowed() {
|
||||
const comp = parseInt(this.formGroup.get('comparison')?.value, 10) as FilterComparison;
|
||||
return comp === FilterComparison.Contains || comp === FilterComparison.NotContains || comp === FilterComparison.MustContains;
|
||||
@ -149,30 +187,36 @@ export class MetadataFilterRowComponent implements OnInit {
|
||||
|
||||
|
||||
this.formGroup!.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)).subscribe(_ => {
|
||||
const stmt = {
|
||||
comparison: parseInt(this.formGroup.get('comparison')?.value, 10) as FilterComparison,
|
||||
field: parseInt(this.formGroup.get('input')?.value, 10) as FilterField,
|
||||
value: this.formGroup.get('filterValue')?.value!
|
||||
};
|
||||
|
||||
// Some ids can get through and be numbers, convert them to strings for the backend
|
||||
if (typeof stmt.value === 'number' && !Number.isNaN(stmt.value)) {
|
||||
stmt.value = stmt.value + '';
|
||||
}
|
||||
|
||||
if (typeof stmt.value === 'boolean') {
|
||||
stmt.value = stmt.value + '';
|
||||
}
|
||||
|
||||
if (!stmt.value && (stmt.field !== FilterField.SeriesName && !BooleanFields.includes(stmt.field))) return;
|
||||
this.filterStatement.emit(stmt);
|
||||
this.propagateFilterUpdate();
|
||||
});
|
||||
|
||||
this.loaded = true;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
propagateFilterUpdate() {
|
||||
const stmt = {
|
||||
comparison: parseInt(this.formGroup.get('comparison')?.value, 10) as FilterComparison,
|
||||
field: parseInt(this.formGroup.get('input')?.value, 10) as FilterField,
|
||||
value: this.formGroup.get('filterValue')?.value!
|
||||
};
|
||||
|
||||
if (typeof stmt.value === 'object' && DateFields.includes(stmt.field)) {
|
||||
stmt.value = this.dateParser.format(stmt.value);
|
||||
}
|
||||
|
||||
// Some ids can get through and be numbers, convert them to strings for the backend
|
||||
if (typeof stmt.value === 'number' && !Number.isNaN(stmt.value)) {
|
||||
stmt.value = stmt.value + '';
|
||||
}
|
||||
|
||||
if (typeof stmt.value === 'boolean') {
|
||||
stmt.value = stmt.value + '';
|
||||
}
|
||||
|
||||
if (!stmt.value && (![FilterField.SeriesName, FilterField.Summary].includes(stmt.field) && !BooleanFields.includes(stmt.field))) return;
|
||||
this.filterStatement.emit(stmt);
|
||||
}
|
||||
|
||||
populateFromPreset() {
|
||||
const val = this.preset.value === "undefined" || !this.preset.value ? '' : this.preset.value;
|
||||
@ -183,7 +227,10 @@ export class MetadataFilterRowComponent implements OnInit {
|
||||
this.formGroup.get('filterValue')?.patchValue(val);
|
||||
} else if (BooleanFields.includes(this.preset.field)) {
|
||||
this.formGroup.get('filterValue')?.patchValue(val);
|
||||
} else if (DropdownFields.includes(this.preset.field)) {
|
||||
} else if (DateFields.includes(this.preset.field)) {
|
||||
this.formGroup.get('filterValue')?.patchValue(this.dateParser.parse(val)); // TODO: Figure out how this works
|
||||
}
|
||||
else if (DropdownFields.includes(this.preset.field)) {
|
||||
if (this.MultipleDropdownAllowed || val.includes(',')) {
|
||||
this.formGroup.get('filterValue')?.patchValue(val.split(',').map(d => parseInt(d, 10)));
|
||||
} else {
|
||||
@ -281,6 +328,16 @@ export class MetadataFilterRowComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
if (DateFields.includes(inputVal)) {
|
||||
this.validComparisons$.next(DateComparisons);
|
||||
this.predicateType$.next(PredicateType.Date);
|
||||
|
||||
if (this.loaded) {
|
||||
this.formGroup.get('filterValue')?.patchValue(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (BooleanFields.includes(inputVal)) {
|
||||
this.validComparisons$.next(BooleanComparisons);
|
||||
this.predicateType$.next(PredicateType.Boolean);
|
||||
@ -306,4 +363,15 @@ export class MetadataFilterRowComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
onDateSelect(event: NgbDate) {
|
||||
console.log('date selected: ', event);
|
||||
this.propagateFilterUpdate();
|
||||
}
|
||||
updateIfDateFilled() {
|
||||
console.log('date inputted: ', this.formGroup.get('filterValue')?.value);
|
||||
this.propagateFilterUpdate();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -64,6 +64,8 @@ export class FilterFieldPipe implements PipeTransform {
|
||||
return translate('filter-field-pipe.file-path');
|
||||
case FilterField.WantToRead:
|
||||
return translate('filter-field-pipe.want-to-read');
|
||||
case FilterField.ReadingDate:
|
||||
return translate('filter-field-pipe.read-date');
|
||||
default:
|
||||
throw new Error(`Invalid FilterField value: ${value}`);
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
<ng-container *transloco="let t; read: 'metadata-filter'">
|
||||
<ng-container *ngIf="toggleService.toggleState$ | async as isOpen">
|
||||
<div class="phone-hidden" *ngIf="utilityService.getActiveBreakpoint() > Breakpoint.Tablet">
|
||||
<div *ngIf="utilityService.getActiveBreakpoint() >= Breakpoint.Tablet">
|
||||
<div #collapse="ngbCollapse" [ngbCollapse]="!isOpen" (ngbCollapseChange)="setToggle($event)">
|
||||
<ng-container [ngTemplateOutlet]="filterSection"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="not-phone-hidden" *ngIf="utilityService.getActiveBreakpoint() < Breakpoint.Desktop">
|
||||
<div *ngIf="utilityService.getActiveBreakpoint() < Breakpoint.Desktop">
|
||||
<app-drawer #commentDrawer="drawer" [isOpen]="isOpen" [options]="{topOffset: 56}" (drawerClosed)="toggleService.set(false)">
|
||||
<h5 header>
|
||||
{{t('filter-title')}}
|
||||
@ -51,6 +51,16 @@
|
||||
<div class="col-md-3 col-sm-10">
|
||||
<label for="filter-name" class="form-label">{{t('filter-name-label')}}</label>
|
||||
<input id="filter-name" type="text" class="form-control" formControlName="name">
|
||||
<!-- <select2 [data]="smartFilters"-->
|
||||
<!-- id="filter-name"-->
|
||||
<!-- formControlName="name"-->
|
||||
<!-- (update)="updateFilterValue($event)"-->
|
||||
<!-- [autoCreate]="true"-->
|
||||
<!-- [multiple]="false"-->
|
||||
<!-- [infiniteScroll]="false"-->
|
||||
<!-- [hideSelectedItems]="true"-->
|
||||
<!-- [resettable]="true">-->
|
||||
<!-- </select2>-->
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="utilityService.getActiveBreakpoint() > Breakpoint.Tablet" [ngTemplateOutlet]="buttons"></ng-container>
|
||||
@ -63,15 +73,13 @@
|
||||
</ng-template>
|
||||
<ng-template #buttons>
|
||||
<!-- TODO: I might want to put a Clear button which blanks out the whole filter -->
|
||||
<div class="col-md-1 col-sm-6 mt-4 pt-1">
|
||||
<button class="btn btn-secondary col-12" (click)="clear()">{{t('reset')}}</button>
|
||||
<div class="col-md-2 col-sm-6 mt-4 pt-2 d-flex justify-content-between">
|
||||
<button class="btn btn-secondary col-6 me-1" (click)="clear()"><i class="fa-solid fa-arrow-rotate-left me-1" aria-hidden="true"></i>{{t('reset')}}</button>
|
||||
<button class="btn btn-primary col-6" (click)="apply()"><i class="fa-solid fa-play me-1" aria-hidden="true"></i>{{t('apply')}}</button>
|
||||
</div>
|
||||
<div class="col-md-1 col-sm-6 mt-4 pt-1">
|
||||
<button class="btn btn-primary col-12" (click)="apply()">{{t('apply')}}</button>
|
||||
</div>
|
||||
<div class="col-md-1 col-sm-6 mt-4 pt-1">
|
||||
<div class="col-md-1 col-sm-6 mt-4 pt-2">
|
||||
<button class="btn btn-primary col-12" (click)="save()" [disabled]="filterSettings.saveDisabled || !this.sortGroup.get('name')?.value">
|
||||
<!-- TODO: Icon here -->
|
||||
<i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
|
||||
{{t('save')}}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -30,6 +30,8 @@ import {MetadataService} from "../_services/metadata.service";
|
||||
import {FilterUtilitiesService} from "../shared/_services/filter-utilities.service";
|
||||
import {FilterService} from "../_services/filter.service";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
import {Select2Module, Select2Option, Select2UpdateEvent} from "ng-select2-component";
|
||||
import {SmartFilter} from "../_models/metadata/v2/smart-filter";
|
||||
|
||||
@Component({
|
||||
selector: 'app-metadata-filter',
|
||||
@ -38,7 +40,7 @@ import {ToastrService} from "ngx-toastr";
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [NgIf, NgbCollapse, NgTemplateOutlet, DrawerComponent, NgbTooltip, TypeaheadComponent,
|
||||
ReactiveFormsModule, FormsModule, NgbRating, AsyncPipe, TranslocoModule, SortFieldPipe, MetadataBuilderComponent, NgForOf]
|
||||
ReactiveFormsModule, FormsModule, NgbRating, AsyncPipe, TranslocoModule, SortFieldPipe, MetadataBuilderComponent, NgForOf, Select2Module]
|
||||
})
|
||||
export class MetadataFilterComponent implements OnInit {
|
||||
|
||||
@ -78,16 +80,22 @@ export class MetadataFilterComponent implements OnInit {
|
||||
allSortFields = allSortFields;
|
||||
allFilterFields = allFields;
|
||||
|
||||
handleFilters(filter: SeriesFilterV2) {
|
||||
this.filterV2 = filter;
|
||||
}
|
||||
|
||||
smartFilters!: Array<Select2Option>;
|
||||
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
|
||||
|
||||
constructor(public toggleService: ToggleService, private filterService: FilterService) {}
|
||||
constructor(public toggleService: ToggleService, private filterService: FilterService) {
|
||||
this.filterService.getAllFilters().subscribe(res => {
|
||||
this.smartFilters = res.map(r => {
|
||||
return {
|
||||
value: r,
|
||||
label: r.name,
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.filterSettings === undefined) {
|
||||
@ -106,6 +114,11 @@ export class MetadataFilterComponent implements OnInit {
|
||||
this.loadFromPresetsAndSetup();
|
||||
}
|
||||
|
||||
updateFilterValue(event: Select2UpdateEvent<any>) {
|
||||
console.log('event: ', event);
|
||||
}
|
||||
|
||||
|
||||
close() {
|
||||
this.filterOpen.emit(false);
|
||||
this.filteringCollapsed = true;
|
||||
@ -137,6 +150,10 @@ export class MetadataFilterComponent implements OnInit {
|
||||
return clonedObj;
|
||||
}
|
||||
|
||||
handleFilters(filter: SeriesFilterV2) {
|
||||
this.filterV2 = filter;
|
||||
}
|
||||
|
||||
|
||||
loadFromPresetsAndSetup() {
|
||||
this.fullyLoaded = false;
|
||||
@ -187,7 +204,7 @@ export class MetadataFilterComponent implements OnInit {
|
||||
|
||||
apply() {
|
||||
this.applyFilter.emit({isFirst: this.updateApplied === 0, filterV2: this.filterV2!});
|
||||
|
||||
|
||||
if (this.utilityService.getActiveBreakpoint() === Breakpoint.Mobile && this.updateApplied !== 0) {
|
||||
this.toggleSelected();
|
||||
}
|
||||
|
@ -14,17 +14,19 @@ export class SortFieldPipe implements PipeTransform {
|
||||
transform(value: SortField): string {
|
||||
switch (value) {
|
||||
case SortField.SortName:
|
||||
return this.translocoService.translate('sort-field-pipe.sort-name')
|
||||
return this.translocoService.translate('sort-field-pipe.sort-name');
|
||||
case SortField.Created:
|
||||
return this.translocoService.translate('sort-field-pipe.created')
|
||||
return this.translocoService.translate('sort-field-pipe.created');
|
||||
case SortField.LastModified:
|
||||
return this.translocoService.translate('sort-field-pipe.last-modified')
|
||||
return this.translocoService.translate('sort-field-pipe.last-modified');
|
||||
case SortField.LastChapterAdded:
|
||||
return this.translocoService.translate('sort-field-pipe.last-chapter-added')
|
||||
return this.translocoService.translate('sort-field-pipe.last-chapter-added');
|
||||
case SortField.TimeToRead:
|
||||
return this.translocoService.translate('sort-field-pipe.time-to-read')
|
||||
return this.translocoService.translate('sort-field-pipe.time-to-read');
|
||||
case SortField.ReleaseYear:
|
||||
return this.translocoService.translate('sort-field-pipe.release-year')
|
||||
return this.translocoService.translate('sort-field-pipe.release-year');
|
||||
case SortField.ReadProgress:
|
||||
return this.translocoService.translate('sort-field-pipe.read-progress');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -74,7 +74,7 @@
|
||||
<button ngbDropdownItem (click)="read(true)">
|
||||
<span>
|
||||
<i class="fa fa-glasses" aria-hidden="true"></i>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? t('continue') : t('read')}} Incognito</span>
|
||||
<span class="read-btn--text"> {{(hasReadingProgress) ? t('continue-incognito') : t('read-incognito')}}</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@ -101,7 +101,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-auto ms-2 d-none d-md-block" *ngIf="isAdmin || hasDownloadingRole">
|
||||
<button class="btn btn-secondary" (click)="downloadSeries()" title="Download Series" [disabled]="downloadInProgress">
|
||||
<button class="btn btn-secondary" (click)="downloadSeries()" [title]="t('download-series--tooltip')" [disabled]="downloadInProgress">
|
||||
<ng-container *ngIf="downloadInProgress; else notDownloading">
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">{{t('downloading-status')}}</span>
|
||||
|
@ -681,6 +681,8 @@
|
||||
"continue-from": "Continue {{title}}",
|
||||
"read": "{{common.read}}",
|
||||
"continue": "Continue",
|
||||
"read-incognito": "Read Incognito",
|
||||
"continue-incognito": "Continue Incognito",
|
||||
"read-options-alt": "Read options",
|
||||
"incognito": "Incognito",
|
||||
"remove-from-want-to-read": "Remove from Want to Read",
|
||||
@ -1509,7 +1511,7 @@
|
||||
"reset": "{{common.reset}}",
|
||||
"apply": "{{common.apply}}",
|
||||
"save": "{{common.save}}",
|
||||
"limit-label": "Limit To",
|
||||
"limit-label": "Limit",
|
||||
|
||||
"format-label": "Format",
|
||||
"libraries-label": "Libraries",
|
||||
@ -1541,13 +1543,19 @@
|
||||
"max": "Max"
|
||||
},
|
||||
|
||||
"metadata-filter-row": {
|
||||
"unit-reading-date": "Date",
|
||||
"unit-reading-progress": "Percent"
|
||||
},
|
||||
|
||||
"sort-field-pipe": {
|
||||
"sort-name": "Sort Name",
|
||||
"created": "Created",
|
||||
"last-modified": "Last Modified",
|
||||
"last-chapter-added": "Item Added",
|
||||
"time-to-read": "Time to Read",
|
||||
"release-year": "Release Year"
|
||||
"release-year": "Release Year",
|
||||
"read-progress": "Read Progress"
|
||||
},
|
||||
|
||||
"edit-series-modal": {
|
||||
@ -1751,7 +1759,8 @@
|
||||
"writers": "Writers",
|
||||
"path": "Path",
|
||||
"file-path": "File Path",
|
||||
"want-to-read": "Want to Read"
|
||||
"want-to-read": "Want to Read",
|
||||
"read-date": "Reading Date"
|
||||
},
|
||||
|
||||
"filter-comparison-pipe": {
|
||||
|
@ -1,27 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
mkdir Projects
|
||||
|
||||
cd Projects
|
||||
|
||||
git clone https://github.com/Kareadita/Kavita.git
|
||||
git clone https://github.com/Kareadita/Kavita-webui.git
|
||||
|
||||
cd Kavita
|
||||
chmod +x build.sh
|
||||
|
||||
#Builds program based on the target platform
|
||||
|
||||
if [ "$TARGETPLATFORM" == "linux/amd64" ]
|
||||
then
|
||||
./build.sh linux-x64
|
||||
mv /Projects/Kavita/_output/linux-x64 /Projects/Kavita/_output/build
|
||||
elif [ "$TARGETPLATFORM" == "linux/arm/v7" ]
|
||||
then
|
||||
./build.sh linux-arm
|
||||
mv /Projects/Kavita/_output/linux-arm /Projects/Kavita/_output/build
|
||||
elif [ "$TARGETPLATFORM" == "linux/arm64" ]
|
||||
then
|
||||
./build.sh linux-arm64
|
||||
mv /Projects/Kavita/_output/linux-arm64 /Projects/Kavita/_output/build
|
||||
fi
|
@ -15,9 +15,9 @@ ProgressEnd()
|
||||
|
||||
Build()
|
||||
{
|
||||
local RID="$1"
|
||||
local RID="$1"
|
||||
|
||||
ProgressStart 'Build for $RID'
|
||||
ProgressStart "Build for $RID"
|
||||
|
||||
slnFile=Kavita.sln
|
||||
|
||||
@ -26,7 +26,7 @@ Build()
|
||||
|
||||
dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform="Any CPU" -p:RuntimeIdentifiers=$RID
|
||||
|
||||
ProgressEnd 'Build for $RID'
|
||||
ProgressEnd "Build for $RID"
|
||||
}
|
||||
|
||||
BuildUI()
|
||||
@ -54,17 +54,16 @@ BuildUI()
|
||||
|
||||
Package()
|
||||
{
|
||||
local framework="$1"
|
||||
local runtime="$2"
|
||||
local runtime="$1"
|
||||
local lOutputFolder=../_output/"$runtime"/Kavita
|
||||
|
||||
ProgressStart "Creating $runtime Package for $framework"
|
||||
ProgressStart "Creating $runtime Package"
|
||||
|
||||
# TODO: Use no-restore? Because Build should have already done it for us
|
||||
echo "Building"
|
||||
cd API
|
||||
echo dotnet publish -c Release --no-restore --self-contained --runtime $runtime -o "$lOutputFolder" --framework $framework
|
||||
dotnet publish -c Release --no-restore --self-contained --runtime $runtime -o "$lOutputFolder" --framework $framework
|
||||
echo dotnet publish -c Release --no-restore --self-contained --runtime $runtime -o "$lOutputFolder"
|
||||
dotnet publish -c Release --no-restore --self-contained --runtime $runtime -o "$lOutputFolder"
|
||||
|
||||
echo "Copying Install information"
|
||||
cp ../INSTALL.txt "$lOutputFolder"/README.txt
|
||||
@ -79,7 +78,7 @@ Package()
|
||||
cd ../$outputFolder/"$runtime"/
|
||||
tar -czvf ../kavita-$runtime.tar.gz Kavita
|
||||
|
||||
ProgressEnd "Creating $runtime Package for $framework"
|
||||
ProgressEnd "Creating $runtime Package"
|
||||
|
||||
}
|
||||
|
||||
@ -94,17 +93,17 @@ BuildUI
|
||||
|
||||
#Build for x64
|
||||
Build "linux-x64"
|
||||
Package "net6.0" "linux-x64"
|
||||
Package "linux-x64"
|
||||
cd "$dir"
|
||||
|
||||
#Build for arm
|
||||
Build "linux-arm"
|
||||
Package "net6.0" "linux-arm"
|
||||
Package "linux-arm"
|
||||
cd "$dir"
|
||||
|
||||
#Build for arm64
|
||||
Build "linux-arm64"
|
||||
Package "net6.0" "linux-arm64"
|
||||
Package "linux-arm64"
|
||||
cd "$dir"
|
||||
|
||||
#Builds Docker images
|
||||
|
@ -7,7 +7,7 @@
|
||||
"name": "GPL-3.0",
|
||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||
},
|
||||
"version": "0.7.8.2"
|
||||
"version": "0.7.8.4"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
@ -14142,7 +14142,8 @@
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
26,
|
||||
27
|
||||
],
|
||||
"type": "integer",
|
||||
"description": "Represents the field which will dictate the value type and the Extension used for filtering",
|
||||
|
Loading…
x
Reference in New Issue
Block a user