Support deleting multiple volumes, fixes #3737
The UI doesn't seem to update while the VolumeDeleteEvent is send out
This commit is contained in:
parent
4c3dd48147
commit
48e6b5a080
6 changed files with 79 additions and 8 deletions
|
|
@ -1,4 +1,6 @@
|
||||||
using System.Threading.Tasks;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.Constants;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
|
|
@ -54,4 +56,29 @@ public class VolumeController : BaseApiController
|
||||||
|
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "RequireAdminRole")]
|
||||||
|
[HttpPost("multiple")]
|
||||||
|
public async Task<ActionResult<bool>> DeleteMultipleVolumes(int[] volumesIds)
|
||||||
|
{
|
||||||
|
var volumes = (await _unitOfWork.VolumeRepository.GetVolumesById(volumesIds)).ToList();
|
||||||
|
if (volumes.Count != volumesIds.Length)
|
||||||
|
{
|
||||||
|
return BadRequest(_localizationService.Translate(User.GetUserId(), "volume-doesnt-exist"));
|
||||||
|
}
|
||||||
|
|
||||||
|
_unitOfWork.VolumeRepository.Remove(volumes);
|
||||||
|
|
||||||
|
if (!await _unitOfWork.CommitAsync())
|
||||||
|
{
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var volume in volumes)
|
||||||
|
{
|
||||||
|
await _eventHub.SendMessageAsync(MessageFactory.VolumeRemoved, MessageFactory.VolumeRemovedEvent(volume.Id, volume.SeriesId), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ public interface IVolumeRepository
|
||||||
void Add(Volume volume);
|
void Add(Volume volume);
|
||||||
void Update(Volume volume);
|
void Update(Volume volume);
|
||||||
void Remove(Volume volume);
|
void Remove(Volume volume);
|
||||||
|
void Remove(IList<Volume> volumes);
|
||||||
Task<IList<MangaFile>> GetFilesForVolume(int volumeId);
|
Task<IList<MangaFile>> GetFilesForVolume(int volumeId);
|
||||||
Task<string?> GetVolumeCoverImageAsync(int volumeId);
|
Task<string?> GetVolumeCoverImageAsync(int volumeId);
|
||||||
Task<IList<int>> GetChapterIdsByVolumeIds(IReadOnlyList<int> volumeIds);
|
Task<IList<int>> GetChapterIdsByVolumeIds(IReadOnlyList<int> volumeIds);
|
||||||
|
|
@ -43,6 +44,7 @@ public interface IVolumeRepository
|
||||||
Task<VolumeDto?> GetVolumeDtoAsync(int volumeId, int userId);
|
Task<VolumeDto?> GetVolumeDtoAsync(int volumeId, int userId);
|
||||||
Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> seriesIds, bool includeChapters = false);
|
Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> seriesIds, bool includeChapters = false);
|
||||||
Task<IEnumerable<Volume>> GetVolumes(int seriesId);
|
Task<IEnumerable<Volume>> GetVolumes(int seriesId);
|
||||||
|
Task<IEnumerable<Volume>> GetVolumesById(IList<int> volumeIds, VolumeIncludes includes = VolumeIncludes.None);
|
||||||
Task<Volume?> GetVolumeByIdAsync(int volumeId);
|
Task<Volume?> GetVolumeByIdAsync(int volumeId);
|
||||||
Task<IList<Volume>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat);
|
Task<IList<Volume>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat);
|
||||||
Task<IEnumerable<string>> GetCoverImagesForLockedVolumesAsync();
|
Task<IEnumerable<string>> GetCoverImagesForLockedVolumesAsync();
|
||||||
|
|
@ -72,6 +74,10 @@ public class VolumeRepository : IVolumeRepository
|
||||||
{
|
{
|
||||||
_context.Volume.Remove(volume);
|
_context.Volume.Remove(volume);
|
||||||
}
|
}
|
||||||
|
public void Remove(IList<Volume> volumes)
|
||||||
|
{
|
||||||
|
_context.Volume.RemoveRange(volumes);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a list of non-tracked files for a given volume.
|
/// Returns a list of non-tracked files for a given volume.
|
||||||
|
|
@ -180,6 +186,15 @@ public class VolumeRepository : IVolumeRepository
|
||||||
.OrderBy(vol => vol.MinNumber)
|
.OrderBy(vol => vol.MinNumber)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
public async Task<IEnumerable<Volume>> GetVolumesById(IList<int> volumeIds, VolumeIncludes includes = VolumeIncludes.None)
|
||||||
|
{
|
||||||
|
return await _context.Volume
|
||||||
|
.Where(vol => volumeIds.Contains(vol.Id))
|
||||||
|
.Includes(includes)
|
||||||
|
.AsSplitQuery()
|
||||||
|
.OrderBy(vol => vol.MinNumber)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a single volume with Chapter and Files
|
/// Returns a single volume with Chapter and Files
|
||||||
|
|
|
||||||
|
|
@ -472,8 +472,18 @@ export class ActionService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteMultipleVolumes(volumes: Array<Volume>, callback?: BooleanActionCallback) {
|
||||||
|
if (!await this.confirmService.confirm(translate('toasts.confirm-delete-multiple-volumes', {count: volumes.length}))) return;
|
||||||
|
|
||||||
|
this.volumeService.deleteMultipleVolumes(volumes.map(v => v.id)).subscribe((success) => {
|
||||||
|
if (callback) {
|
||||||
|
callback(success);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async deleteMultipleChapters(seriesId: number, chapterIds: Array<Chapter>, callback?: BooleanActionCallback) {
|
async deleteMultipleChapters(seriesId: number, chapterIds: Array<Chapter>, callback?: BooleanActionCallback) {
|
||||||
if (!await this.confirmService.confirm(translate('toasts.confirm-delete-multiple-chapters'))) return;
|
if (!await this.confirmService.confirm(translate('toasts.confirm-delete-multiple-chapters', {count: chapterIds.length}))) return;
|
||||||
|
|
||||||
this.chapterService.deleteMultipleChapters(seriesId, chapterIds.map(c => c.id)).subscribe(() => {
|
this.chapterService.deleteMultipleChapters(seriesId, chapterIds.map(c => c.id)).subscribe(() => {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@ export class VolumeService {
|
||||||
return this.httpClient.delete<boolean>(this.baseUrl + 'volume?volumeId=' + volumeId);
|
return this.httpClient.delete<boolean>(this.baseUrl + 'volume?volumeId=' + volumeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteMultipleVolumes(volumeIds: number[]) {
|
||||||
|
return this.httpClient.post<boolean>(this.baseUrl + "volume/multiple", volumeIds)
|
||||||
|
}
|
||||||
|
|
||||||
updateVolume(volume: any) {
|
updateVolume(volume: any) {
|
||||||
return this.httpClient.post(this.baseUrl + 'volume/update', volume, TextResonse);
|
return this.httpClient.post(this.baseUrl + 'volume/update', volume, TextResonse);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -353,11 +353,25 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
break;
|
break;
|
||||||
case Action.Delete:
|
case Action.Delete:
|
||||||
await this.actionService.deleteMultipleChapters(seriesId, chapters, () => {
|
if (chapters.length > 0) {
|
||||||
// No need to update the page as the backend will spam volume/chapter deletions
|
await this.actionService.deleteMultipleChapters(seriesId, chapters, () => {
|
||||||
this.bulkSelectionService.deselectAll();
|
// No need to update the page as the backend will spam volume/chapter deletions
|
||||||
this.cdRef.markForCheck();
|
this.bulkSelectionService.deselectAll();
|
||||||
});
|
this.cdRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
// It's not possible to select both chapters and volumes
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedVolumeIds.length > 0) {
|
||||||
|
await this.actionService.deleteMultipleVolumes(selectedVolumeIds, () => {
|
||||||
|
// No need to update the page as the backend will spam volume deletions
|
||||||
|
this.bulkSelectionService.deselectAll();
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -660,7 +674,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
||||||
case (Action.Delete):
|
case (Action.Delete):
|
||||||
await this.actionService.deleteChapter(chapter.id, (success) => {
|
await this.actionService.deleteChapter(chapter.id, (success) => {
|
||||||
if (!success) return;
|
if (!success) return;
|
||||||
|
|
||||||
this.chapters = this.chapters.filter(c => c.id != chapter.id);
|
this.chapters = this.chapters.filter(c => c.id != chapter.id);
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2628,6 +2628,7 @@
|
||||||
"alert-long-running": "This is a long running process. Please give it the time to complete before invoking again.",
|
"alert-long-running": "This is a long running process. Please give it the time to complete before invoking again.",
|
||||||
"confirm-delete-multiple-series": "Are you sure you want to delete {{count}} series? It will not modify files on disk.",
|
"confirm-delete-multiple-series": "Are you sure you want to delete {{count}} series? It will not modify files on disk.",
|
||||||
"confirm-delete-multiple-chapters": "Are you sure you want to delete {{count}} chapter/volumes? It will not modify files on disk.",
|
"confirm-delete-multiple-chapters": "Are you sure you want to delete {{count}} chapter/volumes? It will not modify files on disk.",
|
||||||
|
"confirm-delete-multiple-volumes": "Are you sure you want to delete {{count}} volumes? It will not modify files on disk.",
|
||||||
"confirm-delete-series": "Are you sure you want to delete this series? It will not modify files on disk.",
|
"confirm-delete-series": "Are you sure you want to delete this series? It will not modify files on disk.",
|
||||||
"confirm-delete-chapter": "Are you sure you want to delete this chapter? It will not modify files on disk.",
|
"confirm-delete-chapter": "Are you sure you want to delete this chapter? It will not modify files on disk.",
|
||||||
"confirm-delete-volume": "Are you sure you want to delete this volume? It will not modify files on disk.",
|
"confirm-delete-volume": "Are you sure you want to delete this volume? It will not modify files on disk.",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue