From 8eaf3805175b812b00fa906194f5d8862c0b95a9 Mon Sep 17 00:00:00 2001 From: Ramiro Saenz Date: Mon, 11 Jul 2016 17:47:48 -0300 Subject: [PATCH] Firebase improvement Real syncronization --- app.js | 14 --- app/Application.js | 9 ++ app/model/Service.js | 6 +- app/profile/Offline.js | 10 ++ app/profile/Online.js | 12 ++ app/store/Services.js | 56 +++++++-- app/ux/Firebase.js | 107 +++++++++++++++++ app/view/main/Main.js | 20 +--- app/view/main/MainController.js | 199 ++++++++++++++++++++------------ 9 files changed, 316 insertions(+), 117 deletions(-) create mode 100644 app/profile/Offline.js create mode 100644 app/profile/Online.js create mode 100644 app/ux/Firebase.js diff --git a/app.js b/app.js index 0eb80cb7..6430d718 100644 --- a/app.js +++ b/app.js @@ -22,20 +22,6 @@ Ext.application({ ,autoCreateViewport: 'Rambox.view.main.Main' }); -// Syncronize with Firebase -function sync() { - // Is not logged, Skip - if ( !localStorage.getItem('id_token') ) return; - - var services = []; - Ext.getStore('Services').each(function(service) { - services.push(service.data); - }); - fireRef.database().ref('users/' + Ext.decode(localStorage.getItem('profile')).user_id).set({ - services: services - }); -} - require('electron').ipcRenderer.on('showAbout', function(event, message) { !Ext.cq1('about') ? Ext.create('Rambox.view.main.About') : ''; }); diff --git a/app/Application.js b/app/Application.js index 02374eb0..31a18418 100644 --- a/app/Application.js +++ b/app/Application.js @@ -3,11 +3,20 @@ Ext.define('Rambox.Application', { ,name: 'Rambox' + ,requires: [ + 'Rambox.ux.Firebase' + ] + ,stores: [ 'ServicesList' ,'Services' ] + ,profiles: [ + 'Offline' + ,'Online' + ] + ,config: { totalServicesLoaded: 0 ,totalNotifications: 0 diff --git a/app/model/Service.js b/app/model/Service.js index da0127b9..7f652c9a 100644 --- a/app/model/Service.js +++ b/app/model/Service.js @@ -13,9 +13,6 @@ Ext.define('Rambox.model.Service', { },{ name: 'position' ,type: 'int' - ,convert: function( value, record ) { - return value ? value : Ext.getStore('Services').getCount() + 1; - } },{ name: 'type' ,type: 'string' @@ -44,5 +41,8 @@ Ext.define('Rambox.model.Service', { name: 'js_unread' ,type: 'string' ,defaultValue: '' + },{ + name: 'firebase_key' + ,type: 'int' }] }); diff --git a/app/profile/Offline.js b/app/profile/Offline.js new file mode 100644 index 00000000..bffd9015 --- /dev/null +++ b/app/profile/Offline.js @@ -0,0 +1,10 @@ +Ext.define('Rambox.profile.Offline', { + extend: 'Ext.app.Profile' + ,isActive: function() { + return !localStorage.getItem('id_token'); + } + + ,launch: function() { + console.warn('USER NOT LOGGED IN'); + } +}); diff --git a/app/profile/Online.js b/app/profile/Online.js new file mode 100644 index 00000000..bce6c502 --- /dev/null +++ b/app/profile/Online.js @@ -0,0 +1,12 @@ +Ext.define('Rambox.profile.Online', { + extend: 'Ext.app.Profile' + ,isActive: function() { + return localStorage.getItem('id_token'); + } + + ,launch: function() { + console.info('USER LOGGED IN'); + + Rambox.ux.Firebase.createEvents(false); + } +}); diff --git a/app/store/Services.js b/app/store/Services.js index 4db0a0bb..95cae6ea 100644 --- a/app/store/Services.js +++ b/app/store/Services.js @@ -20,10 +20,7 @@ Ext.define('Rambox.store.Services', { ,listeners: { load: function( store, records, successful ) { - if ( Ext.isEmpty(records) ) { - Ext.cq1('app-main').add({ tabConfig : { xtype : 'tbfill' } }); - return; - } + Ext.cq1('app-main').suspendEvent('add'); var servicesLeft = []; var servicesRight = []; @@ -45,21 +42,56 @@ Ext.define('Rambox.store.Services', { service.get('align') === 'left' ? servicesLeft.push(cfg) : servicesRight.push(cfg); }); - Ext.cq1('app-main').add(servicesLeft); - Ext.cq1('app-main').add({ tabConfig : { xtype : 'tbfill' } }); + if ( !Ext.isEmpty(servicesLeft) ) Ext.cq1('app-main').insert(1, servicesLeft); + if ( !Ext.isEmpty(servicesRight) ) Ext.cq1('app-main').add(servicesRight); - if ( !Ext.isEmpty(servicesRight) ) { - Ext.cq1('app-main').add(servicesRight); - } + store.suspendEvent('load'); + Ext.cq1('app-main').resumeEvent('add'); } ,add: function(store, records, index) { - sync(); + var record = records[0]; + if ( !localStorage.getItem('id_token') || (!Ext.isEmpty(record.previousValues) && !Ext.isEmpty(record.previousValues.position)) ) return true; + + console.info('Saving into Firebase...', record.data); + + var ref = fireRef.database().ref('test/' + Ext.decode(localStorage.getItem('profile')).user_id).child('services'); + + ref.once('value', function(snap) { + // Generate Key + var i = 0; + while ( snap.child(i).exists() ) { i++; } + + // Save Firebase Key into record + record.set('firebase_key', i); + + // Prevent saving local ID and Firebase Key into Firebase + var data = Ext.clone(record.data); + delete data.id; + delete data.firebase_key; + + // Make the call + ref.child(i).set(data); + }); } ,update: function(store, record, operation, data) { - if ( operation === 'edit' ) sync(); + // Is not logged, Skip + if ( !localStorage.getItem('id_token') || operation === 'commit' ) return; + + if ( operation === 'edit' && data[0] !== 'firebase_key' ) { + var obj = {}; + Ext.each(data, function(prop) { + obj[prop] = record.get(prop); + }); + + fireRef.database().ref('test/' + Ext.decode(localStorage.getItem('profile')).user_id + '/services').child(record.get('firebase_key')).update(obj); + } } ,remove: function(store, records, index, isMove) { - sync(); + if ( !localStorage.getItem('id_token') ) return; + + Ext.each(records, function(rec) { + fireRef.database().ref('test/' + Ext.decode(localStorage.getItem('profile')).user_id).child('services').child(rec.get('firebase_key')).remove(); + }); } } }); diff --git a/app/ux/Firebase.js b/app/ux/Firebase.js new file mode 100644 index 00000000..83d4d3d5 --- /dev/null +++ b/app/ux/Firebase.js @@ -0,0 +1,107 @@ +Ext.define('Rambox.ux.Firebase', { + singleton: true + + // private + ,eventsDefined: false + + ,createEvents: function(createTabs) { + if ( this.eventsDefined || !localStorage.getItem('id_token') ) return; + + console.log('Define listeners for Firebase'); + + var ref = fireRef.database().ref('test/' + Ext.decode(localStorage.getItem('profile')).user_id).child('services'); + + // Attach an asynchronous callback to read the data at our posts reference + ref.on("child_changed", function(snapshot, prevChildKey) { + console.info('Firebase - Child Changed', snapshot.val(), snapshot.key, prevChildKey); + + Ext.getStore('Services').suspendEvent('update'); + var rec = Ext.getStore('Services').findRecord('firebase_key', snapshot.key); + + + Ext.getCmp('tab_'+rec.get('id')).setTitle(snapshot.val().name); + + if ( rec.get('position') !== snapshot.val().position ) { + var pos = parseInt(snapshot.val().position); + if ( rec.get('align') === 'right' ) pos++; + Ext.cq1('app-main').move(Ext.getCmp('tab_'+rec.get('id')), pos); + } + + rec.set(snapshot.val()); + rec.save(); + Ext.getStore('Services').resumeEvent('update'); + Ext.getStore('Services').load(); + }, function (errorObject) { + + }); + + ref.on("child_added", function(snapshot, prevChildKey) { + console.info('Firebase - Child Added', snapshot.val(), snapshot.key, prevChildKey); + + Ext.getStore('Services').suspendEvents(['add', 'update']); + var rec = Ext.getStore('Services').findRecord('firebase_key', snapshot.key); + + // Update current services + if ( rec ) { + rec.set(snapshot.val()); + rec.save(); + } else { // Add new ones if exist + var data = snapshot.val(); + data.firebase_key = snapshot.key; + rec = Ext.create('Rambox.model.Service', data); + rec.save(); + Ext.getStore('Services').add(rec); + + var tabData = { + xtype: 'webview' + ,id: 'tab_'+rec.get('id') + ,title: rec.get('name') + ,icon: rec.get('type') !== 'custom' ? 'resources/icons/'+rec.get('logo') : ( rec.get('logo') === '' ? 'resources/icons/custom.png' : rec.get('logo')) + ,src: rec.get('url') + ,type: rec.get('type') + ,align: rec.get('align') + ,notifications: rec.get('notifications') + ,muted: rec.get('muted') + ,record: rec + ,tabConfig: { + service: rec + } + }; + + if ( rec.get('align') === 'left' ) { + var tbfill = Ext.cq1('app-main').getTabBar().down('tbfill'); + Ext.cq1('app-main').insert(Ext.cq1('app-main').getTabBar().items.indexOf(tbfill), tabData); + } else { + Ext.cq1('app-main').add(tabData); + } + } + + Ext.getStore('Services').resumeEvents(['add', 'update']); + Ext.getStore('Services').load(); + //rec.commit(); + }, function (errorObject) { + + }); + + ref.on("child_removed", function(snapshot) { + console.info('Firebase - Child Removed', snapshot.val(), snapshot.key); + + var rec = Ext.getStore('Services').findRecord('firebase_key', snapshot.key); + + // Remove record from localStorage + if ( rec ) { + Ext.getStore('Services').suspendEvent('remove'); + Ext.getStore('Services').remove(rec); + Ext.cq1('app-main').suspendEvent('remove'); + Ext.getCmp('tab_'+rec.get('id')).close(); + Ext.cq1('app-main').resumeEvent('remove'); + Ext.getStore('Services').resumeEvent('remove'); + Ext.getStore('Services').load(); + } + }, function (errorObject) { + + }); + + this.eventsDefined = true; + } +}); diff --git a/app/view/main/Main.js b/app/view/main/Main.js index 9f54787f..c5e8a012 100644 --- a/app/view/main/Main.js +++ b/app/view/main/Main.js @@ -18,20 +18,6 @@ Ext.define('Rambox.view.main.Main', { ,plugins: [ { ptype: 'tabreorderer' - ,listeners: { - // I put the code here because it cannot be listened into the Controller - Drop: function( box, tabBar, tab, startIdx, index ) { - var idx = 0; - Ext.each(tabBar.items.items, function(t) { - if ( idx > 0 && t.xtype !== 'tbfill' ) { // Skip first tab because is the configuration tab - t.card.record.set('position', idx); - } else if ( t.xtype === 'tbfill' ) { - idx--; - } - idx++; - }); - } - } } ] @@ -41,6 +27,7 @@ Ext.define('Rambox.view.main.Main', { ,items: [ { icon: 'resources/IconTray@2x.png' + ,id: 'ramboxTab' ,closable: false ,reorderable: false ,autoScroll: true @@ -289,6 +276,9 @@ Ext.define('Rambox.view.main.Main', { ] ,listeners: { - tabchange: 'onTabChange' + tabchange: 'onTabChange' + ,add: 'updatePositions' + ,remove: 'updatePositions' + ,childmove: 'updatePositions' } }); diff --git a/app/view/main/MainController.js b/app/view/main/MainController.js index 690af35b..095955c5 100644 --- a/app/view/main/MainController.js +++ b/app/view/main/MainController.js @@ -1,9 +1,6 @@ Ext.define('Rambox.view.main.MainController', { extend: 'Ext.app.ViewController' - ,requires: [ - ] - ,alias: 'controller.main' // Make focus on webview every time the user change tabs, to enable the autofocus in websites @@ -14,6 +11,26 @@ Ext.define('Rambox.view.main.MainController', { if ( webview ) webview.focus(); } + ,updatePositions: function(tabPanel, tab) { + if ( tab.id === 'ramboxTab' || tab.id === 'tbfill' ) return true; + + console.log('Updating Tabs positions...'); + + var store = Ext.getStore('Services'); + store.suspendEvent('remove'); + Ext.each(tabPanel.items.items, function(t, i) { + if ( t.id !== 'ramboxTab' && t.id !== 'tbfill' ) { + if ( t.record.get('align') === 'right' ) i--; + var rec = store.getById(t.record.get('id')); + rec.set('position', i); + rec.save(); + } + }); + + store.load(); + store.resumeEvent('remove'); + } + ,showSimpleModal: function(record, edit) { var me = this; @@ -76,10 +93,10 @@ Ext.define('Rambox.view.main.MainController', { } ,{ xtype: 'container' - ,hidden: record.get('note') === '' - ,data: { note: record.get('note') } + ,hidden: (edit ? Ext.getStore('ServicesList').getById(record.get('type')).get('note') === '' : record.get('note') === '') + ,data: { note: (edit ? Ext.getStore('ServicesList').getById(record.get('type')).get('note') : record.get('note')) } ,margin: '10 0 0 0' - ,style: 'background-color:#93CFE0;color:#053767;' + ,style: 'background-color:#93CFE0;color:#053767;border-radius:6px;' ,tpl: [ '' ,'{note}' @@ -122,7 +139,6 @@ Ext.define('Rambox.view.main.MainController', { ,align: formValues.align ,notifications: formValues.notifications ,muted: formValues.muted - ,js_unread: record.get('js_unread') }); service.save(); Ext.getStore('Services').add(service); @@ -131,7 +147,7 @@ Ext.define('Rambox.view.main.MainController', { xtype: 'webview' ,id: 'tab_'+service.get('id') ,title: service.get('name') - ,icon: 'resources/icons/'+record.get('logo') + ,icon: 'resources/icons/'+service.get('logo') ,src: service.get('url') ,type: service.get('type') ,align: formValues.align @@ -201,6 +217,7 @@ Ext.define('Rambox.view.main.MainController', { ,submitEmptyText: false ,emptyText: record.get('url') === '___' ? 'http://' : '' ,vtype: record.get('url') === '___' ? 'url' : '' + ,width: 220 ,listeners: { specialkey: function(field, e) { if(e.getKey() == e.ENTER && field.up('form').isValid()) { @@ -247,10 +264,10 @@ Ext.define('Rambox.view.main.MainController', { } ,{ xtype: 'container' - ,hidden: record.get('note') === '' - ,data: { note: record.get('note') } + ,hidden: (edit ? Ext.getStore('ServicesList').getById(record.get('type')).get('note') === '' : record.get('note') === '') + ,data: { note: (edit ? Ext.getStore('ServicesList').getById(record.get('type')).get('note') : record.get('note')) } ,margin: '10 0 0 0' - ,style: 'background-color:#93CFE0;color:#053767;' + ,style: 'background-color:#93CFE0;color:#053767;border-radius:6px;' ,tpl: [ '' ,'{note}' @@ -293,7 +310,6 @@ Ext.define('Rambox.view.main.MainController', { ,align: formValues.align ,notifications: formValues.notifications ,muted: formValues.muted - ,js_unread: record.get('js_unread') }); service.save(); Ext.getStore('Services').add(service); @@ -302,7 +318,7 @@ Ext.define('Rambox.view.main.MainController', { xtype: 'webview' ,id: 'tab_'+service.get('id') ,title: service.get('name') - ,icon: 'resources/icons/'+record.get('logo') + ,icon: 'resources/icons/'+service.get('logo') ,src: service.get('url') ,type: service.get('type') ,align: formValues.align @@ -352,11 +368,11 @@ Ext.define('Rambox.view.main.MainController', { tab.down('component').el.dom.getWebContents().session.clearCache(Ext.emptyFn); tab.down('component').el.dom.getWebContents().session.clearStorageData({}, Ext.emptyFn); - // Close tab - tab.close(); - // Remove record from localStorage Ext.getStore('Services').remove(Ext.getStore('Services').getById(serviceId)); + + // Close tab + tab.close(); } ,removeService: function( gridView, rowIndex, colIndex, col, e, rec, rowEl ) { @@ -376,17 +392,23 @@ Ext.define('Rambox.view.main.MainController', { if ( btn ) { Ext.Msg.confirm('Please confirm...', 'Are you sure you want to remove all services?', function(btnId) { if ( btnId === 'yes' ) { + Ext.cq1('app-main').suspendEvent('remove'); Ext.Array.each(Ext.getStore('Services').collect('id'), function(serviceId) { me.removeServiceFn(serviceId); }); + Ext.getStore('Services').load(); if ( Ext.isFunction(callback) ) callback(); + Ext.cq1('app-main').resumeEvent('remove'); } }); } else { + Ext.cq1('app-main').suspendEvent('remove'); Ext.Array.each(Ext.getStore('Services').collect('id'), function(serviceId) { me.removeServiceFn(serviceId); }); + Ext.getStore('Services').load(); if ( Ext.isFunction(callback) ) callback(); + Ext.cq1('app-main').resumeEvent('remove'); } } @@ -564,7 +586,7 @@ Ext.define('Rambox.view.main.MainController', { xtype: 'webview' ,id: 'tab_'+service.get('id') ,title: service.get('name') - ,icon: formValues.logo + ,icon: 'resources/icons/'+service.get('logo') ,src: service.get('url') ,type: service.get('type') ,align: formValues.align @@ -745,7 +767,8 @@ Ext.define('Rambox.view.main.MainController', { icon: 'resources/Icon.png' }, function(err, profile, id_token) { // There was an error logging the user in - if (err) return console.error(err); + //if (err) return console.error(err); + console.log('LOGIN', err, profile, id_token); // Display a spinner while waiting Ext.Msg.wait('Please wait until we get your configuration.', 'Connecting...'); @@ -763,62 +786,80 @@ Ext.define('Rambox.view.main.MainController', { if ( !err ) { // Exchange the delegate token for a Firebase auth token firebase.auth().signInWithCustomToken(result.id_token).then(function(snapshot) { - fireRef.database().ref('users/' + profile.user_id).once('value').then(function(snapshot) { - me.removeAllServices(false, function() { - if ( snapshot.val() === null || Ext.isEmpty(snapshot.val().services) ) return; - - Ext.each(snapshot.val().services, function(s) { - var service = Ext.create('Rambox.model.Service', { - id: s.id - ,position: s.position - ,type: s.type - ,logo: s.logo - ,name: s.name - ,url: s.url - ,align: s.align - ,notifications: s.notifications - ,muted: s.muted - ,js_unread: s.js_unread - }); + fireRef.database().ref('test/' + profile.user_id).child('services').orderByChild('position').once('value', function(snapshot2) { + Ext.Msg.hide(); + + // Import Services function + var importServices = function(snap) { + snap.forEach(function(data) { + var s = data.val(); + s.firebase_key = data.key; + var service = Ext.create('Rambox.model.Service', s); service.save(); Ext.getStore('Services').add(service); + }); + Ext.getStore('Services').resumeEvent('load'); + Ext.getStore('Services').load(); + + // User is logged in + // Save the profile and JWT. + localStorage.setItem('profile', JSON.stringify(profile)); + localStorage.setItem('id_token', id_token); + Rambox.ux.Firebase.createEvents(); + } - var tabData = { - xtype: 'webview' - ,id: 'tab_'+service.get('id') - ,title: service.get('name') - ,icon: 'resources/icons/' + s.logo - ,src: service.get('url') - ,type: service.get('type') - ,align: s.align - ,notifications: s.notifications - ,muted: s.muted - ,record: service - ,tabConfig: { - service: service - } - }; - - if ( s.align === 'left' ) { - var tbfill = Ext.cq1('app-main').getTabBar().down('tbfill'); - Ext.cq1('app-main').insert(Ext.cq1('app-main').getTabBar().items.indexOf(tbfill), tabData); + // Firebase empty and Have Services + if ( !snapshot2.hasChildren() && Ext.getStore('Services').getCount() > 0 ) { + Ext.Msg.confirm('Import', 'You don\'t have any service saved. Do you want to import your current services?', function(btnId) { + if ( btnId === 'yes' ) { + var services = []; + Ext.getStore('Services').each(function(service, index) { + service.set('firebase_key', index); + // Prevent saving local ID into Firebase + var data = Ext.clone(service.data); + delete data.id; + + services.push(data); + }); + fireRef.database().ref('test/' + profile.user_id).set({ + services: services + }); + + // User is logged in + // Save the profile and JWT. + localStorage.setItem('profile', JSON.stringify(profile)); + localStorage.setItem('id_token', id_token); + Rambox.ux.Firebase.createEvents(); } else { - Ext.cq1('app-main').add(tabData); + Ext.Msg.confirm('Clear services', 'Do you want to remove all your current services to start over?

