diff --git a/frontend/src/app/components/library-management/library-management.component.html b/frontend/src/app/components/library-management/library-management.component.html
new file mode 100644
index 0000000..4a7aba7
--- /dev/null
+++ b/frontend/src/app/components/library-management/library-management.component.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+ | Path |
+ {{element.path}} |
+
+
+
+ Game |
+ {{element.title}} ({{getFullYearFromTimestamp(element.releaseDate)}}) |
+
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+ | Path |
+ {{element.path}} |
+
+
+
+
+
+ |
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/app/components/library-management/library-management.component.scss b/frontend/src/app/components/library-management/library-management.component.scss
new file mode 100644
index 0000000..169006a
--- /dev/null
+++ b/frontend/src/app/components/library-management/library-management.component.scss
@@ -0,0 +1,3 @@
+td, th {
+ padding: 16px !important;
+}
diff --git a/frontend/src/app/components/library-management/library-management.component.spec.ts b/frontend/src/app/components/library-management/library-management.component.spec.ts
new file mode 100644
index 0000000..3ac170a
--- /dev/null
+++ b/frontend/src/app/components/library-management/library-management.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LibraryManagementComponent } from './library-management.component';
+
+describe('LibraryManagementComponent', () => {
+ let component: LibraryManagementComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ LibraryManagementComponent ]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(LibraryManagementComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/components/library-management/library-management.component.ts b/frontend/src/app/components/library-management/library-management.component.ts
new file mode 100644
index 0000000..96f92ff
--- /dev/null
+++ b/frontend/src/app/components/library-management/library-management.component.ts
@@ -0,0 +1,64 @@
+import {Component, OnInit} from '@angular/core';
+import {DetectedGameDto} from "../../models/dtos/DetectedGameDto";
+import {GamesService} from "../../services/games.service";
+import {LibraryManagementService} from "../../services/library-management.service";
+import {UnmappedFileDto} from "../../models/dtos/UnmappedFileDto";
+import {LibraryService} from "../../services/library.service";
+import {DialogService} from "../../services/dialog.service";
+
+@Component({
+ selector: 'app-library-management',
+ templateUrl: './library-management.component.html',
+ styleUrls: ['./library-management.component.scss']
+})
+export class LibraryManagementComponent implements OnInit {
+
+ gameMappingTableColumns: string[] = ["path", "game", "actions"];
+ unmappedGameTableColumns: string[] = ["path", "actions"];
+
+ mappedGames!: DetectedGameDto[];
+ unmappedFiles!: UnmappedFileDto[];
+
+ constructor(private gameService: GamesService,
+ private libraryManagementService: LibraryManagementService,
+ private dialogService: DialogService) {
+ }
+
+ ngOnInit(): void {
+ this.refreshMappedGamesList();
+ this.refreshUnmappedFilesList();
+ }
+
+ refreshMappedGamesList(): void {
+ this.gameService.getAllGames().subscribe(games => this.mappedGames = games);
+ }
+
+ getFullYearFromTimestamp(timestamp: number): number {
+ return new Date(timestamp).getFullYear();
+ }
+
+ confirmGameMapping(mappedGame: DetectedGameDto): void {
+ this.libraryManagementService.confirmGameMapping(mappedGame.slug).subscribe(() => mappedGame.confirmedMatch = true);
+ }
+
+ deleteGameMapping(mappedGame: DetectedGameDto): void {
+ this.libraryManagementService.deleteGame(mappedGame.slug).subscribe(() => this.mappedGames = this.mappedGames.filter(game => game !== mappedGame));
+ }
+
+ openCorrectMappingDialog(mappedGame: DetectedGameDto): void {
+ this.dialogService.correctGameMappingDialog(mappedGame);
+ }
+
+ refreshUnmappedFilesList(): void {
+ this.libraryManagementService.getUnmappedFiles().subscribe(unmappedFiles => this.unmappedFiles = unmappedFiles);
+ }
+
+ deleteUnmappedFile(unmappedFile: UnmappedFileDto): void {
+ this.libraryManagementService.deleteUnmappedFile(unmappedFile.id).subscribe(() => this.unmappedFiles = this.unmappedFiles.filter(uf => uf !== unmappedFile));
+ }
+
+ openMapUnmappedFileDialog(unmappedFile: UnmappedFileDto): void {
+ this.dialogService.mapUnmappedGameDialog(unmappedFile);
+ }
+
+}
diff --git a/frontend/src/app/components/map-game-dialog/map-game-dialog.component.html b/frontend/src/app/components/map-game-dialog/map-game-dialog.component.html
new file mode 100644
index 0000000..25412bd
--- /dev/null
+++ b/frontend/src/app/components/map-game-dialog/map-game-dialog.component.html
@@ -0,0 +1,15 @@
+Map game to IGDB slug
+
+
+
+
+
+
+
diff --git a/frontend/src/app/components/map-game-dialog/map-game-dialog.component.scss b/frontend/src/app/components/map-game-dialog/map-game-dialog.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/app/components/map-game-dialog/map-game-dialog.component.spec.ts b/frontend/src/app/components/map-game-dialog/map-game-dialog.component.spec.ts
new file mode 100644
index 0000000..fc22839
--- /dev/null
+++ b/frontend/src/app/components/map-game-dialog/map-game-dialog.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MapGameDialogComponent } from './map-game-dialog.component';
+
+describe('MapGameDialogComponent', () => {
+ let component: MapGameDialogComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ MapGameDialogComponent ]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(MapGameDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/components/map-game-dialog/map-game-dialog.component.ts b/frontend/src/app/components/map-game-dialog/map-game-dialog.component.ts
new file mode 100644
index 0000000..30df899
--- /dev/null
+++ b/frontend/src/app/components/map-game-dialog/map-game-dialog.component.ts
@@ -0,0 +1,37 @@
+import {Component, Inject, OnInit} from '@angular/core';
+import {FormBuilder, FormControl} from "@angular/forms";
+import {LibraryManagementService} from "../../services/library-management.service";
+import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
+import {PathToSlugDto} from "../../models/dtos/PathToSlugDto";
+
+@Component({
+ selector: 'app-map-game-dialog',
+ templateUrl: './map-game-dialog.component.html',
+ styleUrls: ['./map-game-dialog.component.scss']
+})
+export class MapGameDialogComponent implements OnInit {
+
+ path: string;
+ currentSlug?: string;
+ newSlugInput: FormControl;
+
+ constructor(private fb: FormBuilder,
+ private libraryManagementService: LibraryManagementService,
+ public dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) data: any) {
+ this.path = data.path;
+ this.currentSlug = data.slug;
+ this.newSlugInput = new FormControl(this.currentSlug);
+ }
+
+ ngOnInit() {
+ }
+
+ close() {
+ this.dialogRef.close();
+ }
+
+ submit(): void {
+ this.libraryManagementService.mapGame(new PathToSlugDto(this.newSlugInput.value, this.path)).subscribe(() => this.close())
+ }
+}
diff --git a/frontend/src/app/services/library-management.service.spec.ts b/frontend/src/app/services/library-management.service.spec.ts
new file mode 100644
index 0000000..741b253
--- /dev/null
+++ b/frontend/src/app/services/library-management.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { LibraryManagementService } from './library-management.service';
+
+describe('LibraryManagementService', () => {
+ let service: LibraryManagementService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(LibraryManagementService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/services/library-management.service.ts b/frontend/src/app/services/library-management.service.ts
new file mode 100644
index 0000000..23e992b
--- /dev/null
+++ b/frontend/src/app/services/library-management.service.ts
@@ -0,0 +1,39 @@
+import {Injectable} from '@angular/core';
+import {HttpClient} from "@angular/common/http";
+import {Observable} from "rxjs";
+import {DetectedGameDto} from "../models/dtos/DetectedGameDto";
+import {PathToSlugDto} from "../models/dtos/PathToSlugDto";
+import {UnmappedFileDto} from "../models/dtos/UnmappedFileDto";
+import {LibraryManagementApi} from "../api/LibraryManagementApi";
+
+@Injectable({
+ providedIn: 'root'
+})
+export class LibraryManagementService implements LibraryManagementApi {
+
+ private readonly apiPath = '/library-management';
+
+ constructor(private http: HttpClient) {
+ }
+
+ mapGame(pathToSlugDto: PathToSlugDto): Observable {
+ return this.http.post(`${this.apiPath}/map-path`, pathToSlugDto);
+ }
+
+ getUnmappedFiles(): Observable {
+ return this.http.get(`${this.apiPath}/unmapped-files`);
+ }
+
+ confirmGameMapping(slug: string): Observable {
+ return this.http.get(`${this.apiPath}/confirm-game/${slug}`);
+ }
+
+ deleteGame(slug: string): Observable {
+ return this.http.delete(`${this.apiPath}/delete-game/${slug}`);
+ }
+
+ deleteUnmappedFile(id: number): Observable {
+ return this.http.delete(`${this.apiPath}/delete-unmapped-file/${id}`);
+ }
+
+}