image-cropper component, drag & drop bilder
This commit is contained in:
parent
840d7a63b1
commit
89bb85a512
|
|
@ -30,8 +30,6 @@ export class FileService {
|
||||||
return this.subscriptions
|
return this.subscriptions
|
||||||
}
|
}
|
||||||
async storeProfilePicture(file: Express.Multer.File, userId: string) {
|
async storeProfilePicture(file: Express.Multer.File, userId: string) {
|
||||||
// const suffix = file.mimetype.includes('png') ? 'png' : 'jpg'
|
|
||||||
// await fs.outputFile(`./pictures/profile/${userId}`, file.buffer);
|
|
||||||
let quality = 50;
|
let quality = 50;
|
||||||
const output = await sharp(file.buffer)
|
const output = await sharp(file.buffer)
|
||||||
.resize({ width: 300 })
|
.resize({ width: 300 })
|
||||||
|
|
@ -45,7 +43,6 @@ export class FileService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async storeCompanyLogo(file: Express.Multer.File, userId: string) {
|
async storeCompanyLogo(file: Express.Multer.File, userId: string) {
|
||||||
// const suffix = file.mimetype.includes('png') ? 'png' : 'jpg'
|
|
||||||
let quality = 50;
|
let quality = 50;
|
||||||
const output = await sharp(file.buffer)
|
const output = await sharp(file.buffer)
|
||||||
.resize({ width: 300 })
|
.resize({ width: 300 })
|
||||||
|
|
@ -110,17 +107,11 @@ export class FileService {
|
||||||
let quality = 50; // AVIF kann mit niedrigeren Qualitätsstufen gute Ergebnisse erzielen
|
let quality = 50; // AVIF kann mit niedrigeren Qualitätsstufen gute Ergebnisse erzielen
|
||||||
let output;
|
let output;
|
||||||
let start = Date.now();
|
let start = Date.now();
|
||||||
// do {
|
|
||||||
output = await sharp(buffer)
|
output = await sharp(buffer)
|
||||||
.resize({ width: 1500 })
|
.resize({ width: 1500 })
|
||||||
.avif({ quality }) // Verwende AVIF
|
.avif({ quality }) // Verwende AVIF
|
||||||
//.webp({ quality }) // Verwende Webp
|
//.webp({ quality }) // Verwende Webp
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
|
|
||||||
// if (output.byteLength > maxSize) {
|
|
||||||
// quality -= 5; // Justiere Qualität in feineren Schritten
|
|
||||||
// }
|
|
||||||
// } while (output.byteLength > maxSize && quality > 0);
|
|
||||||
await sharp(output).toFile(`${directory}/${imageName}.avif`); // Ersetze Dateierweiterung
|
await sharp(output).toFile(`${directory}/${imageName}.avif`); // Ersetze Dateierweiterung
|
||||||
let timeTaken = Date.now() - start;
|
let timeTaken = Date.now() - start;
|
||||||
this.logger.info(`Quality: ${quality} - Time: ${timeTaken} milliseconds`)
|
this.logger.info(`Quality: ${quality} - Time: ${timeTaken} milliseconds`)
|
||||||
|
|
@ -141,4 +132,8 @@ export class FileService {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
deleteImage(path:string){
|
||||||
|
fs.unlinkSync(path);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,22 @@ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { FileService } from '../file/file.service.js';
|
import { FileService } from '../file/file.service.js';
|
||||||
|
import { SelectOptionsService } from '../select-options/select-options.service.js';
|
||||||
|
|
||||||
@Controller('image')
|
@Controller('image')
|
||||||
export class ImageController {
|
export class ImageController {
|
||||||
|
|
||||||
constructor(private fileService:FileService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
|
constructor(private fileService:FileService,
|
||||||
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||||
|
private selectOptions:SelectOptionsService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('uploadPropertyPicture/:id')
|
@Post('uploadPropertyPicture/:id')
|
||||||
@UseInterceptors(FileInterceptor('file'),)
|
@UseInterceptors(FileInterceptor('file'),)
|
||||||
async uploadFile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
async uploadPropertyPicture(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
||||||
await this.fileService.storePropertyPicture(file,id);
|
await this.fileService.storePropertyPicture(file,id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
|
||||||
async getPropertyImagesById(@Param('id') id:string): Promise<any> {
|
|
||||||
return await this.fileService.getPropertyImages(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('uploadProfile/:id')
|
@Post('uploadProfile/:id')
|
||||||
@UseInterceptors(FileInterceptor('file'),)
|
@UseInterceptors(FileInterceptor('file'),)
|
||||||
async uploadProfile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
async uploadProfile(@UploadedFile() file: Express.Multer.File,@Param('id') id:string) {
|
||||||
|
|
@ -33,6 +31,10 @@ export class ImageController {
|
||||||
await this.fileService.storeCompanyLogo(file,id);
|
await this.fileService.storeCompanyLogo(file,id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
async getPropertyImagesById(@Param('id') id:string): Promise<any> {
|
||||||
|
return await this.fileService.getPropertyImages(id);
|
||||||
|
}
|
||||||
@Get('profileImages/:userids')
|
@Get('profileImages/:userids')
|
||||||
async getProfileImagesForUsers(@Param('userids') userids:string): Promise<any> {
|
async getProfileImagesForUsers(@Param('userids') userids:string): Promise<any> {
|
||||||
return await this.fileService.getProfileImagesForUsers(userids);
|
return await this.fileService.getProfileImagesForUsers(userids);
|
||||||
|
|
@ -41,4 +43,16 @@ export class ImageController {
|
||||||
async getCompanyLogosForUsers(@Param('userids') userids:string): Promise<any> {
|
async getCompanyLogosForUsers(@Param('userids') userids:string): Promise<any> {
|
||||||
return await this.fileService.getCompanyLogosForUsers(userids);
|
return await this.fileService.getCompanyLogosForUsers(userids);
|
||||||
}
|
}
|
||||||
|
@Delete('propertyPicture/:listingid/:imagename')
|
||||||
|
async deletePropertyImagesById(@Param('listingid') listingid:string,@Param('imagename') imagename:string): Promise<any> {
|
||||||
|
this.fileService.deleteImage(`pictures/property/${listingid}/${imagename}`)
|
||||||
|
}
|
||||||
|
@Delete('logo/:userid/')
|
||||||
|
async deleteLogoImagesById(@Param('id') id:string): Promise<any> {
|
||||||
|
this.fileService.deleteImage(`pictures/property//${id}`)
|
||||||
|
}
|
||||||
|
@Delete('profile/:userid/')
|
||||||
|
async deleteProfileImagesById(@Param('id') id:string): Promise<any> {
|
||||||
|
this.fileService.deleteImage(`pictures/property//${id}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
||||||
import { ImageController } from './image.controller.js';
|
import { ImageController } from './image.controller.js';
|
||||||
import { ImageService } from './image.service.js';
|
import { ImageService } from './image.service.js';
|
||||||
import { FileService } from '../file/file.service.js';
|
import { FileService } from '../file/file.service.js';
|
||||||
|
import { SelectOptionsService } from '../select-options/select-options.service.js';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [ImageController],
|
controllers: [ImageController],
|
||||||
providers: [ImageService,FileService]
|
providers: [ImageService,FileService,SelectOptionsService]
|
||||||
})
|
})
|
||||||
export class ImageModule {}
|
export class ImageModule {}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { KeyValue, KeyValueStyle } from '../models/main.model.js';
|
import { ImageType, KeyValue, KeyValueStyle } from '../models/main.model.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SelectOptionsService {
|
export class SelectOptionsService {
|
||||||
|
|
@ -45,6 +45,11 @@ export class SelectOptionsService {
|
||||||
{ name: 'Broker', value: 'broker', icon:'pi-image',bgColorClass:'bg-green-100',textColorClass:'text-green-600' },
|
{ name: 'Broker', value: 'broker', icon:'pi-image',bgColorClass:'bg-green-100',textColorClass:'text-green-600' },
|
||||||
{ name: 'Professional', value: 'professional', icon:'pi-globe',bgColorClass:'bg-yellow-100',textColorClass:'text-yellow-600' },
|
{ name: 'Professional', value: 'professional', icon:'pi-globe',bgColorClass:'bg-yellow-100',textColorClass:'text-yellow-600' },
|
||||||
]
|
]
|
||||||
|
public imageTypes:ImageType[] = [
|
||||||
|
{name:'propertyPicture',upload:'uploadPropertyPicture',delete:'propertyPicture'},
|
||||||
|
{name:'companyLogo',upload:'uploadCompanyLogo',delete:'logo'},
|
||||||
|
{name:'profile',upload:'uploadProfile',delete:'profile'},
|
||||||
|
]
|
||||||
private usStates = [
|
private usStates = [
|
||||||
{ name: 'ALABAMA', abbreviation: 'AL'},
|
{ name: 'ALABAMA', abbreviation: 'AL'},
|
||||||
{ name: 'ALASKA', abbreviation: 'AK'},
|
{ name: 'ALASKA', abbreviation: 'AK'},
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^17.2.2",
|
"@angular/animations": "^17.2.2",
|
||||||
|
"@angular/cdk": "^17.3.2",
|
||||||
"@angular/common": "^17.2.2",
|
"@angular/common": "^17.2.2",
|
||||||
"@angular/compiler": "^17.2.2",
|
"@angular/compiler": "^17.2.2",
|
||||||
"@angular/core": "^17.2.2",
|
"@angular/core": "^17.2.2",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<angular-cropper #cropper [imageUrl]="imageUrl" [cropperOptions]="cropperConfig"></angular-cropper>
|
||||||
|
<div class="flex justify-content-between mt-3">
|
||||||
|
@if(ratioVariable){
|
||||||
|
<div>
|
||||||
|
<p-selectButton [options]="stateOptions" [ngModel]="value" (ngModelChange)="changeAspectRation($event)"
|
||||||
|
optionLabel="label" optionValue="value"></p-selectButton>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div></div>
|
||||||
|
}
|
||||||
|
<div>
|
||||||
|
<p-button icon="pi" (click)="cancelUpload()" label="Cancel" [outlined]="true"></p-button>
|
||||||
|
<p-button icon="pi pi-check" (click)="sendImage()" label="Finish" pAutoFocus [autofocus]="true"></p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { Component, ViewChild } from '@angular/core';
|
||||||
|
import { AngularCropperjsModule, CropperComponent } from 'angular-cropperjs';
|
||||||
|
import { LoadingService } from '../../services/loading.service';
|
||||||
|
import { ImageService } from '../../services/image.service';
|
||||||
|
import { HttpEventType } from '@angular/common/http';
|
||||||
|
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
|
||||||
|
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
import { KeyValueRatio, User } from '../../../../../common-models/src/main.model';
|
||||||
|
import { SharedModule } from '../../shared/shared/shared.module';
|
||||||
|
import { SelectButtonModule } from 'primeng/selectbutton';
|
||||||
|
export const stateOptions:KeyValueRatio[]=[
|
||||||
|
{label:'16/9',value:16/9},
|
||||||
|
{label:'1/1',value:1},
|
||||||
|
{label:'2/3',value:2/3},
|
||||||
|
]
|
||||||
|
@Component({
|
||||||
|
selector: 'app-image-cropper',
|
||||||
|
standalone: true,
|
||||||
|
imports: [SharedModule,FileUploadModule,AngularCropperjsModule,SelectButtonModule],
|
||||||
|
templateUrl: './image-cropper.component.html',
|
||||||
|
styleUrl: './image-cropper.component.scss'
|
||||||
|
})
|
||||||
|
export class ImageCropperComponent {
|
||||||
|
@ViewChild(CropperComponent) public angularCropper: CropperComponent;
|
||||||
|
imageUrl:string; //wird im Template verwendet
|
||||||
|
fileUpload:FileUpload
|
||||||
|
value:number = stateOptions[0].value;
|
||||||
|
cropperConfig={aspectRatio: this.value}
|
||||||
|
ratioVariable:boolean
|
||||||
|
stateOptions=stateOptions
|
||||||
|
constructor(
|
||||||
|
private loadingService:LoadingService,
|
||||||
|
private imageUploadService: ImageService,
|
||||||
|
public config: DynamicDialogConfig,
|
||||||
|
public ref: DynamicDialogRef
|
||||||
|
){}
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.config.data) {
|
||||||
|
this.imageUrl = this.config.data.imageUrl;
|
||||||
|
this.fileUpload = this.config.data.fileUpload;
|
||||||
|
this.cropperConfig = this.config.data.config ? this.config.data.config: this.cropperConfig;
|
||||||
|
this.ratioVariable = this.config.data.ratioVariable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendImage(){
|
||||||
|
this.loadingService.startLoading('uploadImage');
|
||||||
|
setTimeout(()=>{
|
||||||
|
this.angularCropper.cropper.getCroppedCanvas().toBlob(async(blob) => {
|
||||||
|
this.fileUpload.clear()
|
||||||
|
this.ref.close(blob);
|
||||||
|
}, 'image/png');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelUpload(){
|
||||||
|
this.fileUpload.clear();
|
||||||
|
this.ref.close();
|
||||||
|
}
|
||||||
|
changeAspectRation(ratio:number){
|
||||||
|
this.cropperConfig={aspectRatio: ratio}
|
||||||
|
this.angularCropper.cropper.setAspectRatio(ratio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Description</div>
|
<div class="text-500 w-full md:w-2 font-medium">Description</div>
|
||||||
<div class="text-900 w-full md:w-10 line-height-3">{{listing?.description}}</div>
|
<div class="text-900 w-full md:w-10 line-height-3" [innerHTML]="description"></div>
|
||||||
</li>
|
</li>
|
||||||
@if (listing && (listing.listingsCategory==='business')){
|
@if (listing && (listing.listingsCategory==='business')){
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import { MessageService } from 'primeng/api';
|
||||||
import { SharedModule } from '../../../shared/shared/shared.module';
|
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||||
import { GalleriaModule } from 'primeng/galleria';
|
import { GalleriaModule } from 'primeng/galleria';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-details-listing',
|
selector: 'app-details-listing',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
|
|
@ -59,13 +60,15 @@ export class DetailsListingComponent {
|
||||||
propertyImages: ImageProperty[] = []
|
propertyImages: ImageProperty[] = []
|
||||||
environment = environment;
|
environment = environment;
|
||||||
user:User
|
user:User
|
||||||
|
description:SafeHtml;
|
||||||
constructor(private activatedRoute: ActivatedRoute,
|
constructor(private activatedRoute: ActivatedRoute,
|
||||||
private listingsService: ListingsService,
|
private listingsService: ListingsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
public selectOptions: SelectOptionsService,
|
public selectOptions: SelectOptionsService,
|
||||||
private mailService: MailService,
|
private mailService: MailService,
|
||||||
private messageService: MessageService) {
|
private messageService: MessageService,
|
||||||
|
private sanitizer: DomSanitizer) {
|
||||||
this.userService.getUserObservable().subscribe(user => {
|
this.userService.getUserObservable().subscribe(user => {
|
||||||
this.user = user
|
this.user = user
|
||||||
});
|
});
|
||||||
|
|
@ -76,6 +79,7 @@ export class DetailsListingComponent {
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, this.type));
|
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, this.type));
|
||||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
||||||
|
this.description=this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
|
||||||
}
|
}
|
||||||
back() {
|
back() {
|
||||||
this.router.navigate(['listings', this.criteria.listingsCategory])
|
this.router.navigate(['listings', this.criteria.listingsCategory])
|
||||||
|
|
|
||||||
|
|
@ -56,34 +56,14 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="companyOverview" class="block font-medium text-900 mb-2">Company Overview</label>
|
<label for="companyOverview" class="block font-medium text-900 mb-2">Company Overview</label>
|
||||||
<p-editor [(ngModel)]="user.companyOverview" [style]="{ height: '320px' }">
|
<p-editor [(ngModel)]="user.companyOverview" [style]="{ height: '320px' }" [modules]="editorModules">
|
||||||
<ng-template pTemplate="header">
|
<ng-template pTemplate="header"></ng-template>
|
||||||
<span class="ql-formats">
|
|
||||||
<button type="button" class="ql-bold" aria-label="Bold"></button>
|
|
||||||
<button type="button" class="ql-italic" aria-label="Italic"></button>
|
|
||||||
<button type="button" class="ql-underline" aria-label="Underline"></button>
|
|
||||||
<button value="ordered" aria-label="Ordered List" type="button"
|
|
||||||
class="ql-list"></button>
|
|
||||||
<button value="bullet" aria-label="Unordered List" type="button"
|
|
||||||
class="ql-list"></button>
|
|
||||||
</span>
|
|
||||||
</ng-template>
|
|
||||||
</p-editor>
|
</p-editor>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="companyOverview" class="block font-medium text-900 mb-2">Services We offer</label>
|
<label for="companyOverview" class="block font-medium text-900 mb-2">Services We offer</label>
|
||||||
<p-editor [(ngModel)]="user.offeredServices" [style]="{ height: '320px' }">
|
<p-editor [(ngModel)]="user.offeredServices" [style]="{ height: '320px' }" [modules]="editorModules">
|
||||||
<ng-template pTemplate="header">
|
<ng-template pTemplate="header"></ng-template>
|
||||||
<span class="ql-formats">
|
|
||||||
<button type="button" class="ql-bold" aria-label="Bold"></button>
|
|
||||||
<button type="button" class="ql-italic" aria-label="Italic"></button>
|
|
||||||
<button type="button" class="ql-underline" aria-label="Underline"></button>
|
|
||||||
<button value="ordered" aria-label="Ordered List" type="button"
|
|
||||||
class="ql-list"></button>
|
|
||||||
<button value="bullet" aria-label="Unordered List" type="button"
|
|
||||||
class="ql-list"></button>
|
|
||||||
</span>
|
|
||||||
</ng-template>
|
|
||||||
</p-editor>
|
</p-editor>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -126,7 +106,7 @@
|
||||||
<span class="font-medium text-900 mb-2">Company Logo</span>
|
<span class="font-medium text-900 mb-2">Company Logo</span>
|
||||||
<span class="font-medium text-xs mb-2">(is shown in every offer)</span>
|
<span class="font-medium text-xs mb-2">(is shown in every offer)</span>
|
||||||
@if(user.hasCompanyLogo){
|
@if(user.hasCompanyLogo){
|
||||||
<img src="{{environment.apiBaseUrl}}/logo/{{user.id}}.avif" class="rounded-profile" />
|
<img src="{{companyLogoUrl}}" class="rounded-profile" />
|
||||||
} @else {
|
} @else {
|
||||||
<img src="assets/images/placeholder.png" class="rounded-profile" />
|
<img src="assets/images/placeholder.png" class="rounded-profile" />
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +118,7 @@
|
||||||
<div class="flex flex-column align-items-center flex-or">
|
<div class="flex flex-column align-items-center flex-or">
|
||||||
<span class="font-medium text-900 mb-2">Your Profile Picture</span>
|
<span class="font-medium text-900 mb-2">Your Profile Picture</span>
|
||||||
@if(user.hasProfile){
|
@if(user.hasProfile){
|
||||||
<img src="{{environment.apiBaseUrl}}/profile/{{user.id}}.avif" class="rounded-profile" />
|
<img src="{{profileUrl}}" class="rounded-profile" />
|
||||||
} @else {
|
} @else {
|
||||||
<img src="assets/images/person_placeholder.jpg" class="rounded-profile" />
|
<img src="assets/images/person_placeholder.jpg" class="rounded-profile" />
|
||||||
}
|
}
|
||||||
|
|
@ -212,8 +192,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p-dialog header="Edit Image" [visible]="imageUrl" [modal]="true" [style]="{ width: '50vw' }" [draggable]="false" [resizable]="false">
|
<!-- <p-dialog header="Edit Image" [visible]="imageUrl" [modal]="true" [style]="{ width: '50vw' }" [draggable]="false" [resizable]="false">
|
||||||
<!-- <app-cropper #cropper [imageUrl]="imageUrl"></app-cropper> -->
|
|
||||||
<angular-cropper #cropper [imageUrl]="imageUrl" [cropperOptions]="config"></angular-cropper>
|
<angular-cropper #cropper [imageUrl]="imageUrl" [cropperOptions]="config"></angular-cropper>
|
||||||
<ng-template pTemplate="footer" let-config="config">
|
<ng-template pTemplate="footer" let-config="config">
|
||||||
<div class="flex justify-content-between">
|
<div class="flex justify-content-between">
|
||||||
|
|
@ -230,4 +209,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</p-dialog>
|
</p-dialog> -->
|
||||||
|
|
@ -32,16 +32,19 @@ import { AngularCropperjsModule, CropperComponent } from 'angular-cropperjs';
|
||||||
import { ImageService } from '../../../services/image.service';
|
import { ImageService } from '../../../services/image.service';
|
||||||
import { DialogModule } from 'primeng/dialog';
|
import { DialogModule } from 'primeng/dialog';
|
||||||
import { SelectButtonModule } from 'primeng/selectbutton';
|
import { SelectButtonModule } from 'primeng/selectbutton';
|
||||||
|
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
|
||||||
|
import { ImageCropperComponent, stateOptions } from '../../../components/image-cropper/image-cropper.component';
|
||||||
|
import Quill from 'quill'
|
||||||
|
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account',
|
selector: 'app-account',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule,FileUploadModule,EditorModule,AngularCropperjsModule,DialogModule,SelectButtonModule],
|
imports: [SharedModule, FileUploadModule, EditorModule, AngularCropperjsModule, DialogModule, SelectButtonModule, DynamicDialogModule],
|
||||||
providers:[MessageService],
|
providers: [MessageService, DialogService],
|
||||||
templateUrl: './account.component.html',
|
templateUrl: './account.component.html',
|
||||||
styleUrl: './account.component.scss'
|
styleUrl: './account.component.scss'
|
||||||
})
|
})
|
||||||
export class AccountComponent {
|
export class AccountComponent {
|
||||||
@ViewChild(CropperComponent) public angularCropper: CropperComponent;
|
|
||||||
@ViewChild('companyUpload') public companyUpload: FileUpload;
|
@ViewChild('companyUpload') public companyUpload: FileUpload;
|
||||||
@ViewChild('profileUpload') public profileUpload: FileUpload;
|
@ViewChild('profileUpload') public profileUpload: FileUpload;
|
||||||
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
||||||
|
|
@ -51,16 +54,10 @@ export class AccountComponent {
|
||||||
maxFileSize = 1000000;
|
maxFileSize = 1000000;
|
||||||
companyLogoUrl: string;
|
companyLogoUrl: string;
|
||||||
profileUrl: string;
|
profileUrl: string;
|
||||||
imageUrl;
|
|
||||||
type: 'company' | 'profile'
|
type: 'company' | 'profile'
|
||||||
stateOptions:KeyValueRatio[]=[
|
dialogRef: DynamicDialogRef | undefined;
|
||||||
{label:'16/9',value:16/9},
|
|
||||||
{label:'1/1',value:1},
|
|
||||||
{label:'2/3',value:2/3},
|
|
||||||
]
|
|
||||||
value:number = this.stateOptions[0].value;
|
|
||||||
config={aspectRatio: this.value}
|
|
||||||
environment = environment
|
environment = environment
|
||||||
|
editorModules = TOOLBAR_OPTIONS
|
||||||
constructor(public userService: UserService,
|
constructor(public userService: UserService,
|
||||||
private subscriptionService: SubscriptionsService,
|
private subscriptionService: SubscriptionsService,
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
|
|
@ -69,9 +66,8 @@ export class AccountComponent {
|
||||||
private cdref: ChangeDetectorRef,
|
private cdref: ChangeDetectorRef,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private loadingService: LoadingService,
|
private loadingService: LoadingService,
|
||||||
private imageUploadService: ImageService) {
|
private imageUploadService: ImageService,
|
||||||
|
public dialogService: DialogService) {}
|
||||||
}
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.user = await this.userService.getById(this.id);
|
this.user = await this.userService.getById(this.id);
|
||||||
this.userSubscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions());
|
this.userSubscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions());
|
||||||
|
|
@ -85,7 +81,6 @@ export class AccountComponent {
|
||||||
printInvoice(invoice: Invoice) { }
|
printInvoice(invoice: Invoice) { }
|
||||||
|
|
||||||
async updateProfile(user: User) {
|
async updateProfile(user: User) {
|
||||||
//this.messageService.add({ severity: 'warn', summary: 'Information', detail: 'This function is not yet available, please send an email to info@bizmatch.net for changes to your customer data', life: 15000 });
|
|
||||||
await this.userService.save(this.user);
|
await this.userService.save(this.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,58 +112,48 @@ export class AccountComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
select(event: any, type: 'company' | 'profile') {
|
select(event: any, type: 'company' | 'profile') {
|
||||||
this.imageUrl = URL.createObjectURL(event.files[0]);
|
const imageUrl = URL.createObjectURL(event.files[0]);
|
||||||
this.type = type
|
this.type = type
|
||||||
this.config={aspectRatio: type==='company'?this.stateOptions[0].value:this.stateOptions[2].value}
|
const config = { aspectRatio: type === 'company' ? stateOptions[0].value : stateOptions[2].value }
|
||||||
}
|
this.dialogRef = this.dialogService.open(ImageCropperComponent, {
|
||||||
sendImage(){
|
data: {
|
||||||
this.imageUrl=null
|
imageUrl: imageUrl,
|
||||||
this.loadingService.startLoading('uploadImage');
|
fileUpload: type === 'company' ? this.companyUpload : this.profileUpload,
|
||||||
this.angularCropper.cropper.getCroppedCanvas().toBlob(async(blob) => {
|
config: config,
|
||||||
|
ratioVariable: type === 'company' ? true : false
|
||||||
|
},
|
||||||
|
header: 'Edit Image',
|
||||||
|
width: '50vw',
|
||||||
|
modal: true,
|
||||||
|
closeOnEscape: true,
|
||||||
|
keepInViewport: true,
|
||||||
|
closable: false,
|
||||||
|
breakpoints: {
|
||||||
|
'960px': '75vw',
|
||||||
|
'640px': '90vw'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.dialogRef.onClose.subscribe(blob => {
|
||||||
|
if (blob) {
|
||||||
|
this.imageUploadService.uploadImage(blob, type==='company'?'uploadCompanyLogo':'uploadProfile',this.user.id).subscribe(async(event) => {
|
||||||
|
if (event.type === HttpEventType.UploadProgress) {
|
||||||
|
const progress = event.total ? event.loaded / event.total : 0;
|
||||||
|
console.log(`Upload-Fortschritt: ${progress * 100}%`);
|
||||||
|
} else if (event.type === HttpEventType.Response) {
|
||||||
|
console.log('Upload abgeschlossen', event.body);
|
||||||
|
this.loadingService.stopLoading('uploadImage');
|
||||||
if (this.type==='company'){
|
if (this.type==='company'){
|
||||||
this.imageUploadService.uploadCompanyLogo(blob,this.user.id).subscribe(async(event) => {
|
this.user.hasCompanyLogo=true;
|
||||||
if (event.type === HttpEventType.UploadProgress) {
|
|
||||||
// Berechne und zeige den Fortschritt basierend auf event.loaded und event.total
|
|
||||||
const progress = event.total ? event.loaded / event.total : 0;
|
|
||||||
console.log(`Upload-Fortschritt: ${progress * 100}%`);
|
|
||||||
// Hier könntest du beispielsweise eine Fortschrittsanzeige aktualisieren
|
|
||||||
} else if (event.type === HttpEventType.Response) {
|
|
||||||
console.log('Upload abgeschlossen', event.body);
|
|
||||||
this.companyUpload.clear();
|
|
||||||
this.loadingService.stopLoading('uploadImage');
|
|
||||||
this.companyLogoUrl=`${environment.apiBaseUrl}/logo/${this.user.id}.avif?_ts=${new Date().getTime()}`
|
this.companyLogoUrl=`${environment.apiBaseUrl}/logo/${this.user.id}.avif?_ts=${new Date().getTime()}`
|
||||||
}
|
|
||||||
}, error => console.error('Fehler beim Upload:', error));
|
|
||||||
} else {
|
} else {
|
||||||
this.imageUploadService.uploadProfileImage(blob,this.user.id).subscribe(async(event) => {
|
this.user.hasProfile=true;
|
||||||
if (event.type === HttpEventType.UploadProgress) {
|
|
||||||
// Berechne und zeige den Fortschritt basierend auf event.loaded und event.total
|
|
||||||
const progress = event.total ? event.loaded / event.total : 0;
|
|
||||||
console.log(`Upload-Fortschritt: ${progress * 100}%`);
|
|
||||||
// Hier könntest du beispielsweise eine Fortschrittsanzeige aktualisieren
|
|
||||||
} else if (event.type === HttpEventType.Response) {
|
|
||||||
console.log('Upload abgeschlossen', event.body);
|
|
||||||
this.profileUpload.clear();
|
|
||||||
this.loadingService.stopLoading('uploadImage');
|
|
||||||
this.profileUrl=`${environment.apiBaseUrl}/profile/${this.user.id}.avif?_ts=${new Date().getTime()}`
|
this.profileUrl=`${environment.apiBaseUrl}/profile/${this.user.id}.avif?_ts=${new Date().getTime()}`
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, error => console.error('Fehler beim Upload:', error));
|
}, error => console.error('Fehler beim Upload:', error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// this.fileUpload.upload();
|
|
||||||
}, 'image/png');
|
|
||||||
}
|
|
||||||
cancelUpload(){
|
|
||||||
this.imageUrl=null
|
|
||||||
if (this.type==='company'){
|
|
||||||
this.companyUpload.clear();
|
|
||||||
} else {
|
|
||||||
this.profileUpload.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
changeAspectRation(ratio:number){
|
|
||||||
this.config={aspectRatio: ratio}
|
|
||||||
this.angularCropper.cropper.setAspectRatio(ratio);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,10 @@
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="description" class="block font-medium text-900 mb-2">Description</label>
|
<label for="description" class="block font-medium text-900 mb-2">Description</label>
|
||||||
<textarea id="description" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.description"></textarea>
|
<!-- <textarea id="description" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.description"></textarea> -->
|
||||||
|
<p-editor [(ngModel)]="listing.description" [style]="{ height: '320px' }" [modules]="editorModules">
|
||||||
|
<ng-template pTemplate="header"></ng-template>
|
||||||
|
</p-editor>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (listing.listingsCategory==='business'){
|
@if (listing.listingsCategory==='business'){
|
||||||
|
|
@ -77,7 +80,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4 col-12 md:col-6">
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<div class="flex flex-column align-items-center flex-or">
|
<div class="flex flex-column align-items-center flex-or">
|
||||||
<span class="font-medium text-900 mb-2">Property Picture</span>
|
<span class="font-medium text-900 mb-2">Property Pictures</span>
|
||||||
<!-- <img [src]="propertyPictureUrl" (error)="setImageToFallback($event)" class="image"/> -->
|
<!-- <img [src]="propertyPictureUrl" (error)="setImageToFallback($event)" class="image"/> -->
|
||||||
<p-fileUpload mode="basic"
|
<p-fileUpload mode="basic"
|
||||||
chooseLabel="Upload"
|
chooseLabel="Upload"
|
||||||
|
|
@ -91,7 +94,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p-carousel [value]="propertyImages" [numVisible]="3" [numScroll]="3" [circular]="false" [responsiveOptions]="responsiveOptions">
|
<!-- <p-carousel [value]="propertyImages" [numVisible]="3" [numScroll]="3" [circular]="false" [responsiveOptions]="responsiveOptions">
|
||||||
<ng-template let-image pTemplate="item">
|
<ng-template let-image pTemplate="item">
|
||||||
<div class="border-1 surface-border border-round m-2 text-center py-5 px-3">
|
<div class="border-1 surface-border border-round m-2 text-center py-5 px-3">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|
@ -100,12 +103,22 @@
|
||||||
<div>
|
<div>
|
||||||
<div class="mt-5 flex align-items-center justify-content-center gap-2">
|
<div class="mt-5 flex align-items-center justify-content-center gap-2">
|
||||||
<p-button icon="pi pi-file-edit" [rounded]="true" />
|
<p-button icon="pi pi-file-edit" [rounded]="true" />
|
||||||
<p-button icon="fa-solid fa-trash-can" [rounded]="true" severity="danger"></p-button>
|
<p-button icon="fa-solid fa-trash-can" [rounded]="true" severity="danger" (click)="deleteConfirm(image.name)"></p-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</p-carousel>
|
</p-carousel> -->
|
||||||
|
<div class="p-2 border-1 surface-border border-round mb-4 image-container" cdkDropList (cdkDropListDropped)="onDrop($event)"
|
||||||
|
cdkDropListOrientation="horizontal">
|
||||||
|
<ul >
|
||||||
|
<li *ngFor="let image of propertyImages" class="p-2 border-round shadow-1" >
|
||||||
|
<!-- <div class="image-container"> -->
|
||||||
|
<img src="{{environment.apiBaseUrl}}/property/{{listing.id}}/{{image.name}}" [alt]="image.name" class="shadow-2" cdkDrag/>
|
||||||
|
<!-- </div> -->
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
@if (listing.listingsCategory==='business'){
|
@if (listing.listingsCategory==='business'){
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
|
|
@ -195,12 +208,5 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p-toast></p-toast>
|
||||||
<p-dialog header="Edit Image" [visible]="imageUrl" [modal]="true" [style]="{ width: '50vw' }" [draggable]="false" [resizable]="false">
|
<p-confirmDialog></p-confirmDialog>
|
||||||
<!-- <app-cropper #cropper [imageUrl]="imageUrl"></app-cropper> -->
|
|
||||||
<angular-cropper #cropper [imageUrl]="imageUrl" [cropperOptions]="config"></angular-cropper>
|
|
||||||
<ng-template pTemplate="footer">
|
|
||||||
<p-button icon="pi" (click)="imageUrl = null" label="Cancel" [outlined]="true"></p-button>
|
|
||||||
<p-button icon="pi pi-check" (click)="sendImage()" label="Finish" pAutoFocus [autofocus]="true"></p-button>
|
|
||||||
</ng-template>
|
|
||||||
</p-dialog>
|
|
||||||
|
|
@ -1,10 +1,87 @@
|
||||||
.translate-y-5 {
|
.translate-y-5 {
|
||||||
transform: translateY(5px);
|
transform: translateY(5px);
|
||||||
}
|
}
|
||||||
.image {
|
|
||||||
width: 120px;
|
// .image {
|
||||||
height: 30px;
|
// width: 120px;
|
||||||
border: 1px solid #6b7280;
|
// height: 30px;
|
||||||
padding: 1px 1px;
|
// border: 1px solid #6b7280;
|
||||||
object-fit: contain;
|
// padding: 1px 1px;
|
||||||
|
// object-fit: contain;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .image-container img {
|
||||||
|
// width: 200px;
|
||||||
|
// box-shadow: 0 3px 6px #00000029, 0 3px 6px #0000003b;
|
||||||
|
// margin-right: 1rem;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .container {
|
||||||
|
// width: 100%;
|
||||||
|
// min-height: 200px;
|
||||||
|
// border: 1px solid #ccc;
|
||||||
|
// display: flex;
|
||||||
|
// flex-wrap: wrap;
|
||||||
|
// }
|
||||||
|
.image-container {
|
||||||
|
width: 100%;
|
||||||
|
/* oder eine spezifische Breite */
|
||||||
|
overflow-x: auto;
|
||||||
|
/* Ermöglicht das Scrollen, wenn die Bilder zu breit sind */
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container ul {
|
||||||
|
display: flex;
|
||||||
|
padding: 0;
|
||||||
|
/* Entfernt den Standard-Abstand des ul-Elements */
|
||||||
|
margin: 0;
|
||||||
|
/* Entfernt den Standard-Außenabstand des ul-Elements */
|
||||||
|
list-style-type: none;
|
||||||
|
/* Entfernt die Listenpunkte */
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container li {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
/* Erlaubt den li-Elementen, zu wachsen und zu schrumpfen, aber füllt den Raum gleichmäßig */
|
||||||
|
/* Optional: Füge hier Abstände zwischen den li-Elementen hinzu */
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container img {
|
||||||
|
max-width: 100%;
|
||||||
|
/* Stellt sicher, dass die Bilder nicht über ihre natürliche Größe hinaus wachsen */
|
||||||
|
height: auto;
|
||||||
|
/* Behält das Seitenverhältnis bei */
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draggable-image {
|
||||||
|
margin: 8px;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draggable-image:active {
|
||||||
|
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
|
||||||
|
0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||||
|
0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
// .cdk-drag-preview {
|
||||||
|
// box-shadow: 0 5px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .cdk-drag-placeholder {
|
||||||
|
// background-color: #ccc;
|
||||||
|
// }
|
||||||
|
|
||||||
|
.drop-area {
|
||||||
|
border: 2px dashed #ccc;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CSS-Klasse für den Drop-Bereich, wenn ein Element darüber gezogen wird */
|
||||||
|
.drop-area-active {
|
||||||
|
border-color: #2196F3;
|
||||||
|
background-color: #E3F2FD;
|
||||||
}
|
}
|
||||||
|
|
@ -24,7 +24,7 @@ import { lastValueFrom } from 'rxjs';
|
||||||
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
||||||
import { UserService } from '../../../services/user.service';
|
import { UserService } from '../../../services/user.service';
|
||||||
import { SharedModule } from '../../../shared/shared/shared.module';
|
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||||
import { MessageService } from 'primeng/api';
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
import { AutoCompleteCompleteEvent, BusinessListing, CommercialPropertyListing, ImageProperty, ListingType, User } from '../../../../../../common-models/src/main.model';
|
import { AutoCompleteCompleteEvent, BusinessListing, CommercialPropertyListing, ImageProperty, ListingType, User } from '../../../../../../common-models/src/main.model';
|
||||||
import { GeoResult, GeoService } from '../../../services/geo.service';
|
import { GeoResult, GeoService } from '../../../services/geo.service';
|
||||||
import { InputNumberComponent, InputNumberModule } from '../../../components/inputnumber/inputnumber.component';
|
import { InputNumberComponent, InputNumberModule } from '../../../components/inputnumber/inputnumber.component';
|
||||||
|
|
@ -37,16 +37,21 @@ import { AngularCropperjsModule, CropperComponent } from 'angular-cropperjs';
|
||||||
import { HttpClient, HttpEventType } from '@angular/common/http';
|
import { HttpClient, HttpEventType } from '@angular/common/http';
|
||||||
import { ImageService } from '../../../services/image.service'
|
import { ImageService } from '../../../services/image.service'
|
||||||
import { LoadingService } from '../../../services/loading.service';
|
import { LoadingService } from '../../../services/loading.service';
|
||||||
|
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||||
|
import { EditorModule } from 'primeng/editor';
|
||||||
|
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
|
||||||
|
import { ImageCropperComponent } from '../../../components/image-cropper/image-cropper.component';
|
||||||
|
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
||||||
|
import { CdkDragDrop, CdkDragEnter, CdkDragExit, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'create-listing',
|
selector: 'create-listing',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule,ArrayToStringPipe, InputNumberModule,CarouselModule,DialogModule,AngularCropperjsModule,FileUploadModule],
|
imports: [SharedModule, ArrayToStringPipe, InputNumberModule, CarouselModule, DialogModule, AngularCropperjsModule, FileUploadModule, EditorModule, DynamicDialogModule, ConfirmDialogModule,DragDropModule],
|
||||||
providers:[MessageService],
|
providers: [MessageService, DialogService, ConfirmationService],
|
||||||
templateUrl: './edit-listing.component.html',
|
templateUrl: './edit-listing.component.html',
|
||||||
styleUrl: './edit-listing.component.scss'
|
styleUrl: './edit-listing.component.scss'
|
||||||
})
|
})
|
||||||
export class EditListingComponent {
|
export class EditListingComponent {
|
||||||
@ViewChild(CropperComponent) public angularCropper: CropperComponent;
|
|
||||||
@ViewChild(FileUpload) public fileUpload: FileUpload;
|
@ViewChild(FileUpload) public fileUpload: FileUpload;
|
||||||
listingCategory: 'Business' | 'Commercial Property';
|
listingCategory: 'Business' | 'Commercial Property';
|
||||||
category: string;
|
category: string;
|
||||||
|
|
@ -77,8 +82,11 @@ export class EditListingComponent {
|
||||||
numScroll: 1
|
numScroll: 1
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
imageUrl
|
|
||||||
config = { aspectRatio: 16 / 9 }
|
config = { aspectRatio: 16 / 9 }
|
||||||
|
editorModules = TOOLBAR_OPTIONS
|
||||||
|
dialogRef: DynamicDialogRef | undefined;
|
||||||
|
draggedImage:ImageProperty
|
||||||
|
dropAreaActive = false;
|
||||||
constructor(public selectOptions: SelectOptionsService,
|
constructor(public selectOptions: SelectOptionsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
|
|
@ -86,8 +94,10 @@ export class EditListingComponent {
|
||||||
public userService: UserService,
|
public userService: UserService,
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
private geoService: GeoService,
|
private geoService: GeoService,
|
||||||
private imageUploadService: ImageService,
|
private imageService: ImageService,
|
||||||
private loadingService:LoadingService){
|
private loadingService: LoadingService,
|
||||||
|
public dialogService: DialogService,
|
||||||
|
private confirmationService: ConfirmationService) {
|
||||||
this.user = this.userService.getUser();
|
this.user = this.userService.getUser();
|
||||||
// Abonniere Router-Events, um den aktiven Link zu ermitteln
|
// Abonniere Router-Events, um den aktiven Link zu ermitteln
|
||||||
this.router.events.subscribe(event => {
|
this.router.events.subscribe(event => {
|
||||||
|
|
@ -127,29 +137,75 @@ export class EditListingComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
select(event: any) {
|
select(event: any) {
|
||||||
this.imageUrl = URL.createObjectURL(event.files[0]);
|
const imageUrl = URL.createObjectURL(event.files[0]);
|
||||||
}
|
this.dialogRef = this.dialogService.open(ImageCropperComponent, {
|
||||||
sendImage(){
|
data: {
|
||||||
this.imageUrl=null
|
imageUrl: imageUrl,
|
||||||
this.loadingService.startLoading('uploadImage');
|
fileUpload: this.fileUpload,
|
||||||
this.angularCropper.cropper.getCroppedCanvas().toBlob(async(blob) => {
|
ratioVariable: false
|
||||||
|
},
|
||||||
this.imageUploadService.uploadPropertyImage(blob,this.listing.id).subscribe(async(event) => {
|
header: 'Edit Image',
|
||||||
if (event.type === HttpEventType.UploadProgress) {
|
width: '50vw',
|
||||||
// Berechne und zeige den Fortschritt basierend auf event.loaded und event.total
|
modal: true,
|
||||||
const progress = event.total ? event.loaded / event.total : 0;
|
closeOnEscape: true,
|
||||||
console.log(`Upload-Fortschritt: ${progress * 100}%`);
|
keepInViewport: true,
|
||||||
// Hier könntest du beispielsweise eine Fortschrittsanzeige aktualisieren
|
closable: false,
|
||||||
} else if (event.type === HttpEventType.Response) {
|
breakpoints: {
|
||||||
|
'960px': '75vw',
|
||||||
|
'640px': '90vw'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.dialogRef.onClose.subscribe(blob => {
|
||||||
|
if (blob) {
|
||||||
|
this.imageService.uploadImage(blob, 'uploadPropertyPicture', this.listing.id).subscribe(async (event) => {
|
||||||
|
if (event.type === HttpEventType.Response) {
|
||||||
console.log('Upload abgeschlossen', event.body);
|
console.log('Upload abgeschlossen', event.body);
|
||||||
// Hier könntest du die Ladeanimation ausblenden
|
|
||||||
this.propertyImages=await this.listingsService.getPropertyImages(this.listing.id)
|
|
||||||
this.fileUpload.clear();
|
|
||||||
this.loadingService.stopLoading('uploadImage');
|
this.loadingService.stopLoading('uploadImage');
|
||||||
|
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
||||||
}
|
}
|
||||||
}, error => console.error('Fehler beim Upload:', error));
|
}, error => console.error('Fehler beim Upload:', error));
|
||||||
|
}
|
||||||
|
|
||||||
// this.fileUpload.upload();
|
});
|
||||||
}, 'image/png');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteConfirm(imageName: string) {
|
||||||
|
this.confirmationService.confirm({
|
||||||
|
target: event.target as EventTarget,
|
||||||
|
message: `Do you want to delete this image ${imageName}?`,
|
||||||
|
header: 'Delete Confirmation',
|
||||||
|
icon: 'pi pi-info-circle',
|
||||||
|
acceptButtonStyleClass: "p-button-danger p-button-text",
|
||||||
|
rejectButtonStyleClass: "p-button-text p-button-text",
|
||||||
|
acceptIcon: "none",
|
||||||
|
rejectIcon: "none",
|
||||||
|
|
||||||
|
accept: async () => {
|
||||||
|
await this.imageService.deleteListingImage(this.listing.id, imageName);
|
||||||
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' });
|
||||||
|
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id)
|
||||||
|
|
||||||
|
},
|
||||||
|
reject: () => {
|
||||||
|
// this.messageService.add({ severity: 'error', summary: 'Rejected', detail: 'You have rejected' });
|
||||||
|
console.log('deny')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDrop(event: CdkDragDrop<string[]>) {
|
||||||
|
this.dropAreaActive = false;
|
||||||
|
moveItemInArray(this.propertyImages, event.previousIndex, event.currentIndex);
|
||||||
|
//console.log(event.previousIndex, event.currentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onDragEnter(event: CdkDragEnter<any,any>) {
|
||||||
|
this.dropAreaActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragExit(event: CdkDragExit<any,any>) {
|
||||||
|
this.dropAreaActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
export const TOOLBAR_OPTIONS = {
|
||||||
|
toolbar: [
|
||||||
|
['bold', 'italic', 'underline'], // Einige Standardoptionen
|
||||||
|
[{'header': [1, 2, 3, false]}], // Benutzerdefinierte Header
|
||||||
|
[{'list': 'ordered'}, {'list': 'bullet'}],
|
||||||
|
[{'color': []}], // Dropdown mit Standardfarben
|
||||||
|
['clean'] // Entfernt Formatierungen
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
@ -2,6 +2,7 @@ import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
import { ImageType } from '../../../../common-models/src/main.model';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
|
@ -12,19 +13,8 @@ export class ImageService {
|
||||||
|
|
||||||
constructor(private http: HttpClient) { }
|
constructor(private http: HttpClient) { }
|
||||||
|
|
||||||
uploadPropertyImage(imageBlob: Blob,listingId:string) {
|
uploadImage(imageBlob: Blob,type:'uploadPropertyPicture'|'uploadCompanyLogo'|'uploadProfile',id:string) {
|
||||||
const uploadUrl = `${this.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${listingId}`;
|
const uploadUrl = `${this.apiBaseUrl}/bizmatch/image/${type}/${id}`;
|
||||||
return this.uploadImage(imageBlob,uploadUrl);
|
|
||||||
}
|
|
||||||
uploadCompanyLogo(imageBlob: Blob,userId:string) {
|
|
||||||
const uploadUrl = `${this.apiBaseUrl}/bizmatch/image/uploadCompanyLogo/${userId}`;
|
|
||||||
return this.uploadImage(imageBlob,uploadUrl);
|
|
||||||
}
|
|
||||||
uploadProfileImage(imageBlob: Blob,userId:string) {
|
|
||||||
const uploadUrl = `${this.apiBaseUrl}/bizmatch/image/uploadProfile/${userId}`;
|
|
||||||
return this.uploadImage(imageBlob,uploadUrl);
|
|
||||||
}
|
|
||||||
uploadImage(imageBlob: Blob,uploadUrl:string) {
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', imageBlob, 'image.png');
|
formData.append('file', imageBlob, 'image.png');
|
||||||
|
|
||||||
|
|
@ -34,7 +24,12 @@ export class ImageService {
|
||||||
observe: 'events',
|
observe: 'events',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
async deleteUserImage(userid:string,type:ImageType,name?:string){
|
||||||
|
return await lastValueFrom(this.http.delete<[]>(`${this.apiBaseUrl}/bizmatch/image/${type.delete}${userid}`));
|
||||||
|
}
|
||||||
|
async deleteListingImage(listingid:string,name?:string){
|
||||||
|
return await lastValueFrom(this.http.delete<[]>(`${this.apiBaseUrl}/bizmatch/image/propertyPicture/${listingid}/${name}`));
|
||||||
|
}
|
||||||
async getProfileImagesForUsers(userids:string[]){
|
async getProfileImagesForUsers(userids:string[]){
|
||||||
return await lastValueFrom(this.http.get<[]>(`${this.apiBaseUrl}/bizmatch/image/profileImages/${userids.join(',')}`));
|
return await lastValueFrom(this.http.get<[]>(`${this.apiBaseUrl}/bizmatch/image/profileImages/${userids.join(',')}`));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,10 @@ export class LoadingService {
|
||||||
public startLoading(type: string,request?:string): void {
|
public startLoading(type: string,request?:string): void {
|
||||||
if (!this.loading$.value.includes(type)) {
|
if (!this.loading$.value.includes(type)) {
|
||||||
this.loading$.next(this.loading$.value.concat(type));
|
this.loading$.next(this.loading$.value.concat(type));
|
||||||
if (type==='uploadImage' || request?.includes('uploadPropertyPicture')|| request?.includes('uploadProfile')|| request?.includes('uploadCompanyLogo')) {
|
if (type==='uploadImage'
|
||||||
|
|| request?.includes('uploadPropertyPicture')
|
||||||
|
|| request?.includes('uploadProfile')
|
||||||
|
|| request?.includes('uploadCompanyLogo')) {
|
||||||
this.loadingTextSubject.next("Please wait - we're processing your image...");
|
this.loadingTextSubject.next("Please wait - we're processing your image...");
|
||||||
} else {
|
} else {
|
||||||
this.loadingTextSubject.next(null);
|
this.loadingTextSubject.next(null);
|
||||||
|
|
|
||||||
|
|
@ -71,3 +71,8 @@ p-menubarsub ul {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
.p-editor-container .ql-toolbar{
|
||||||
|
background: #f9fafb;
|
||||||
|
border-top-right-radius: 6px;
|
||||||
|
border-top-left-radius: 6px;
|
||||||
|
}
|
||||||
|
|
@ -17,12 +17,15 @@ export type SelectOption<T = number> = {
|
||||||
value: T;
|
value: T;
|
||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
|
export type ImageType = {
|
||||||
|
name:'propertyPicture'|'companyLogo'|'profile',upload:string,delete:string,
|
||||||
|
}
|
||||||
export interface Listing {
|
export interface Listing {
|
||||||
id: string;
|
id: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
type: string; //enum
|
type: string; //enum
|
||||||
title: string;
|
title: string;
|
||||||
description: Array<string>;
|
description: string;
|
||||||
country: string;
|
country: string;
|
||||||
city: string,
|
city: string,
|
||||||
state: string;//enum
|
state: string;//enum
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue