import {ChangeDetectorRef, Component, HostListener, inject, input} from '@angular/core'; import { TuiAppearance, TuiButton, TuiDialog, TuiGroup, TuiIcon, TuiScrollbarDirective, TuiTextfield } from '@taiga-ui/core'; import {TranslatePipe} from '@ngx-translate/core'; import {ServiceManager} from '../../../service-manager'; import {MessageBoxViewModel} from './message-box-viewmodel'; import {FormsModule} from '@angular/forms'; import {v4 as uuidv4} from 'uuid'; import {Masonry} from '../masonry/masonry'; import {Oimg} from '../oimg/oimg'; import {FileData} from '@chatenium/chatenium-sdk/domain/fileUploadService.schema'; import {TuiTextarea, TuiTextareaComponent} from '@taiga-ui/kit'; @Component({ selector: 'message-box', imports: [ TuiAppearance, TranslatePipe, TuiScrollbarDirective, TuiGroup, TuiIcon, TuiButton, FormsModule, TuiDialog, Oimg, Masonry, TuiTextfield, TuiTextarea ], templateUrl: './message-box.html', styleUrl: './message-box.scss', }) export class MessageBox { viewModel = input.required() cdr = inject(ChangeDetectorRef) textareaHeight = 25 messageBoxRadius = 200 private dragCounter = 0; isDraggingOverWindow = false; @HostListener('window:dragenter', ['$event']) onDragEnter(event: DragEvent) { event.preventDefault(); this.dragCounter++; if (this.dragCounter === 1) { this.isDraggingOverWindow = true; console.log("Drag entered the window area"); } } @HostListener('window:dragleave', ['$event']) onDragLeave(event: DragEvent) { event.preventDefault(); this.dragCounter--; if (this.dragCounter === 0) { this.isDraggingOverWindow = false; console.log("Drag left the window completely"); } } onDragOver(event: DragEvent) { // This is the "magic" line that enables the drop event to fire event.preventDefault(); event.stopPropagation(); } onDrop(event: DragEvent) { event.preventDefault(); event.stopPropagation(); const files = event.dataTransfer?.files; if (files) this.processFiles(files) this.isDraggingOverWindow = false this.cdr.detectChanges(); } onTextAreaInput(e: HTMLTextAreaElement) { const calculatedHeight = e.value.split(/\r?\n/).length * 25 const calculatedRadius = (200 - this.textareaHeight) this.textareaHeight = calculatedHeight > 180 ? 180 : calculatedHeight this.messageBoxRadius = calculatedRadius < 30 ? 30 : calculatedRadius } handleFileInput(event: any) { this.processFiles(event.target.files) } sendMessageWithCaption() { this.viewModel().onMessageSend(this.viewModel().message(), this.viewModel().files()) this.viewModel().files.set([]) this.viewModel().dialogOpen.set(false) } async processFiles(fileList: FileList) { for (let i = 0; i < fileList.length; i++) { const file = fileList[i]; const type = file.type.split("/").shift() ?? "" let blob = "" let thumbnailBlob = undefined let height = 0 let width = 0 console.log(type) blob = URL.createObjectURL(file) if (type == "image") { const imgData = await fetchImageDimensions(file) console.log(imgData) height = imgData.height width = imgData.width } else if (type == "video") { const videoData = await processVideo(file) thumbnailBlob = videoData.thumbnailBlob height = videoData.height width = videoData.width } console.log("push") this.viewModel().files().push({ fileId: uuidv4(), data: file, name: file.name, type: type, extension: file.name.split(".").pop() ?? "", blob: blob, height: height, width: width, videoThumbnail: thumbnailBlob, }) } this.viewModel().dialogOpen.set(true) function fetchImageDimensions(file: File): Promise<{ height: number, width: number }> { return new Promise((resolve, reject) => { const img = new Image(); const objectUrl = URL.createObjectURL(file); img.onload = () => { console.log("Loaded image") resolve({ width: img.naturalWidth, height: img.naturalHeight, }); }; img.onerror = (err) => { URL.revokeObjectURL(objectUrl); console.error("Error loading image:", err); reject(err); }; img.src = objectUrl; }); } async function processVideo(file: File): Promise<{ thumbnailBlob: string, height: number, width: number }> { const objectUrl = URL.createObjectURL(file); const video = document.createElement('video'); video.src = objectUrl; video.crossOrigin = 'anonymous'; video.muted = true; return new Promise((resolve) => { video.addEventListener('loadeddata', async () => { video.currentTime = 0; const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext('2d'); ctx!.drawImage(video, 0, 0, canvas.width, canvas.height); const thumbnail = canvas.toDataURL('image/jpeg') const thumbnailResp = await fetch(thumbnail); const thumbnailBlob = await thumbnailResp.blob(); const thumbnailBlobUrl = URL.createObjectURL(thumbnailBlob); resolve({ thumbnailBlob: thumbnailBlobUrl, height: video.videoHeight, width: video.videoWidth, }); }); }); } } protected readonly console = console; } /** * All the extra data for client-side rendering. (With finished messages these are generated on the server) */ export interface FileDataWithPreview extends FileData { blob: string; videoThumbnail?: string; height: number; width: number; }