Skip to content

Commit d20a272

Browse files
committed
Added filtering comic files by subdirectory [#2494]
1 parent 1259685 commit d20a272

File tree

9 files changed

+271
-27
lines changed

9 files changed

+271
-27
lines changed

comixed-webui/src/app/comic-files/actions/comic-file-list.actions.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ export const loadComicFileListFailure = createAction(
3939
'[Comic File List] Failed to load comic files in a file system'
4040
);
4141

42+
export const updateCurrentPath = createAction(
43+
'[Comic File List] Set the current path to be shown',
44+
props<{
45+
path: string | null;
46+
}>()
47+
);
48+
4249
export const toggleComicFileSelections = createAction(
4350
'[Comic File List] Toggle comic file selections',
4451
props<{

comixed-webui/src/app/comic-files/pages/import-comics-page/import-comics-page.component.html

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ <h2>
8282
}}
8383
</h2>
8484

85+
<mat-form-field class="cx-width-100" appearance="fill">
86+
<mat-label class="cx-text-nowrap">
87+
{{ "comic-files.label.filter-options" | translate }}
88+
</mat-label>
89+
<mat-select
90+
[value]="currentPath"
91+
(selectionChange)="onChangeCurrentPath($event.value)"
92+
[placeholder]="'comic-files.text.all-directories' | translate"
93+
>
94+
@for (option of pathOptions$ | async; track option) {
95+
<mat-option [value]="option.value">
96+
{{ option.label | translate }}
97+
</mat-option>
98+
}
99+
</mat-select>
100+
<input matInput readonly hidden />
101+
</mat-form-field>
102+
85103
<mat-paginator
86104
class="cx-height-100"
87105
showFirstLastButtons="true"
@@ -108,13 +126,14 @@ <h2>
108126
>
109127
<ng-container matColumnDef="selection">
110128
<mat-header-cell *matHeaderCellDef mat-sort-header>
111-
<mat-checkbox
112-
[disabled]="!dataSource.data.length"
113-
[checked]="allSelected"
114-
(change)="onSelectAll($event.checked)"
115-
(keyup)="onSelectAll($event.checked)"
116-
(click)="$event.stopPropagation()"
117-
></mat-checkbox>
129+
@if (!currentPath) {
130+
<mat-checkbox
131+
[disabled]="!dataSource.data.length"
132+
[checked]="allSelected"
133+
(change)="onSelectAll($event.checked)"
134+
(click)="$event.stopPropagation()"
135+
></mat-checkbox>
136+
}
118137
</mat-header-cell>
119138
<mat-cell *matCellDef="let entry">
120139
<span class="cx-width-100 cx-text-nowrap">

comixed-webui/src/app/comic-files/pages/import-comics-page/import-comics-page.component.spec.ts

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,18 @@ import { ComicFileLoaderComponent } from '@app/comic-files/components/comic-file
6969
import { RouterTestingModule } from '@angular/router/testing';
7070
import { MatSortModule } from '@angular/material/sort';
7171
import { Router } from '@angular/router';
72-
import { toggleComicFileSelections } from '@app/comic-files/actions/comic-file-list.actions';
72+
import {
73+
toggleComicFileSelections,
74+
updateCurrentPath
75+
} from '@app/comic-files/actions/comic-file-list.actions';
7376
import {
7477
FEATURE_ENABLED_FEATURE_KEY,
7578
initialState as initialFeatureEnabledState
7679
} from '@app/admin/reducers/feature-enabled.reducer';
7780
import { getFeatureEnabled } from '@app/admin/actions/feature-enabled.actions';
7881
import { BLOCKED_PAGES_ENABLED } from '@app/admin/admin.constants';
7982
import { importComicFiles } from '@app/comic-files/actions/import-comic-files.actions';
83+
import { ComicFileGroup } from '@app/comic-files/models/comic-file-group';
8084

8185
describe('ImportComicsPageComponent', () => {
8286
const USER = USER_READER;
@@ -89,6 +93,16 @@ describe('ImportComicsPageComponent', () => {
8993
},
9094
{ ...COMIC_FILE_4, selected: true }
9195
];
96+
const GROUPS: ComicFileGroup[] = [
97+
{
98+
directory: 'directory1',
99+
files: [COMIC_FILE_1, COMIC_FILE_3]
100+
},
101+
{
102+
directory: 'directory2',
103+
files: [COMIC_FILE_2, COMIC_FILE_4]
104+
}
105+
];
92106
const FILE = COMIC_FILE_3;
93107
const PAGE_SIZE = 400;
94108
const BLOCKED_PAGES_ENABLED_FEATURE_ENABLED = Math.random() > 0.5;
@@ -420,4 +434,89 @@ describe('ImportComicsPageComponent', () => {
420434
});
421435
});
422436
});
437+
438+
describe('changing the current path filter', () => {
439+
describe('setting a value', () => {
440+
beforeEach(() => {
441+
component.onChangeCurrentPath(GROUPS[0].directory);
442+
});
443+
444+
it('fires an action', () => {
445+
expect(store.dispatch).toHaveBeenCalledWith(
446+
updateCurrentPath({ path: GROUPS[0].directory })
447+
);
448+
});
449+
});
450+
451+
describe('clearing the path', () => {
452+
beforeEach(() => {
453+
component.onChangeCurrentPath(null);
454+
});
455+
456+
it('fires an action', () => {
457+
expect(store.dispatch).toHaveBeenCalledWith(
458+
updateCurrentPath({ path: null })
459+
);
460+
});
461+
});
462+
});
463+
464+
describe('updating displayed comic files', () => {
465+
describe('when a current path is set', () => {
466+
beforeEach(() => {
467+
component.currentPath = GROUPS[0].directory;
468+
component.dataSource.data = [];
469+
store.setState({
470+
...initialState,
471+
[COMIC_FILE_LIST_FEATURE_KEY]: {
472+
...initialComicFileListState,
473+
loading: false,
474+
groups: GROUPS
475+
}
476+
});
477+
});
478+
479+
it('updates the data', () => {
480+
expect(component.dataSource.data).not.toEqual([]);
481+
});
482+
});
483+
484+
describe('when a current path is invalid', () => {
485+
beforeEach(() => {
486+
component.currentPath = GROUPS[0].directory.substring(1);
487+
component.dataSource.data = [];
488+
store.setState({
489+
...initialState,
490+
[COMIC_FILE_LIST_FEATURE_KEY]: {
491+
...initialComicFileListState,
492+
loading: false,
493+
groups: GROUPS
494+
}
495+
});
496+
});
497+
498+
it('updates the data', () => {
499+
expect(component.dataSource.data).toEqual([]);
500+
});
501+
});
502+
503+
describe('when no current path is set', () => {
504+
beforeEach(() => {
505+
component.currentPath = null;
506+
component.dataSource.data = [];
507+
store.setState({
508+
...initialState,
509+
[COMIC_FILE_LIST_FEATURE_KEY]: {
510+
...initialComicFileListState,
511+
loading: false,
512+
files: FILES
513+
}
514+
});
515+
});
516+
517+
it('updates the data', () => {
518+
expect(component.dataSource.data).not.toEqual([]);
519+
});
520+
});
521+
});
423522
});

comixed-webui/src/app/comic-files/pages/import-comics-page/import-comics-page.component.ts

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
OnInit,
2525
ViewChild
2626
} from '@angular/core';
27-
import { Subscription } from 'rxjs';
27+
import { BehaviorSubject, Subscription } from 'rxjs';
2828
import { ComicFile } from '@app/comic-files/models/comic-file';
2929
import { LoggerService } from '@angular-ru/cdk/logger';
3030
import { Store } from '@ngrx/store';
@@ -34,7 +34,9 @@ import { filter } from 'rxjs/operators';
3434
import { Title } from '@angular/platform-browser';
3535
import {
3636
selectComicFileListState,
37-
selectComicFiles
37+
selectComicFiles,
38+
selectComicFilesCurrentPath,
39+
selectComicGroups
3840
} from '@app/comic-files/selectors/comic-file-list.selectors';
3941
import { selectImportComicFilesState } from '@app/comic-files/selectors/import-comic-files.selectors';
4042
import { setBusyState } from '@app/core/actions/busy.actions';
@@ -68,7 +70,8 @@ import {
6870
import { QueryParameterService } from '@app/core/services/query-parameter.service';
6971
import {
7072
loadComicFilesFromSession,
71-
toggleComicFileSelections
73+
toggleComicFileSelections,
74+
updateCurrentPath
7275
} from '@app/comic-files/actions/comic-file-list.actions';
7376
import { Router } from '@angular/router';
7477
import { selectFeatureEnabledState } from '@app/admin/selectors/feature-enabled.selectors';
@@ -86,9 +89,13 @@ import {
8689
} from '@angular/material/card';
8790
import { ComicFileLoaderComponent } from '../../components/comic-file-loader/comic-file-loader.component';
8891
import { MatCheckbox } from '@angular/material/checkbox';
89-
import { MatLabel } from '@angular/material/form-field';
92+
import { MatFormField, MatLabel } from '@angular/material/form-field';
9093
import { AsyncPipe, DecimalPipe } from '@angular/common';
9194
import { ComicFileCoverUrlPipe } from '../../pipes/comic-file-cover-url.pipe';
95+
import { MatOption, MatSelect } from '@angular/material/select';
96+
import { ComicFileGroup } from '@app/comic-files/models/comic-file-group';
97+
import { SelectionOption } from '@app/core/models/ui/selection-option';
98+
import { MatInput } from '@angular/material/input';
9299

93100
@Component({
94101
selector: 'cx-import-comics',
@@ -125,7 +132,11 @@ import { ComicFileCoverUrlPipe } from '../../pipes/comic-file-cover-url.pipe';
125132
AsyncPipe,
126133
DecimalPipe,
127134
TranslateModule,
128-
ComicFileCoverUrlPipe
135+
ComicFileCoverUrlPipe,
136+
MatSelect,
137+
MatOption,
138+
MatFormField,
139+
MatInput
129140
]
130141
})
131142
export class ImportComicsPageComponent
@@ -146,6 +157,8 @@ export class ImportComicsPageComponent
146157
langChangeSubscription: Subscription;
147158
filesSubscription$: Subscription;
148159
files: ComicFile[];
160+
groupsSubscription$: Subscription;
161+
groups: ComicFileGroup[];
149162
translateSubscription$: Subscription;
150163
userSubscription$: Subscription;
151164
user: User;
@@ -160,6 +173,9 @@ export class ImportComicsPageComponent
160173
comicFile: ComicFile = null;
161174
featureEnabledSubscription$: Subscription;
162175
blockedPagesEnabled = false;
176+
currentPathSubscription$: Subscription;
177+
currentPath: string | null = null;
178+
pathOptions$ = new BehaviorSubject<SelectionOption<string>[]>([]);
163179

