Files
Nexum/src/app/chat/elements/message-box/message-box.ts

212 lines
5.8 KiB
TypeScript

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<MessageBoxViewModel>()
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;
}