Skip to content

Commit

Permalink
feat: add support for http file upload
Browse files Browse the repository at this point in the history
  • Loading branch information
trampi committed Feb 18, 2019
1 parent e82e644 commit 99c7fd1
Show file tree
Hide file tree
Showing 16 changed files with 227 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div>
<div class="drop-message"
[class.drop-message--visible]="isDropTarget">
{{dropMessage}}
</div>
<div>
<ng-content></ng-content>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.drop-message {
pointer-events: none;
display: none;
}

.drop-message--visible {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;

display: flex;
justify-content: center;
align-content: center;
flex-direction: column;

text-align: center;
font-size: 1.5em;
z-index: 999;
background-color: rgba(255, 255, 255, 0.6);
padding: 1em;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Component, EventEmitter, HostListener, Input, OnInit, Output } from '@angular/core';

@Component({
selector: 'ngx-chat-filedrop',
templateUrl: './file-drop.component.html',
styleUrls: ['./file-drop.component.less']
})
export class FileDropComponent implements OnInit {

@Output()
public fileUpload = new EventEmitter<File>();

@Input()
dropMessage: string;

isDropTarget = false;

constructor() { }

ngOnInit() {
}

@HostListener('dragover', ['$event'])
@HostListener('dragenter', ['$event'])
onDragOver(event: DragEvent) {
event.preventDefault();
event.stopPropagation();
this.isDropTarget = true;
}

@HostListener('dragleave', ['$event'])
@HostListener('dragexit', ['$event'])
onDragLeave(event: DragEvent) {
event.preventDefault();
event.stopPropagation();
this.isDropTarget = false;
}

@HostListener('drop', ['$event'])
async onDrop(event: DragEvent) {
event.preventDefault();
event.stopPropagation();

this.isDropTarget = false;

for (let i = 0; i < event.dataTransfer.items.length; i++) {
const dataTransferItem = event.dataTransfer.items[i];
if (dataTransferItem.kind === 'file') {
this.fileUpload.emit(dataTransferItem.getAsFile());
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
min-height: 10em;
max-height: 20em;
overflow: scroll;
word-break: break-all;
}

.chat-messages-empty {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<img *ngIf="message.direction === 'out'" [src]="chatService.userAvatar$ | async"/>
</div>
<span [ngxChatLinks]="message.body"></span>
<div class="chat-message-image-wrapper" *ngIf="imageLink">
<img class="chat-message-image" [src]="imageLink"/>
</div>
<div class="chat-message-footer">
<small title="{{nick}}" class="chat-message-name">{{nick}}</small>
<small class="chat-message-datetime">{{message.datetime | date:chatService.translations.timeFormat}}</small>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@
}
}

.chat-message-image-wrapper {
max-width: 100%;
height: auto;
margin-top: 1em;

img {
width: 100%;
}
}

.chat-message-footer {
font-size: 0.75em;
text-align: right;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { HttpClient } from '@angular/common/http';
import { Component, Inject, Input, OnInit } from '@angular/core';
import { Message } from '../../core';
import { extractUrls } from '../../core/utils-links';
import { XmppChatAdapter } from '../../services/adapters/xmpp/xmpp-chat-adapter.service';
import { ChatService, ChatServiceToken } from '../../services/chat-service';

export const MAX_IMAGE_SIZE = 250 * 1024;

@Component({
selector: 'ngx-chat-message',
templateUrl: './chat-message.component.html',
Expand All @@ -21,9 +26,32 @@ export class ChatMessageComponent implements OnInit {
@Input()
nick: string;

constructor(@Inject(ChatServiceToken) public chatService: ChatService) {}
imageLink: string;

constructor(
@Inject(ChatServiceToken) public chatService: ChatService,
private httpClient: HttpClient,
) {}

ngOnInit() {
this.tryFindImageLink();
}

private async tryFindImageLink() {
if (this.chatService instanceof XmppChatAdapter) {
for (const url of extractUrls(this.message.body)) {
try {
const headRequest = await this.httpClient.head(url, {observe: 'response'}).toPromise();
const contentType = headRequest.headers.get('Content-Type');
const isImage = contentType && contentType.startsWith('image');
const contentLength = headRequest.headers.get('Content-Length');
if (isImage && parseInt(contentLength, 10) < MAX_IMAGE_SIZE) {
this.imageLink = url;
break;
}
} catch (e) {
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<div class="chat-messages" #messageArea>
<!-- TODO: avatar -->
<ngx-chat-message *ngFor="let message of room.messages"
[message]="message"
[avatar]="getOrCreateContactWithFullJid(message.from).avatar"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
<div class="chat-window">
<div (click)="onClickHeader()" class="chat-contact-header" [title]="chatWindowState.contact.name">
<div class="chat-contact-avatar">
<img [src]="chatWindowState.contact.avatar"/>
<ngx-chat-filedrop (fileUpload)="onFileUpload($event)" [dropMessage]="chatService.translations.dropMessage">
<div (click)="onClickHeader()" class="chat-contact-header" [title]="chatWindowState.contact.name">
<div class="chat-contact-avatar">
<img [src]="chatWindowState.contact.avatar"/>
</div>
<div class="chat-contact-name">
{{chatWindowState.contact.name}}
</div>
<div class="chat-close" (click)="onClickClose()">
&times;
</div>
</div>
<div class="chat-contact-name">
{{chatWindowState.contact.name}}
</div>
<div class="chat-close" (click)="onClickClose()">
&times;
</div>
</div>
<div *ngIf="!chatWindowState.isCollapsed" class="chat-content">
<div *ngIf="!chatWindowState.isCollapsed" class="chat-content">

<ngx-chat-message-list [contact]="chatWindowState.contact" [showAvatars]="false"></ngx-chat-message-list>
<ngx-chat-message-list [contact]="chatWindowState.contact" [showAvatars]="false"></ngx-chat-message-list>

<div class="chat-input-container">
<ngx-chat-message-input [contact]="chatWindowState.contact"></ngx-chat-message-input>
<div class="chat-input-container">
<ngx-chat-message-input [contact]="chatWindowState.contact"></ngx-chat-message-input>
</div>
</div>
</div>
</ngx-chat-filedrop>
</div>
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Component, Inject, Input, OnDestroy, OnInit } from '@angular/core';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
import { Direction } from '../../core';
import { HttpFileUploadPlugin } from '../../services/adapters/xmpp/plugins/http-file-upload.plugin';
import { ChatListStateService, ChatWindowState } from '../../services/chat-list-state.service';
import { ChatService, ChatServiceToken } from '../../services/chat-service';

@Component({
selector: 'ngx-chat-window',
Expand All @@ -16,8 +18,10 @@ export class ChatWindowComponent implements OnInit, OnDestroy {

private ngDestroy = new Subject<void>();

constructor(private chatListService: ChatListStateService) {
}
constructor(
@Inject(ChatServiceToken) public chatService: ChatService,
private chatListService: ChatListStateService,
) {}

ngOnInit() {
this.chatWindowState.contact.messages$
Expand All @@ -43,4 +47,8 @@ export class ChatWindowComponent implements OnInit, OnDestroy {
this.chatListService.closeChat(this.chatWindowState.contact);
}

async onFileUpload(file: File) {
const url = await this.chatService.getPlugin(HttpFileUploadPlugin).upload(file);
this.chatService.sendMessage(this.chatWindowState.contact.jidBare.toString(), url);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ export class ChatComponent implements OnInit, OnChanges {
'denySubscriptionRequest': 'Deny',
'timeFormat': 'shortTime',
'dateFormat': 'EEEE, MM/dd/yyyy',
'locale': undefined
'locale': undefined,
'dropMessage': 'Drop your file to send it',
};

constructor(@Inject(ChatServiceToken) private chatService: ChatService) {
Expand Down
1 change: 1 addition & 0 deletions projects/pazznetwork/ngx-chat/src/lib/core/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export interface Translations {
timeFormat: string;
dateFormat: string;
locale: string;
dropMessage: string;
}
5 changes: 5 additions & 0 deletions projects/pazznetwork/ngx-chat/src/lib/core/utils-links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const urlRegex = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;

export function extractUrls(message: string) {
return message.match(urlRegex) || [];
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ComponentFactoryResolver, Directive, Input, OnChanges, ViewContainerRef } from '@angular/core';
import { ChatMessageLinkComponent } from '../components/chat-message-link/chat-message-link.component';
import { ChatMessageTextComponent } from '../components/chat-message-text/chat-message-text.component';
import { extractUrls } from '../core/utils-links';

@Directive({
selector: '[ngxChatLinks]'
Expand All @@ -17,8 +18,7 @@ export class LinksDirective implements OnChanges {
const message: string = this.ngxChatLinks;

if (message) {
const urlRegex = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
const links = message.match(urlRegex) || [];
const links = extractUrls(message);

const chatMessageTextFactory = this.resolver.resolveComponentFactory(ChatMessageTextComponent);
const chatMessageLinkFactory = this.resolver.resolveComponentFactory(ChatMessageLinkComponent);
Expand Down
10 changes: 9 additions & 1 deletion projects/pazznetwork/ngx-chat/src/lib/ngx-chat.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CommonModule } from '@angular/common';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule, NgZone } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Client } from '@xmpp/client-core';
Expand All @@ -7,6 +8,7 @@ import reconnect from '@xmpp/plugins/reconnect';
import plain from '@xmpp/plugins/sasl-plain';
import sessionEstablishment from '@xmpp/plugins/session-establishment';
import websocket from '@xmpp/plugins/websocket';
import { FileDropComponent } from './components/chat-filedrop/file-drop.component';
import { ChatMessageInputComponent } from './components/chat-message-input/chat-message-input.component';
import { ChatMessageLinkComponent } from './components/chat-message-link/chat-message-link.component';
import { ChatMessageListComponent } from './components/chat-message-list/chat-message-list.component';
Expand All @@ -32,6 +34,7 @@ import {
ServiceDiscoveryPlugin,
UnreadMessageCountPlugin
} from './services/adapters/xmpp/plugins';
import { HttpFileUploadPlugin } from './services/adapters/xmpp/plugins/http-file-upload.plugin';
import { MessageCarbonsPlugin } from './services/adapters/xmpp/plugins/message-carbons.plugin';
import { XmppChatAdapter } from './services/adapters/xmpp/xmpp-chat-adapter.service';
import { XmppChatConnectionService, XmppClientToken } from './services/adapters/xmpp/xmpp-chat-connection.service';
Expand All @@ -46,6 +49,7 @@ import { LogService } from './services/log.service';
imports: [
CommonModule,
FormsModule,
HttpClientModule,
],
declarations: [
ChatComponent,
Expand All @@ -60,12 +64,14 @@ import { LogService } from './services/log.service';
LinksDirective,
RosterContactComponent,
RosterListComponent,
FileDropComponent,
],
exports: [
ChatComponent,
ChatMessageInputComponent,
ChatMessageListComponent,
ChatRoomMessagesComponent,
FileDropComponent,
],
entryComponents: [
ChatMessageLinkComponent,
Expand Down Expand Up @@ -122,6 +128,7 @@ export class NgxChatModule {
const chatMessageListRegistryService = injector.get(ChatMessageListRegistryService);
const unreadMessageCountPlugin = new UnreadMessageCountPlugin(
xmppChatAdapter, chatMessageListRegistryService, publishSubscribePlugin);
const serviceDiscoveryPlugin = new ServiceDiscoveryPlugin(xmppChatAdapter);

xmppChatAdapter.addPlugins([
new BookmarkPlugin(xmppChatAdapter),
Expand All @@ -131,12 +138,13 @@ export class NgxChatModule {
new MultiUserChatPlugin(xmppChatAdapter, logService),
publishSubscribePlugin,
new RosterPlugin(xmppChatAdapter, logService),
new ServiceDiscoveryPlugin(xmppChatAdapter),
serviceDiscoveryPlugin,
new PushPlugin(xmppChatAdapter),
// new PingPlugin(xmppChatAdapter, logService, ngZone),
new RegistrationPlugin(logService, ngZone),
new MessageCarbonsPlugin(xmppChatAdapter),
unreadMessageCountPlugin,
new HttpFileUploadPlugin(injector.get(HttpClient), serviceDiscoveryPlugin, xmppChatAdapter),
]);
};
return initializer;
Expand Down

0 comments on commit 99c7fd1

Please sign in to comment.