update file upload & download

This commit is contained in:
Chneemann 2024-06-12 20:42:23 +02:00
parent 1a3f4b8989
commit cc9377290f
12 changed files with 97 additions and 176 deletions

View file

@ -13,7 +13,7 @@ import { UserService } from '../../../service/user.service';
import { SingleChatComponent } from '../single-chat/single-chat.component'; import { SingleChatComponent } from '../single-chat/single-chat.component';
import { ChatMsgBoxComponent } from '../chat-msg-box/chat-msg-box.component'; import { ChatMsgBoxComponent } from '../chat-msg-box/chat-msg-box.component';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { DownloadFilesService } from '../../../service/download-files.service'; import { DownloadFilesService } from '../../../service/files.service';
import { ChannelService } from '../../../service/channel.service'; import { ChannelService } from '../../../service/channel.service';
import { InfoComponent } from '../info/info.component'; import { InfoComponent } from '../info/info.component';
@ -30,7 +30,7 @@ import { InfoComponent } from '../info/info.component';
templateUrl: './chat-content.component.html', templateUrl: './chat-content.component.html',
styleUrl: './chat-content.component.scss', styleUrl: './chat-content.component.scss',
}) })
export class ChatContentComponent implements AfterViewInit, AfterViewChecked { export class ChatContentComponent implements AfterViewInit {
@Input() currentChannel: string = ''; @Input() currentChannel: string = '';
@Input() isPrivatChannel: boolean = false; @Input() isPrivatChannel: boolean = false;
@Input() isSearchChannel: boolean = false; @Input() isSearchChannel: boolean = false;
@ -46,20 +46,11 @@ export class ChatContentComponent implements AfterViewInit, AfterViewChecked {
private chatService: ChatService, private chatService: ChatService,
private userService: UserService, private userService: UserService,
public channelService: ChannelService, public channelService: ChannelService,
private downloadFilesService: DownloadFilesService,
private renderer: Renderer2 private renderer: Renderer2
) {} ) {}
ngAfterViewInit() { ngAfterViewInit() {
this.scrollToBottom(); this.scrollToBottom();
this.checkIfLoadedFirebaseFiles();
}
ngAfterViewChecked() {
if (this.filesLoaded) {
this.scrollToBottom();
this.filesLoaded = false;
}
} }
/** /**
@ -73,17 +64,6 @@ export class ChatContentComponent implements AfterViewInit, AfterViewChecked {
} }
} }
/**
* Checks whether Firebase files have been loaded from the server
*/
checkIfLoadedFirebaseFiles() {
this.downloadFilesService.downloadedFiles.subscribe((files) => {
if (files.length > 0) {
this.filesLoaded = true;
}
});
}
/** /**
* Scrolls to the bottom of the message body element. * Scrolls to the bottom of the message body element.
*/ */

View file

@ -102,7 +102,7 @@
(click)="sendMessage()" (click)="sendMessage()"
/> />
} }
<!--- show chnnels or users by pressing @ or #--> <!--- show channels or users by pressing @ or #-->
@if (openSmallWindow) { @if (openSmallWindow) {
<div class="filteredElementWindow2"> <div class="filteredElementWindow2">
@if (showChannels) { @for (i of checkIfUserHasAccessToChannel(); track @if (showChannels) { @for (i of checkIfUserHasAccessToChannel(); track

View file

@ -10,7 +10,7 @@ import {
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { PickerComponent } from '@ctrl/ngx-emoji-mart'; import { PickerComponent } from '@ctrl/ngx-emoji-mart';
import { Firestore, addDoc, collection } from '@angular/fire/firestore'; import { Firestore, addDoc, collection } from '@angular/fire/firestore';
import { DownloadFilesService } from '../../../service/download-files.service'; import { DownloadFilesService } from '../../../service/files.service';
import { UserService } from '../../../service/user.service'; import { UserService } from '../../../service/user.service';
import { EmojiPickerComponent } from '../../../shared/components/emoji-picker/emoji-picker.component'; import { EmojiPickerComponent } from '../../../shared/components/emoji-picker/emoji-picker.component';
import { SmallBtnComponent } from '../../../shared/components/small-btn/small-btn.component'; import { SmallBtnComponent } from '../../../shared/components/small-btn/small-btn.component';

View file

@ -1,9 +1,8 @@
@if(imageUrl) {
<div class="files"> <div class="files">
<div class="filesView"> <div class="filesView">
@if (['png', 'jpg', 'jpeg', 'gif'].includes(getFileType(imageUrl))) { @if (['png', 'jpg', 'jpeg', 'gif'].includes(getFileType(filePath))) {
<img <img
src="{{ imageUrl }}" src="{{ filePath }}"
alt="image" alt="image"
[ngStyle]="{ [ngStyle]="{
height: height:
@ -11,13 +10,13 @@
? '5vw' ? '5vw'
: '7.5vw' : '7.5vw'
}" }"
(click)="showOverlay(imageUrl)" (click)="showOverlay(filePath)"
/> />
} @if (['pdf', 'doc', 'txt'].includes(getFileType(imageUrl))) { } @if (['pdf', 'doc', 'txt'].includes(getFileType(filePath))) {
<a href="{{ imageUrl }}" target="_blank"> <a href="{{ filePath }}" target="_blank">
<img <img
class="otherFiles" class="otherFiles"
src="./assets/img/attachments/{{ getFileType(imageUrl) }}.png" src="./assets/img/attachments/{{ getFileType(filePath) }}.png"
alt="image" alt="image"
[ngStyle]="{ [ngStyle]="{
height: height:
@ -26,11 +25,11 @@
: '5vw' : '5vw'
}" }"
/></a> /></a>
} @if (['mp3', 'wav', 'wma'].includes(getFileType(imageUrl))) { } @if (['mp3', 'wav', 'wma'].includes(getFileType(filePath))) {
<audio <audio
controls controls
preload="auto" preload="auto"
src="{{ imageUrl }}" src="{{ filePath }}"
[ngStyle]="{ [ngStyle]="{
width: width:
openOnSecondaryChat && viewWidth >= RESPONSIVE_THRESHOLD openOnSecondaryChat && viewWidth >= RESPONSIVE_THRESHOLD
@ -38,10 +37,10 @@
: '' : ''
}" }"
></audio> ></audio>
}@if (getFileType(imageUrl) === 'mp4') { }@if (getFileType(filePath) === 'mp4') {
<div (click)="showOverlay(imageUrl)"> <div (click)="showOverlay(filePath)">
<video <video
src="{{ imageUrl }}" src="{{ filePath }}"
alt="video" alt="video"
[ngStyle]="{ [ngStyle]="{
height: height:
@ -54,4 +53,3 @@
} }
</div> </div>
</div> </div>
}

View file

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { DownloadFilesService } from '../../../../service/download-files.service'; import { DownloadFilesService } from '../../../../service/files.service';
import { OverlayService } from '../../../../service/overlay.service'; import { OverlayService } from '../../../../service/overlay.service';
import { OverlayComponent } from '../../../../shared/components/overlay/overlay.component'; import { OverlayComponent } from '../../../../shared/components/overlay/overlay.component';
import { SharedService } from '../../../../service/shared.service'; import { SharedService } from '../../../../service/shared.service';
@ -18,7 +18,6 @@ export class AttachmentsComponent {
@Input() openOnSecondaryChat: boolean = false; @Input() openOnSecondaryChat: boolean = false;
@Input() viewWidth: number = 0; @Input() viewWidth: number = 0;
loadingUrl: string = './../../../assets/img/loading.svg'; loadingUrl: string = './../../../assets/img/loading.svg';
imageUrl: string | null = '';
constructor( constructor(
public downloadFilesService: DownloadFilesService, public downloadFilesService: DownloadFilesService,
@ -28,14 +27,6 @@ export class AttachmentsComponent {
RESPONSIVE_THRESHOLD = this.sharedService.RESPONSIVE_THRESHOLD; RESPONSIVE_THRESHOLD = this.sharedService.RESPONSIVE_THRESHOLD;
async ngOnInit() {
if (this.filePath) {
this.imageUrl = await this.downloadFilesService.downloadFiles(
this.filePath
);
}
}
// Type of files: // Type of files:
// Img: PNG, GIF, JPG, JPEG // Img: PNG, GIF, JPG, JPEG
// Files: PDF, DOC, TXT // Files: PDF, DOC, TXT

View file

@ -6,7 +6,7 @@ import { FormsModule, NgForm } from '@angular/forms';
import { ChatService } from '../../../../service/chat.service'; import { ChatService } from '../../../../service/chat.service';
import { PickerComponent } from '@ctrl/ngx-emoji-mart'; import { PickerComponent } from '@ctrl/ngx-emoji-mart';
import { SmallBtnComponent } from '../../../../shared/components/small-btn/small-btn.component'; import { SmallBtnComponent } from '../../../../shared/components/small-btn/small-btn.component';
import { DownloadFilesService } from '../../../../service/download-files.service'; import { DownloadFilesService } from '../../../../service/files.service';
import { EmojiPickerComponent } from '../../../../shared/components/emoji-picker/emoji-picker.component'; import { EmojiPickerComponent } from '../../../../shared/components/emoji-picker/emoji-picker.component';
import { UserService } from '../../../../service/user.service'; import { UserService } from '../../../../service/user.service';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';

View file

@ -89,12 +89,12 @@
<span>{{ "single-chat.edit" | translate }}</span> <span>{{ "single-chat.edit" | translate }}</span>
} }
</div> </div>
@if(filePath) { @if(chat.attachments) {
<app-attachments <app-attachments
[chatId]="chat.id" [chatId]="chat.id"
[openOnSecondaryChat]="openOnSecondaryChat" [openOnSecondaryChat]="openOnSecondaryChat"
[viewWidth]="viewWidth" [viewWidth]="viewWidth"
[filePath]="filePath" [filePath]="chat.attachments"
></app-attachments> ></app-attachments>
} }
</div> </div>

View file

@ -4,7 +4,7 @@ import { Chat, ChatAnswers } from '../../../interface/chat.interface';
import { ChatContentComponent } from '../chat-content/chat-content.component'; import { ChatContentComponent } from '../chat-content/chat-content.component';
import { CommonModule, NgSwitchCase } from '@angular/common'; import { CommonModule, NgSwitchCase } from '@angular/common';
import { ChatService } from '../../../service/chat.service'; import { ChatService } from '../../../service/chat.service';
import { DownloadFilesService } from '../../../service/download-files.service'; import { DownloadFilesService } from '../../../service/files.service';
import { NgxExtendedPdfViewerModule } from 'ngx-extended-pdf-viewer'; import { NgxExtendedPdfViewerModule } from 'ngx-extended-pdf-viewer';
import { OptionsMenuComponent } from './options-menu/options-menu.component'; import { OptionsMenuComponent } from './options-menu/options-menu.component';
import { AttachmentsComponent } from './attachments/attachments.component'; import { AttachmentsComponent } from './attachments/attachments.component';
@ -63,16 +63,6 @@ export class SingleChatComponent {
public downloadFilesService: DownloadFilesService public downloadFilesService: DownloadFilesService
) {} ) {}
ngOnInit() {
this.checkChatHasFiles();
}
async checkChatHasFiles() {
this.filePath = await this.downloadFilesService.checkChatHasFiles(
this.chat.id
);
}
/** /**
* Emits a signal to edit a message. * Emits a signal to edit a message.
* @param {boolean} variable - The value indicating whether the message edit form should be open or not. * @param {boolean} variable - The value indicating whether the message edit form should be open or not.

View file

@ -1,10 +1,12 @@
export interface Chat { export interface Chat {
id: string; id: string;
userId: string; userId: string;
chatId: string;
channelId: string; channelId: string;
message: string; message: string;
edited: boolean; edited: boolean;
publishedTimestamp: number; publishedTimestamp: number;
attachments: string;
} }
export interface ChatAnswers { export interface ChatAnswers {
@ -14,6 +16,7 @@ export interface ChatAnswers {
edited: boolean; edited: boolean;
publishedTimestamp: number; publishedTimestamp: number;
userId: string; userId: string;
attachments: string;
} }
export interface ChatReactions { export interface ChatReactions {

View file

@ -11,6 +11,12 @@ import {
updateDoc, updateDoc,
} from '@angular/fire/firestore'; } from '@angular/fire/firestore';
import { Chat, ChatAnswers, ChatReactions } from '../interface/chat.interface'; import { Chat, ChatAnswers, ChatReactions } from '../interface/chat.interface';
import {
getDownloadURL,
getStorage,
ref,
uploadBytes,
} from '@angular/fire/storage';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -26,6 +32,7 @@ export class ChatService implements OnDestroy {
getChannelId: string = ''; getChannelId: string = '';
getPrvChatId: string = ''; getPrvChatId: string = '';
inputValue: string = ''; inputValue: string = '';
fileSrc: string = '';
unsubChat; unsubChat;
unsubChatAnswers; unsubChatAnswers;
@ -101,6 +108,14 @@ export class ChatService implements OnDestroy {
}); });
} }
async updateChatFile(chatId: string, chatFile: string) {
await updateDoc(doc(collection(this.firestore, 'chats'), chatId), {
attachments: chatFile,
}).catch((err) => {
console.error(err);
});
}
/** /**
* Updates the reaction document with the specified ID with the provided array of user IDs. * Updates the reaction document with the specified ID with the provided array of user IDs.
* @param reactionId The ID of the reaction document to update. * @param reactionId The ID of the reaction document to update.
@ -171,6 +186,24 @@ export class ChatService implements OnDestroy {
} }
} }
uploadFile(file: File) {
const storage = getStorage();
const storageRef = ref(storage, 'chatFiles/' + file.name);
uploadBytes(storageRef, file)
.then((snapshot) => {
getDownloadURL(storageRef)
.then((url) => {
this.fileSrc = url;
})
.catch((error) =>
console.error('Error retrieving the download URL:', error)
);
})
.catch((error) => {
console.error('Upload error:', error);
});
}
/** /**
* Unsubscribes from all Firestore subscriptions when the service is destroyed. * Unsubscribes from all Firestore subscriptions when the service is destroyed.
*/ */

View file

@ -1,115 +0,0 @@
import { Injectable } from '@angular/core';
import {
getDownloadURL,
getStorage,
listAll,
ref,
uploadBytes,
} from '@angular/fire/storage';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class DownloadFilesService {
uploadFiles: File[] = [];
downloadedFiles: BehaviorSubject<{ id: string; files: string[] }[]> =
new BehaviorSubject<{ id: string; files: string[] }[]>([]);
fileNames: any[] = [];
constructor() {
this.listAllFiles();
}
async checkChatHasFiles(chatId: string): Promise<string | undefined> {
try {
const storage = getStorage();
const listRef = ref(storage, `chatFiles/${chatId}`);
const result = await listAll(listRef);
if (result.items.length > 0) {
return result.items[0].fullPath;
} else {
return undefined;
}
} catch (error) {
console.error('Error listing files:', error);
return undefined;
}
}
/**
* Uploads all files and updates the list.
* @param {string} docID - The documentId under which the files are to be saved.
*/
uploadAllFiles(docID: string) {
const storage = getStorage();
for (const file of this.uploadFiles) {
const storageRef = ref(storage, `chatFiles/${docID}/${file.name}`);
uploadBytes(storageRef, file).then((snapshot) => {
this.listAllFiles();
});
}
}
/**
* Lists all files in 'chatFiles' and updates the downloaded files.
*/
listAllFiles() {
const storage = getStorage();
const listRef2 = ref(storage, 'chatFiles');
listAll(listRef2)
.then(async (res) => {
const downloadedFilesData: { id: string; files: string[] }[] = [];
for (const folderRef of res.prefixes) {
const folderID = folderRef.name;
const folderFiles = [];
const folderRes = await listAll(folderRef);
for (const fileRef of folderRes.items) {
const url = await getDownloadURL(fileRef);
folderFiles.push(url);
}
downloadedFilesData.push({ id: folderID, files: folderFiles });
}
// Emit the updated value
this.downloadedFiles.next(downloadedFilesData);
})
.catch((error) => {
console.error('Error when retrieving the files:', error);
});
}
checkFiles(chatId: string) {
const storage = getStorage();
const listRef = ref(storage, `chatFiles/${chatId}`);
return listAll(listRef)
.then((result) => {
if (result.items.length > 0) {
return result.items[0].fullPath;
}
return;
})
.catch((error) => {
console.error('Error listing files:', error);
return false;
});
}
async downloadFiles(path: string): Promise<string | null> {
try {
const storage = getStorage();
const forestRef = ref(storage, path);
const downloadUrl = await getDownloadURL(forestRef);
return downloadUrl;
} catch (error) {
console.error('Error downloading file:', error);
return null;
}
}
}

View file

@ -0,0 +1,41 @@
import { Injectable } from '@angular/core';
import {
getDownloadURL,
getStorage,
ref,
uploadBytes,
} from '@angular/fire/storage';
import { ChatService } from './chat.service';
@Injectable({
providedIn: 'root',
})
export class DownloadFilesService {
uploadFiles: File[] = [];
constructor(private chatService: ChatService) {}
/**
* Uploads all files and updates the list.
* @param {string} docID - The documentId under which the files are to be saved.
*/
uploadAllFiles(docID: string) {
const storage = getStorage();
for (const file of this.uploadFiles) {
const storageRef = ref(storage, `chatFiles/${docID}/${file.name}`);
uploadBytes(storageRef, file)
.then((snapshot) => {
getDownloadURL(storageRef)
.then((url) => {
this.chatService.updateChatFile(docID, url);
})
.catch((error) =>
console.error('Error retrieving the download URL:', error)
);
})
.catch((error) => {
console.error('Upload error:', error);
});
}
}
}