From f3c4d387b5ab79b8df074910142f93cb9c292597 Mon Sep 17 00:00:00 2001 From: Beatriz Cardoso Date: Mon, 30 Aug 2021 18:08:42 -0300 Subject: [PATCH 01/12] First JS features --- css/style.css | 2 +- index.html | 3 + src/app.js | 1 + src/controller/WhatsAppController.js | 332 +++++++++++++++++++++++++++ src/util/Format.js | 12 + 5 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 src/app.js create mode 100644 src/controller/WhatsAppController.js create mode 100644 src/util/Format.js diff --git a/css/style.css b/css/style.css index 8f16af928..c9790cec5 100644 --- a/css/style.css +++ b/css/style.css @@ -3356,7 +3356,7 @@ html[dir=rtl] ._3DeDN{ height:100% } html[dir] ._3qlW9{ - background-color:#f7f9fa; + background-color:#052433 /* #f7f9fa */; cursor:default; padding-bottom:28px; padding-top:28px diff --git a/index.html b/index.html index a847b69bc..b59eac41b 100644 --- a/index.html +++ b/index.html @@ -1331,6 +1331,9 @@

WhatsApp Clone da Hcode Treinamentos

+ + + diff --git a/src/app.js b/src/app.js new file mode 100644 index 000000000..9915bd2f1 --- /dev/null +++ b/src/app.js @@ -0,0 +1 @@ +window.app = new WhatsAppController(); \ No newline at end of file diff --git a/src/controller/WhatsAppController.js b/src/controller/WhatsAppController.js new file mode 100644 index 000000000..a7f7fc40e --- /dev/null +++ b/src/controller/WhatsAppController.js @@ -0,0 +1,332 @@ +class WhatsAppController{ + constructor(){ + console.log("olá WhatsApp"); + this.loadElements(); + this.elementsPrototype(); + this.initEvents(); + } + + // Percorre os elementos da tela e os coloca em this.el + loadElements(){ + + this.el = {}; + + document.querySelectorAll('[id]').forEach(element=>{ + + this.el[Format.getCamelCase(element.id)] = element; + + }); + } + + // Adiciona funções para alguns elementos + elementsPrototype(){ + + // Element é uma classe nativa do js, e aqui são adicionadas algumas funções a ela. + // this.style é nativo da classe Element que pode mudar os estilos. + + Element.prototype.hide = function(){ + this.style.display = 'none'; + return this; + }; + + Element.prototype.show = function(){ + this.style.display = 'block'; + return this; + }; + + Element.prototype.toggle = function(){ + this.style.display = (this.style.display === 'none') ? 'block' : 'none'; + return this; + }; + + // Realizar eventos + Element.prototype.on = function(events, fn){ + events.split(' ').forEach(event=>{ + this.addEventListener(event,fn); + }); + return this; + }; + + // Alterar características do css + Element.prototype.css = function(styles){ + for (let name in styles){ + this.style[name] = styles[name]; + } + return this; + }; + + + /* Alterar características das classes html */ + + Element.prototype.addClass = function(name){ + this.classList.add(name); + return this; + }; + + Element.prototype.removeClass = function(name){ + this.classList.remove(name); + return this; + }; + + Element.prototype.toggleClass = function(name){ + this.classList.toggle(name); + return this; + }; + + Element.prototype.hasClass = function(name){ + return this.classList.contains(name); + }; + + + //Retorna um FormData para o formulário + HTMLFormElement.prototype.getForm = function(){ + + return new FormData(this); + + }; + + // Retorna os campos do formulário em objeto JSON + HTMLFormElement.prototype.toJSON = function(){ + + let json = {}; + + this.getForm().forEach((value, key) => { + + json[key] = value; + + }); + + return json; + + } + + } + + // Inicia os eventos que os componentes da tela realizam + initEvents(){ + + // Abre o painel para Editar Perfil + this.el.myPhoto.on('click', e => { + + this.closeAllLeftPanel(); + this.el.panelEditProfile.show(); + + setTimeout(()=>{ + this.el.panelEditProfile.addClass('open'); + }, 300); + + }); + + // Abre o Painel para Adicionar Novo Contato + this.el.btnNewContact.on('click', e => { + + this.closeAllLeftPanel(); + this.el.panelAddContact.show(); + + setTimeout(()=>{ + this.el.panelAddContact.addClass('open'); + }, 300); + + }); + + // Fecha o painel para Editar Perfil + this.el.btnClosePanelEditProfile.on('click', e=> { + + this.el.panelEditProfile.removeClass('open'); + + }); + + // Fecha o Painel para Adicionar Novo Contato + this.el.btnClosePanelAddContact.on('click', e => { + + this.el.panelAddContact.removeClass('open'); + + }); + + // Aciona o input para importar foto + this.el.photoContainerEditProfile.on('click', e=> { + + this.el.inputProfilePhoto.click(); + + }); + + // Salva o nome quando a tecla Enter é acionada + this.el.inputNamePanelEditProfile.on('keypress', e=>{ + + if(e.key === 'Enter'){ + + e.preventDefault(); + this.el.btnSavePanelEditProfile.click(); + + } + + }); + + // Salva as alterações no Perfil + this.el.btnSavePanelEditProfile.on('click',e => { + + console.log(this.el.inputNamePanelEditProfile.innerHTML); + + }); + + // Submete os dados do formulário + this.el.formPanelAddContact.on('submit', e => { + + e.preventDefault(); + + let formData = new FormData(this.el.formPanelAddContact); + + }); + + // Abre a conversa do contato selecionado na Lista de Contatos + this.el.contactsMessagesList.querySelectorAll('.contact-item').forEach(item => { + + item.on('click', e => { + + this.el.home.hide(); + + this.el.main.css({ + display:'flex' + }); + + }); + + }); + + // Abre o Menu de Anexos + this.el.btnAttach.on('click', e => { + + e.stopPropagation(); + + this.el.menuAttach.addClass('open'); + + document.addEventListener('click', this.closeMenuAttach.bind(this)); + + }); + + // Aciona o input para importar foto + this.el.btnAttachPhoto.on('click', e => { + + this.el.inputPhoto.click(); + + }); + + // Seleciona as fotos a serem importadas + this.el.inputPhoto.on('change', e => { + + console.log(this.el.inputPhoto.files); + + [...this.el.inputPhoto.files].forEach(file => { + + console.log(file); + + }) + + }); + + // Abre o Painel da Câmera + this.el.btnAttachCamera.on('click', e => { + + this.closeAllMainPanel(); + + this.el.panelCamera.addClass('open'); + + this.el.panelCamera.css({ + + 'height':'calc(100% - 120px)' + + }); + + }); + + // Fecha todos os painéis e abre a conversa + this.el.btnClosePanelCamera.on('click', e => { + + this.closeAllMainPanel(); + + this.el.panelMessagesContainer.show(); + + + }); + + // Tira foto + this.el.btnTakePicture.on('click', e=> { + + console.log("take picture"); + + }); + + // Abre o Painel de Visualizar Documentos + this.el.btnAttachDocument.on('click', e => { + + this.closeAllMainPanel(); + + this.el.panelDocumentPreview.addClass('open'); + + this.el.panelDocumentPreview.css({ + + 'height':'calc(100% - 120px)' + + }); + + }); + + // Fecha todos os painéis e abre a conversa + this.el.btnClosePanelDocumentPreview.on('click', e => { + + this.closeAllMainPanel(); + + this.el.panelMessagesContainer.show(); + + }); + + // Envia documentos + this.el.btnSendDocument.on('click', e => { + + console.log('send document'); + + }); + + // Mostra a Lista de Contatos + this.el.btnAttachContact.on('click', e => { + + this.el.modalContacts.show(); + + }); + + // Fecha a Lista de Contatos + this.el.btnCloseModalContacts.on('click', e => { + + this.el.modalContacts.hide(); + + }); + + } + + // Fecha todos os painéis principais + closeAllMainPanel(){ + + this.el.panelMessagesContainer.hide(); + this.el.panelDocumentPreview.removeClass('open'); + this.el.panelCamera.removeClass('open'); + + } + + // Fecha o menu de anexos + closeMenuAttach(e){ + + document.removeEventListener('click', this.closeMenuAttach); + this.el.menuAttach.removeClass('open'); + + } + + // Fecha todos os painéis que ficam na esquerda da tela + closeAllLeftPanel(){ + + this.el.panelEditProfile.hide(); + this.el.panelAddContact.hide(); + + } + + +} diff --git a/src/util/Format.js b/src/util/Format.js new file mode 100644 index 000000000..4c86e70a5 --- /dev/null +++ b/src/util/Format.js @@ -0,0 +1,12 @@ +class Format{ + + static getCamelCase(text){ + + let div = document.createElement('div'); + + div.innerHTML = `
`; + + return Object.keys(div.firstChild.dataset)[0]; + } + +} \ No newline at end of file From c4145002bf26e1b3b8bf7b1ce23d8dfcd0ab2e29 Mon Sep 17 00:00:00 2001 From: Beatriz Cardoso Date: Tue, 31 Aug 2021 18:08:05 -0300 Subject: [PATCH 02/12] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20=20emojis,=20grav?= =?UTF-8?q?ador,=20texto=20e=20c=C3=A2mera?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 1 + src/controller/CameraController.js | 22 ++++ src/controller/WhatsAppController.js | 148 ++++++++++++++++++++++++++- src/util/Format.js | 14 +++ 4 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 src/controller/CameraController.js diff --git a/index.html b/index.html index b59eac41b..1b7fd74bc 100644 --- a/index.html +++ b/index.html @@ -1332,6 +1332,7 @@

WhatsApp Clone da Hcode Treinamentos

+ diff --git a/src/controller/CameraController.js b/src/controller/CameraController.js new file mode 100644 index 000000000..fde2e49d2 --- /dev/null +++ b/src/controller/CameraController.js @@ -0,0 +1,22 @@ +class CameraController{ + + constructor(videoEl){ + + this._videoEl = videoEl; + + navigator.mediaDevices.getUserMedia({ + video: true + }).then(stream => { + + // Cria um arquivo + this._videoEl.src = URL.createObjectURL(stream); + + // Toca o vídeo + this._videoEl.play(); + + }).catch(err => { + console.error(err); + }); + } + +} \ No newline at end of file diff --git a/src/controller/WhatsAppController.js b/src/controller/WhatsAppController.js index a7f7fc40e..9b23ba52f 100644 --- a/src/controller/WhatsAppController.js +++ b/src/controller/WhatsAppController.js @@ -129,14 +129,14 @@ class WhatsAppController{ }); - // Fecha o painel para Editar Perfil + // Fecha o painel de Editar Perfil this.el.btnClosePanelEditProfile.on('click', e=> { this.el.panelEditProfile.removeClass('open'); }); - // Fecha o Painel para Adicionar Novo Contato + // Fecha o Painel de Adicionar Novo Contato this.el.btnClosePanelAddContact.on('click', e => { this.el.panelAddContact.removeClass('open'); @@ -237,6 +237,8 @@ class WhatsAppController{ }); + this._camera = new CameraController(this.el.videoCamera); + }); // Fecha todos os painéis e abre a conversa @@ -246,7 +248,6 @@ class WhatsAppController{ this.el.panelMessagesContainer.show(); - }); // Tira foto @@ -301,6 +302,144 @@ class WhatsAppController{ }); + // Abre gravação do microfone + this.el.btnSendMicrophone.on('click', e => { + + this.el.recordMicrophone.show(); + this.el.btnSendMicrophone.hide(); + + this.startRecordMicrophoneTime(); + + }); + + // Cancela gravação do microfone + this.el.btnCancelMicrophone.on('click', e => { + + this.closeRecordMicrophone(); + + + }); + + // Envia Gravação do Microfone + this.el.btnFinishMicrophone.on('click', e => { + + this.closeRecordMicrophone(); + + }); + + // Enviar mensagem pela tecla Enter + this.el.inputText.on('keypress', e => { + + if(e.key === 'Enter' && !e.ctrlKey){ + e.preventDefault(); + this.el.btnSend.click(); + } + + }); + + // Alterações de estilo na hora de digitar + this.el.inputText.on('keyup', e => { + + if(this.el.inputText.innerHTML.length){ + + this.el.inputPlaceholder.hide(); + this.el.btnSendMicrophone.hide(); + this.el.btnSend.show(); + + } else { + + this.el.inputPlaceholder.show(); + this.el.btnSendMicrophone.show(); + this.el.btnSend.hide(); + + } + + }); + + // Enviar Mensagem + this.el.btnSend.on('click', e => { + + console.log(this.el.inputText.innerHTML); + this.el.inputText.innerHTML = ''; + this.el.inputPlaceholder.show(); + + }); + + // Abrir/Fechar painel de emojis + this.el.btnEmojis.on('click', e => { + + this.el.panelEmojis.toggleClass('open'); + + }); + + // Selecionar emoji + this.el.panelEmojis.querySelectorAll('.emojik').forEach(emoji => { + + emoji.on('click', e => { + + // Clona o emoji para a variável img + let img = this.el.imgEmojiDefault.cloneNode(); + img.style.cssText = emoji.style.cssText; + img.dataset.unicode = emoji.dataset.unicode; + img.alt = emoji.alt; + emoji.classList.forEach(name => { + img.classList.add(name); + }); + + // Seleciona a posição onde está o cursor + let cursor = window.getSelection(); + + // Verifica o foco do cursor (onde ele está) + if(!cursor.focusNode || cursor.focusNode.id == 'input-text'){ + this.el.inputText.focus(); + cursor = window.getSelection(); + } + + // Pega a faixa de texto selecionada e apaga para colocar o emoji ali + let range = document.createRange(); + range = cursor.getRangeAt(0); + range.deleteContents(); + + // Cria um espaço para colocar a imagem do emoji + let frag = document.createDocumentFragment(); + frag.appendChild(img); + range.insertNode(frag); + + // Coloca o cursor depois da imagem do emoji + range.setStartAfter(img); + + this.el.inputText.dispatchEvent(new Event('keyup')); + + }); + + }); + + } + + // Inicia o contador de tempo do Microfone + startRecordMicrophoneTime(){ + + let start = Date.now(); + + this._recordMicrophoneInterval = setInterval(() => { + + this.el.recordMicrophoneTimer.innerHTML = Format.toTime(Date.now() - start); + + }, 100); + + } + + // Fecha a gravação do Microfone + closeRecordMicrophone(){ + + this.el.recordMicrophone.hide(); + + this.el.btnSendMicrophone.show(); + + clearInterval(this._recordMicrophoneInterval); + + this.el.recordMicrophoneTimer.innerHTML = Format.toTime(0); + } // Fecha todos os painéis principais @@ -328,5 +467,4 @@ class WhatsAppController{ } - -} +} \ No newline at end of file diff --git a/src/util/Format.js b/src/util/Format.js index 4c86e70a5..476dab695 100644 --- a/src/util/Format.js +++ b/src/util/Format.js @@ -9,4 +9,18 @@ class Format{ return Object.keys(div.firstChild.dataset)[0]; } + static toTime(duration){ + + let seconds = parseInt((duration/(1000)) % 60); + let minutes = parseInt((duration/(1000 * 60)) % 60); + let hours = parseInt((duration/(1000 * 60 * 60)) % 24); + + if(hours > 0){ + return `${hours}:${minutes.toString().padStart(2,'0')}:${seconds.toString().padStart(2,'0')}`; + } else { + return `${minutes}:${seconds.toString().padStart(2,'0')}`; + } + + } + } \ No newline at end of file From c4a26ad6593f80b06629de198fa6219fb153f4f2 Mon Sep 17 00:00:00 2001 From: Beatriz Cardoso Date: Wed, 1 Sep 2021 11:35:16 -0300 Subject: [PATCH 03/12] add webpack --- .gitignore | 4 +++ README.md | 38 +++++++++++++++++++++++++++- index.html | 5 +--- src/app.js | 2 ++ src/controller/CameraController.js | 5 ++-- src/controller/WhatsAppController.js | 5 +++- src/util/Format.js | 2 +- webpack.config.js | 12 +++++++++ 8 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 .gitignore create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..aa0cfb197 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +package.json +package-lock.json + +node_modules/ \ No newline at end of file diff --git a/README.md b/README.md index e8e4ea159..b07daaaff 100644 --- a/README.md +++ b/README.md @@ -19,4 +19,40 @@ Lista de recursos usados em aula para este projeto | Cloud Functions | https://firebase.google.com/docs/functions/?hl=pt-br | | Cloud Storage | https://firebase.google.com/docs/storage/?authuser=0 | | PDF.js | https://mozilla.github.io/pdf.js/ | -| MediaDevices.getUserMedia() | https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia | \ No newline at end of file +| MediaDevices.getUserMedia() | https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia | + + +### Instalação de Dependências + +Atualização do npm: +```bash +npm install -g npm +``` + +Inicialização do npm: +```bash +npm init +``` + +Webpack 3.1.0 e Webpack Dev Server 2.5.1: +```bash +npm install webpack@3.1.0 --save +npm install webpack-dev-server@2.5.1 --save +``` + +### Alterando o arquivo package.json + +Adicione as seguintes linhas em "script" no arquivo package.json, logo abaixo de "test": +```js +"build": "webpack --config webpack.config.js", +"start": "webpack-dev-server" +``` + +### Executando + +Rodando no servidor +```bash +npm run start +``` + +Acesse: http://localhost:8080/ diff --git a/index.html b/index.html index 1b7fd74bc..7c48be5dc 100644 --- a/index.html +++ b/index.html @@ -1331,10 +1331,7 @@

WhatsApp Clone da Hcode Treinamentos

- - - - + diff --git a/src/app.js b/src/app.js index 9915bd2f1..6ff2b7784 100644 --- a/src/app.js +++ b/src/app.js @@ -1 +1,3 @@ +import {WhatsAppController} from './controller/WhatsAppController' + window.app = new WhatsAppController(); \ No newline at end of file diff --git a/src/controller/CameraController.js b/src/controller/CameraController.js index fde2e49d2..0a103dade 100644 --- a/src/controller/CameraController.js +++ b/src/controller/CameraController.js @@ -1,4 +1,4 @@ -class CameraController{ +export class CameraController{ constructor(videoEl){ @@ -9,7 +9,8 @@ class CameraController{ }).then(stream => { // Cria um arquivo - this._videoEl.src = URL.createObjectURL(stream); + //this._videoEl.src = URL.createObjectURL(stream); + this._videoEl.srcObject = stream; // Toca o vídeo this._videoEl.play(); diff --git a/src/controller/WhatsAppController.js b/src/controller/WhatsAppController.js index 9b23ba52f..76010758a 100644 --- a/src/controller/WhatsAppController.js +++ b/src/controller/WhatsAppController.js @@ -1,4 +1,7 @@ -class WhatsAppController{ +import {Format} from './../util/Format'; +import {CameraController} from './CameraController'; + +export class WhatsAppController{ constructor(){ console.log("olá WhatsApp"); this.loadElements(); diff --git a/src/util/Format.js b/src/util/Format.js index 476dab695..c6a1035e4 100644 --- a/src/util/Format.js +++ b/src/util/Format.js @@ -1,4 +1,4 @@ -class Format{ +export class Format{ static getCamelCase(text){ diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 000000000..4bd254e38 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,12 @@ +let path = require('path'); + +module.exports = { + + entry: './src/app.js', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname,'/dist'), + publicPath: 'dist' + } + +} \ No newline at end of file From 883ba50ef5fee25ccba1eb6b6cc25f68c337f66d Mon Sep 17 00:00:00 2001 From: Beatriz Cardoso Date: Thu, 2 Sep 2021 11:52:27 -0300 Subject: [PATCH 04/12] Camera Working, Update files --- css/style.css | 9 +++ src/controller/CameraController.js | 27 ++++++++ src/controller/DocumentPreviewController.js | 46 +++++++++++++ src/controller/WhatsAppController.js | 74 ++++++++++++++++++++- 4 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 src/controller/DocumentPreviewController.js diff --git a/css/style.css b/css/style.css index c9790cec5..ca4f37367 100644 --- a/css/style.css +++ b/css/style.css @@ -16136,3 +16136,12 @@ html[dir=rtl] ._2ICcy:after{ #main { display:none; } +._2oKVP{ + display: flex; + justify-content: center; +} +#picture-camera { + height: 100%; + width: auto; + text-align: center; +} \ No newline at end of file diff --git a/src/controller/CameraController.js b/src/controller/CameraController.js index 0a103dade..6183c52c8 100644 --- a/src/controller/CameraController.js +++ b/src/controller/CameraController.js @@ -8,6 +8,8 @@ export class CameraController{ video: true }).then(stream => { + this._stream = stream; + // Cria um arquivo //this._videoEl.src = URL.createObjectURL(stream); this._videoEl.srcObject = stream; @@ -20,4 +22,29 @@ export class CameraController{ }); } + // Percorre cada track (como áudio e vídeo) e manda parar + stop(){ + this._stream.getTracks().forEach(track => { + track.stop(); + }); + } + + takePicture(mimeType = 'image/png'){ + + let canvas = document.createElement('canvas'); + + // Pega a altura e largura da imagem + canvas.setAttribute('height', this._videoEl.videoHeight); + canvas.setAttribute('width', this._videoEl.videoWidth); + + let context = canvas.getContext('2d'); + + // Desenhar Imagem (elemento, x, y, w, h) + context.drawImage(this._videoEl, 0, 0, canvas.width, canvas.height);; + + return canvas.toDataURL(mimeType); + + + } + } \ No newline at end of file diff --git a/src/controller/DocumentPreviewController.js b/src/controller/DocumentPreviewController.js new file mode 100644 index 000000000..2ed10dcd7 --- /dev/null +++ b/src/controller/DocumentPreviewController.js @@ -0,0 +1,46 @@ +export class DocumentPreviewController{ + + constructor(file){ + + this._file = file; + + } + + getPreviewData(){ + return new Promise((resolve,reject)=>{ + + switch(this._file.type){ + + case 'image/png': + case 'image/jpeg': + case 'image/jpg': + case 'image/gif': + + let reader = new FileReader(); + + reader.onload = e => { + resolve({ + src:reader.result, + info:this._file.name + }); + }; + + reader.onerror = e => { + reject(e); + } + + reader.readAsDataURL(this._file); + + break; + + case 'application/pdf': + + break; + + default: + reject(); + } + + }); + } +} \ No newline at end of file diff --git a/src/controller/WhatsAppController.js b/src/controller/WhatsAppController.js index 76010758a..652f162ed 100644 --- a/src/controller/WhatsAppController.js +++ b/src/controller/WhatsAppController.js @@ -1,5 +1,6 @@ import {Format} from './../util/Format'; import {CameraController} from './CameraController'; +import {DocumentPreviewController} from './DocumentPreviewController' export class WhatsAppController{ constructor(){ @@ -244,22 +245,58 @@ export class WhatsAppController{ }); - // Fecha todos os painéis e abre a conversa + // Fecha todos os painéis, para a câmera e abre a conversa this.el.btnClosePanelCamera.on('click', e => { this.closeAllMainPanel(); this.el.panelMessagesContainer.show(); + this._camera.stop(); + }); // Tira foto this.el.btnTakePicture.on('click', e=> { - console.log("take picture"); + let dataUrl = this._camera.takePicture(); + + this.el.pictureCamera.src = dataUrl; + + this.el.pictureCamera.show(); + + this.el.videoCamera.hide(); + + this.el.btnReshootPanelCamera.show(); + + this.el.containerTakePicture.hide(); + + this.el.containerSendPicture.show(); }); + // Tirar foto novamente + this.el.btnReshootPanelCamera.on('click', e => { + + this.el.pictureCamera.hide(); + + this.el.videoCamera.show(); + + this.el.btnReshootPanelCamera.hide(); + + this.el.containerTakePicture.show(); + + this.el.containerSendPicture.hide(); + + }); + + // Enviar foto + this.el.btnSendPicture.on('click', e => { + + console.log(this.el.pictureCamera.src); + + }) + // Abre o Painel de Visualizar Documentos this.el.btnAttachDocument.on('click', e => { @@ -273,6 +310,39 @@ export class WhatsAppController{ }); + // Clica no input de Documentos abrirá os documentos do computador para que um seja selecionado + this.el.inputDocument.click(); + + }); + + // Mostra o documento no painel de Pré-visualização + this.el.inputDocument.on('change', e => { + + if(this.el.inputDocument.files.length){ + + let file = this.el.inputDocument.files[0]; + + this._documentPreviewController = new DocumentPreviewController(file); + + // Traz o arquivo do DocumentPreviewController + this._documentPreviewController.getPreviewData().then(result => { + + this.el.imgPanelDocumentPreview.src = result.src; + this.el.infoPanelDocumentPreview.innerHTML = result.info; + this.el.imagePanelDocumentPreview.show(); + this.el.filePanelDocumentPreview.hide(); + + }).catch(err => { + + this.el.iconPanelDocumentPreview.className = 'jcxhw icon-doc-generic'; + + this.el.filenamePanelDocumentPreview.innerHTML = file.name; + this.el.imagePanelDocumentPreview.hide(); + this.el.filePanelDocumentPreview.show(); + + }); + } + }); // Fecha todos os painéis e abre a conversa From f941fd1148634959bd2730596bc9b7e9ba946ce8 Mon Sep 17 00:00:00 2001 From: Beatriz Cardoso Date: Fri, 3 Sep 2021 17:26:10 -0300 Subject: [PATCH 05/12] update audio --- src/controller/MicrophoneController.js | 152 +++++++++++++++++++++++++ src/controller/WhatsAppController.js | 34 +++--- src/util/ClassEvent.js | 36 ++++++ 3 files changed, 206 insertions(+), 16 deletions(-) create mode 100644 src/controller/MicrophoneController.js create mode 100644 src/util/ClassEvent.js diff --git a/src/controller/MicrophoneController.js b/src/controller/MicrophoneController.js new file mode 100644 index 000000000..72329207b --- /dev/null +++ b/src/controller/MicrophoneController.js @@ -0,0 +1,152 @@ +import {ClassEvent} from "../util/ClassEvent"; + +export class MicrophoneController extends ClassEvent{ + + constructor(){ + + // Chama o construtor do pai ClassEvent + super(); + + this._mimeType = 'audio/webm'; + + // Confere se o usuário permitiu o uso do mic + this._available = false; + + navigator.mediaDevices.getUserMedia({ + audio: true + }).then(stream => { + + this._available = true; + + this._stream = stream; + + // Faz o audio tocar + /* + let audio = new Audio(); + audio.srcObject = stream; + audio.play(); + */ + + this.trigger('ready', this._stream); + + + }).catch(err => { + console.error(err); + }); + + } + + isAvailable(){ + return this._available; + } + + // Percorre cada track (como áudio e vídeo) e manda parar + stop(){ + this._stream.getTracks().forEach(track => { + track.stop(); + }); + } + + startRecorder(){ + + if(this.isAvailable()){ + + // Para saber o suporte do navegador: MediaRecorder.isTypeSupported('audio/webm') + + this._mediaRecorder = new MediaRecorder(this._stream, { + mymeTipe: this._mimeType + }); + + // Array onde serão armazenados os pedaços de audios gravados + this._recordedChunks = []; + + this._mediaRecorder.addEventListener('dataavailable', e => { + + // Se já houver algum pedaço de audio gravado, guarda no array + if(e.data.size > 0){ + this._recordedChunks.push(e.data); + } + + }); + + + this._mediaRecorder.addEventListener('stop', e => { + + // Prepara o arquivo de audio + + let blob = new Blob(this._recordedChunks,{ + type: this._mimeType + }); + + let filename = `rec${Date.now()}.webm`; + + let file = new File([blob], filename, { + type: this._mimeType, + lastModified: Date.now() + }); + + console.log('file', file); + + /* + // Toca áudio + + let reader = new FileReader(); + + reader.onload = e => { + + console.log('reader file', file); + + let audio = new Audio(reader.result); + + audio.play(); + + } + + reader.readAsDataURL(file); + */ + + }); + + this._mediaRecorder.start(); + + this.startTimer(); + + } + + } + + stopRecorder(){ + + if(this.isAvailable()){ + + // Para a gravação + this._mediaRecorder.stop(); + + // Para o microfone + this.stop(); + + } + + this.stopTimer(); + + } + + startTimer(){ + + let start = Date.now(); + + this._recordMicrophoneInterval = setInterval(() => { + + this.trigger('recordtimer', (Date.now() - start)); + + }, 100); + + } + + stopTimer(){ + + clearInterval(this._recordMicrophoneInterval); + + } + +} \ No newline at end of file diff --git a/src/controller/WhatsAppController.js b/src/controller/WhatsAppController.js index 652f162ed..06c26df59 100644 --- a/src/controller/WhatsAppController.js +++ b/src/controller/WhatsAppController.js @@ -1,6 +1,7 @@ import {Format} from './../util/Format'; import {CameraController} from './CameraController'; import {DocumentPreviewController} from './DocumentPreviewController' +import {MicrophoneController} from './MicrophoneController' export class WhatsAppController{ constructor(){ @@ -381,13 +382,28 @@ export class WhatsAppController{ this.el.recordMicrophone.show(); this.el.btnSendMicrophone.hide(); - this.startRecordMicrophoneTime(); + this._microphoneController = new MicrophoneController(); + + this._microphoneController.on('ready', musica => { + + console.log('ready'); + + this._microphoneController.startRecorder(); + + }); + + this._microphoneController.on('recordtimer', timer => { + + this.el.recordMicrophoneTimer.innerHTML = Format.toTime(timer); + + }); }); // Cancela gravação do microfone this.el.btnCancelMicrophone.on('click', e => { + this._microphoneController.stopRecorder(); this.closeRecordMicrophone(); @@ -396,6 +412,7 @@ export class WhatsAppController{ // Envia Gravação do Microfone this.el.btnFinishMicrophone.on('click', e => { + this._microphoneController.stopRecorder(); this.closeRecordMicrophone(); }); @@ -489,19 +506,6 @@ export class WhatsAppController{ } - // Inicia o contador de tempo do Microfone - startRecordMicrophoneTime(){ - - let start = Date.now(); - - this._recordMicrophoneInterval = setInterval(() => { - - this.el.recordMicrophoneTimer.innerHTML = Format.toTime(Date.now() - start); - - }, 100); - - } - // Fecha a gravação do Microfone closeRecordMicrophone(){ @@ -509,8 +513,6 @@ export class WhatsAppController{ this.el.btnSendMicrophone.show(); - clearInterval(this._recordMicrophoneInterval); - this.el.recordMicrophoneTimer.innerHTML = Format.toTime(0); } diff --git a/src/util/ClassEvent.js b/src/util/ClassEvent.js new file mode 100644 index 000000000..42cb0f190 --- /dev/null +++ b/src/util/ClassEvent.js @@ -0,0 +1,36 @@ +export class ClassEvent{ + + constructor(){ + this._events = {}; + } + + on(eventName, fn){ + + if(!this._events[eventName]) this._events[eventName] = new Array(); + + this._events[eventName].push(fn); + + } + + trigger(){ + + // Contém 0 ou todos os argumentos passados no parâmetro + let args = [...arguments]; + + // Retorna o primeiro elemento de um array e remove ele + let eventName = args.shift(); + + args.push(new Event(eventName)); + + if(this._events[eventName] instanceof Array){ + + this._events[eventName].forEach(fn => { + + // executa a fn + fn.apply(null, args); + + }); + + } + } +} \ No newline at end of file From c5e5015b819c9d2113e2fef6ee33d7a96fd32745 Mon Sep 17 00:00:00 2001 From: Beatriz Cardoso Date: Thu, 9 Sep 2021 14:50:09 -0300 Subject: [PATCH 06/12] initial firebase settings --- README.md | 1 + src/controller/WhatsAppController.js | 12 ++++++ src/util/Firebase.js | 60 ++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 src/util/Firebase.js diff --git a/README.md b/README.md index b07daaaff..f7691a7df 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Webpack 3.1.0 e Webpack Dev Server 2.5.1: ```bash npm install webpack@3.1.0 --save npm install webpack-dev-server@2.5.1 --save +npm install firebase --save ``` ### Alterando o arquivo package.json diff --git a/src/controller/WhatsAppController.js b/src/controller/WhatsAppController.js index 06c26df59..e46212a39 100644 --- a/src/controller/WhatsAppController.js +++ b/src/controller/WhatsAppController.js @@ -2,15 +2,27 @@ import {Format} from './../util/Format'; import {CameraController} from './CameraController'; import {DocumentPreviewController} from './DocumentPreviewController' import {MicrophoneController} from './MicrophoneController' +import {Firebase} from './../util/Firebase' export class WhatsAppController{ constructor(){ console.log("olá WhatsApp"); + + this._firebase = new Firebase(); + this.initAuth(); this.loadElements(); this.elementsPrototype(); this.initEvents(); } + initAuth(){ + this._firebase.initAuth().then(response=>{ + console.log('response',response); + }). catch(err=>{ + console.error(err); + }); + } + // Percorre os elementos da tela e os coloca em this.el loadElements(){ diff --git a/src/util/Firebase.js b/src/util/Firebase.js new file mode 100644 index 000000000..e8728045b --- /dev/null +++ b/src/util/Firebase.js @@ -0,0 +1,60 @@ +// v9 compat packages are API compatible with v8 code +import firebase from 'firebase/compat/app'; +import 'firebase/compat/auth'; +import 'firebase/compat/firestore'; + + +export class Firebase{ + constructor(){ + this._config = { + // configurações do firebase + } + this.init(); + } + + init(){ + // Initialize Firebase + + if(!this._initialized){ + + const firebaseApp = firebase.initializeApp(this._config); + + this._initialized = true; + + } + } + + static db(){ + return firebase.firestore(); + } + + static hd(){ + return firebase.storage(); + } + + initAuth(){ + // Autenticação por email + + return new Promise((s,f) => { + + let provider = new firebase.auth.GoogleAuthProvider(); + + firebase.auth().signInWithPopup(provider).then(result => { + + let token = result.credential.accessToken; + let user = result.user; + + s(user,token); + + }).catch(err=>{ + + f(err); + + }); + + + }) + } + +} + From 4e19ebc1a2d3f504daf46f244bb2b1843346e53c Mon Sep 17 00:00:00 2001 From: Beatriz Cardoso Date: Thu, 9 Sep 2021 15:12:20 -0300 Subject: [PATCH 07/12] initial firebase settings --- README.md | 2 + src/controller/WhatsAppController.js | 12 ++++++ src/util/Firebase.js | 60 ++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 src/util/Firebase.js diff --git a/README.md b/README.md index b07daaaff..63f901dad 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ Webpack 3.1.0 e Webpack Dev Server 2.5.1: ```bash npm install webpack@3.1.0 --save npm install webpack-dev-server@2.5.1 --save +npm install firebase --save +npm i firebase@9.0.0-beta.7 ``` ### Alterando o arquivo package.json diff --git a/src/controller/WhatsAppController.js b/src/controller/WhatsAppController.js index 06c26df59..e46212a39 100644 --- a/src/controller/WhatsAppController.js +++ b/src/controller/WhatsAppController.js @@ -2,15 +2,27 @@ import {Format} from './../util/Format'; import {CameraController} from './CameraController'; import {DocumentPreviewController} from './DocumentPreviewController' import {MicrophoneController} from './MicrophoneController' +import {Firebase} from './../util/Firebase' export class WhatsAppController{ constructor(){ console.log("olá WhatsApp"); + + this._firebase = new Firebase(); + this.initAuth(); this.loadElements(); this.elementsPrototype(); this.initEvents(); } + initAuth(){ + this._firebase.initAuth().then(response=>{ + console.log('response',response); + }). catch(err=>{ + console.error(err); + }); + } + // Percorre os elementos da tela e os coloca em this.el loadElements(){ diff --git a/src/util/Firebase.js b/src/util/Firebase.js new file mode 100644 index 000000000..a7a77a1b5 --- /dev/null +++ b/src/util/Firebase.js @@ -0,0 +1,60 @@ +// v9 compat packages are API compatible with v8 code +import firebase from 'firebase/compat/app'; +import 'firebase/compat/auth'; +import 'firebase/compat/firestore'; + + +export class Firebase{ + constructor(){ + this._config = { + // firebase settings + } + this.init(); + } + + init(){ + // Initialize Firebase + + if(!this._initialized){ + + const firebaseApp = firebase.initializeApp(this._config); + + this._initialized = true; + + } + } + + static db(){ + return firebase.firestore(); + } + + static hd(){ + return firebase.storage(); + } + + initAuth(){ + // Autenticação por email + + return new Promise((s,f) => { + + let provider = new firebase.auth.GoogleAuthProvider(); + + firebase.auth().signInWithPopup(provider).then(result => { + + let token = result.credential.accessToken; + let user = result.user; + + s(user,token); + + }).catch(err=>{ + + f(err); + + }); + + + }) + } + +} + From a9e7584536d74ca2e6b74a58f9cd5410c4dcf24a Mon Sep 17 00:00:00 2001 From: Beatriz Cardoso Date: Thu, 9 Sep 2021 17:11:35 -0300 Subject: [PATCH 08/12] =?UTF-8?q?altera=C3=A7=C3=B5es=20firebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/Firebase.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/util/Firebase.js b/src/util/Firebase.js index a7a77a1b5..740a51931 100644 --- a/src/util/Firebase.js +++ b/src/util/Firebase.js @@ -7,7 +7,6 @@ import 'firebase/compat/firestore'; export class Firebase{ constructor(){ this._config = { - // firebase settings } this.init(); } From 2cb11967eea13a58cd5d15ee8722549f4445f58c Mon Sep 17 00:00:00 2001 From: Beatriz Cardoso Date: Fri, 10 Sep 2021 00:17:01 -0300 Subject: [PATCH 09/12] New model and util classes --- README.md | 1 + index.html | 293 +----------------------- src/controller/WhatsAppController.js | 174 +++++++++++++- src/model/Message.js | 326 +++++++++++++++++++++++++++ src/model/Model.js | 24 ++ src/model/User.js | 107 +++++++++ src/util/Firebase.js | 13 +- 7 files changed, 638 insertions(+), 300 deletions(-) create mode 100644 src/model/Message.js create mode 100644 src/model/Model.js create mode 100644 src/model/User.js diff --git a/README.md b/README.md index f7691a7df..63f901dad 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Webpack 3.1.0 e Webpack Dev Server 2.5.1: npm install webpack@3.1.0 --save npm install webpack-dev-server@2.5.1 --save npm install firebase --save +npm i firebase@9.0.0-beta.7 ``` ### Alterando o arquivo package.json diff --git a/index.html b/index.html index 7c48be5dc..88657fd55 100644 --- a/index.html +++ b/index.html @@ -126,7 +126,7 @@ -
+ -
-
- -
- - -
-
-
-
- -
- - - - - - - - -
-
-
-
-
Nome do Contato Anexado
-
-
-
- 17:01 -
- - - - - -
-
-
-
-
-
Enviar mensagem
-
-
- -
-
-
- -
- - -
-
- Oi! -
-
-
- 11:33 -
-
-
-
-
-
- -
-
- -
-
-
-
-
-
- Arquivo.pdf -
-
- - - - - - - -
-
-
-
- 32 páginas - PDF - 4 MB -
-
-
- 18:56 -
- - - - - -
-
-
-
-
-
-
-
- - -
-
- Oi, tudo bem? -
-
-
- 11:52 -
- - - - - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- - - -
-
- - - - - -
-
-
- -
-
-
-
- Texto da foto -
-
-
-
- 17:22 -
- - - - - -
-
-
-
-
- -
- - - - - -
-
-
- -
- -
-
-
-
-
-
- - - - - -
-
-
0:05
-
- - - -
-
-
-
-
-
- - - - - - -
-
-
-
- -
- - - - - - - - -
-
-
-
-
-
- 17:48 -
- - - - - -
-
-
-
- -
- - - - - -
-
-
-
+
diff --git a/src/controller/WhatsAppController.js b/src/controller/WhatsAppController.js index e46212a39..9760f76e3 100644 --- a/src/controller/WhatsAppController.js +++ b/src/controller/WhatsAppController.js @@ -3,6 +3,7 @@ import {CameraController} from './CameraController'; import {DocumentPreviewController} from './DocumentPreviewController' import {MicrophoneController} from './MicrophoneController' import {Firebase} from './../util/Firebase' +import {User} from './../model/User' export class WhatsAppController{ constructor(){ @@ -15,14 +16,154 @@ export class WhatsAppController{ this.initEvents(); } + // Inicia a autenticação por email initAuth(){ - this._firebase.initAuth().then(response=>{ - console.log('response',response); + this._firebase.initAuth().then((response)=>{ + + console.log(response); + this._user = new User(response.user.email); + + this._user.on('datachange', data => { + + document.querySelector('title').innerHTML = data.name + ' - WhatsApp Clone'; + + this.el.inputNamePanelEditProfile.innerHTML = data.name; + + if(data.photo) { + + let photo = this.el.imgPanelEditProfile; + photo.src = data.photo; + photo.show(); + this.el.imgDefaultPanelEditProfile.hide(); + + let photo2 = this.el.myPhoto.querySelector('img'); + photo2.src = data.photo; + photo2.show(); + + } + + this.initContacts(); + + }); + + + this._user.name = response.user.displayName; + this._user.email = response.user.email; + this._user.photo = response.user.photoURL; + + this._user.save().then(()=>{ + + // Exibe o painel do whatsapp + this.el.appContent.css({ + display:'flex' + }); + + }); + }). catch(err=>{ console.error(err); }); } + // Carrega a lista de Conversas com os Contatos + initContacts(){ + + this._user.on('contactschange', docs => { + + this.el.contactsMessagesList.innerHTML = ''; + + docs.forEach(doc => { + + let contact = doc.data(); + + let div = document.createElement('div'); + div.className = 'contact-item'; + div.innerHTML = ` +
+
+ +
+ + + + + + + + +
+
+
+
+
+
+ ${contact.name} +
+
+ ${contact.lastMessageTime} +
+
+
+
+ + + +
+ + + + + +
+ ${contact.lastMessage} +
+ +
+ +
+
+
+
+
+
+ `; + + if(contact.photo){ + let img = div.querySelector('.photo'); + img.src = contact.photo; + img.show(); + } + + div.on('click', e=>{ + + this.el.activeName.innerHTML = contact.name; + this.el.activeStatus.innerHTML = contact.status; + + if(contact.photo){ + + let img = this.el.activePhoto; + img.src = contact.photo; + img.show(); + + } + + this.el.home.hide(); + this.el.main.css({ + display:'flex' + }) + + }); + + this.el.contactsMessagesList.appendChild(div); + + }); + + + }); + + this._user.getContacts(); + } + // Percorre os elementos da tela e os coloca em this.el loadElements(){ @@ -182,7 +323,15 @@ export class WhatsAppController{ // Salva as alterações no Perfil this.el.btnSavePanelEditProfile.on('click',e => { - console.log(this.el.inputNamePanelEditProfile.innerHTML); + this.el.btnSavePanelEditProfile.disabled = true; + + this._user.name = this.el.inputNamePanelEditProfile.innerHTML; + + this._user.save().then(()=>{ + + this.el.btnSavePanelEditProfile.disabled = false; + + }); }); @@ -193,6 +342,25 @@ export class WhatsAppController{ let formData = new FormData(this.el.formPanelAddContact); + let contact = new User(formData.get('email')); + + contact.on('datachange', data => { + + if(data.name){ + + this._user.addContact(contact).then(() => { + + this.el.btnClosePanelAddContact.click(); + console.info('Contato adicionado') + + }); + + } else { + console.error('Usuário não foi encontrado'); + } + + }); + }); // Abre a conversa do contato selecionado na Lista de Contatos diff --git a/src/model/Message.js b/src/model/Message.js new file mode 100644 index 000000000..a52d66eef --- /dev/null +++ b/src/model/Message.js @@ -0,0 +1,326 @@ +import {Model} from "./Model" + +export class Message extends Model { + + constructor(){ + + super(); + + } + + get content(){ + return this._data.content; + } + set content(value){ + return this._data.content = value; + } + + get type(){ + return this._data.type; + } + set type(value){ + return this._data.type = value; + } + + get timeStamp(){ + return this._data.timeStamp; + } + set timeStamp(value){ + return this._data.timeStamp = value; + } + + get status(){ + return this._data.status; + } + set status(value){ + return this._data.status = value; + } + + // Mostra a mensagem de acordo com seu tipo + getViewElement(me = true){ + + let div = document.createElement('div'); + + div.className = 'message'; + + switch(this.type){ + + case 'contact': + + div.innerHTML = ` +
+ + +
+
+
+
+ +
+ + + + + + + + +
+
+
+
+
Nome do Contato Anexado
+
+
+
+ 17:01 +
+ + + + + +
+
+
+
+
+
Enviar mensagem
+
+
+ +
+ `; + + break; + + case 'image': + + div.innerHTML = ` +
+
+
+
+
+
+
+ + + +
+
+ + + + + +
+
+
+ +
+
+
+
+ Texto da foto +
+
+
+
+ 17:22 +
+ + + + + +
+
+
+
+
+ +
+ + + + + +
+
+ + `; + + break; + + case 'document': + + div.innerHTML = ` +
+
+ +
+
+
+
+
+
+ Arquivo.pdf +
+
+ + + + + + + +
+
+
+
+ 32 páginas + PDF + 4 MB +
+
+
+ 18:56 +
+ + + + + +
+
+
+
+
+ `; + + break; + + case 'audio': + + div.innerHTML = ` +
+
+
+
+
+
+ + + + + +
+
+
0:05
+
+ + + +
+
+
+
+
+
+ + + + + + +
+
+
+
+ +
+ + + + + + + + +
+
+
+
+
+
+ 17:48 +
+ + + + + +
+
+
+
+ +
+ + + + + +
+
+ `; + + break; + + default: + + div.innerHTML = ` +
+ + +
+
+ Oi! +
+
+
+ 11:33 +
+
+
+
+ `; + + } + + let className = (me) ? 'message-out' : 'message-in'; + + div.firstElementChild.classList.add(className); + + return div; + + } + +} \ No newline at end of file diff --git a/src/model/Model.js b/src/model/Model.js new file mode 100644 index 000000000..924bf9392 --- /dev/null +++ b/src/model/Model.js @@ -0,0 +1,24 @@ +import {ClassEvent} from './../util/ClassEvent' + +export class Model extends ClassEvent{ + + constructor(){ + super(); + + this._data = {}; + } + + fromJSON(json){ + // Pega o json e junta no _data + this._data = Object.assign(this._data, json); + + // Define o que o evento datachange faz + this.trigger('datachange', this.toJSON()); + } + + toJSON(){ + return this._data; + } + + +} \ No newline at end of file diff --git a/src/model/User.js b/src/model/User.js new file mode 100644 index 000000000..50cd68144 --- /dev/null +++ b/src/model/User.js @@ -0,0 +1,107 @@ +import {Firebase} from './../util/Firebase' +import {Model} from './Model' + +export class User extends Model{ + + constructor(id){ + + super(); + + if(id) this.getById(id); + + } + + get name(){ + return this._data.name; + } + set name(value){ + this._data.name = value; + } + + get email(){ + return this._data.email; + } + set email(value){ + this._data.email = value; + } + + get photo(){ + return this._data.photo; + } + set photo(value){ + this._data.photo = value; + } + + // Retorna a promessa com os documentos do usuário + getById(id){ + + return new Promise((s,f) => { + + User.findByEmail(id).onSnapshot(doc => { + + this.fromJSON(doc.data()); + + s(doc); + + }); + + }); + + } + + save(){ + return User.findByEmail(this.email).set(this.toJSON()); + } + + // Pega a referência do bando de dados + static getRef(){ + return Firebase.db().collection('/users'); + + } + + static getContactsRef(id){ + return User.getRef() + .doc(id) + .collection('contacts'); + } + + // Busca informações do documento do usuário por email + static findByEmail(email){ + return User.getRef().doc(email); + } + + addContact(contact){ + return User.getContactsRef(this.email) + .doc(btoa(contact.email))// btoa() base64 + .set(contact.toJSON()); + } + + getContacts(){ + + return new Promise((s,f) => { + + User.getContactsRef(this.email).onSnapshot(docs=>{ + + let contacts = []; + + docs.forEach(doc => { + + let data = doc.data(); + + data.id = doc.id; + + contacts.push(data); + + }); + + this.trigger('contactschange', docs); + + s(contacts); + + }); + + }); + + } + +} \ No newline at end of file diff --git a/src/util/Firebase.js b/src/util/Firebase.js index e8728045b..db0ac0a09 100644 --- a/src/util/Firebase.js +++ b/src/util/Firebase.js @@ -7,7 +7,7 @@ import 'firebase/compat/firestore'; export class Firebase{ constructor(){ this._config = { - // configurações do firebase + // firebase config } this.init(); } @@ -15,11 +15,11 @@ export class Firebase{ init(){ // Initialize Firebase - if(!this._initialized){ + if(!window._initializedFirebase){ const firebaseApp = firebase.initializeApp(this._config); - this._initialized = true; + window._initializedFirebase = true; } } @@ -41,10 +41,12 @@ export class Firebase{ firebase.auth().signInWithPopup(provider).then(result => { + // token do usuário no firebase, é usada por segurança pois é temporário let token = result.credential.accessToken; + let user = result.user; - s(user,token); + s({user:user,token:token}); }).catch(err=>{ @@ -56,5 +58,4 @@ export class Firebase{ }) } -} - +} \ No newline at end of file From 4fcc3fca81d1f2f2ecff4953fe4e9417819cd645 Mon Sep 17 00:00:00 2001 From: Beatriz Cardoso Date: Fri, 10 Sep 2021 18:40:34 -0300 Subject: [PATCH 10/12] chats --- src/controller/WhatsAppController.js | 107 +++++++++++++++++++++------ src/model/Chat.js | 96 ++++++++++++++++++++++++ src/model/Message.js | 36 ++++++++- src/model/User.js | 7 ++ src/util/Format.js | 15 ++++ 5 files changed, 236 insertions(+), 25 deletions(-) create mode 100644 src/model/Chat.js diff --git a/src/controller/WhatsAppController.js b/src/controller/WhatsAppController.js index 9760f76e3..54767334b 100644 --- a/src/controller/WhatsAppController.js +++ b/src/controller/WhatsAppController.js @@ -4,6 +4,8 @@ import {DocumentPreviewController} from './DocumentPreviewController' import {MicrophoneController} from './MicrophoneController' import {Firebase} from './../util/Firebase' import {User} from './../model/User' +import {Chat} from './../model/Chat' +import {Message} from './../model/Message' export class WhatsAppController{ constructor(){ @@ -136,21 +138,7 @@ export class WhatsAppController{ div.on('click', e=>{ - this.el.activeName.innerHTML = contact.name; - this.el.activeStatus.innerHTML = contact.status; - - if(contact.photo){ - - let img = this.el.activePhoto; - img.src = contact.photo; - img.show(); - - } - - this.el.home.hide(); - this.el.main.css({ - display:'flex' - }) + this.setActiveChat(contact); }); @@ -164,6 +152,64 @@ export class WhatsAppController{ this._user.getContacts(); } + // Faz o chat do contato especificado aparecer + setActiveChat(contact){ + + if(this._contactActive){ + + Message.getRef(this._contactActive.chatId).onSnapshot(() => {}); + + } + + this._contactActive = contact; + + this.el.activeName.innerHTML = contact.name; + this.el.activeStatus.innerHTML = contact.status; + + if(contact.photo){ + + let img = this.el.activePhoto; + img.src = contact.photo; + img.show(); + + } + + this.el.home.hide(); + this.el.main.css({ + display:'flex' + }) + + // Ordenação das mensagens na tela + Message.getRef(this._contactActive.chatId).orderBy('timeStamp') + .onSnapshot(docs=>{ + + this.el.panelMessagesContainer.innerHTML = ''; + + docs.forEach(doc => { + + let data = doc.data(); + data.id = doc.id; + + if(!this.el.panelMessagesContainer.querySelector('#'+data.id)){ + + let message = new Message(); + + message.fromJSON(data); + + let me = (data.from === this._user.email); + + let view = message.getViewElement(me); + + this.el.panelMessagesContainer.appendChild(view); + + } + + }); + + }); + + } + // Percorre os elementos da tela e os coloca em this.el loadElements(){ @@ -335,7 +381,7 @@ export class WhatsAppController{ }); - // Submete os dados do formulário + // Submete os dados do formulário de Adicionar Contato this.el.formPanelAddContact.on('submit', e => { e.preventDefault(); @@ -348,11 +394,20 @@ export class WhatsAppController{ if(data.name){ - this._user.addContact(contact).then(() => { + Chat.createIfNotExists(this._user.email, contact.email).then(chat => { + + contact.chatId = chat.id; + this._user.chatId = chat.id; + + contact.addContact(this._user); + + this._user.addContact(contact).then(() => { + + this.el.btnClosePanelAddContact.click(); + console.info('Contato adicionado') + + }); - this.el.btnClosePanelAddContact.click(); - console.info('Contato adicionado') - }); } else { @@ -629,9 +684,17 @@ export class WhatsAppController{ // Enviar Mensagem this.el.btnSend.on('click', e => { - console.log(this.el.inputText.innerHTML); + Message.send( + this._contactActive.chatId, + this._user.email, + 'text', + this.el.inputText.innerHTML + ); + this.el.inputText.innerHTML = ''; - this.el.inputPlaceholder.show(); + this.el.panelEmojis.removeClass('open'); + + // this.el.inputPlaceholder.show(); }); diff --git a/src/model/Chat.js b/src/model/Chat.js new file mode 100644 index 000000000..76ee2ed8f --- /dev/null +++ b/src/model/Chat.js @@ -0,0 +1,96 @@ +import {Model} from "./Model" +import {Firebase} from "../util/Firebase" + +export class Chat extends Model{ + + constructor(){ + super(); + } + + get users(){ + return this._data.users; + } + set users(value){ + this._data.users = value + } + + get timeStamp(){ + return this._data.timeStamp; + } + set timeStamp(value){ + this._data.timeStamp = value + } + + static getRef(){ + return Firebase.db().collection('/chats'); + } + + static create(meEmail, contactEmail){ + + return new Promise((s,f) => { + + let users = {}; + + users[btoa(meEmail)] = true; + users[btoa(contactEmail)] = true; + + Chat.getRef().add({ + users, + timeStamp: new Date() + }).then(doc => { + + Chat.getRef().doc(doc.id).get().then(chat => { + + s(chat); + + }).catch(err => { f(err); }); + + }).catch(err => { f(err); }); + + }); + + } + + static find(meEmail, contactEmail){ + + // .where faz a busca (chave que procuramos,compara, segundo elemento a comparar com o primeiro) + return Chat.getRef() + .where(btoa(meEmail),'==', true) + .where(btoa(contactEmail),'==', true).get(); + + + } + + static createIfNotExists(meEmail, contactEmail){ + + return new Promise ((s,f) => { + + Chat.find(meEmail, contactEmail).then(chats => { + + if(chats.empty){ + + Chat.create(meEmail, contactEmail).then(chat => { + + s(chat); + + }); + + } else { + + chats.forEach(chat => { + + s(chat); + + }); + + } + + }).catch(err => { + f(err); + }); + + }); + + } + +} \ No newline at end of file diff --git a/src/model/Message.js b/src/model/Message.js index a52d66eef..6ec16d5ae 100644 --- a/src/model/Message.js +++ b/src/model/Message.js @@ -1,4 +1,6 @@ import {Model} from "./Model" +import {Firebase} from "../util/Firebase"; +import {Format} from "../util/Format"; export class Message extends Model { @@ -8,6 +10,13 @@ export class Message extends Model { } + get id(){ + return this._data.id; + } + set id(value){ + return this._data.id = value; + } + get content(){ return this._data.content; } @@ -297,16 +306,16 @@ export class Message extends Model { default: div.innerHTML = ` -
+
- Oi! + ${this.content}
- 11:33 + ${Format.timeStampToTime(this.timeStamp)}
@@ -323,4 +332,25 @@ export class Message extends Model { } + static send(chatId, from, type, content){ + + return Message.getRef(chatId).add({ + content, + timeStamp: new Date(), + status: 'wait', + type, + from + }); + + } + + static getRef(chatId){ + + return Firebase.db() + .collection('chats') + .doc(chatId) + .collection('messages'); + + } + } \ No newline at end of file diff --git a/src/model/User.js b/src/model/User.js index 50cd68144..11886f54f 100644 --- a/src/model/User.js +++ b/src/model/User.js @@ -32,6 +32,13 @@ export class User extends Model{ this._data.photo = value; } + get chatId(){ + return this._data.chatId; + } + set chatId(value){ + this._data.chatId = value; + } + // Retorna a promessa com os documentos do usuário getById(id){ diff --git a/src/util/Format.js b/src/util/Format.js index c6a1035e4..3c45c6703 100644 --- a/src/util/Format.js +++ b/src/util/Format.js @@ -23,4 +23,19 @@ export class Format{ } + static dateToTime(date, locale = 'pt-BR'){ + + return date.toLocaleTimeString(locale, { + hours: '2-digit', + minutes: '2-digit' + }); + + } + + static timeStampToTime(timeStamp){ + + return (timeStamp && typeof timeStamp.toDate === 'function') ? Format.dateToTime(timeStamp.toDate()) : ''; + + } + } \ No newline at end of file From b2a4a78bb1de7b0d780e135d608d3c6b1ac6f94c Mon Sep 17 00:00:00 2001 From: Beatriz Cardoso Date: Sat, 11 Sep 2021 22:32:02 -0300 Subject: [PATCH 11/12] scroll e filter --- index.html | 4 +-- src/controller/WhatsAppController.js | 39 ++++++++++++++++++++++++++-- src/model/Message.js | 2 +- src/model/User.js | 7 +++-- src/util/Firebase.js | 2 +- 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index 88657fd55..53c70a046 100644 --- a/index.html +++ b/index.html @@ -359,9 +359,9 @@
-
Procurar ou começar uma nova conversa
+
Procurar ou começar uma nova conversa
diff --git a/src/controller/WhatsAppController.js b/src/controller/WhatsAppController.js index 54767334b..697ea5248 100644 --- a/src/controller/WhatsAppController.js +++ b/src/controller/WhatsAppController.js @@ -179,18 +179,27 @@ export class WhatsAppController{ display:'flex' }) + this.el.panelMessagesContainer.innerHTML = ''; + // Ordenação das mensagens na tela Message.getRef(this._contactActive.chatId).orderBy('timeStamp') .onSnapshot(docs=>{ - this.el.panelMessagesContainer.innerHTML = ''; + let scrollTop = this.el.panelMessagesContainer.scrollTop; // topo do scroll + + let scrollTopMax = ( // máximo que se consegue empurrar o scroll + this.el.panelMessagesContainer.scrollHeight // altura total de toda a conversa + - this.el.panelMessagesContainer.offsetHeight // tamanho fixo da conversa na tela + ); + + let autoScroll = (scrollTop >= scrollTopMax); // Se o topo do scroll for igual ao topo máximo, então a pessoa está no fim da conversa docs.forEach(doc => { let data = doc.data(); data.id = doc.id; - if(!this.el.panelMessagesContainer.querySelector('#'+data.id)){ + if(!this.el.panelMessagesContainer.querySelector('#_'+data.id)){ let message = new Message(); @@ -206,6 +215,18 @@ export class WhatsAppController{ }); + if(autoScroll) { + + this.el.panelMessagesContainer.scrollTop = + (this.el.panelMessagesContainer.scrollHeight + - this.el.panelMessagesContainer.offsetHeight); + + } else { + + this.el.panelMessagesContainer.scrollTop = scrollTop; + + } + }); } @@ -309,6 +330,20 @@ export class WhatsAppController{ // Inicia os eventos que os componentes da tela realizam initEvents(){ + // Busca de contatos + this.el.inputSearchContacts.on('keyup', e=>{ + + if(this.el.inputSearchContacts.value.length > 0){ + this.el.inputSearchContactsPlaceholder.hide(); + } else { + this.el.inputSearchContactsPlaceholder.show(); + } + + // Faz a busca de acordo com o que o usuário escreveu + this._user.getContacts(this.el.inputSearchContacts.value); + + }); + // Abre o painel para Editar Perfil this.el.myPhoto.on('click', e => { diff --git a/src/model/Message.js b/src/model/Message.js index 6ec16d5ae..ecc12e112 100644 --- a/src/model/Message.js +++ b/src/model/Message.js @@ -306,7 +306,7 @@ export class Message extends Model { default: div.innerHTML = ` -
+
diff --git a/src/model/User.js b/src/model/User.js index 11886f54f..7a19ba633 100644 --- a/src/model/User.js +++ b/src/model/User.js @@ -83,11 +83,14 @@ export class User extends Model{ .set(contact.toJSON()); } - getContacts(){ + getContacts(filter = ''){ + + console.log(filter); return new Promise((s,f) => { - User.getContactsRef(this.email).onSnapshot(docs=>{ + // Esse comando >= tem problema + User.getContactsRef(this.email).where('name', '>=', filter).onSnapshot(docs=>{ let contacts = []; diff --git a/src/util/Firebase.js b/src/util/Firebase.js index db0ac0a09..11b2e1ef7 100644 --- a/src/util/Firebase.js +++ b/src/util/Firebase.js @@ -7,7 +7,7 @@ import 'firebase/compat/firestore'; export class Firebase{ constructor(){ this._config = { - // firebase config + //config } this.init(); } From 373480a0f8dfd4e040356340d4c757be588ee690 Mon Sep 17 00:00:00 2001 From: Beatriz Cardoso Date: Wed, 15 Sep 2021 17:18:12 -0300 Subject: [PATCH 12/12] envio de imagens com problemas --- .gitignore | 3 +- package.json | 27 ++++ src/controller/WhatsAppController.js | 31 +++-- src/model/Message.js | 176 ++++++++++++++++++++------- src/util/Firebase.js | 10 +- src/util/Format.js | 4 +- 6 files changed, 195 insertions(+), 56 deletions(-) create mode 100644 package.json diff --git a/.gitignore b/.gitignore index aa0cfb197..ec44aacbe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -package.json package-lock.json -node_modules/ \ No newline at end of file +node_modules/ diff --git a/package.json b/package.json new file mode 100644 index 000000000..b9a214e3b --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "curso-javascript-projeto-whatsapp-clone", + "version": "1.0.0", + "description": "[![Hcode Treinamentos](https://www.hcode.com.br/res/img/hcode-200x100.png)](https://www.hcode.com.br)", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "webpack --config webpack.config.js", + "start": "webpack-dev-server" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/btrcardoso/curso-javascript-projeto-whatsapp-clone.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/btrcardoso/curso-javascript-projeto-whatsapp-clone/issues" + }, + "homepage": "https://github.com/btrcardoso/curso-javascript-projeto-whatsapp-clone#readme", + "dependencies": { + "firebase": "^9.0.0-beta.7", + "pdfjs-dist": "^2.0.489", + "webpack": "^3.1.0", + "webpack-dev-server": "^2.5.1" + } +} diff --git a/src/controller/WhatsAppController.js b/src/controller/WhatsAppController.js index 697ea5248..e245818d8 100644 --- a/src/controller/WhatsAppController.js +++ b/src/controller/WhatsAppController.js @@ -9,8 +9,6 @@ import {Message} from './../model/Message' export class WhatsAppController{ constructor(){ - console.log("olá WhatsApp"); - this._firebase = new Firebase(); this.initAuth(); this.loadElements(); @@ -22,7 +20,6 @@ export class WhatsAppController{ initAuth(){ this._firebase.initAuth().then((response)=>{ - console.log(response); this._user = new User(response.user.email); this._user.on('datachange', data => { @@ -199,18 +196,35 @@ export class WhatsAppController{ let data = doc.data(); data.id = doc.id; + let message = new Message(); + + message.fromJSON(data); + + let me = (data.from === this._user.email); + if(!this.el.panelMessagesContainer.querySelector('#_'+data.id)){ - let message = new Message(); + if(!me){ - message.fromJSON(data); + doc.ref.set({ + status: 'read' + }, { + merge: true + }); - let me = (data.from === this._user.email); + } let view = message.getViewElement(me); this.el.panelMessagesContainer.appendChild(view); + } else if(me){ + + let msgEl = this.el.panelMessagesContainer.querySelector('#_'+data.id); + + // outerHTML pega também o conteúdo que envolve o html especificado + msgEl.querySelector('.message-status').innerHTML = message.getStatusViewElement().outerHTML; + } }); @@ -489,11 +503,10 @@ export class WhatsAppController{ // Seleciona as fotos a serem importadas this.el.inputPhoto.on('change', e => { - console.log(this.el.inputPhoto.files); - [...this.el.inputPhoto.files].forEach(file => { - console.log(file); + Message.sendImage(this._contactActive.chatId, this._user.email, file); + }) diff --git a/src/model/Message.js b/src/model/Message.js index ecc12e112..996f9b98a 100644 --- a/src/model/Message.js +++ b/src/model/Message.js @@ -82,14 +82,7 @@ export class Message extends Model {
- 17:01 -
- - - - - -
+ ${Format.timeStampToTime(this.timeStamp)}
@@ -126,7 +119,7 @@ export class Message extends Model {
- +
@@ -136,14 +129,8 @@ export class Message extends Model {
- 17:22 -
- - - - - -
+ ${Format.timeStampToTime(this.timeStamp)} +
@@ -159,6 +146,10 @@ export class Message extends Model { `; + + div.querySelector('.message-photo').on('load', e=>{ + console.log('load ok'); + }); break; @@ -198,14 +189,7 @@ export class Message extends Model {
- 18:56 -
- - - - - -
+ ${Format.timeStampToTime(this.timeStamp)}
@@ -279,14 +263,7 @@ export class Message extends Model {
- 17:48 -
- - - - - -
+ ${Format.timeStampToTime(this.timeStamp)}
@@ -315,7 +292,7 @@ export class Message extends Model {
- ${Format.timeStampToTime(this.timeStamp)} + ${Format.timeStampToTime(this.timeStamp)}
@@ -324,7 +301,16 @@ export class Message extends Model { } - let className = (me) ? 'message-out' : 'message-in'; + let className = 'message-in'; + + if (me) { + + className = 'message-out'; + + // coloca o status como irmão do message-time + div.querySelector('.message-time').parentElement.appendChild(this.getStatusViewElement()); + + } div.firstElementChild.classList.add(className); @@ -332,18 +318,70 @@ export class Message extends Model { } + static sendImage(chatId, from, file){ + + return new Promise((s,f) => { + + // put é pra colocar o blob no banco + let uploadTask = Firebase.hd().ref(from).child(Date.now() + '_', + file.name).put(file); + + uploadTask.on('state_changed', e => { + + console.info('upload', e); + + }, err => { + + console.error(err); + + }, () => { + + console.log('uploadTask.snapshot.downloadURL: ',uploadTask.snapshot.downloadURL); + + Message.send( + chatId, + from, + 'image', + uploadTask.snapshot.downloadURL) + .then(()=>{ + s(); + }); + + }); + + }); + + } + + // Envia mensagem static send(chatId, from, type, content){ - return Message.getRef(chatId).add({ - content, - timeStamp: new Date(), - status: 'wait', - type, - from + return new Promise((s,f) => { + + // Adiciona a nova mensagem ao banco de dados + Message.getRef(chatId).add({ + content, + timeStamp: new Date(), + status: 'wait', + type, + from + }).then(result => { + + // Vai no documento pai e procura o result.id, ou seja o id da mensagem que acabou de ser inserida, e muda o status para enviada + result.parent.doc(result.id).set({ + status: 'sent' + }, { + merge: true // as demais informações continuam + }).then(()=>{ + s(); + }); + + }); + }); } + // Pega a referência do banco de dados static getRef(chatId){ return Firebase.db() @@ -353,4 +391,60 @@ export class Message extends Model { } + getStatusViewElement(){ + + let div = document.createElement('div'); + + div.className = 'message-status'; + + switch(this.status){ + + case 'wait': + div.innerHTML = ` + + + + + + `; + break; + + case 'sent': + div.innerHTML = ` + + + + + + `; + break; + + case 'received': + div.innerHTML = ` + + + + + + ` + break; + + case 'read': + div.innerHTML = ` + + + + + + `; + break; + + //default: + + } + + return div; + + } + } \ No newline at end of file diff --git a/src/util/Firebase.js b/src/util/Firebase.js index 11b2e1ef7..dc77aeb93 100644 --- a/src/util/Firebase.js +++ b/src/util/Firebase.js @@ -2,12 +2,18 @@ import firebase from 'firebase/compat/app'; import 'firebase/compat/auth'; import 'firebase/compat/firestore'; - +import 'firebase/compat/storage'; export class Firebase{ constructor(){ this._config = { - //config + apiKey: "AIzaSyCgk1c7coRQI00XkOB1-8srhrzcmDbwPRU", + authDomain: "whatsapp-clone-7683c.firebaseapp.com", + projectId: "whatsapp-clone-7683c", + storageBucket: "whatsapp-clone-7683c.appspot.com", + messagingSenderId: "568059323995", + appId: "1:568059323995:web:7870f38cc6105c66ea817e", + measurementId: "G-SM1RBM5TD1" } this.init(); } diff --git a/src/util/Format.js b/src/util/Format.js index 3c45c6703..b533df201 100644 --- a/src/util/Format.js +++ b/src/util/Format.js @@ -26,8 +26,8 @@ export class Format{ static dateToTime(date, locale = 'pt-BR'){ return date.toLocaleTimeString(locale, { - hours: '2-digit', - minutes: '2-digit' + hour: '2-digit', + minute: '2-digit' }); }