164180
logger = inject(LoggerService);
165181
title = inject(Title);
@@ -185,18 +201,33 @@ export class ImportComicsPageComponent
185201
.select(selectComicFiles)
186202
.subscribe(files => {
187203
this.files = files;
188-
this.dataSource.data = files;
189-
this.updateSelectionState();
204+
this.updateDisplayedFilesAndSelections();
190205
this.showFinderForm = false;
191206
this.selectedFileCount = this.files.filter(
192207
file => file.selected
193208
).length;
194209
});
210+
this.groupsSubscription$ = this.store
211+
.select(selectComicGroups)
212+
.subscribe(groups => {
213+
this.groups = groups;
214+
this.updateDisplayedFilesAndSelections();
215+
});
195216
this.comicFileListStateSubscription$ = this.store
196217
.select(selectComicFileListState)
197-
.subscribe(state =>
198-
this.store.dispatch(setBusyState({ enabled: state.busy }))
199-
);
218+
.subscribe(state => {
219+
this.store.dispatch(setBusyState({ enabled: state.busy }));
220+
this.pathOptions$.next(
221+
[{ label: 'comic-files.text.all-directories', value: null }].concat(
222+
state.groups.map(group => {
223+
return {
224+
label: group.directory,
225+
value: group.directory
226+
} as SelectionOption<string>;
227+
})
228+
)
229+
);
230+
});
200231
this.sendComicFilesStateSubscription$ = this.store
201232
.select(selectImportComicFilesState)
202233
.subscribe(state =>
@@ -217,6 +248,12 @@ export class ImportComicsPageComponent
217248
);
218249
}
219250
});
251+
this.currentPathSubscription$ = this.store
252+
.select(selectComicFilesCurrentPath)
253+
.subscribe(path => {
254+
this.currentPath = path;
255+
this.updateDisplayedFilesAndSelections();
256+
});
220257
}
221258

222259
ngAfterViewInit(): void {
@@ -252,12 +289,16 @@ export class ImportComicsPageComponent
252289
this.userSubscription$.unsubscribe();
253290
this.logger.trace('Unsubscribing from comic file updates');
254291
this.filesSubscription$.unsubscribe();
292+
this.logger.trace('Unsubscribing from comic group updates');
293+
this.groupsSubscription$.unsubscribe();
255294
this.logger.trace('Unsubscribing from comic file list state updates');
256295
this.comicFileListStateSubscription$.unsubscribe();
257296
this.logger.trace('Unsubscribing from send comic file state updates');
258297
this.sendComicFilesStateSubscription$.unsubscribe();
259298
this.logger.trace('Unsubscribing from feature enabled updates');
260299
this.featureEnabledSubscription$.unsubscribe();
300+
this.logger.trace('Unsubscribing from current path updates');
301+
this.currentPathSubscription$.unsubscribe();
261302
}
262303

263304
onStartImport(): void {
@@ -302,14 +343,28 @@ export class ImportComicsPageComponent
302343
}
303344
}
304345

346+
onChangeCurrentPath(path: string | null): void {
347+
this.logger.debug('Changing current path:', path);
348+
this.store.dispatch(updateCurrentPath({ path }));
349+
}
350+
305351
private loadTranslations(): void {
306352
this.logger.trace('Loading page title');
307353
this.titleService.setTitle(
308354
this.translateService.instant('comic-files.tab-title')
309355
);
310356
}
311357

312-
private updateSelectionState(): void {
358+
private updateDisplayedFilesAndSelections(): void {
359+
if (!!this.currentPath) {
360+
this.logger.info('Showing comic files from group:', this.currentPath);
361+
this.dataSource.data =
362+
this.groups.find(group => group.directory === this.currentPath)
363+
?.files || [];
364+
} else {
365+
this.logger.info('Showing all comic files');
366+
this.dataSource.data = this.files;
367+
}
313368
this.allSelected = this.dataSource.data.every(entry => entry.selected);
314369
this.anySelected = this.dataSource.data.some(entry => entry.selected);
315370
}

0 commit comments

Comments
 (0)