Added file uploading + drag'n'drop

This commit is contained in:
2026-04-09 17:02:13 +02:00
parent 97f7712d55
commit 7d9737e9c2
9 changed files with 53 additions and 32 deletions

8
package-lock.json generated
View File

@@ -15,7 +15,7 @@
"@angular/forms": "^21.2.0", "@angular/forms": "^21.2.0",
"@angular/platform-browser": "^21.2.0", "@angular/platform-browser": "^21.2.0",
"@angular/router": "^21.2.0", "@angular/router": "^21.2.0",
"@chatenium/chatenium-sdk": "^1.0.10", "@chatenium/chatenium-sdk": "^1.0.11",
"@ngx-translate/core": "^17.0.0", "@ngx-translate/core": "^17.0.0",
"@ngx-translate/http-loader": "^17.0.0", "@ngx-translate/http-loader": "^17.0.0",
"@taiga-ui/addon-charts": "^5.1.0", "@taiga-ui/addon-charts": "^5.1.0",
@@ -988,9 +988,9 @@
} }
}, },
"node_modules/@chatenium/chatenium-sdk": { "node_modules/@chatenium/chatenium-sdk": {
"version": "1.0.10", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/@chatenium/chatenium-sdk/-/chatenium-sdk-1.0.10.tgz", "resolved": "https://registry.npmjs.org/@chatenium/chatenium-sdk/-/chatenium-sdk-1.0.11.tgz",
"integrity": "sha512-AhWtM4bD3p1nXW/tC/eiBlPGesk/vjwjf1CPuScYaD2OwmXLGZXNZPXe3g6T5dRAA1Zyo18QkWEJXglA+6VZSQ==", "integrity": "sha512-2vkN+W541bMEdWTStrXorsEmbQ9mva6drKOU11yFdVMzpPEsMJr9bvk5Lwc5COpOgNTIbnFSAkzPg+0USFruVQ==",
"dependencies": { "dependencies": {
"@faker-js/faker": "^10.4.0", "@faker-js/faker": "^10.4.0",
"axios": "^1.14.0", "axios": "^1.14.0",

View File

@@ -18,7 +18,7 @@
"@angular/forms": "^21.2.0", "@angular/forms": "^21.2.0",
"@angular/platform-browser": "^21.2.0", "@angular/platform-browser": "^21.2.0",
"@angular/router": "^21.2.0", "@angular/router": "^21.2.0",
"@chatenium/chatenium-sdk": "^1.0.10", "@chatenium/chatenium-sdk": "^1.0.11",
"@ngx-translate/core": "^17.0.0", "@ngx-translate/core": "^17.0.0",
"@ngx-translate/http-loader": "^17.0.0", "@ngx-translate/http-loader": "^17.0.0",
"@taiga-ui/addon-charts": "^5.1.0", "@taiga-ui/addon-charts": "^5.1.0",

View File

@@ -42,12 +42,6 @@ export class Dm implements OnInit {
async sendMessage(message: string, files: FileDataWithPreview[] | null) { async sendMessage(message: string, files: FileDataWithPreview[] | null) {
const session = this.serviceManager.currentSession(); const session = this.serviceManager.currentSession();
if (session != null) { if (session != null) {
await this.store.service.sendMessage(message, null, null, files, <FileUploadProgressListener>{
fileProgressUpdate: (fileId, allChunks, chunksDone) => {
this.uploadProgressUpdate(fileId, allChunks, chunksDone)
}
})
let attachments: Attachment[] = [] let attachments: Attachment[] = []
files?.forEach(file => { files?.forEach(file => {
attachments.push({ attachments.push({
@@ -75,10 +69,16 @@ export class Dm implements OnInit {
replyToId: "", replyToId: "",
forwardedFromName: "" forwardedFromName: ""
}]) }])
await this.store.service.sendMessage("", message, null, null, files, <FileUploadProgressListener>{
fileProgressUpdate: (tempMsgId, fileId, allChunks, chunksDone) => {
this.uploadProgressUpdate(tempMsgId, fileId, allChunks, chunksDone)
}
})
} }
} }
uploadProgressUpdate(fileId: string, allChunks: number, chunksDone: number) { uploadProgressUpdate(tempMsgId: string, fileId: string, allChunks: number, chunksDone: number) {
console.log(fileId, allChunks, chunksDone) console.log(fileId, allChunks, chunksDone)
} }

View File

@@ -11,11 +11,5 @@ export class MessageBoxViewModel {
message = signal<string>("") message = signal<string>("")
files = signal<FileDataWithPreview[]>([]) files = signal<FileDataWithPreview[]>([])
dialogOpen = signal<boolean>(false)
get dialogOpen() {
return this.files().length != 0
}
set dialogOpen(value: boolean) {
this.files.set([])
}
} }

View File

@@ -1,5 +1,5 @@
<div id="dragOverlay" tuiGroup orientation="vertical" [class.show]="isDraggingOverWindow"> <div id="dragOverlay" tuiGroup orientation="vertical" [class.show]="isDraggingOverWindow">
<div class="method"> <div class="method" (drop)="onDrop($event)" (dragover)="onDragOver($event)">
<div class="icon-holder"> <div class="icon-holder">
<tui-icon icon="@tui.file-up"/> <tui-icon icon="@tui.file-up"/>
</div> </div>
@@ -33,7 +33,7 @@
></textarea> ></textarea>
</tui-textfield> </tui-textfield>
<button tuiButton iconStart="@tui.send" (click)="sendMessageWithCaption(caption)"></button> <button tuiButton iconStart="@tui.send" (click)="sendMessageWithCaption()"></button>
</div> </div>
</ng-template> </ng-template>

View File

@@ -1,4 +1,4 @@
import {Component, HostListener, inject, input} from '@angular/core'; import {ChangeDetectorRef, Component, HostListener, inject, input} from '@angular/core';
import { import {
TuiAppearance, TuiAppearance,
TuiButton, TuiButton,
@@ -40,6 +40,8 @@ import {TuiTextarea, TuiTextareaComponent} from '@taiga-ui/kit';
export class MessageBox { export class MessageBox {
viewModel = input.required<MessageBoxViewModel>() viewModel = input.required<MessageBoxViewModel>()
cdr = inject(ChangeDetectorRef)
textareaHeight = 25 textareaHeight = 25
messageBoxRadius = 200 messageBoxRadius = 200
@@ -68,6 +70,22 @@ export class MessageBox {
} }
} }
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) { onTextAreaInput(e: HTMLTextAreaElement) {
const calculatedHeight = e.value.split(/\r?\n/).length * 25 const calculatedHeight = e.value.split(/\r?\n/).length * 25
const calculatedRadius = (200 - this.textareaHeight) const calculatedRadius = (200 - this.textareaHeight)
@@ -79,8 +97,10 @@ export class MessageBox {
this.processFiles(event.target.files) this.processFiles(event.target.files)
} }
sendMessageWithCaption(e: TuiTextareaComponent) { sendMessageWithCaption() {
this.viewModel().onMessageSend((e.content() as string), this.viewModel().files()) this.viewModel().onMessageSend(this.viewModel().message(), this.viewModel().files())
this.viewModel().files.set([])
this.viewModel().dialogOpen.set(false)
} }
async processFiles(fileList: FileList) { async processFiles(fileList: FileList) {
@@ -116,8 +136,8 @@ export class MessageBox {
height: height, height: height,
width: width, width: width,
}) })
console.log(this.viewModel().files())
} }
this.viewModel().dialogOpen.set(true)
function makePicturePreview(file: File): Promise<{ preview: string, height: number, width: number }> { function makePicturePreview(file: File): Promise<{ preview: string, height: number, width: number }> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -143,6 +163,8 @@ export class MessageBox {
}); });
} }
} }
protected readonly console = console;
} }
/** /**

View File

@@ -12,11 +12,13 @@
</div> </div>
<div class="bubble"> <div class="bubble">
<span class="message-text">{{message.message}}</span> <span class="message-text">{{message.message}}</span>
@for (file of message.files; track file) { <masonry style="max-height: 300px">
@if (file.type == "image") { @for (file of message.files; track file) {
<img [src]="file.path"/> @if (file.type == "image") {
<img [src]="file.path" style="width: 100%; height: 100%; max-height: 300px; object-fit: cover; border-radius: 25px"/>
}
} }
} </masonry>
</div> </div>
<div class="below"></div> <div class="below"></div>
</div> </div>

View File

@@ -57,8 +57,9 @@
min-width: 250px; min-width: 250px;
min-height: 40px; min-height: 40px;
display: flex; display: flex;
align-items: center; flex-direction: column;
padding: 5px; justify-content: center;
padding: 10px;
.message-text { .message-text {
white-space: none; white-space: none;

View File

@@ -3,11 +3,13 @@ import {Message} from '@chatenium/chatenium-sdk/domain/dmService.schema';
import {Message as NetworkMessage} from '@chatenium/chatenium-sdk/domain/textChannelService.schema' import {Message as NetworkMessage} from '@chatenium/chatenium-sdk/domain/textChannelService.schema'
import {ServiceManager} from '../../../service-manager'; import {ServiceManager} from '../../../service-manager';
import {DatePipe} from '@angular/common'; import {DatePipe} from '@angular/common';
import {Masonry} from '../masonry/masonry';
@Component({ @Component({
selector: 'messages', selector: 'messages',
imports: [ imports: [
DatePipe DatePipe,
Masonry
], ],
templateUrl: './messages.html', templateUrl: './messages.html',
styleUrl: './messages.scss', styleUrl: './messages.scss',