Add fileName property to models and update related components for file handling
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
<p>dialog-cropp-image.component works!</p>
|
||||
<mat-dialog-content class="mat-typography">
|
||||
<image-cropper
|
||||
|
||||
[imageURL]="image"
|
||||
style="width: 400px; height: 300px;"
|
||||
[imageChangedEvent]="imageChangedEvent"
|
||||
[maintainAspectRatio]="true"
|
||||
[aspectRatio]="4 / 3"
|
||||
format="png"
|
||||
(imageCropped)="imageCropped($event)"
|
||||
(imageLoaded)="imageLoaded($event)"
|
||||
(cropperReady)="cropperReady()"
|
||||
(loadImageFailed)="loadImageFailed()"
|
||||
></image-cropper>
|
||||
|
||||
<img style="width: 358px; height: 200px" [src]="croppedImage" />
|
||||
|
||||
</mat-dialog-content>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DialogCroppImageComponent } from './dialog-cropp-image.component';
|
||||
|
||||
describe('DialogCroppImageComponent', () => {
|
||||
let component: DialogCroppImageComponent;
|
||||
let fixture: ComponentFixture<DialogCroppImageComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DialogCroppImageComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DialogCroppImageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import {Component, inject, input} from '@angular/core';
|
||||
import {ImageCroppedEvent, ImageCropperComponent, LoadedImage} from 'ngx-image-cropper';
|
||||
import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
|
||||
import {UserApiService} from '../user-api.service';
|
||||
import {MAT_DIALOG_DATA, MatDialogContent} from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dialog-cropp-image.component',
|
||||
imports: [
|
||||
ImageCropperComponent,
|
||||
MatDialogContent
|
||||
],
|
||||
templateUrl: './dialog-cropp-image.component.html',
|
||||
styleUrl: './dialog-cropp-image.component.scss'
|
||||
})
|
||||
export class DialogCroppImageComponent {
|
||||
|
||||
imageChangedEvent: Event | null = null;
|
||||
croppedImage: SafeUrl = '';
|
||||
|
||||
image?: string;
|
||||
imageCropUrl: string | undefined;
|
||||
|
||||
constructor(
|
||||
private userApiService: UserApiService,
|
||||
private sanitizer: DomSanitizer,
|
||||
|
||||
) {
|
||||
this.cropImage(this.imageCropUrl);
|
||||
}
|
||||
|
||||
///------------
|
||||
|
||||
fileChangeEvent(event: Event): void {
|
||||
this.imageChangedEvent = event;
|
||||
}
|
||||
imageCropped(event: ImageCroppedEvent) {
|
||||
if (event.objectUrl != null) {
|
||||
this.croppedImage = this.sanitizer.bypassSecurityTrustUrl(event.objectUrl);
|
||||
}
|
||||
// event.blob can be used to upload the cropped image
|
||||
}
|
||||
imageLoaded(image: LoadedImage) {
|
||||
// show cropper
|
||||
}
|
||||
cropperReady() {
|
||||
// cropper ready
|
||||
}
|
||||
loadImageFailed() {
|
||||
// show message
|
||||
}
|
||||
|
||||
cropImage(url: string | undefined) {
|
||||
if(url !== undefined){
|
||||
this.userApiService.loadImageAsBase64(url).subscribe(data => {
|
||||
this.image = data;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,25 +1,122 @@
|
||||
<p>images.component works!</p>
|
||||
|
||||
<div class="generate-image-view">
|
||||
|
||||
|
||||
@for (fileInfo of fileInfos; track fileInfo) {
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<div class="image-container">
|
||||
<img src="{{fileInfo.url}}"
|
||||
alt="" width="100%" height="100%">
|
||||
<mat-toolbar class="image-toolbar">
|
||||
<mat-toolbar-row>
|
||||
<label class="image-file-name">{{fileInfo.name}}</label>
|
||||
<span class="menu-spacer"></span>
|
||||
<button matMiniFab matTooltip="Dawnload image" aria-label="Download" (click)="downloadImage(fileInfo.url)">
|
||||
<mat-icon>download</mat-icon>
|
||||
</button>
|
||||
@if(fileInfo.mode !=="CropView"){
|
||||
<img src="{{fileInfo.url}}"
|
||||
alt="" width="100%" height="100%"/>
|
||||
}@else{
|
||||
<image-cropper
|
||||
output = "base64"
|
||||
[imageURL]="fileInfo.base64string"
|
||||
style="width: 100%; height: 100%;"
|
||||
[imageChangedEvent]="imageChangedEvent"
|
||||
[maintainAspectRatio]="true"
|
||||
[aspectRatio]="4 / 3"
|
||||
[canvasRotation]="canvasRotation"
|
||||
[cropperStaticWidth]="cropperStaticWidth"
|
||||
[cropperStaticHeight]="cropperStaticHeight"
|
||||
[(transform)]="transform"
|
||||
format="png"
|
||||
(imageCropped)="imageCropped($event)"
|
||||
(imageLoaded)="imageLoaded($event)"
|
||||
(cropperReady)="cropperReady()"
|
||||
(loadImageFailed)="loadImageFailed()"
|
||||
></image-cropper>
|
||||
|
||||
</mat-toolbar-row>
|
||||
</mat-toolbar>
|
||||
</div>
|
||||
<img #imagePreview width="100%" height="100%" [src]="croppedImage" />
|
||||
|
||||
}
|
||||
|
||||
@if(fileInfo.mode !=="CropView"){
|
||||
<mat-toolbar class="image-toolbar">
|
||||
<mat-toolbar-row>
|
||||
<label class="image-file-name">{{fileInfo.name}}</label>
|
||||
<span class="menu-spacer"></span>
|
||||
<button matMiniFab matTooltip="Crop image" aria-label="Crop" (click)="cropImage(fileInfo)">
|
||||
<mat-icon>photo_size_select_small</mat-icon>
|
||||
</button>
|
||||
<button matMiniFab matTooltip="Dawnload image" aria-label="Download" (click)="downloadImage(fileInfo.url)">
|
||||
<mat-icon>download</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar-row>
|
||||
</mat-toolbar>
|
||||
}@else{
|
||||
<mat-toolbar class="crop-toolbar">
|
||||
<!-- Zoom -->
|
||||
<mat-toolbar-row>
|
||||
<button matMiniFab matTooltip="Size +" aria-label="Crop" (click)="zoomIn()">
|
||||
<mat-icon>zoom_in</mat-icon>
|
||||
</button>
|
||||
<button matMiniFab matTooltip="Size -" aria-label="Download" (click)="zoomOut()">
|
||||
<mat-icon>zoom_out</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar-row>
|
||||
<!-- Move -->
|
||||
<mat-toolbar-row class="move-toolbar-row">
|
||||
<table class="move-toolbar-table">
|
||||
<tr style="display: flex; justify-content: center;">
|
||||
<th colspan="2" >
|
||||
<button matMiniFab matTooltip="Move up" aria-label="Crop" (click)="moveUp()">
|
||||
<mat-icon>north</mat-icon>
|
||||
</button>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button matMiniFab matTooltip="Move left" aria-label="Download" (click)="moveLeft()">
|
||||
<mat-icon>west</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button matMiniFab matTooltip="Move right" aria-label="Crop" (click)="moveRight()">
|
||||
<mat-icon>east</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="display: flex; justify-content: center;">
|
||||
<th colspan="2" >
|
||||
<button matMiniFab matTooltip="Move down" aria-label="Download" (click)="moveDown()">
|
||||
<mat-icon>south</mat-icon>
|
||||
</button>
|
||||
</th>
|
||||
</tr>
|
||||
</table>
|
||||
</mat-toolbar-row>
|
||||
<!-- Rotate -->
|
||||
<mat-toolbar-row>
|
||||
<button matMiniFab matTooltip="Rotate left" aria-label="Crop" (click)="rotateLeft()">
|
||||
<mat-icon>rotate_left</mat-icon>
|
||||
</button>
|
||||
<button matMiniFab matTooltip="Rotate right" aria-label="Download" (click)="rotateRight()">
|
||||
<mat-icon>rotate_right</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar-row>
|
||||
<!-- Flip -->
|
||||
<mat-toolbar-row>
|
||||
<button matMiniFab matTooltip="Flip vertical" aria-label="Crop" (click)="flipHorizontal()">
|
||||
<mat-icon>flip</mat-icon>
|
||||
</button>
|
||||
<button matMiniFab matTooltip="Flip horizontal" aria-label="Download" (click)="flipVertical()">
|
||||
<mat-icon style="transform: rotate(90deg)">flip</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar-row>
|
||||
<mat-toolbar-row>
|
||||
<mat-chip-listbox class="mat-mdc-chip-set-stacked" aria-label="Cutest dog breeds">
|
||||
<mat-chip-option (selectionChange)="cropFixedSize($event)" >Crop 716x400</mat-chip-option>
|
||||
</mat-chip-listbox>
|
||||
<span class="menu-spacer"></span>
|
||||
</mat-toolbar-row>
|
||||
|
||||
<mat-toolbar-row>
|
||||
<button matMiniFab matTooltip="Save to server" aria-label="Save" (click)="save()" >
|
||||
<mat-icon>upload_file</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar-row>
|
||||
</mat-toolbar>
|
||||
}
|
||||
</div>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
</mat-card-content>
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
color: white;
|
||||
position: absolute;
|
||||
left : 50%;
|
||||
top: 5%;
|
||||
top: 47px;
|
||||
transform: translate(-50%, -50%);
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
padding: 0px 2px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-container .menu-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
@@ -29,3 +31,33 @@
|
||||
.mat-mdc-mini-fab{
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
|
||||
.spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.move-toolbar-row{
|
||||
height: 140px;
|
||||
}
|
||||
|
||||
.image-container .crop-toolbar{
|
||||
background-color: rgba(153,153,153,0);
|
||||
|
||||
color: white;
|
||||
position: absolute;
|
||||
left : 59px;
|
||||
top: 240px;
|
||||
transform: translate(-50%, -50%);
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
padding: 0px 2px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
width: 10%;
|
||||
}
|
||||
.move-toolbar-table{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject} from '@angular/core';
|
||||
import {Component, ElementRef, inject, ViewChild} from '@angular/core';
|
||||
import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card';
|
||||
import {MatMiniFabButton} from '@angular/material/button';
|
||||
import {MatToolbar, MatToolbarRow} from '@angular/material/toolbar';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {FileInfo} from '../../../models/file-info';
|
||||
import {UserApiService} from '../user-api.service';
|
||||
import {Base64ImagePayload, UploadImageResponse, UserApiService} from '../user-api.service';
|
||||
import {CropService, ImageCroppedEvent, ImageCropperComponent, ImageTransform, LoadedImage} from 'ngx-image-cropper';
|
||||
import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {DialogCroppImageComponent} from '../dialog-cropp-image.component/dialog-cropp-image.component';
|
||||
import {MatChipListbox, MatChipOption, MatChipSelectionChange} from '@angular/material/chips';
|
||||
|
||||
@Component({
|
||||
selector: 'app-images.component',
|
||||
@@ -18,17 +22,49 @@ import {MatDialog} from '@angular/material/dialog';
|
||||
MatMiniFabButton,
|
||||
MatToolbar,
|
||||
MatToolbarRow,
|
||||
MatTooltip
|
||||
MatTooltip,
|
||||
ImageCropperComponent,
|
||||
MatChipListbox,
|
||||
MatChipOption
|
||||
],
|
||||
templateUrl: './images.component.html',
|
||||
styleUrl: './images.component.scss',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
styleUrl: './images.component.scss'
|
||||
})
|
||||
export class ImagesComponent {
|
||||
|
||||
fileInfos?: FileInfo[] = [];
|
||||
|
||||
readonly dialog = inject(MatDialog);
|
||||
|
||||
constructor(private userApiService: UserApiService) {
|
||||
imageChangedEvent: Event | null = null;
|
||||
croppedImage: string | null | undefined = '';
|
||||
|
||||
croppedImageBase64: string | null | undefined;
|
||||
|
||||
@ViewChild('imagePreview') imagePreview!: ElementRef;
|
||||
|
||||
canvasRotation = 0;
|
||||
transform: ImageTransform = {
|
||||
translateUnit: 'px',
|
||||
scale: 1,
|
||||
rotate: 0,
|
||||
flipH: false,
|
||||
flipV: false,
|
||||
translateH: 0,
|
||||
translateV: 0
|
||||
};
|
||||
cropperStaticWidth = 0;
|
||||
cropperStaticHeight = 0;
|
||||
|
||||
loading = false;
|
||||
|
||||
currentCroppedFile: FileInfo | undefined;
|
||||
|
||||
constructor(
|
||||
|
||||
private userApiService: UserApiService,
|
||||
private sanitizer: DomSanitizer,
|
||||
) {
|
||||
this.userApiService.getImages().subscribe(data =>{
|
||||
this.fileInfos = data;
|
||||
console.log(data);
|
||||
@@ -36,6 +72,214 @@ export class ImagesComponent {
|
||||
}
|
||||
|
||||
downloadImage(url: string | undefined) {
|
||||
const link = document.createElement('a');
|
||||
// @ts-ignore
|
||||
link.download = url;
|
||||
// @ts-ignore
|
||||
link.href = url;
|
||||
link.click();
|
||||
}
|
||||
|
||||
openCropImageDialog(imageUrl: string | undefined, enterAnimationDuration: string, exitAnimationDuration: string) {
|
||||
let dialogCropImageRef = this.dialog.open(DialogCroppImageComponent, {
|
||||
height: '500px',
|
||||
width: '800px',
|
||||
enterAnimationDuration,
|
||||
exitAnimationDuration,
|
||||
|
||||
}
|
||||
|
||||
);
|
||||
dialogCropImageRef.componentInstance.cropImage(imageUrl);
|
||||
|
||||
// dialogCropImageRef.componentInstance.uploadClicked.subscribe(result => {
|
||||
// dialogCropImageRef.close();
|
||||
// this.openUploadImageDialog(enterAnimationDuration, exitAnimationDuration);
|
||||
// })
|
||||
|
||||
// const dialogSelectSubscription = dialogCropImageRef.componentInstance.selectClicked
|
||||
// .subscribe(result => {
|
||||
// console.log('Got the data!', result);
|
||||
//
|
||||
// if (result == null) {
|
||||
// return;
|
||||
// }
|
||||
// this.tutorial.titleimage = result.url;
|
||||
//
|
||||
// });
|
||||
}
|
||||
|
||||
cropImage(fileInfo: FileInfo) {
|
||||
|
||||
// close the crop view for a previous cropped file
|
||||
if(this.currentCroppedFile !== undefined){
|
||||
this.currentCroppedFile.mode = "View";
|
||||
}
|
||||
|
||||
fileInfo.mode = "CropView";
|
||||
this.setBase64String(fileInfo);
|
||||
this.currentCroppedFile = fileInfo;
|
||||
|
||||
}
|
||||
|
||||
setBase64String(fileInfo:FileInfo) {
|
||||
if(fileInfo.url !== undefined){
|
||||
this.userApiService.loadImageAsBase64(fileInfo.url).subscribe(data => {
|
||||
fileInfo.base64string = data;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
imageCropped(event: ImageCroppedEvent) {
|
||||
if (event.objectUrl != null) {
|
||||
|
||||
// this.croppedImage = this.sanitizer.bypassSecurityTrustUrl(event.objectUrl);
|
||||
|
||||
}
|
||||
else {
|
||||
this.croppedImageBase64 = event.base64;
|
||||
this.croppedImage = this.croppedImageBase64;
|
||||
}
|
||||
this.imagePreview.nativeElement.height = event.height;
|
||||
this.imagePreview.nativeElement.width = event.width;
|
||||
}
|
||||
|
||||
imageLoaded(image: LoadedImage) {
|
||||
// show cropper
|
||||
}
|
||||
cropperReady() {
|
||||
// cropper ready
|
||||
}
|
||||
loadImageFailed() {
|
||||
// show message
|
||||
}
|
||||
|
||||
zoomIn() {
|
||||
this.transform = {
|
||||
...this.transform,
|
||||
scale: this.transform.scale! + .1
|
||||
};
|
||||
}
|
||||
|
||||
zoomOut() {
|
||||
this.transform = {
|
||||
...this.transform,
|
||||
scale: this.transform.scale! - .1
|
||||
};
|
||||
}
|
||||
|
||||
moveLeft() {
|
||||
this.transform = {
|
||||
...this.transform,
|
||||
translateH: this.transform.translateH! - 1
|
||||
};
|
||||
}
|
||||
moveRight() {
|
||||
this.transform = {
|
||||
...this.transform,
|
||||
translateH: this.transform.translateH! + 1
|
||||
};
|
||||
}
|
||||
|
||||
moveDown() {
|
||||
this.transform = {
|
||||
...this.transform,
|
||||
translateV: this.transform.translateV! + 1
|
||||
};
|
||||
}
|
||||
|
||||
moveUp() {
|
||||
this.transform = {
|
||||
...this.transform,
|
||||
translateV: this.transform.translateV! - 1
|
||||
};
|
||||
}
|
||||
|
||||
flipHorizontal() {
|
||||
this.transform = {
|
||||
...this.transform,
|
||||
flipH: !this.transform.flipH
|
||||
};
|
||||
}
|
||||
|
||||
flipVertical() {
|
||||
this.transform = {
|
||||
...this.transform,
|
||||
flipV: !this.transform.flipV
|
||||
};
|
||||
}
|
||||
|
||||
rotateLeft() {
|
||||
this.loading = true;
|
||||
setTimeout(() => { // Use timeout because rotating image is a heavy operation and will block the ui thread
|
||||
this.canvasRotation--;
|
||||
this.flipAfterRotate();
|
||||
});
|
||||
}
|
||||
|
||||
rotateRight() {
|
||||
this.loading = true;
|
||||
setTimeout(() => {
|
||||
this.canvasRotation++;
|
||||
this.flipAfterRotate();
|
||||
});
|
||||
}
|
||||
|
||||
private flipAfterRotate() {
|
||||
const flippedH = this.transform.flipH;
|
||||
const flippedV = this.transform.flipV;
|
||||
this.transform = {
|
||||
...this.transform,
|
||||
flipH: flippedV,
|
||||
flipV: flippedH,
|
||||
translateH: 0,
|
||||
translateV: 0
|
||||
};
|
||||
}
|
||||
|
||||
cropFixedSize($event: MatChipSelectionChange) {
|
||||
if ($event.source.selected) {
|
||||
this.cropperStaticWidth = 716;
|
||||
this.cropperStaticHeight = 400;
|
||||
}
|
||||
else {
|
||||
this.cropperStaticWidth = 0;
|
||||
this.cropperStaticHeight = 0;
|
||||
}
|
||||
}
|
||||
|
||||
save() {
|
||||
const payload = this.getBase64ImagePayload();
|
||||
this.userApiService.uploadUserBase64Image(payload).subscribe({
|
||||
next: (res:UploadImageResponse) =>{
|
||||
const fileInfo: FileInfo = {
|
||||
name: res.fileName,
|
||||
url: res.url,
|
||||
mode: "View",
|
||||
base64string: undefined
|
||||
}
|
||||
this.fileInfos?.push(fileInfo);
|
||||
console.log("Uploaded:", res);
|
||||
} ,
|
||||
error: (err) => console.error("Upload failed", err),
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private getBase64ImagePayload(): Base64ImagePayload {
|
||||
|
||||
const match = this.croppedImageBase64?.match(/^data:(.+);base64,(.*)$/);
|
||||
if(match)
|
||||
{
|
||||
const [, mime, base64] = match;
|
||||
return {
|
||||
mime: mime,
|
||||
data: base64
|
||||
};
|
||||
|
||||
}
|
||||
else {
|
||||
throw new Error("Invalid base64 string");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@
|
||||
<mat-divider></mat-divider>
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Markdown text | Result">
|
||||
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
||||
<textarea class="variable-textarea" [(ngModel)]="tutorial.body"></textarea>
|
||||
<markdown class="variable-binding" [data]="tutorial.body"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Editor">
|
||||
|
||||
+5
-5
@@ -70,7 +70,7 @@ const language = 'typescript';
|
||||
|
||||
### Blockquote
|
||||
> Blockquote to the max`;
|
||||
private id: string | null | undefined;
|
||||
private fileName: string | null | undefined;
|
||||
|
||||
constructor(private userApiService: UserApiService,
|
||||
private route: ActivatedRoute,
|
||||
@@ -81,11 +81,11 @@ const language = 'typescript';
|
||||
this.route.queryParams
|
||||
.subscribe(params => {
|
||||
console.log(params);
|
||||
this.id = params['id'];
|
||||
console.log(this.id);
|
||||
this.fileName = params['fileName'];
|
||||
console.log(this.fileName);
|
||||
});
|
||||
|
||||
this.userApiService.getTutorial(this.id).subscribe(
|
||||
this.userApiService.getTutorial(this.fileName).subscribe(
|
||||
data=>{
|
||||
this.tutorial = data;
|
||||
}
|
||||
@@ -148,7 +148,7 @@ const language = 'typescript';
|
||||
}
|
||||
|
||||
updateTutorial(): void {
|
||||
this.userApiService.update(this.id, this.tutorial)
|
||||
this.userApiService.update(this.fileName, this.tutorial)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
|
||||
+4
-4
@@ -37,13 +37,13 @@
|
||||
}
|
||||
@case('isEdit') {
|
||||
<div class="btn-edit" >
|
||||
<button mat-button routerLink='../tutorial-edit' [queryParams]="{id:element.id}" (click)="element.isEdit = !element.isEdit">
|
||||
<button mat-button routerLink='../tutorial-edit' [queryParams]="{fileName:element.fileName}" (click)="element.isEdit = !element.isEdit">
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
mat-button
|
||||
class="button-remove"
|
||||
(click)="removeRow(element.id)"
|
||||
(click)="removeRow(element.fileName)"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
@@ -98,7 +98,7 @@
|
||||
<button
|
||||
mat-button
|
||||
class="button-remove"
|
||||
(click)="removeRow(element.id)"
|
||||
(click)="removeRow(element.fileName)"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
@@ -116,7 +116,7 @@
|
||||
<button
|
||||
mat-button
|
||||
(click)="editRow(element)"
|
||||
[disabled]="disableSubmit(element.id)"
|
||||
[disabled]="disableSubmit(element.fileName)"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
|
||||
+4
-4
@@ -125,17 +125,17 @@ import {MatSlideToggle} from '@angular/material/slide-toggle';
|
||||
// })
|
||||
}
|
||||
|
||||
removeRow(id: number) {
|
||||
this.userApiService.deleteTutorial(id).subscribe(() => {
|
||||
removeRow(fileName: number) {
|
||||
this.userApiService.deleteTutorial(fileName).subscribe(() => {
|
||||
this.dataSource.data = this.dataSource.data.filter(
|
||||
(u: Tutorial) => u.id !== id,
|
||||
(u: Tutorial) => u.fileName !== fileName,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
publish(element: any, checked: boolean){
|
||||
element.tobepublished = checked;
|
||||
this.userApiService.update(element.id, element).subscribe(
|
||||
this.userApiService.update(element.fileName, element).subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
},
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {forkJoin, Observable} from 'rxjs';
|
||||
import {forkJoin, Observable, switchMap} from 'rxjs';
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
import {HttpClient, HttpEvent, HttpHeaders, HttpRequest} from '@angular/common/http';
|
||||
import {GlobalConstants} from '../../global-constants';
|
||||
import {FileInfo} from '../../models/file-info';
|
||||
import {map} from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -15,6 +16,11 @@ export class UserApiService {
|
||||
|
||||
}
|
||||
|
||||
uploadUserBase64Image(base64Image: Base64ImagePayload): Observable<any> {
|
||||
return this.http.post<UploadImageResponse>(`${this.baseUrl}/user-images/upload-base64-image`, base64Image, { headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
upload(file: File): Observable<HttpEvent<any>> {
|
||||
const formData: FormData = new FormData();
|
||||
formData.append('file', file);
|
||||
@@ -36,32 +42,59 @@ export class UserApiService {
|
||||
return this.http.get<FileInfo[]>(`${this.baseUrl}/getImages`);
|
||||
}
|
||||
|
||||
|
||||
loadImageAsBase64(imageUrl: string): Observable<string> {
|
||||
return this.http.post<Base64ImageResponse>(`${this.baseUrl}/user-images/base64-image`, imageUrl)
|
||||
.pipe(map(r => `data:${r.mime};base64,${(r.data || '').trim()}`));
|
||||
}
|
||||
|
||||
getUserAllTutorials(): Observable<Tutorial[]> {
|
||||
return this.http.get<Tutorial[]>(`${this.baseUrl}/tutorials`);
|
||||
}
|
||||
|
||||
getTutorial(id: string | null | undefined): Observable<Tutorial> {
|
||||
return this.http.get<Tutorial>(`${this.baseUrl}/tutorial-get/${id}`);
|
||||
getTutorial(fileName: string | null | undefined): Observable<Tutorial> {
|
||||
return this.http.get<Tutorial>(`${this.baseUrl}/tutorial-get/${fileName}`);
|
||||
}
|
||||
|
||||
create(data: any): Observable<any> {
|
||||
return this.http.post(`${this.baseUrl}/tutorial-add`, data);
|
||||
}
|
||||
|
||||
update(id: any,data: any): Observable<any> {
|
||||
return this.http.put(`${this.baseUrl}/tutorial-update/${id}`, data);
|
||||
update(fileName: any,data: any): Observable<any> {
|
||||
return this.http.put(`${this.baseUrl}/tutorial-update/${fileName}`, data);
|
||||
}
|
||||
|
||||
deleteTutorials(tutorials: Tutorial[]): Observable<Tutorial[]> {
|
||||
return forkJoin(
|
||||
tutorials.map((tutorial) =>
|
||||
this.http.delete<Tutorial>(`${this.baseUrl}/tutorials/${tutorial.id}`)
|
||||
this.http.delete<Tutorial>(`${this.baseUrl}/tutorials/${tutorial.fileName}`)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
deleteTutorial(id: number): Observable<Tutorial> {
|
||||
return this.http.delete<Tutorial>(`${this.baseUrl}/tutorials/${id}`);
|
||||
deleteTutorial(fileName: number): Observable<Tutorial> {
|
||||
return this.http.delete<Tutorial>(`${this.baseUrl}/tutorials/${fileName}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface Base64ImagePayload {
|
||||
mime: string; // e.g., "image/png"
|
||||
data: string; // base64 string (no "data:" prefix)
|
||||
}
|
||||
|
||||
export interface UploadImageResponse {
|
||||
message: string;
|
||||
fileName: string;
|
||||
url: string; // URL to access the stored image
|
||||
mime: string;
|
||||
size: number; // bytes
|
||||
}
|
||||
|
||||
|
||||
export interface Base64ImageResponse {
|
||||
message: string;
|
||||
mime: string;
|
||||
data: string; // Base64 without "data:" prefix
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user