1/** 2 * Copyright 2015 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16'use strict'; 17 18// Initializes FriendlyChat. 19function FriendlyChat() { 20 this.checkSetup(); 21 22 // Shortcuts to DOM Elements. 23 this.messageList = document.getElementById('messages'); 24 this.messageForm = document.getElementById('message-form'); 25 this.messageInput = document.getElementById('message'); 26 this.submitButton = document.getElementById('submit'); 27 this.submitImageButton = document.getElementById('submitImage'); 28 this.imageForm = document.getElementById('image-form'); 29 this.mediaCapture = document.getElementById('mediaCapture'); 30 this.userPic = document.getElementById('user-pic'); 31 this.userName = document.getElementById('user-name'); 32 this.signInButton = document.getElementById('sign-in'); 33 this.signOutButton = document.getElementById('sign-out'); 34 this.signInSnackbar = document.getElementById('must-signin-snackbar'); 35 36 // Saves message on form submit. 37 this.messageForm.addEventListener('submit', this.saveMessage.bind(this)); 38 this.signOutButton.addEventListener('click', this.signOut.bind(this)); 39 this.signInButton.addEventListener('click', this.signIn.bind(this)); 40 41 // Toggle for the button. 42 var buttonTogglingHandler = this.toggleButton.bind(this); 43 this.messageInput.addEventListener('keyup', buttonTogglingHandler); 44 this.messageInput.addEventListener('change', buttonTogglingHandler); 45 46 // Events for image upload. 47 this.submitImageButton.addEventListener('click', function() { 48 this.mediaCapture.click(); 49 }.bind(this)); 50 this.mediaCapture.addEventListener('change', this.saveImageMessage.bind(this)); 51 52 this.initFirebase(); 53} 54 55// Sets up shortcuts to Firebase features and initiate firebase auth. 56FriendlyChat.prototype.initFirebase = function() { 57 // Shortcuts to Firebase SDK features. 58 this.auth = firebase.auth(); 59 this.database = firebase.database(); 60 this.storage = firebase.storage(); 61 // Initiates Firebase auth and listen to auth state changes. 62 this.auth.onAuthStateChanged(this.onAuthStateChanged.bind(this)); 63}; 64 65// Loads chat messages history and listens for upcoming ones. 66FriendlyChat.prototype.loadMessages = function() { 67 // Reference to the /messages/ database path. 68 this.messagesRef = this.database.ref('messages'); 69 // Make sure we remove all previous listeners. 70 this.messagesRef.off(); 71 72 // Loads the last 12 messages and listen for new ones. 73 var setMessage = function(data) { 74 var val = data.val(); 75 this.displayMessage(data.key, val.name, val.text, val.photoUrl, val.imageUrl); 76 }.bind(this); 77 this.messagesRef.limitToLast(12).on('child_added', setMessage); 78 this.messagesRef.limitToLast(12).on('child_changed', setMessage); 79}; 80 81// Saves a new message on the Firebase DB. 82FriendlyChat.prototype.saveMessage = function(e) { 83 e.preventDefault(); 84 // Check that the user entered a message and is signed in. 85 if (this.messageInput.value && this.checkSignedInWithMessage()) { 86 var currentUser = this.auth.currentUser; 87 // Add a new message entry to the Firebase Database. 88 this.messagesRef.push({ 89 name: currentUser.displayName, 90 text: this.messageInput.value, 91 photoUrl: currentUser.photoURL || '/images/profile_placeholder.png' 92 }).then(function() { 93 // Clear message text field and SEND button state. 94 FriendlyChat.resetMaterialTextfield(this.messageInput); 95 this.toggleButton(); 96 }.bind(this)).catch(function(error) { 97 console.error('Error writing new message to Firebase Database', error); 98 });
99 } 100}; 101 102// Sets the URL of the given img element with the URL of the image stored in Firebase Storage. 103FriendlyChat.prototype.setImageUrl = function(imageUri, imgElement) { 104 imgElement.src = imageUri; 105 106 // If the image is a Firebase Storage URI we fetch the URL. 107 if (imageUri.startsWith('gs://')) { 108 imgElement.src = FriendlyChat.LOADING_IMAGE_URL; // Display a loading image first. 109 this.storage.refFromURL(imageUri).getMetadata().then(function(metadata) { 110 imgElement.src = metadata.downloadURLs[0]; 111 }); 112 } else { 113 imgElement.src = imageUri; 114 } 115}; 116 117// Saves a new message containing an image URI in Firebase. 118// This first saves the image in Firebase storage. 119FriendlyChat.prototype.saveImageMessage = function(event) { 120 var file = event.target.files[0]; 121 122 // Clear the selection in the file picker input. 123 this.imageForm.reset(); 124 125 // Check if the file is an image. 126 if (!file.type.match('image.*')) { 127 var data = { 128 message: 'You can only share images', 129 timeout: 2000 130 }; 131 this.signInSnackbar.MaterialSnackbar.showSnackbar(data); 132 return; 133 } 134 // Check if the user is signed-in 135 // Check if the user is signed-in 136 if (this.checkSignedInWithMessage()) { 137 138 // We add a message with a loading icon that will get updated with the shared image. 139 var currentUser = this.auth.currentUser; 140 this.messagesRef.push({ 141 name: currentUser.displayName, 142 imageUrl: FriendlyChat.LOADING_IMAGE_URL, 143 photoUrl: currentUser.photoURL || '/images/profile_placeholder.png' 144 }).then(function(data) { 145 146 // Upload the image to Firebase Storage. 147 var uploadTask = this.storage.ref(currentUser.uid + '/' + Date.now() + '/' + file.name) 148 .put(file, {'contentType': file.type}); 149 // Listen for upload completion. 150 uploadTask.on('state_changed', null, function(error) { 151 console.error('There was an error uploading a file to Firebase Storage:', error); 152 }, function() { 153 154 // Get the file's Storage URI and update the chat message placeholder. 155 var filePath = uploadTask.snapshot.metadata.fullPath; 156 data.update({imageUrl: this.storage.ref(filePath).toString()}); 157 }.bind(this)); 158 }.bind(this)); 159 } 160}; 161 162// Signs-in Friendly Chat. 163FriendlyChat.prototype.signIn = function(googleUser) { 164 // TODO(DEVELOPER): Sign in Firebase with credential from the Google user. 165 // Sign in Firebase using popup auth and Google as the identity provider. 166 var provider = new firebase.auth.GoogleAuthProvider(); 167 this.auth.signInWithPopup(provider); 168}; 169 170// Signs-out of Friendly Chat. 171FriendlyChat.prototype.signOut = function() { 172 // TODO(DEVELOPER): Sign out of Firebase. 173 // Sign out of Firebase. 174 this.auth.signOut(); 175}; 176 177// Triggers when the auth state change for instance when the user signs-in or signs-out. 178FriendlyChat.prototype.onAuthStateChanged = function(user) { 179 if (user) { // User is signed in! 180 // Get profile pic and user's name from the Firebase user object. 181 var profilePicUrl = user.photoURL; 182 var userName = user.displayName; 183 184 // Set the user's profile pic and name. 185 this.userPic.style.backgroundImage = 'url(' + profilePicUrl + ')'; 186 this.userName.textContent = userName; 187 188 // Show user's profile and sign-out button. 189 this.userName.removeAttribute('hidden'); 190 this.userPic.removeAttribute('hidden'); 191 this.signOutButton.removeAttribute('hidden'); 192 193 // Hide sign-in button. 194 this.signInButton.setAttribute('hidden', 'true'); 195 196 // We load currently existing chant messages. 197 this.loadMessages(); 198 } else { // User is signed out! 199 // Hide user's profile and sign-out button. 200 this.userName.setAttribute('hidden', 'true'); 201 this.userPic.setAttribute('hidden', 'true'); 202 this.signOutButton.setAttribute('hidden', 'true'); 203 204 // Show sign-in button. 205 this.signInButton.removeAttribute('hidden'); 206 } 207}; 208 209// Returns true if user is signed-in. Otherwise false and displays a message. 210FriendlyChat.prototype.checkSignedInWithMessage = function() { 211 /* Check if user is signed-in Firebase. */ 212 // Return true if the user is signed in Firebase 213 if (this.auth.currentUser) { 214 return true; 215 } 216 217 // Display a message to the user using a Toast. 218 var data = { 219 message: 'You must sign-in first', 220 timeout: 2000 221 }; 222 this.signInSnackbar.MaterialSnackbar.showSnackbar(data); 223 return false; 224}; 225 226// Resets the given MaterialTextField. 227FriendlyChat.resetMaterialTextfield = function(element) { 228 element.value = ''; 229 element.parentNode.MaterialTextfield.boundUpdateClassesHandler(); 230}; 231 232// Template for messages. 233FriendlyChat.MESSAGE_TEMPLATE = 234 '<div class="message-container">' + 235 '<div class="spacing"><div class="pic"></div></div>' + 236 '<div class="message"></div>' + 237 '<div class="name"></div>' + 238 '</div>'; 239 240// A loading image URL. 241FriendlyChat.LOADING_IMAGE_URL = 'https://www.google.com/images/spin-32.gif'; 242 243// Displays a Message in the UI. 244FriendlyChat.prototype.displayMessage = function(key, name, text, picUrl, imageUri) { 245 var div = document.getElementById(key); 246 // If an element for that message does not exists yet we create it. 247 if (!div) { 248 var container = document.createElement('div'); 249 container.innerHTML = FriendlyChat.MESSAGE_TEMPLATE; 250 div = container.firstChild; 251 div.setAttribute('id', key); 252 this.messageList.appendChild(div); 253 } 254 if (picUrl) { 255 div.querySelector('.pic').style.backgroundImage = 'url(' + picUrl + ')'; 256 } 257 div.querySelector('.name').textContent = name; 258 var messageElement = div.querySelector('.message'); 259 if (text) { // If the message is text. 260 messageElement.textContent = text; 261 // Replace all line breaks by <br>. 262 messageElement.innerHTML = messageElement.innerHTML.replace(/\n/g, '<br>'); 263 } else if (imageUri) { // If the message is an image. 264 var image = document.createElement('img'); 265 image.addEventListener('load', function() { 266 this.messageList.scrollTop = this.messageList.scrollHeight; 267 }.bind(this)); 268 this.setImageUrl(imageUri, image); 269 messageElement.innerHTML = ''; 270 messageElement.appendChild(image); 271 } 272 // Show the card fading-in. 273 setTimeout(function() {div.classList.add('visible')}, 1); 274 this.messageList.scrollTop = this.messageList.scrollHeight; 275 this.messageInput.focus(); 276}; 277 278// Enables or disables the submit button depending on the values of the input 279// fields. 280FriendlyChat.prototype.toggleButton = function() { 281 if (this.messageInput.value) { 282 this.submitButton.removeAttribute('disabled'); 283 } else { 284 this.submitButton.setAttribute('disabled', 'true'); 285 } 286}; 287 288// Checks that the Firebase SDK has been correctly setup and configured. 289FriendlyChat.prototype.checkSetup = function() { 290 if (!window.firebase || !(firebase.app instanceof Function) || !window.config) { 291 window.alert('You have not configured and imported the Firebase SDK. ' + 292 'Make sure you go through the codelab setup instructions.'); 293 } else if (config.storageBucket === '') { 294 window.alert('Your Firebase Storage bucket has not been enabled. Sorry about that. This is ' + 295 'actually a Firebase bug that occurs rarely.' + 296 'Please go and re-generate the Firebase initialisation snippet (step 4 of the codelab) ' + 297 'and make sure the storageBucket attribute is not empty.'); 298 } 299}; 300 301window.onload = function() { 302 window.friendlyChat = new FriendlyChat(); 303};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.