├── .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 |
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 |
--------------------------------------------------------------------------------