Skip to content
Snippets Groups Projects
Commit 1b1c2cd9 authored by Kawtar Laariche's avatar Kawtar Laariche
Browse files

#24: add upload and delete documents functionalities

parent 44e2bd01
No related branches found
No related tags found
No related merge requests found
......@@ -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(
......
......@@ -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>
......
......@@ -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;
}
......@@ -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()),
);
}
}
......@@ -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);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment