├── .firebaserc ├── README.md ├── images └── profile_placeholder.png ├── manifest.json ├── firebase.json ├── manifest.webapp ├── firebase-debug.log ├── css └── main.css ├── index.html └── js └── main.js /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "DevVersion": "hobechat" 4 | } 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hobe 2 | This is a Simple Real Time Web Chat Application using Google's Firebase Platform 3 | -------------------------------------------------------------------------------- /images/profile_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andersy005/Hobe/master/images/profile_placeholder.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hobe Chat", 3 | "short_name": "Hobe Chat", 4 | "start_url": "/index.html", 5 | "display": "standalone", 6 | "orientation": "portrait" 7 | } 8 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "./", 4 | "ignore": [ 5 | "firebase.json", 6 | "database-rules.json", 7 | "storage.rules" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /manifest.webapp: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "name": "Hobe Chat", 4 | "launch_path": "/index.html", 5 | "description": "Chat with friends using Firebase 2.0", 6 | "developer": { 7 | "name": "Anderson B.", 8 | "url": "https://andersy005.github.io" 9 | }, 10 | "installs_allowed_from": [ 11 | "*" 12 | ], 13 | "default_locale": "en", 14 | "permissions": { 15 | }, 16 | "locales": { 17 | "en": { 18 | "name": "Hobe Chat", 19 | "description": "Chat with friends using Firebase 2.0" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /firebase-debug.log: -------------------------------------------------------------------------------- 1 | [debug] ---------------------------------------------------------------------- 2 | [debug] Command: C:\Program Files\nodejs\node.exe C:\Users\qubit\c\Program\node_modules\firebase-tools\bin\firebase serve 3 | [debug] CLI Version: 3.0.8 4 | [debug] Platform: win32 5 | [debug] Node Version: v5.10.1 6 | [debug] Time: Fri Oct 21 2016 23:53:06 GMT-0500 (Central Daylight Time) 7 | [debug] ---------------------------------------------------------------------- 8 | [debug] 9 | [info] Starting Firebase development server... 10 | [info] 11 | [info] Project Directory: D:\GitHub-Projects\FireBase\Hobe 12 | [info] Public Directory: ./ 13 | [info] 14 | [info] Server listening at: http://localhost:5000 15 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | 2 | html, body { 3 | font-family: 'Roboto', 'Helvetica', sans-serif; 4 | } 5 | main, #messages-card { 6 | height: 100%; 7 | padding-bottom: 0; 8 | } 9 | #messages-card-container { 10 | height: calc(100% - 150px); 11 | padding-bottom: 0; 12 | } 13 | #messages-card { 14 | margin-top: 15px; 15 | } 16 | .mdl-layout__header-row span { 17 | margin-left: 15px; 18 | margin-top: 17px; 19 | } 20 | .mdl-grid { 21 | max-width: 1024px; 22 | margin: auto; 23 | } 24 | .material-icons { 25 | font-size: 36px; 26 | top: 8px; 27 | position: relative; 28 | } 29 | .mdl-layout__header-row { 30 | padding: 0; 31 | margin: 0 auto; 32 | } 33 | .mdl-card__supporting-text { 34 | width: auto; 35 | height: 100%; 36 | padding-top: 0; 37 | padding-bottom: 0; 38 | } 39 | #messages { 40 | overflow-y: auto; 41 | margin-bottom: 10px; 42 | height: calc(100% - 80px); 43 | } 44 | #message-filler { 45 | flex-grow: 1; 46 | } 47 | .message-container:first-of-type { 48 | border-top-width: 0; 49 | } 50 | .message-container { 51 | display: block; 52 | margin-top: 10px; 53 | border-top: 1px solid #f3f3f3; 54 | padding-top: 10px; 55 | opacity: 0; 56 | transition: opacity 1s ease-in-out; 57 | } 58 | .message-container.visible { 59 | opacity: 1; 60 | } 61 | .message-container .pic { 62 | background-image: url('/images/profile_placeholder.png'); 63 | background-repeat: no-repeat; 64 | width: 30px; 65 | height: 30px; 66 | background-size: 30px; 67 | border-radius: 20px; 68 | } 69 | .message-container .spacing { 70 | display: table-cell; 71 | vertical-align: top; 72 | } 73 | .message-container .message { 74 | display: table-cell; 75 | width: calc(100% - 40px); 76 | padding: 5px 0 5px 10px; 77 | } 78 | .message-container .name { 79 | display: inline-block; 80 | width: 100%; 81 | padding-left: 40px; 82 | color: #bbb; 83 | font-style: italic; 84 | font-size: 12px; 85 | box-sizing: border-box; 86 | } 87 | #message-form { 88 | display: flex; 89 | flex-direction: row; 90 | width: calc(100% - 48px); 91 | float: left; 92 | } 93 | #image-form { 94 | display: flex; 95 | flex-direction: row; 96 | width: 48px; 97 | float: right; 98 | } 99 | #message-form .mdl-textfield { 100 | width: calc(100% - 100px); 101 | } 102 | #message-form button, #image-form button { 103 | width: 100px; 104 | margin: 15px 0 0 10px; 105 | } 106 | .mdl-card { 107 | min-height: 0; 108 | } 109 | .mdl-card { 110 | background: linear-gradient(white, #f9f9f9); 111 | justify-content: space-between; 112 | } 113 | #user-container { 114 | position: absolute; 115 | display: flex; 116 | flex-direction: row; 117 | top: 22px; 118 | width: 100%; 119 | right: 0; 120 | padding-left: 10px; 121 | justify-content: flex-end; 122 | padding-right: 10px; 123 | } 124 | #user-container #user-pic { 125 | top: -3px; 126 | position: relative; 127 | display: inline-block; 128 | background-image: url('/images/profile_placeholder.png'); 129 | background-repeat: no-repeat; 130 | width: 40px; 131 | height: 40px; 132 | background-size: 40px; 133 | border-radius: 20px; 134 | } 135 | #user-container #user-name { 136 | font-size: 16px; 137 | line-height: 36px; 138 | padding-right: 10px; 139 | padding-left: 20px; 140 | } 141 | #image-form #submitImage { 142 | width: auto; 143 | padding: 0 6px 0 1px; 144 | min-width: 0; 145 | } 146 | #image-form #submitImage .material-icons { 147 | top: -1px; 148 | } 149 | .message img { 150 | max-width: 300px; 151 | max-height: 200px; 152 | } 153 | #mediaCapture { 154 | display: none; 155 | } 156 | @media screen and (max-width: 610px) { 157 | header { 158 | height: 113px; 159 | padding-bottom: 80px !important; 160 | } 161 | #user-container { 162 | top: 72px; 163 | background-color: rgb(3,155,229); 164 | height: 38px; 165 | padding-top: 3px; 166 | padding-right: 2px; 167 | } 168 | #user-container #user-pic { 169 | top: 2px; 170 | width: 33px; 171 | height: 33px; 172 | background-size: 33px; 173 | } 174 | } 175 | .mdl-textfield__label:after { 176 | background-color: #0288D1; 177 | } 178 | .mdl-textfield--floating-label.is-focused .mdl-textfield__label { 179 | color: #0288D1; 180 | } 181 | .mdl-button .material-icons { 182 | top: -1px; 183 | margin-right: 5px; 184 | } 185 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Hobe Chat 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | 44 | 45 |
46 |
47 |
48 |

