From 1b1c2cd98090a8810024d6afe83ac09dc211c4e8 Mon Sep 17 00:00:00 2001
From: kaw67872 <kawtar.laariche@iais.fraunhofer.de>
Date: Wed, 26 Jun 2024 09:42:49 +0200
Subject: [PATCH] #24: add upload and delete documents functionalities

---
 .../core/services/private-catalogs.service.ts |  18 ++-
 ...publish-to-marketplace-page.component.html | 112 +++++++++------
 ...publish-to-marketplace-page.component.scss |  65 +++++++++
 .../publish-to-marketplace-page.component.ts  | 134 ++++++++++++++----
 .../upload-license-profile.component.ts       |   9 +-
 5 files changed, 260 insertions(+), 78 deletions(-)

diff --git a/src/app/core/services/private-catalogs.service.ts b/src/app/core/services/private-catalogs.service.ts
index ff7ff41..d8656f6 100644
--- a/src/app/core/services/private-catalogs.service.ts
+++ b/src/app/core/services/private-catalogs.service.ts
@@ -7,6 +7,7 @@ import {
   AuthorPublisherModel,
   Catalog,
   CommentModel,
+  DocumentModel,
   PublicSolution,
   PublicSolutionDetailsModel,
   PublicSolutionsRequestPayload,
@@ -784,8 +785,9 @@ export class PrivateCatalogsService {
     revisionId: string,
     selectedCatalogId: string,
     file: File,
-  ) {
+  ): Observable<DocumentModel> {
     const uploadUrl =
+      apiConfig.apiBackendURL +
       '/api/solution/' +
       solutionId +
       '/revision/' +
@@ -796,27 +798,31 @@ export class PrivateCatalogsService {
     const formData = new FormData();
     formData.append('file', file);
 
-    const req = new HttpRequest('POST', uploadUrl, formData);
-
-    return this.http.request(req);
+    return this._httpSharedService.post(uploadUrl, undefined, formData).pipe(
+      map((res) => res.response_body),
+      catchError((error) => {
+        throw error;
+      }),
+    );
   }
 
   getSolutionsFiles() {}
 
-  DeleteSolutionsFiles(
+  deleteSolutionsFiles(
     solutionId: string,
     revisionId: string,
     selectedCatalogId: string,
     documentId: string,
   ) {
     const url =
+      apiConfig.apiBackendURL +
       '/api/solution/' +
       solutionId +
       '/revision/' +
       revisionId +
       '/' +
       selectedCatalogId +
-      '/document' +
+      '/document/' +
       documentId;
 
     return this._httpSharedService.delete(url).pipe(
diff --git a/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.html b/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.html
index c3d7aa1..c5c8c0a 100644
--- a/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.html
+++ b/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.html
@@ -49,7 +49,7 @@
             ></div>
           </div>
 
-          <div class="select-category">
+          <div class="flex-column">
             <mat-form-field class="full-width">
               <mat-label>Select category</mat-label>
               <mat-select
@@ -104,7 +104,14 @@
           <div class="upload-file-container">
             <div class="flex-row">
               <span class="upload-file-text">Model Documents </span>
-              <button color="primary" mat-raised-button>upload</button>
+
+              <button class="custom-icon" (click)="onClickUploadDocumentFile()">
+                @if (documents().length > 0) {
+                  <mat-icon>edit</mat-icon>
+                } @else {
+                  <mat-icon>add</mat-icon>
+                }
+              </button>
             </div>
 
             <mat-form-field>
@@ -130,47 +137,66 @@
           </div>
           <!--upload documents end-->
           <!--tags starts-->
-          <div class="flex-row">
-            <mat-form-field class="full-with">
-              <mat-label>Add tag</mat-label>
-              <mat-chip-grid #chipGrid>
-                @for (tag of tagsItems(); track tag) {
-                  <mat-chip-row (removed)="remove(tag)">
-                    {{ tag.tag }}
-                    <button matChipRemove [attr.aria-label]="'remove ' + tag">
-                      <mat-icon>cancel</mat-icon>
-                    </button>
-                  </mat-chip-row>
-                }
-              </mat-chip-grid>
-              <input
-                #tagInput
-                [formControl]="tagCtrl"
-                [matChipInputFor]="chipGrid"
-                [matAutocomplete]="auto"
-              />
-              <mat-autocomplete
-                #auto="matAutocomplete"
-                (optionSelected)="selected($event)"
-              >
-                @for (tag of filteredTags | async; track tag) {
-                  <mat-option [value]="tag"
-                    ><span class="autocomplete-option">{{
-                      tag
-                    }}</span></mat-option
-                  >
-                }
-              </mat-autocomplete>
-            </mat-form-field>
-            <button mat-raised-button color="primary">Add tag</button>
-          </div>
+          <mat-form-field class="flex-grow">
+            <mat-label>Add tag</mat-label>
+            <mat-chip-grid #chipGrid>
+              @for (tag of tagsItems(); track tag) {
+                <mat-chip-row (removed)="remove(tag)">
+                  {{ tag.tag }}
+                  <button matChipRemove [attr.aria-label]="'remove ' + tag">
+                    <mat-icon>cancel</mat-icon>
+                  </button>
+                </mat-chip-row>
+              }
+            </mat-chip-grid>
+            <input
+              #tagInput
+              [formControl]="tagCtrl"
+              [matChipInputFor]="chipGrid"
+              [matAutocomplete]="auto"
+              [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
+              (matChipInputTokenEnd)="addTag($event)"
+              (input)="onChangeInput($event)"
+            />
+            <mat-autocomplete
+              #auto="matAutocomplete"
+              (optionSelected)="selected($event)"
+            >
+              @for (tag of filteredTags | async; track tag) {
+                <mat-option [value]="tag"
+                  ><span class="autocomplete-option">{{
+                    tag
+                  }}</span></mat-option
+                >
+              }
+            </mat-autocomplete>
+          </mat-form-field>
           <!--tags ends-->
           <!--upload image start-->
-          <div class="flex-row">
+          <div class="flex-column">
             <span class="upload-file-text">Upload image model </span>
+            <p class="upload-text-hint">
+              Upload an image that will identify your model in Marketplace. You
+              can upload jpg, jpeg, png and gif file with maximum size of 1MB.
+            </p>
+
+            <div class="single-file">
+              <div class="image-container">
+                <img class="file-image" [src]="imageToShow" alt="file" />
+              </div>
+              <div class="flex-row file-name">
+                <span *ngIf="imageToShow.name"> {{ imageToShow.name }}</span>
+                <span>|</span>
+                <a (click)="onClickUploadImageFile()">Change</a>
+              </div>
+            </div>
 
-            <mat-form-field class="mat-form-field-upload-file">
-              <input matInput [value]="imageFile?.name" />
+            <!--  <div class="file-name">
+              | <a ng-click="showImageUpload = !showImageUpload">Change</a>
+              <!- - <span>Size 800K</span> - ->
+            </div> -->
+            <!--  <mat-form-field class="mat-form-field-upload-file">
+              <input matInput />
 
               <input
                 #fileDropRef
@@ -181,14 +207,14 @@
                 hidden
                 formControlName="image"
               />
-            </mat-form-field>
-            <button class="custom-icon" (click)="onClickUploadImageFile()">
-              @if (publishToMarketPlaceForm.value.image) {
+            </mat-form-field> -->
+            <!--  <button class="custom-icon" (click)="onClickUploadImageFile()">
+              @if (imageToShow) {
                 <mat-icon>edit</mat-icon>
               } @else {
                 <mat-icon>add</mat-icon>
               }
-            </button>
+            </button> -->
           </div>
           <!--upload image end-->
         </div>
diff --git a/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.scss b/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.scss
index ef413c7..c0a41a5 100644
--- a/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.scss
+++ b/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.scss
@@ -79,3 +79,68 @@
     width: 20%;
   }
 }
+
+.single-file {
+  display: flex;
+  padding: 0.5rem;
+  align-items: center;
+  border: 1px dashed #d5d5d5;
+  gap: 8px;
+
+  .name {
+    font-size: 14px;
+    font-weight: 500;
+    margin: 0;
+    color: #666;
+  }
+
+  /*  .info {
+      width: 100%;
+    } */
+}
+.file-image {
+  align-self: flex-start;
+  border: 1px solid #dbcbe8;
+  width: 24px;
+  height: 24px;
+  padding: 3px;
+}
+
+.uploaded-img-des {
+  border: 1px solid #dbcbe8;
+  padding: 5px;
+  font-size: 13px;
+  line-height: 16px;
+  float: left;
+}
+.uploaded-img-des > .imagecontainer {
+  width: 24px;
+  height: 24px;
+  margin-right: 10px;
+  float: left;
+  border: 1px solid #dbcbe8;
+  padding: 3px;
+}
+
+.file-name {
+  gap: 4px;
+  color: #666;
+  float: left;
+  align-items: center;
+  a {
+    color: #0366d6;
+    font-size: 11px;
+    cursor: pointer;
+  }
+}
+
+.upload-text-hint {
+  font-size: 14px;
+  letter-spacing: 0;
+  margin: 0 0 16px;
+  line-height: 24px;
+  word-break: break-word;
+  overflow-wrap: break-word;
+  hyphens: auto;
+  color: #666;
+}
diff --git a/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.ts b/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.ts
index 37ac5cc..898e24d 100644
--- a/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.ts
+++ b/src/app/shared/components/publish-to-marketplace-page/publish-to-marketplace-page.component.ts
@@ -50,6 +50,7 @@ import { MatIconModule } from '@angular/material/icon';
 import { LiveAnnouncer } from '@angular/cdk/a11y';
 import { RichTextEditorDialogComponent } from '../rich-text-editor-dialog/rich-text-editor-dialog.component';
 import {
+  BehaviorSubject,
   catchError,
   combineLatest,
   map,
@@ -65,6 +66,7 @@ import {
   MatAutocompleteModule,
   MatAutocompleteSelectedEvent,
 } from '@angular/material/autocomplete';
+import { COMMA, ENTER } from '@angular/cdk/keycodes';
 interface RouteParams {
   solutionId: string;
   revisionId: string;
@@ -119,7 +121,7 @@ export class PublishToMarketplacePageComponent implements OnInit {
   toolkitTypes: CatalogFilter[] = [];
   tagCtrl = new FormControl('');
   @ViewChild('tagInput') tagInput!: ElementRef<HTMLInputElement>;
-  filteredTags!: Observable<string[]>;
+  filteredTags = new BehaviorSubject<string[]>([]);
 
   imageFile!: { content: File; name: string };
   documentFile!: { content: File; name: string };
@@ -140,6 +142,23 @@ export class PublishToMarketplacePageComponent implements OnInit {
   announcer = inject(LiveAnnouncer);
 
   defaultSolutionData = {};
+  imageToShow: any;
+  readonly separatorKeysCodes: number[] = [ENTER, COMMA];
+
+  createImageFromBlob(image: Blob) {
+    const reader = new FileReader();
+    reader.addEventListener(
+      'load',
+      () => {
+        this.imageToShow = reader.result;
+      },
+      false,
+    );
+
+    if (image) {
+      reader.readAsDataURL(image);
+    }
+  }
 
   constructor(
     private activatedRoute: ActivatedRoute,
@@ -271,19 +290,32 @@ export class PublishToMarketplacePageComponent implements OnInit {
       (err: any) => {},
     );
 
-    this.filteredTags = combineLatest([
-      this.tagCtrl.valueChanges.pipe(startWith('')),
-    ]).pipe(
-      map(([inputValue]) => {
-        const tags = this.allTags();
-        return inputValue ? this.filterTags(inputValue, tags) : tags;
-      }),
+    this.tagCtrl.valueChanges
+      .pipe(
+        startWith(''),
+        map((inputValue) =>
+          inputValue
+            ? this.filterTags(inputValue, this.allTags())
+            : this.allTags(),
+        ),
+      )
+      .subscribe((filteredTags) => {
+        this.filteredTags.next(filteredTags);
+      });
+  }
+
+  private filterTags(inputValue: string, tags: string[]): string[] {
+    return tags.filter((tag) =>
+      tag.toLowerCase().includes(inputValue.toLowerCase()),
     );
   }
 
-  filterTags(inputValue: string, tags: string[]): string[] {
-    const filterValue = inputValue.toLowerCase();
-    return tags.filter((tag) => tag.toLowerCase().includes(filterValue));
+  updateAllTagsSignal(value: string, tags: string[]): string[] {
+    const updatedTags = tags.filter(
+      (tag) => tag.toLowerCase() !== value.toLocaleLowerCase(),
+    );
+    this.allTags.update((tags: string[]) => [...updatedTags]);
+    return updatedTags;
   }
 
   convertToolkitTypesToCodes(
@@ -398,6 +430,15 @@ export class PublishToMarketplacePageComponent implements OnInit {
     dialogRef.afterClosed().subscribe((result) => {});
   }
 
+  onClickUpdateSolutionDocument(file: File) {
+    return this.privateCatalogsService.updateSolutionFiles(
+      this.solutionId,
+      this.revisionId,
+      this.selectedCatalog.catalogId,
+      file,
+    );
+  }
+
   onClickUploadDocumentFile() {
     const dialogRef: MatDialogRef<UploadLicenseProfileComponent> =
       this.dialog.open(UploadLicenseProfileComponent, {
@@ -406,14 +447,31 @@ export class PublishToMarketplacePageComponent implements OnInit {
             title: 'Upload document model',
             isEditMode: false,
             isCheckExtension: false,
-            action: (file: File) => {},
+            action: (file: File) => this.onClickUpdateSolutionDocument(file),
             isLicenseProfile: false,
             isProcessEvent: false,
           },
         },
       });
 
-    dialogRef.afterClosed().subscribe((result) => {});
+    dialogRef.afterClosed().subscribe((result) => {
+      this.updateDocuments();
+    });
+  }
+
+  updateDocuments() {
+    this.publicSolutionsService
+      .getSolutionDocuments(
+        this.solutionId,
+        this.revisionId,
+        this.selectedCatalog.catalogId,
+      )
+      .subscribe({
+        next: (res) => {
+          this.documents.update((dcs) => [...res]);
+        },
+        error: (error) => {},
+      });
   }
 
   onLicenseProfileClick() {
@@ -434,16 +492,25 @@ export class PublishToMarketplacePageComponent implements OnInit {
   }
 
   removeDocument(document: DocumentModel) {
-    this.documents.update((documents: DocumentModel[]) => {
-      const index = documents.indexOf(document);
-      if (index < 0) {
-        return documents;
-      }
-
-      documents.splice(index, 1);
-      this.announcer.announce(`Removed ${document}`);
-      return [...documents];
-    });
+    this.privateCatalogsService
+      .deleteSolutionsFiles(
+        this.solutionId,
+        this.revisionId,
+        this.selectedCatalog.catalogId,
+        document.documentId,
+      )
+      .subscribe({
+        next: (res) => {
+          const alert: Alert = {
+            message: 'Document deleted successfully',
+            type: AlertType.Success,
+          };
+          this.alertService.notify(alert, 5000);
+          this.alertService.notify;
+          this.updateDocuments();
+        },
+        error: () => {},
+      });
   }
 
   addTag(event: MatChipInputEvent): void {
@@ -452,12 +519,20 @@ export class PublishToMarketplacePageComponent implements OnInit {
     // Add our keyword
     if (value) {
       this.tagsItems.update((tags: Tag[]) => [...tags, value]);
+      this.filteredTags.next(
+        this.updateAllTagsSignal(event.value, this.allTags()),
+      );
     }
 
     // Clear the input value
     event.chipInput!.clear();
   }
 
+  onChangeInput(event: any) {
+    // if()
+    //event.target.value.trim().toLowerCase()
+  }
+
   extractDescription(html: string): string {
     return html.replace(/<[^>]+>/gm, '');
   }
@@ -503,8 +578,7 @@ export class PublishToMarketplacePageComponent implements OnInit {
     this.revisionId = data.revisionId;
     this.solutionId = data.solutionId;
 
-    if (data.picture)
-      this.publishToMarketPlaceForm.patchValue({ image: data.picture });
+    if (data.picture) this.createImageFromBlob(data.picture);
     if (data.solution?.modelTypeName && data.solution.modelType) {
       this.publishToMarketPlaceForm.get('category')?.setValue({
         name: data.solution?.modelTypeName,
@@ -591,8 +665,14 @@ export class PublishToMarketplacePageComponent implements OnInit {
   }
 
   selected(event: MatAutocompleteSelectedEvent): void {
-    this.tagsItems.update((tags: Tag[]) => [...tags, event.option.value]);
-    this.tagInput.nativeElement.value = '';
+    this.tagsItems.update((tags: Tag[]) => [
+      ...tags,
+      { tag: event.option.value },
+    ]);
+    event.option.deselect();
     this.tagCtrl.setValue(null);
+    this.filteredTags.next(
+      this.updateAllTagsSignal(event.option.value, this.allTags()),
+    );
   }
 }
diff --git a/src/app/shared/components/upload-license-profile/upload-license-profile.component.ts b/src/app/shared/components/upload-license-profile/upload-license-profile.component.ts
index b887c62..8a2c81a 100644
--- a/src/app/shared/components/upload-license-profile/upload-license-profile.component.ts
+++ b/src/app/shared/components/upload-license-profile/upload-license-profile.component.ts
@@ -132,12 +132,17 @@ export class UploadLicenseProfileComponent implements OnInit, OnDestroy {
       if (fileExtension !== this.expectedExtension) {
         this.handleUploadError(this.errorMessage);
 
-        return; // Stop further execution
+        return;
       }
     }
+
     this.data.dataKey.action(file).subscribe({
       next: (event: any) => {
-        this.processEvent(event);
+        console.log({ event });
+        if (this.isProcessEvent) this.processEvent(event);
+        else {
+          this.handleUploadSuccess(event);
+        }
       },
       error: (err: any) => {
         this.handleUploadError(err);
-- 
GitLab