If NO, you will be logged out.', function(btnId) { + if ( btnId === 'yes' ) { + me.removeAllServices(false); + } else { + me.logout(); + } + }); } }); - - Ext.Msg.hide(); - }); + // Firebase not empty and Have Services + } else if ( snapshot2.hasChildren() && Ext.getStore('Services').getCount() > 0 ) { + Ext.Msg.confirm('Confirm', 'To import your configuration, I need to remove all your current services. Do you want to continue?

If NO, you will be logged out.', function(btnId) { + if ( btnId === 'yes' ) { + me.removeAllServices(false, function() { + importServices(snapshot2); + }); + } else { + me.logout(); + } + }); + // Firebase not empty and Have no Services + } else if ( snapshot2.hasChildren() && Ext.getStore('Services').getCount() === 0 ) { + importServices(snapshot2); + } }); }); } }); - // User is logged in - // Save the profile and JWT. - localStorage.setItem('profile', JSON.stringify(profile)); - localStorage.setItem('id_token', id_token); - Ext.cq1('app-main').getViewModel().set('username', profile.name); Ext.cq1('app-main').getViewModel().set('avatar', profile.picture); }, function() { @@ -829,22 +870,34 @@ Ext.define('Rambox.view.main.MainController', { ,logout: function(btn) { var me = this; - Ext.Msg.confirm('Logout', 'Are you sure you want to logout?', function(btnId) { - if ( btnId === 'yes' ) { + var logoutFn = function(callback) { + Ext.Msg.wait('Closing you session...', 'Logout'); + + firebase.auth().signOut().then(function() { localStorage.removeItem('profile'); localStorage.removeItem('id_token'); Ext.cq1('app-main').getViewModel().set('username', ''); Ext.cq1('app-main').getViewModel().set('avatar', ''); - firebase.auth().signOut().then(function() { - Ext.Array.each(Ext.getStore('Services').collect('id'), function(serviceId) { - me.removeServiceFn(serviceId); + if ( Ext.isFunction(callback) ) callback(); + + Ext.Msg.hide(); + }, function(error) { + console.error(error); + }); + } + + if ( btn ) { + Ext.Msg.confirm('Logout', 'Are you sure you want to logout?', function(btnId) { + if ( btnId === 'yes' ) { + logoutFn(function() { + me.removeAllServices(); }); - }, function(error) { - console.error(error); - }); - } - }) + } + }); + } else { + logoutFn(); + } } });