chat_bubble_outline Hobe Chat

49 |
50 |
51 | 52 | 53 | 56 | 59 |
60 |
61 |
62 | 63 |
64 |
65 | 66 | 67 |
68 |
69 |
70 | 71 |
72 |
73 |
74 | 75 | 76 |
77 | 80 |
81 |
82 | 83 | 86 |
87 |
88 |
89 | 90 |
91 |
92 | 93 |
94 | 95 |
96 |
97 |
98 | 99 | 100 | 103 | 104 | 105 | 106 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | // Initializes HobeChat. 5 | function HobeChat() { 6 | this.checkSetup(); 7 | 8 | // Shortcuts to DOM Elements. 9 | this.messageList = document.getElementById('messages'); 10 | this.messageForm = document.getElementById('message-form'); 11 | this.messageInput = document.getElementById('message'); 12 | this.submitButton = document.getElementById('submit'); 13 | this.submitImageButton = document.getElementById('submitImage'); 14 | this.imageForm = document.getElementById('image-form'); 15 | this.mediaCapture = document.getElementById('mediaCapture'); 16 | this.userPic = document.getElementById('user-pic'); 17 | this.userName = document.getElementById('user-name'); 18 | this.signInButton = document.getElementById('sign-in'); 19 | this.signOutButton = document.getElementById('sign-out'); 20 | this.signInSnackbar = document.getElementById('must-signin-snackbar'); 21 | 22 | // Saves message on form submit. 23 | this.messageForm.addEventListener('submit', this.saveMessage.bind(this)); 24 | this.signOutButton.addEventListener('click', this.signOut.bind(this)); 25 | this.signInButton.addEventListener('click', this.signIn.bind(this)); 26 | 27 | // Toggle for the button. 28 | var buttonTogglingHandler = this.toggleButton.bind(this); 29 | this.messageInput.addEventListener('keyup', buttonTogglingHandler); 30 | this.messageInput.addEventListener('change', buttonTogglingHandler); 31 | 32 | // Events for image upload. 33 | this.submitImageButton.addEventListener('click', function() { 34 | this.mediaCapture.click(); 35 | }.bind(this)); 36 | this.mediaCapture.addEventListener('change', this.saveImageMessage.bind(this)); 37 | 38 | this.initFirebase(); 39 | } 40 | 41 | // Sets up shortcuts to Firebase features and initiate firebase auth. 42 | HobeChat.prototype.initFirebase = function() { 43 | // TODO(DEVELOPER): Initialize Firebase. 44 | }; 45 | 46 | // Loads chat messages history and listens for upcoming ones. 47 | HobeChat.prototype.loadMessages = function() { 48 | // TODO(DEVELOPER): Load and listens for new messages. 49 | }; 50 | 51 | // Saves a new message on the Firebase DB. 52 | HobeChat.prototype.saveMessage = function(e) { 53 | e.preventDefault(); 54 | // Check that the user entered a message and is signed in. 55 | if (this.messageInput.value && this.checkSignedInWithMessage()) { 56 | 57 | // TODO(DEVELOPER): push new message to Firebase. 58 | 59 | } 60 | }; 61 | 62 | // Sets the URL of the given img element with the URL of the image stored in Firebase Storage. 63 | HobeChat.prototype.setImageUrl = function(imageUri, imgElement) { 64 | imgElement.src = imageUri; 65 | 66 | // TODO(DEVELOPER): If image is on Firebase Storage, fetch image URL and set img element's src. 67 | }; 68 | 69 | // Saves a new message containing an image URI in Firebase. 70 | // This first saves the image in Firebase storage. 71 | HobeChat.prototype.saveImageMessage = function(event) { 72 | var file = event.target.files[0]; 73 | 74 | // Clear the selection in the file picker input. 75 | this.imageForm.reset(); 76 | 77 | // Check if the file is an image. 78 | if (!file.type.match('image.*')) { 79 | var data = { 80 | message: 'You can only share images', 81 | timeout: 2000 82 | }; 83 | this.signInSnackbar.MaterialSnackbar.showSnackbar(data); 84 | return; 85 | } 86 | // Check if the user is signed-in 87 | if (this.checkSignedInWithMessage()) { 88 | 89 | // TODO(DEVELOPER): Upload image to Firebase storage and add message. 90 | 91 | } 92 | }; 93 | 94 | // Signs-in Hobe Chat. 95 | HobeChat.prototype.signIn = function() { 96 | // TODO(DEVELOPER): Sign in Firebase with credential from the Google user. 97 | }; 98 | 99 | // Signs-out of Hobe Chat. 100 | HobeChat.prototype.signOut = function() { 101 | // TODO(DEVELOPER): Sign out of Firebase. 102 | }; 103 | 104 | // Triggers when the auth state change for instance when the user signs-in or signs-out. 105 | HobeChat.prototype.onAuthStateChanged = function(user) { 106 | if (user) { // User is signed in! 107 | // Get profile pic and user's name from the Firebase user object. 108 | var profilePicUrl = null; // TODO(DEVELOPER): Get profile pic. 109 | var userName = null; // TODO(DEVELOPER): Get user's name. 110 | 111 | // Set the user's profile pic and name. 112 | this.userPic.style.backgroundImage = 'url(' + profilePicUrl + ')'; 113 | this.userName.textContent = userName; 114 | 115 | // Show user's profile and sign-out button. 116 | this.userName.removeAttribute('hidden'); 117 | this.userPic.removeAttribute('hidden'); 118 | this.signOutButton.removeAttribute('hidden'); 119 | 120 | // Hide sign-in button. 121 | this.signInButton.setAttribute('hidden', 'true'); 122 | 123 | // We load currently existing chant messages. 124 | this.loadMessages(); 125 | } else { // User is signed out! 126 | // Hide user's profile and sign-out button. 127 | this.userName.setAttribute('hidden', 'true'); 128 | this.userPic.setAttribute('hidden', 'true'); 129 | this.signOutButton.setAttribute('hidden', 'true'); 130 | 131 | // Show sign-in button. 132 | this.signInButton.removeAttribute('hidden'); 133 | } 134 | }; 135 | 136 | // Returns true if user is signed-in. Otherwise false and displays a message. 137 | HobeChat.prototype.checkSignedInWithMessage = function() { 138 | /* TODO(DEVELOPER): Check if user is signed-in Firebase. */ 139 | 140 | // Display a message to the user using a Toast. 141 | var data = { 142 | message: 'You must sign-in first', 143 | timeout: 2000 144 | }; 145 | this.signInSnackbar.MaterialSnackbar.showSnackbar(data); 146 | return false; 147 | }; 148 | 149 | // Resets the given MaterialTextField. 150 | HobeChat.resetMaterialTextfield = function(element) { 151 | element.value = ''; 152 | element.parentNode.MaterialTextfield.boundUpdateClassesHandler(); 153 | }; 154 | 155 | // Template for messages. 156 | HobeChat.MESSAGE_TEMPLATE = 157 | '
' + 158 | '
' + 159 | '
' + 160 | '
' + 161 | '
'; 162 | 163 | // A loading image URL. 164 | HobeChat.LOADING_IMAGE_URL = 'https://www.google.com/images/spin-32.gif'; 165 | 166 | // Displays a Message in the UI. 167 | HobeChat.prototype.displayMessage = function(key, name, text, picUrl, imageUri) { 168 | var div = document.getElementById(key); 169 | // If an element for that message does not exists yet we create it. 170 | if (!div) { 171 | var container = document.createElement('div'); 172 | container.innerHTML = HobeChat.MESSAGE_TEMPLATE; 173 | div = container.firstChild; 174 | div.setAttribute('id', key); 175 | this.messageList.appendChild(div); 176 | } 177 | if (picUrl) { 178 | div.querySelector('.pic').style.backgroundImage = 'url(' + picUrl + ')'; 179 | } 180 | div.querySelector('.name').textContent = name; 181 | var messageElement = div.querySelector('.message'); 182 | if (text) { // If the message is text. 183 | messageElement.textContent = text; 184 | // Replace all line breaks by
. 185 | messageElement.innerHTML = messageElement.innerHTML.replace(/\n/g, '
'); 186 | } else if (imageUri) { // If the message is an image. 187 | var image = document.createElement('img'); 188 | image.addEventListener('load', function() { 189 | this.messageList.scrollTop = this.messageList.scrollHeight; 190 | }.bind(this)); 191 | this.setImageUrl(imageUri, image); 192 | messageElement.innerHTML = ''; 193 | messageElement.appendChild(image); 194 | } 195 | // Show the card fading-in. 196 | setTimeout(function() {div.classList.add('visible')}, 1); 197 | this.messageList.scrollTop = this.messageList.scrollHeight; 198 | this.messageInput.focus(); 199 | }; 200 | 201 | // Enables or disables the submit button depending on the values of the input 202 | // fields. 203 | HobeChat.prototype.toggleButton = function() { 204 | if (this.messageInput.value) { 205 | this.submitButton.removeAttribute('disabled'); 206 | } else { 207 | this.submitButton.setAttribute('disabled', 'true'); 208 | } 209 | }; 210 | 211 | // Checks that the Firebase SDK has been correctly setup and configured. 212 | HobeChat.prototype.checkSetup = function() { 213 | if (!window.firebase || !(firebase.app instanceof Function) || !window.config) { 214 | window.alert('You have not configured and imported the Firebase SDK. ' + 215 | 'Make sure you go through the codelab setup instructions.'); 216 | } else if (config.storageBucket === '') { 217 | window.alert('Your Firebase Storage bucket has not been enabled. Sorry about that. This is ' + 218 | 'actually a Firebase bug that occurs rarely. ' + 219 | 'Please go and re-generate the Firebase initialisation snippet (step 4 of the codelab) ' + 220 | 'and make sure the storageBucket attribute is not empty. ' + 221 | 'You may also need to visit the Storage tab and paste the name of your bucket which is ' + 222 | 'displayed there.'); 223 | } 224 | }; 225 | 226 | window.onload = function() { 227 | window.HobeChat = new HobeChat(); 228 | }; 229 | --------------------------------------------------------------------------------